/* @(#)MD5Test.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.MD5", "org.rodage.pub.java.security.MD5");
 */
//package org.rodage.pub.java.security;
import java.security.*;
//--+---+1--+---+--2+---+---+3--+---+--4+---+---+5--+---+--6+---+---+7--+---+--
//34567890123456789012345678901234567890123456789012345678901234567890123456789

public class MD5Test {
    
    private static final MD5 hash = new MD5();
    private static final MessageDigest md = getMD5();
    private static final MessageDigest getMD5() {
        try {
            return MessageDigest.getInstance("MD5");
        } catch(NoSuchAlgorithmException nsae) {}
        System.out.println("No MD5 algorithm in local JCE Security Providers");
        return null;
    }

    public static void main(String args[]) {
// http://www.ietf.org/rfc/rfc1321.txt, Section A.5
        System.out.println("**********************************");
        System.out.println("* Basic RFC 1321 test vectors... *");
        System.out.println("**********************************");
        tst(1, 1,
            "",
            "d41d8cd9 8f00b204 e9800998 ecf8427e",//MD5(m)
            "59ADB24E F3CDBE02 97F05B39 5827453F");//MD5(MD5(m))
        tst(1, 2,
            "a",
            "0cc175b9 c0f1b6a8 31c399e2 69772661",//MD5(m)
            "B6FF9A06 B7E20BCB 2858C5B8 FF744AEA");//MD5(MD5(m))
        tst(1, 3,
            "abc",
            "90015098 3cd24fb0 d6963f7d 28e17f72",//MD5(m)
            "AF5DA9F4 5AF7A300 E3ADED97 2F8FF687");//MD5(MD5(m))
        tst(1, 4,
            "message digest",
            "f96b697d 7cb7938d 525a2f31 aaf161d0",//MD5(m)
            "77199FBA C311931F 6849B99B 8610ED77");//MD5(MD5(m))
        tst(1, 5,
            "abcdefghijklmnopqrstuvwxyz",
            "c3fcd3d7 6192e400 7dfb496c ca67e13b",//MD5(m)
            "1C8003CE 2CE5966D A43E92DC D7E35656");//MD5(MD5(m))
        tst(1, 6,
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
            "d174ab98 d277d9f5 a5611c2c 9f419d9f",//MD5(m)
            "793F325A 002C2514 0DCC4B02 5FB4E8E9");//MD5(MD5(m))
        tst(1, 7,
            "123456789012345678901234567890123456789012345678901234567890" +
            "12345678901234567890",
            "57edf4a2 2be3c955 ac49da2e 2107b67a",//MD5(m)
            "D66E6E66 FF4A8DC6 F3665740 268FE1BC");//MD5(MD5(m))
        System.out.println();

        {
            final int RETRIES = 6;
            final int ITERATIONS = 1200;
            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 beta2-b51, on Pentium M 1600MHz:
            // with java -client: Our rate = 94.82 Megabytes/s.
            // with java -server: Our rate = 108.54 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 beta-b32c, on Athlon XP 1800+:
                // with java -client: JCE rate = 59.90 Megabytes/s.
                // with java -server: JCE rate = 90.25 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 MD5=" + expect);
            System.out.println("our    MD5=" + result1);
            if (md != null)
                System.out.println("JCE    MD5=" + 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 MD5(MD5)=" + expect2);
            System.out.println("our    MD5(MD5)=" + result1);
            if (md != null)
                System.out.println("JCE    MD5(MD5)=" + 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 + (bytes.length - 1) / 4);
        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";
}

