package org.bouncycastle.crypto.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.StreamCipher;
import org.bouncycastle.crypto.engines.HC128Engine;
import org.bouncycastle.crypto.engines.HC256Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.test.TestResourceFinder;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTest;

/**
 * HC-128 and HC-256 Tests. Based on the test vectors in the official reference
 * papers, respectively:
 * 
 * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
 * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
 */
public class HCFamilyVecTest
    extends SimpleTest
{
    private static class PeekableLineReader extends BufferedReader
    {
        public PeekableLineReader(Reader r) throws IOException
        {
            super(r);

            peek = super.readLine();
        }

        public String peekLine()
        {
            return peek;
        }

        public String readLine() throws IOException
        {
            String tmp = peek;
            peek = super.readLine();
            return tmp;
        }

        private String peek; 
    }

    public String getName()
    {
        return "HC-128 and HC-256 (ecrypt)";
    }

    public void performTest() throws Exception
    {
        runTests(new HC128Engine(), "ecrypt_HC-128.txt");
        runTests(new HC256Engine(), "ecrypt_HC-256_128K_128IV.txt");
        runTests(new HC256Engine(), "ecrypt_HC-256_256K_128IV.txt");
        runTests(new HC256Engine(), "ecrypt_HC-256_128K_256IV.txt");
        runTests(new HC256Engine(), "ecrypt_HC-256_256K_256IV.txt");
    }

    private void runTests(StreamCipher hc, String fileName) throws IOException
    {
        Reader resource = new InputStreamReader(TestResourceFinder.findTestResource("crypto", fileName));
        PeekableLineReader r = new PeekableLineReader(resource);
        runAllVectors(hc, fileName, r);
    }

    private void runAllVectors(StreamCipher hc, String fileName, PeekableLineReader r)
        throws IOException
    {
        for (;;)
        {
            String line = r.readLine();
            if (line == null)
            {
                break;
            }

            line = line.trim();

            if (line.startsWith("Set "))
            {
                runVector(hc, fileName, r, dellChar(line, ':'));
            }
        }
    }

    private String dellChar(String s, char c)
    {
        StringBuilder b = new StringBuilder();

        for (int i = 0; i != s.length(); i++)
        {
            if (s.charAt(i) != c)
            {
                b.append(s.charAt(i));
            }
        }

        return b.toString();
    }

    private void runVector(StreamCipher hc, String fileName, PeekableLineReader r, String vectorName)
        throws IOException
    {
//        System.out.println(fileName + " => " + vectorName);
        String hexKey = readBlock(r);
        String hexIV = readBlock(r);

        CipherParameters cp = new KeyParameter(Hex.decode(hexKey));
        cp = new ParametersWithIV(cp, Hex.decode(hexIV));
        hc.init(true, cp);

        byte[] input = new byte[64];
        byte[] output = new byte[64];
        byte[] digest = new byte[64];
        int pos = 0;

        for (;;)
        {
            String line1 = r.peekLine().trim();
            int equalsPos = line1.indexOf('=');
            String lead = line1.substring(0, equalsPos - 1);

            String hexData = readBlock(r);
            byte[] data = Hex.decode(hexData);

            if (lead.equals("xor-digest"))
            {
                if (!Arrays.areEqual(data, digest))
                {
                    fail("Failed in " + fileName + " for test vector: " + vectorName + " at " + lead);
//                  System.out.println(fileName + " => " + vectorName + " failed at " + lead); return;
                }
                break;
            }

            int posA = lead.indexOf('[');
            int posB = lead.indexOf("..");
            int posC = lead.indexOf(']');
            int start = Integer.parseInt(lead.substring(posA + 1, posB));
            int end = Integer.parseInt(lead.substring(posB + 2, posC));

            if (start % 64 != 0 || (end - start != 63))
            {
                throw new IllegalStateException(vectorName + ": " + lead + " not on 64 byte boundaries");
            }

            while (pos < end)
            {
                hc.processBytes(input, 0, input.length, output, 0);
                xor(digest, output);
                pos += 64;
            }

            if (!Arrays.areEqual(data, output))
            {
                fail("Failed in " + fileName + " for test vector: " + vectorName + " at " + lead);
//              System.out.println(fileName + " => " + vectorName + " failed at " + lead); return;
            }
        }
    }

    private static String readBlock(PeekableLineReader r) throws IOException
    {
        String first = r.readLine().trim();
        String result = first.substring(first.lastIndexOf(' ') + 1);

        for (;;)
        {
            String peek = r.peekLine().trim();
            if (peek.length() < 1 || peek.indexOf('=') >= 0)
            {
                break;
            }
            result += r.readLine().trim();
        }

        return result;
    }

    private static void xor(byte[] digest, byte[] block)
    {
        for (int i = 0; i < digest.length; ++i)
        {
            digest[i] ^= block[i];
        }
    }

    public static void main(String[] args)
    {
        runTest(new HCFamilyVecTest());
    }
}
