/**<PRE>
 * SHA1.java - An implementation of the SHA-1 Algorithm
 * This version integrated into Freenet by Ian Clarke (02-02-2000) 
 * (i.clarke@dynamicblue.com) from a previous public domain version by 
 * Chuck McManis (cmcmanis@netcom.com) which was public domain
 * Tweaked by Mr.Tines<tines@windsong.demon.co.uk> for pegwit, June 1997
 * - added method 'frob()' which wipes the contents, so as to match
 * the bizarre behaviour of Pegwit's double barreled hashing.
 *
 * Based on the C code that Steve Reid wrote his header
 * was :
 *      SHA-1 in C
 *      By Steve Reid <steve@edmweb.com>
 *      100% Public Domain
 *
 *      Test Vectors (from FIPS PUB 180-1)
 *      "abc"
 *      A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
 *      "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
 *      84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
 *      A million repetitions of "a"
 *      34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
 </pre>*/
package freenet.crypt;
import java.util.Random;

import freenet.support.HexUtil;

/**
 * This is a simple port of Steve Reid's SHA-1 code into Java.
 * I've run his test vectors through the code and they all pass.
 */
public final class SHA1 implements Digest {

    protected static boolean alwaysThisOne = false;
    
    public static Digest getInstance(boolean needProgressive) {
	if(alwaysThisOne) needProgressive = true;
	if(needProgressive) return new SHA1();
	else try {
	    return new JavaSHA1();
	} catch (java.security.NoSuchAlgorithmException e) {
	    alwaysThisOne = true;
	    return new SHA1();
	} catch (Exception e) {
	    return new SHA1();
	}
    }
    
    public static Digest getInstance() {
	return getInstance(false);
    }
    
    public final int digestSize() {
        return 160;
    }
    
    
    private int state[] = new int[5];
    private long count;
    private boolean digestValid = false;
    private byte[] digestBits;
    private boolean NSA = true;

    /**
     * Retrieves the value of a hash.
     * @param digest int[] into which to place 5 elements
     * @param offset index of first of the 5 elements
     */
    public final void extract(int[] digest, int offset) {
        for(int i=0; i<5; ++i) {
            digest[i+offset] = 
                ((digestBits[4*i+0]<<24) & 0xFF000000) | 
                ((digestBits[4*i+1]<<16) & 0x00FF0000) |
                ((digestBits[4*i+2]<< 8) & 0x0000FF00) | 
                ((digestBits[4*i+3]    ) & 0x000000FF);
        }
    }

    /**
     * Variant constructor
     * @param b true for SHA-1, false for the original SHA-0
     */
    public SHA1(boolean b) {
        count = 0;
        digestValid = false;
        NSA = b;
        init();
    }
    
    /**
     * Simple constructor for SHA-1
     */
    public SHA1() {
        this(true);
    }

    /*
     * The following array forms the basis for the transform
     * buffer. Update puts bytes into this buffer and then
     * transform adds it into the state of the digest.
     */
    private int block[] = new int[16];
    private int blockIndex;

    /*
     * These functions are taken out of #defines in Steve's
     * code. Java doesn't have a preprocessor so the first
     * step is to just promote them to real methods.
     * Later we can optimize them out into inline code,
     * note that by making them final some compilers will
     * inline them when given the -O flag.
     */
    private final int rol(int value, int bits) {
        int q = (value << bits) | (value >>> (32 - bits));
        return q;
    }

    private final int blk0(int i) {
        block[i] = (rol(block[i],24)&0xFF00FF00) |
         (rol(block[i],8)&0x00FF00FF);
        return block[i];
    }

    private final int blk(int i) {
        block[i&15] = block[(i+13)&15]^block[(i+8)&15]^
                          block[(i+2)&15]^block[i&15];
        if(NSA)
        {   // this makes it SHA-1
            block[i&15] = rol(block[i&15], 1);
        }
        return (block[i&15]);
    }

    private final void R0(int data[], int v, int w, int x , int y, int z, int i) {
        data[z] += ((data[w] & (data[x] ^ data[y] )) ^ data[y]) +
                                blk0(i) + 0x5A827999 + rol(data[v] ,5);
        data[w] = rol(data[w], 30);
    }

    private final void R1(int data[], int v, int w, int x, int y, int z, int i) {
        data[z] += ((data[w] & (data[x] ^ data[y])) ^ data[y]) +
                                blk(i) + 0x5A827999 + rol(data[v] ,5);
        data[w] = rol(data[w], 30);
    }

    private final void R2(int data[], int v, int w, int x, int y, int z, int i) {
        data[z] += (data[w] ^ data[x] ^ data[y]) +
                                blk(i) + 0x6ED9EBA1 + rol(data[v] ,5);
        data[w] = rol(data[w], 30);
    }

