/* @(#)SHA224Test.java	1.12 2004-08-08
 * This file was freely contributed to the LimeWire project and is covered
 * by its existing GPL licence, but it may be used individually as a public
 * domain implementation of a published algorithm (see below for references).
 * It was also freely contributed to the Bitzi public domain sources.
 * @author  Philippe Verdy
 */

/* Sun may wish to change the following package name, if integrating this
 * class in the Sun JCE Security Provider for Java 1.5 (code-named Tiger).
 *
 * You can include it in your own Security Provider by inserting
 * this property in your Provider derived class:
 * put("MessageDigest.SHA-224", "org.rodage.pub.java.security.SHA224");
 */
//package org.rodage.pub.java.security;
import java.security.*;
//--+---+1--+---+--2+---+---+3--+---+--4+---+---+5--+---+--6+---+---+7--+---+--
//34567890123456789012345678901234567890123456789012345678901234567890123456789

public class SHA224Test {
    
    private static final SHA224 hash = new SHA224();
    private static final MessageDigest md = getSHA224();
    private static final MessageDigest getSHA224() {
        try {
            return MessageDigest.getInstance("SHA-224");
        } catch(NoSuchAlgorithmException nsae) {}
        System.out.println("No SHA-224 algorithm in local JCE Security Providers");
        return null;
    }

