package org.bouncycastle.crypto.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.engines.AESLightEngine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTestResult;
import org.bouncycastle.util.test.Test;
import org.bouncycastle.util.test.TestResult;

/**
 * Test vectors from the NIST standard tests and Brian Gladman's vector set
 * <a href="https://fp.gladman.plus.com/cryptography_technology/rijndael/">
 * https://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
 */
public class AESVectorFileTest
    implements Test
{

    private int countOfTests = 0;
    private int testNum = 0;

    protected BlockCipher createNewEngineForTest()
    {
        return AESEngine.newInstance();
    }

    private Test[] readTestVectors(InputStream inStream)
    {
        // initialize key, plaintext, ciphertext = null
        // read until find BLOCKSIZE=
        // return if not 128
        // read KEYSIZE= or ignore
        // loop
        // read a line
        // if starts with BLOCKSIZE=
        // parse the rest. return if not 128
        // if starts with KEY=
        // parse the rest and set KEY
        // if starts with PT=
        // parse the rest and set plaintext
        // if starts with CT=
        // parse the rest and set ciphertext
        // if starts with TEST= or end of file
        // if key, plaintext, ciphertext are all not null
        // save away their values as the next test
        // until end of file
        List   tests = new ArrayList();
        String key = null;
        String plaintext = null;
        String ciphertext = null;

        BufferedReader in = new BufferedReader(new InputStreamReader(inStream));

        try
        {
            String line = in.readLine();

            while (line != null)
            {
                line = line.trim().toLowerCase();
                if (line.startsWith("blocksize="))
                {
                    int i = 0;
                    try
                    {
                        i = Integer.parseInt(line.substring(10).trim());
                    }
                    catch (Exception e)
                    {
                    }
                    if (i != 128)
                    {
                        return null;
                    }
                }
                else if (line.startsWith("keysize="))
                {
                    int i = 0;
                    try
                    {
                        i = Integer.parseInt(line.substring(10).trim());
                    }
                    catch (Exception e)
                    {
                    }
                    if ((i != 128) && (i != 192) && (i != 256))
                    {
                        return null;
                    }
                }
                else if (line.startsWith("key="))
                {
                    key = line.substring(4).trim();
                }
                else if (line.startsWith("pt="))
                {
                    plaintext = line.substring(3).trim();
                }
                else if (line.startsWith("ct="))
                {
                    ciphertext = line.substring(3).trim();
                }
                else if (line.startsWith("test="))
                {
                    if ((key != null) && (plaintext != null)
                            && (ciphertext != null))
                    {
                        tests.add(new BlockCipherVectorTest(testNum++,
                                createNewEngineForTest(), new KeyParameter(Hex
                                        .decode(key)), plaintext, ciphertext));
                    }
                }

                line = in.readLine();
            }
            try
            {
                in.close();
            }
            catch (IOException e)
            {
            }
        }
        catch (IOException e)
        {
        }
        if ((key != null) && (plaintext != null) && (ciphertext != null))
        {
            tests.add(new BlockCipherVectorTest(testNum++,
                    createNewEngineForTest(),
                    new KeyParameter(Hex.decode(key)), plaintext, ciphertext));
        }
        return (Test[])(tests.toArray(new Test[tests.size()]));
    }

    public String getName()
    {
        return "AES";
    }

    private TestResult performTestsFromZipFile(File zfile)
    {
        try
        {
            ZipFile inZip = new ZipFile(zfile);
            for (Enumeration files = inZip.entries(); files.hasMoreElements();)
            {
                Test[] tests = null;
                try
                {
                    tests = readTestVectors(inZip
                            .getInputStream((ZipEntry)(files.nextElement())));
                }
                catch (Exception e)
                {
                    return new SimpleTestResult(false, getName() + ": threw "
                            + e);
                }
                if (tests != null)
                {
                    for (int i = 0; i != tests.length; i++)
                    {
                        TestResult res = tests[i].perform();
                        countOfTests++;

                        if (!res.isSuccessful())
                        {
                            return res;
                        }
                    }
                }
            }
            inZip.close();
            return new SimpleTestResult(true, getName() + ": Okay");
        }
        catch (Exception e)
        {
            return new SimpleTestResult(false, getName() + ": threw " + e);
        }
    }

    private static final String[] zipFileNames = { "rijn.tv.ecbnk.zip",
            "rijn.tv.ecbnt.zip", "rijn.tv.ecbvk.zip", "rijn.tv.ecbvt.zip" };

    public TestResult perform()
    {
        countOfTests = 0;
        for (int i = 0; i < zipFileNames.length; i++)
        {
            File inf = new File(zipFileNames[i]);
            TestResult res = performTestsFromZipFile(inf);
            if (!res.isSuccessful())
            {
                return res;
            }
        }
        return new SimpleTestResult(true, getName() + ": " + countOfTests
                + " performed Okay");
    }

    public static void main(String[] args)
    {
        AESVectorFileTest test = new AESVectorFileTest();
        TestResult result = test.perform();
        System.out.println(result);

        test = new AESLightVectorFileTest();
        result = test.perform();
        System.out.println(result);

        test = new AESFastVectorFileTest();
        result = test.perform();
        System.out.println(result);

    }

    private static class AESLightVectorFileTest extends AESVectorFileTest
    {
        protected BlockCipher createNewEngineForTest()
        {
            return new AESLightEngine();
        }

        public String getName()
        {
            return "AESLight";
        }

    }

    private static class AESFastVectorFileTest extends AESVectorFileTest
    {
        protected BlockCipher createNewEngineForTest()
        {
            return new AESFastEngine();
        }

        public String getName()
        {
            return "AESFast";
        }

    }
}