    private final void R3(int data[], int v, int w, int x, int y, int z, int i) {
        data[z] += (((data[w] | data[x]) & data[y]) | (data[w] & data[x])) +
                                blk(i) + 0x8F1BBCDC + rol(data[v] ,5);
        data[w] = rol(data[w], 30);
    }

    private final void R4(int data[], int v, int w, int x, int y, int z, int i) {
        data[z] += (data[w] ^ data[x] ^ data[y]) +
                                blk(i) + 0xCA62C1D6 + rol(data[v] ,5);
        data[w] = rol(data[w], 30);
    }


    /*
     * Steve's original code and comments :
     *
     * blk0() and blk() perform the initial expand.
     * I got the idea of expanding during the round function from SSLeay
     *
     * #define blk0(i) block->l[i]
     * #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
     *   ^block->l[(i+2)&15]^block->l[i&15],1))
     *
     * (R0+R1), R2, R3, R4 are the different operations used in SHA1
     * #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
     * #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
     * #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
     * #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
     * #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
     */

    private int dd[] = new int[5];

    /**
     * Hash a single 512-bit block. This is the core of the algorithm.
     *
     * Note that working with arrays is very inefficent in Java as it
     * does a class cast check each time you store into the array.
     *
     */

    private final void transform() {

        /* Copy context->state[] to working vars */
        dd[0] = state[0];
        dd[1] = state[1];
        dd[2] = state[2];
        dd[3] = state[3];
        dd[4] = state[4];
        /* 4 rounds of 20 operations each. Loop unrolled. */
        R0(dd,0,1,2,3,4, 0); R0(dd,4,0,1,2,3, 1); R0(dd,3,4,0,1,2, 2); R0(dd,2,3,4,0,1, 3);
        R0(dd,1,2,3,4,0, 4); R0(dd,0,1,2,3,4, 5); R0(dd,4,0,1,2,3, 6); R0(dd,3,4,0,1,2, 7);
        R0(dd,2,3,4,0,1, 8); R0(dd,1,2,3,4,0, 9); R0(dd,0,1,2,3,4,10); R0(dd,4,0,1,2,3,11);
        R0(dd,3,4,0,1,2,12); R0(dd,2,3,4,0,1,13); R0(dd,1,2,3,4,0,14); R0(dd,0,1,2,3,4,15);
        R1(dd,4,0,1,2,3,16); R1(dd,3,4,0,1,2,17); R1(dd,2,3,4,0,1,18); R1(dd,1,2,3,4,0,19);
        R2(dd,0,1,2,3,4,20); R2(dd,4,0,1,2,3,21); R2(dd,3,4,0,1,2,22); R2(dd,2,3,4,0,1,23);
        R2(dd,1,2,3,4,0,24); R2(dd,0,1,2,3,4,25); R2(dd,4,0,1,2,3,26); R2(dd,3,4,0,1,2,27);
        R2(dd,2,3,4,0,1,28); R2(dd,1,2,3,4,0,29); R2(dd,0,1,2,3,4,30); R2(dd,4,0,1,2,3,31);
        R2(dd,3,4,0,1,2,32); R2(dd,2,3,4,0,1,33); R2(dd,1,2,3,4,0,34); R2(dd,0,1,2,3,4,35);
        R2(dd,4,0,1,2,3,36); R2(dd,3,4,0,1,2,37); R2(dd,2,3,4,0,1,38); R2(dd,1,2,3,4,0,39);
        R3(dd,0,1,2,3,4,40); R3(dd,4,0,1,2,3,41); R3(dd,3,4,0,1,2,42); R3(dd,2,3,4,0,1,43);
        R3(dd,1,2,3,4,0,44); R3(dd,0,1,2,3,4,45); R3(dd,4,0,1,2,3,46); R3(dd,3,4,0,1,2,47);
        R3(dd,2,3,4,0,1,48); R3(dd,1,2,3,4,0,49); R3(dd,0,1,2,3,4,50); R3(dd,4,0,1,2,3,51);
        R3(dd,3,4,0,1,2,52); R3(dd,2,3,4,0,1,53); R3(dd,1,2,3,4,0,54); R3(dd,0,1,2,3,4,55);
        R3(dd,4,0,1,2,3,56); R3(dd,3,4,0,1,2,57); R3(dd,2,3,4,0,1,58); R3(dd,1,2,3,4,0,59);
        R4(dd,0,1,2,3,4,60); R4(dd,4,0,1,2,3,61); R4(dd,3,4,0,1,2,62); R4(dd,2,3,4,0,1,63);
        R4(dd,1,2,3,4,0,64); R4(dd,0,1,2,3,4,65); R4(dd,4,0,1,2,3,66); R4(dd,3,4,0,1,2,67);
        R4(dd,2,3,4,0,1,68); R4(dd,1,2,3,4,0,69); R4(dd,0,1,2,3,4,70); R4(dd,4,0,1,2,3,71);
        R4(dd,3,4,0,1,2,72); R4(dd,2,3,4,0,1,73); R4(dd,1,2,3,4,0,74); R4(dd,0,1,2,3,4,75);
        R4(dd,4,0,1,2,3,76); R4(dd,3,4,0,1,2,77); R4(dd,2,3,4,0,1,78); R4(dd,1,2,3,4,0,79);
        /* Add the working vars back into context.state[] */
        state[0] += dd[0];
        state[1] += dd[1];
        state[2] += dd[2];
        state[3] += dd[3];
        state[4] += dd[4];
    }
    