    public static void main(String args[]) {
// http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf
        System.out.println("****************************************");
        System.out.println("* Basic FIPS PUB 180-2 test vectors... *");
        System.out.println("****************************************");
        tst(1, 1,
            "abc",
            "23097D22 3405D822 8642A477 BDA255B3 2AADBCE4 BDA0B3F7 E36C9DA7",
            "2F6268DE 8B610175 69D68593 C00A7B44 2F9DCB89 8743F426 2BE4D444");
        tst(1, 2,
            "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
            "75388B16 512776CC 5DBA5DA1 FD890150 B0C6455C B4F58B19 52522525",
            "062A8D12 C44DA3BB 1F1A0AD6 EDBBA37F 7D613A87 9A779C88 BDA0A785");
        tst(1, 3, /* one million bytes */
            1000000, "a",
            "20794655 980C91D8 BBB4C1EA 97618A4B F03F4258 1948B2EE 4EE7AD67",
            "73428D2F 2293300A 1E5FA5C1 CD2174C9 5E922FEF C0EC26B3 2AB41C82");
        System.out.println();

// http://csrc.ncsl.nist.gov/cryptval/shs/SHAVS.pdf
        System.out.println("********************************************************");
        System.out.println("* SHSV Examples of the selected short messages test... *");
        System.out.println("********************************************************");
        tst(2, 2, new byte[] {/* 8 bits, i.e. 1 byte */
            (byte)0x5e},
            "8A1CB442 40325EBF CF6BF956 0112A625 27B6F11D 2B89DBE4 0CCF89D3",
            "CFB44BC0 57310D66 BC5C2121 FA71BE05 006DF42A E352250B D6F2220C");
        tst(2, 4, new byte[] {/* 128 bits, i.e. 16 bytes */
            (byte)0x9a,(byte)0x7d,(byte)0xfd,(byte)0xf1,(byte)0xec,(byte)0xea,(byte)0xd0,(byte)0x6e,
            (byte)0xd6,(byte)0x46,(byte)0xaa,(byte)0x55,(byte)0xfe,(byte)0x75,(byte)0x71,(byte)0x46},
            "6A014523 D97D2AEB 069EA95A ED083996 CDAC8176 CD213AA8 C2D96A21",
            "3B894160 F690FF32 FD18C857 4F02B8AC 7C63E87B 4FD5A6D3 03C09D55");
        System.out.println();

        System.out.println("*******************************************************");
        System.out.println("* SHSV Examples of the selected long messages test... *");
        System.out.println("*******************************************************");
        tst(3, 2, new byte[] {/* 1304 bits, i.e. 163 bytes */
            (byte)0xf7,(byte)0x8f,(byte)0x92,(byte)0x14,(byte)0x1b,(byte)0xcd,(byte)0x17,(byte)0x0a,
            (byte)0xe8,(byte)0x9b,(byte)0x4f,(byte)0xba,(byte)0x15,(byte)0xa1,(byte)0xd5,(byte)0x9f,
            (byte)0x3f,(byte)0xd8,(byte)0x4d,(byte)0x22,(byte)0x3c,(byte)0x92,(byte)0x51,(byte)0xbd,
            (byte)0xac,(byte)0xbb,(byte)0xae,(byte)0x61,(byte)0xd0,(byte)0x5e,(byte)0xd1,(byte)0x15,
            (byte)0xa0,(byte)0x6a,(byte)0x7c,(byte)0xe1,(byte)0x17,(byte)0xb7,(byte)0xbe,(byte)0xea,
            (byte)0xd2,(byte)0x44,(byte)0x21,(byte)0xde,(byte)0xd9,(byte)0xc3,(byte)0x25,(byte)0x92,
            (byte)0xbd,(byte)0x57,(byte)0xed,(byte)0xea,(byte)0xe3,(byte)0x9c,(byte)0x39,(byte)0xfa,
            (byte)0x1f,(byte)0xe8,(byte)0x94,(byte)0x6a,(byte)0x84,(byte)0xd0,(byte)0xcf,(byte)0x1f,
            (byte)0x7b,(byte)0xee,(byte)0xad,(byte)0x17,(byte)0x13,(byte)0xe2,(byte)0xe0,(byte)0x95,
            (byte)0x98,(byte)0x97,(byte)0x34,(byte)0x7f,(byte)0x67,(byte)0xc8,(byte)0x0b,(byte)0x04,
            (byte)0x00,(byte)0xc2,(byte)0x09,(byte)0x81,(byte)0x5d,(byte)0x6b,(byte)0x10,(byte)0xa6,
            (byte)0x83,(byte)0x83,(byte)0x6f,(byte)0xd5,(byte)0x56,(byte)0x2a,(byte)0x56,(byte)0xca,
            (byte)0xb1,(byte)0xa2,(byte)0x8e,(byte)0x81,(byte)0xb6,(byte)0x57,(byte)0x66,(byte)0x54,
            (byte)0x63,(byte)0x1c,(byte)0xf1,(byte)0x65,(byte)0x66,(byte)0xb8,(byte)0x6e,(byte)0x3b,
            (byte)0x33,(byte)0xa1,(byte)0x08,(byte)0xb0,(byte)0x53,(byte)0x07,(byte)0xc0,(byte)0x0a,
            (byte)0xff,(byte)0x14,(byte)0xa7,(byte)0x68,(byte)0xed,(byte)0x73,(byte)0x50,(byte)0x60,
            (byte)0x6a,(byte)0x0f,(byte)0x85,(byte)0xe6,(byte)0xa9,(byte)0x1d,(byte)0x39,(byte)0x6f,
            (byte)0x5b,(byte)0x5c,(byte)0xbe,(byte)0x57,(byte)0x7f,(byte)0x9b,(byte)0x38,(byte)0x80,
            (byte)0x7c,(byte)0x7d,(byte)0x52,(byte)0x3d,(byte)0x6d,(byte)0x79,(byte)0x2f,(byte)0x6e,
            (byte)0xbc,(byte)0x24,(byte)0xa4,(byte)0xec,(byte)0xf2,(byte)0xb3,(byte)0xa4,(byte)0x27,
            (byte)0xcd,(byte)0xbb,(byte)0xfb},
            "6B7C3E74 D3810C2B 8CF0F804 56E06C8A 98EF8D25 F995C7F0 DEF4B951",
            "4936AF12 D935484F BFF5CC73 1FCA4796 FC09E1EC CC055CB4 71905F9F");
        System.out.println();

// See also http://csrc.ncsl.nist.gov/cryptval/shs/sha1-vectors.zip

        {
            final int RETRIES = 6;
            final int ITERATIONS = 600;
            final int BLOCKSIZE = 65536;
            byte[] input = new byte[BLOCKSIZE];
            for (int i = BLOCKSIZE; --i >= 0; )
                input[i] = (byte)i;
            long best = 0;
            for (int i = 0; i < 1000; i++) // training for stable measure
                 System.currentTimeMillis();

            for (int retry = 0; retry < RETRIES; retry++) {
                long t0 = System.currentTimeMillis();
                for (int i = ITERATIONS; --i >= 0; );
                long t1 = System.currentTimeMillis();
                for (int i = ITERATIONS; --i >= 0; )
                    hash.engineUpdate(input, 0, BLOCKSIZE);
                long t2 = System.currentTimeMillis();
                long time = (t2 - t1) - (t1 - t0);
                if (retry == 0 || time < best)
                     best = time;
            }
            hash.engineReset();
            double rate = 1000.0 * ITERATIONS * BLOCKSIZE / best;
            System.out.println("Our rate = " +
                               (float)(rate * 8) + " bits/s = " +
                               (float)(rate / (1024 * 1024)) + " Megabytes/s");
            // Java 1.5.0-beta2-b51, on Pentium M 1600MHz:
            // with java -client: our rate = 31.22 Megabytes/s.
            // with java -server: our rate = 26.37 Megabytes/s.

            if (md != null) {
                for (int retry = 0; retry < RETRIES; retry++) {
                    long t0 = System.currentTimeMillis();
                    for (int i = ITERATIONS; --i >= 0; );
                    long t1 = System.currentTimeMillis();
                    for (int i = ITERATIONS; --i >= 0; )
                        md.update(input, 0, BLOCKSIZE);
                    long t2 = System.currentTimeMillis();
                    long time = (t2 - t1) - (t1 - t0);
                    if (retry == 0 || time < best)
                         best = time;
                }
                md.reset();
                rate = 1000.0 * ITERATIONS * BLOCKSIZE / best;
                System.out.println("JCE rate = " +
                                   (float)(rate * 8) + " bits/s = " +
                                   (float)(rate / (1024 * 1024)) + " Megabytes/s");
                // Java 1.5.0-beta2-b51, on Pentium M 1600MHz:
                // with java -client: JCE rate = ??.?? Megabytes/s.
                // with java -server: JCE rate = ??.?? Megabytes/s.
            }
        }
    }

