#!/usr/bin/env perl
#
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

#
# This script parses the NIST PKI Test Suite test descriptions document
# and creates a .java file with test cases.
#

use strict;

my $enabled = 0;
my $readingPath = 0;
my $sectionName;
my $testNumber;
my $testName;
my $pathEntry = "";
my $expectedOutcome;
my @pathEntries;

my @usedFiles = ();

my $delimiter = "\x{2022}";
utf8::encode($delimiter);

if ($#ARGV != 2) {
    die "Usage: $0 <text-descriptions> <java-output> <used-files-output>";
}

open(DESC_FILE, "<", $ARGV[0]);
open(OUTPUT_FILE, ">", $ARGV[1]);
open(USED_FILES, ">", $ARGV[2]);

sub trim($) {
    my $s = shift;
    $s =~ s/^\s+//g;
    $s =~ s/\s+$//g;
    return $s;
}

sub printTest() {
    my @certNames;
    my @crlNames;

    foreach my $entry (@pathEntries) {
        $entry =~ s/ //g;
        $entry =~ s/-//g;
        my @parts = split(/,/, $entry);
        for my $part (@parts) {
            if ($part =~ /CRL[0-9]*$/) {
                my $crlName = $part . ".crl";
                push(@crlNames, $crlName);
                push(@usedFiles, "crls/" . $crlName);
            } else {
                my $certName = $part . ".crt";
                push(@certNames, $certName);
                push(@usedFiles, "certs/" . $certName);
            }
        }
    }

    print OUTPUT_FILE <<EOF;
    /** NIST PKITS test ${testNumber} */
    public void test${sectionName}_${testName}() throws Exception {
EOF
    print OUTPUT_FILE " " x 8 . "String trustAnchor = \"" . (shift @certNames) . "\";\n";

    print OUTPUT_FILE <<EOF;

        String[] certs = {
EOF

    # Print the CertPath in reverse order.
    for (0..$#certNames) {
        print OUTPUT_FILE " " x 16 . "\"${certNames[$#certNames - $_]}\",\n";
    }
    print OUTPUT_FILE <<EOF;
        };

        String[] crls = {
EOF
    foreach my $crlName (@crlNames) {
        print OUTPUT_FILE " " x 16 . "\"${crlName}\",\n";
    }
    print OUTPUT_FILE <<EOF;
        };

EOF
    if ($expectedOutcome) {
        print OUTPUT_FILE <<EOF;
        assertValidPath(trustAnchor, certs, crls);
EOF
    } else {
        print OUTPUT_FILE <<EOF;
        assertInvalidPath(trustAnchor, certs, crls);
EOF
    }

        print OUTPUT_FILE <<EOF;
    }

EOF
}

sub stopReadingPath() {
    if ($readingPath) {
        if (defined($pathEntry) and $pathEntry ne "") {
            push(@pathEntries, $pathEntry);
            $pathEntry = "";
        }

        printTest();
        @pathEntries = ();
        $readingPath = 0;
    }
}


while (<DESC_FILE>) {
    chomp;

    if ($_ =~ /^\s*4 Certification Path Validation Tests$/) {
        $enabled = 1;
        next;
    }

    #
    # TODO: this script needs to be fixed to support the test cases in
    # 4.8 to 4.12
    #

    if ($_ =~ /^\s*4\.8 Certificate Policies\s*$/) {
        stopReadingPath();
        $enabled = 0;

        print OUTPUT_FILE " "x4 . "// skipping sections 4.8 to 4.12\n\n";
        next;
    }

    if ($_ =~ /^\s*4\.13 Name Constraints\s*$/) {
        $enabled = 1;
        next;
    }

    if ($_ =~ /^\s*5 Relationship to Previous Test Suite\s*[^.]/) {
        stopReadingPath();
        $enabled = 0;
        exit;
    }

    if (!$enabled) {
        next;
    }

    if ($_ =~ /^\s*4\.[0-9]+ (.*)$/) {
        stopReadingPath();
        $sectionName = $1;
        $sectionName =~ s/ //g;
        $sectionName =~ s/-//g;
    }

    if ($_ =~ /^\s*(4\.[0-9]+\.[0-9]+) (.*)$/) {
        stopReadingPath();
        $testNumber = $1;
        $testName = $2;
        $testName =~ s/ //g;
        $testName =~ s/-//g;
    }

    if ($_ =~ /Expected Result:.*(should validate|should not validate)/) {
        if ($1 eq "should validate") {
            $expectedOutcome = 1;
        } else {
            $expectedOutcome = 0;
        }
    } elsif ($_ =~ /Expected Result:/) {
        die "Can not determine expected result for test:\n\t${testName}";
    }

    if ($_ =~ /^\s*Certification Path:/) {
        $readingPath = 1;
        next;
    }

    if ($readingPath) {
        # Page number from the PDF
        if (trim($_) =~ /^[0-9]+$/) {
            do {
                $_ = <DESC_FILE>;
                if ($_ =~ /^\s*$/) {
                    next;
                }
            } while (1);
        }

        if ($_ =~ /${delimiter}\s*(.*)$/u) {
            if (defined($pathEntry) and $pathEntry ne "") {
                push(@pathEntries, $pathEntry);
            }
            $pathEntry = trim($1);
        } else {
            if ($_ =~ /The certification path is composed of the following objects:(.*)$/) {
                $pathEntry = trim($1);
            } else {
                $pathEntry .= trim($_);
            }
        }
    }
}

print USED_FILES join("\n", keys %{{map{$_ => 1} @usedFiles}});

close(DESC_FILE);
close(OUTPUT_FILE);
close(USED_FILES);