    /**
     * zero the count and state arrays; used to support
     * Pegwit's anomalous 2-barrel hashing
     */
    public final void frob() { // Pegwit's little anomaly
        count=0;
        state[0] = state[1] = state[2] = state[3] = state[4] = 0;
    }

    /**
     *
     * Initializes new context
     */
    protected final void init() {
        /* SHA1 initialization constants */
        state[0] = 0x67452301;
        state[1] = 0xEFCDAB89;
        state[2] = 0x98BADCFE;
        state[3] = 0x10325476;
        state[4] = 0xC3D2E1F0;
        count = 0;
        digestBits = new byte[20];
        digestValid = false;
        blockIndex = 0;
    }

    /**
     * Add one byte to the digest. When this is implemented
     * all of the abstract class methods end up calling
     * this method for types other than bytes.
     * @param b byte to add
     */
    public final void update(byte b) {
        int mask = (blockIndex & 3) << 3;
        count += 8;
        block[blockIndex >> 2] &= ~(0xff << mask);
        block[blockIndex >> 2] |= (b & 0xff) << mask;
        blockIndex++;
        if (blockIndex == 64) {
            transform();
            blockIndex = 0;
        }
    }

    /**
     * Add many bytes to the digest.
     * @param data byte data to add
     * @param offset start byte
     * @param length number of bytes to hash
     */
    public final void update(byte[] data, int offset, int length) {
        for (int i=0; i<length; ++i)
            update(data[offset+i]);
    }

    public final void update(byte[] data) {
        update(data, 0, data.length);
    }

    /**
     * Write completed digest into the given buffer.
     * @param reset If true, the hash function is reinitialized
     */
    public final void digest(boolean reset, byte[] buffer, int offset) {
        finish();
        System.arraycopy(digestBits, 0, buffer, offset, digestBits.length);
        if (reset)
            init();
    }

    /**
     * Return completed digest.
     * @return the byte array result
     * @param reset If true, the hash function is reinitialized
     */
    public final byte[] digest(boolean reset) {
        byte[] out = new byte[20];
        digest(reset, out, 0);
        return out;
    }

    /**
     * Return completed digest.
     * @return the byte array result
     */
    public final byte[] digest() {
        return digest(true);
    }

    /**
     * Complete processing on the message digest.
     */
    protected final void finish() {
        byte bits[] = new byte[8];
        int i, j;
        for (i = 0; i < 8; i++) {
            bits[i] = (byte)((count >>> (((7 - i) << 3))) & 0xff);
        }

        update((byte) 128);
        while (blockIndex != 56)
            update((byte) 0);
        // This should cause a transform to happen.
        for(i=0; i<8; ++i) update(bits[i]);
        for (i = 0; i < 20; i++) {
            digestBits[i] = (byte)
                ((state[i>>2] >>> ((3-(i & 3)) << 3) ) & 0xff);
        }
        digestValid = true;
    }