    private static final boolean tst(final int set, final int vector,
                                     final String source,
                                     final String expect,
                                     final String expect2) {
        byte[] input = new byte[source.length()];
        for (int i = 0; i < input.length; i++)
            input[i] = (byte)source.charAt(i);
        return tst(set, vector, input, expect, expect2);
    }

    private static final boolean tst(final int set, final int vector,
                                     final byte[] input,
                                     final String expect,
                                     final String expect2) {
        System.out.print("Set " + set + ", vector# " + vector + ": ");
        hash.engineUpdate(input, 0, input.length);
        if (md != null)
            md.update(input, 0, input.length);
        return tstResult(expect, expect2);
    }

    private static final boolean tst(final int set, final int vector,
                                     final int times, final String source,
                                     final String expect,
                                     final String expect2) {
        byte[] input = new byte[source.length()];
        for (int i = 0; i < input.length; i++)
            input[i] = (byte)source.charAt(i);
        System.out.print("Set " + set + ", vector# " + vector + ": ");
        for (int i = 0; i < times; i++)  {
            hash.engineUpdate(input, 0, input.length);
            if (md != null)
                md.update(input, 0, input.length);
        }
        return tstResult(expect, expect2);
    }

    private static final boolean tstResult(String expect, String expect2) {
        byte[] digest;
        String result1, result2 = null;
        result1 = toHex(digest = hash.engineDigest());
        if (md != null)
            result2 = toHex(digest = md.digest());
        expect = expect.toUpperCase();
        if (!result1.equals(expect) || md != null && !result2.equals(expect)) {
            System.out.println("**************** WRONG ***************");
            System.out.println("expect SHA=" + expect);
            System.out.println("our    SHA=" + result1);
            if (md != null)
                System.out.println("JCE    SHA=" + result2);
            return false;
        }
        hash.engineUpdate(digest, 0, digest.length);
        if (md != null)
            md.update(digest, 0, digest.length);
        result1 = toHex(digest = hash.engineDigest());
        if (md != null)
            result2 = toHex(digest = md.digest());
        expect2 = expect2.toUpperCase();
        if (!result1.equals(expect2) || md != null && !result2.equals(expect2)) {
            System.out.println("**************** WRONG ***************");
            System.out.println("expect SHA(SHA)=" + expect2);
            System.out.println("our    SHA(SHA)=" + result1);
            if (md != null)
                System.out.println("JCE    SHA(SHA)=" + result2);
            return false;
        } else if (md != null) md.reset();
        System.out.println("OK");
        return true;
    }

    private static final String toHex(final byte[] bytes) {
        StringBuffer buf = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            if ((i & 3) == 0 && i != 0)
               buf.append(' ');
            buf.append(HEX.charAt((bytes[i] >> 4) & 0xF))
               .append(HEX.charAt( bytes[i]       & 0xF));
        }
        return buf.toString();
    }
    private static final String HEX = "0123456789ABCDEF";
}