    /**
     * Print out the digest in a form that can be easily compared
     * to the test vectors.
     */
    protected String digout() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 20; i++) {
            char c1, c2;

            c1 = (char) ((digestBits[i] >>> 4) & 0xf);
            c2 = (char) (digestBits[i] & 0xf);
            c1 = (char) ((c1 > 9) ? 'A' + (c1 - 10) : '0' + c1);
            c2 = (char) ((c2 > 9) ? 'A' + (c2 - 10) : '0' + c2);
            sb.append(c1);
            sb.append(c2);
        }
        return sb.toString();
    }


    /**
     * test driver
     */
    public static void main(String[] args) {
        SHAselfTest();
        SHAbenchTest();
    }
    
    public static void SHAbenchTest() {
	try {
	    JavaSHA1 s = new JavaSHA1();
	    genericBenchTest(s);
	    try {
		JavaSHA1 s1 = (JavaSHA1)(s.clone());
		System.err.println("Cloned successfully");
		genericBenchTest(s1);
	    } catch (CloneNotSupportedException e) {
		System.err.println("Couldn't clone");
	    }
	} catch (Exception e) {
	    System.err.println("Couldn't run benchmark for JavaSHA1");
	}
	SHA1 sha1 = new SHA1(true);
	genericBenchTest(sha1);
    }
    
    public static void genericBenchTest(Digest sha1) {
        System.out.println("\nBegin benchmark");
        long size = 4096;
        long count = 100000;
        byte data[] = createDummyData((int)size);
        long startSameUpdate = System.currentTimeMillis();
        for (int i = 0; i < count; i++) 
            sha1.update(data);
        long endSameUpdate = System.currentTimeMillis();
        byte digested[] = sha1.digest();
        long afterDigested = System.currentTimeMillis();
        long updateTime = endSameUpdate - startSameUpdate;
        long speed = 0;
        long eachTime = 0;
        if (updateTime > 0) {
            speed = size*count*1000 / updateTime;
            eachTime = count / updateTime;
        }
        System.out.println("SHA update " + count + " times for the same data block of size " + size + " bytes:");
        System.out.println(updateTime + "ms [" + speed + "bytes/second, " + eachTime + "updates/ms]\n");
        
        count = 1000;
        byte data2[][] = new byte[(int)count][];
        for (int i = 0; i < count; i++)
            data2[i] =  createDummyData((int)size);
        long differentStartTime = System.currentTimeMillis();
        for (int i = 0; i < count; i++) 
            sha1.update(data2[i]);
        long endDifferentUpdate = System.currentTimeMillis();
        digested = sha1.digest();
        afterDigested = System.currentTimeMillis();
        updateTime = endDifferentUpdate - differentStartTime;
        speed = 0;
        eachTime = 0;
        if (updateTime > 0) {
            speed = size*count*1000 / updateTime;
            eachTime = count / updateTime;
        }
        System.out.println("SHA update " + count + " times for different data blocks of size " + size + " bytes:");
        System.out.println(updateTime + "ms [" + speed + "bytes/second, " + eachTime + "ms/update]");
        
        System.out.println("\nEnd benchmark");
    }
    
    private static byte[] createDummyData(int size) {
        Random rand = new Random();
        byte buf[] = new byte[size];
        rand.nextBytes(buf);
        return buf;
    }
    
    public static void SHAselfTest() {
	System.err.println("Testing JavaSHA1:");
	try {
	    JavaSHA1 js = new JavaSHA1();
	    SHAselfTest(js);
	} catch (Exception e) {
	    System.err.println("Caught exception "+e+
			       " trying to test JavaSHA1");
	}
	System.err.println("Testing SHA1:");
	SHA1 s = new SHA1(true);
	SHAselfTest(s);
    }
    
    /**
     * perfoms a self-test - spits out the test vector outputs on System.out
     */
    public static void SHAselfTest(Digest s) {
        int i, j;
        // This line may be safely deleted, its to make it easy to see
        // the output of the program.
        System.out.println("SHA-1 Test PROGRAM.");
        System.out.println("This code runs the test vectors through the code.");

/*      "abc"
        A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D */

        System.out.println("First test is 'abc'");
        String z = "abc";
        //s.init();
        s.update((byte) 'a');
        s.update((byte) 'b');
        s.update((byte) 'c');
        System.out.println(HexUtil.bytesToHex(s.digest()).toUpperCase());
        System.out.println("A9993E364706816ABA3E25717850C26C9CD0D89D");

/*      "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
        84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 */

        System.out.println("Next Test is 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'");
        z = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
        //s.init(); - initted by digest above
        for (i=0; i<z.length(); ++i) {
            s.update((byte) z.charAt(i));
        }
        System.out.println(HexUtil.bytesToHex(s.digest()).toUpperCase());
        System.out.println("84983E441C3BD26EBAAE4AA1F95129E5E54670F1");
	
	/*      A million repetitions of "a"
        34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */
        long startTime = 0 - System.currentTimeMillis();
	
        System.out.println("Last test is 1 million 'a' characters.");
        //s.init(); - initted by digest above
        for (i = 0; i < 1000000; i++)
            s.update((byte) 'a');
        //s.finish(); - digest() finishes
        System.out.println(HexUtil.bytesToHex(s.digest()).toUpperCase());
        System.out.println("34AA973CD4C4DAA4F61EEB2BDBAD27316534016F");
        startTime += System.currentTimeMillis();
        double d = startTime/1000.0;
        System.out.println(" done, elapsed time = "+d);
    }
}


