// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)Checksum.java, 1.12, 11/30/96





package marimba.util;





import java.io.*;





import marimba.protocol.*;


import marimba.transmitter.*;


import marimba.io.FastInputStream;


import marimba.io.FastOutputStream;





/**


 * This represents a checksum for an array of bytes or a file.


 * The checksum is a 128 bit value and is computed using the


 * MD5 algorithm. The checksum can be converted to a 27 character


 * string representation (two unsigned base 32 numbers).


 *


 * @author	Jonathan Payne


 * @author	Arthur van Hoff


 * @version	1.12, 11/30/96


 */





public class Checksum {


    /** Unit size in bytes that checksums are calculated by. */


    public static final int BLOCK = 64;


    public static final int BLOCK_MASK = (BLOCK - 1);





    /** The checksum. */


    protected long checksum1;


    protected long checksum2;





    /**


     * Create a checksum from a string. The string


     * must be 27 characters long.


     */


    public Checksum(String val) {


	char str[] = new char[27];


	val.getChars(0, 27, str, 0);


	parse(str, 0, 27);


    }





    /**


     * Create a checksum from an array of characaters. The array


     * must be 27 characters long.


     */


    public Checksum(char str[]) {


	parse(str, 0, str.length);


    }





    /**


     * Create a checksum from an array of characaters. The length


     * must be 27 characters.


     */


    public Checksum(char str[], int off, int len) {


	parse(str, off, len);


    }





    /**


     * Create a checksum from an array of two longs.


     */


    public Checksum(long cs[]) {


	checksum1 = cs[0];


	checksum2 = cs[1];


    }





    /**


     * Create a checksum from two longs.


     */


    public Checksum(long checksum1, long checksum2) {


	this.checksum1 = checksum1;


	this.checksum2 = checksum2;


    }





    /**


     * Read a checksum from an input stream.


     */


    public Checksum(FastInputStream in) {


	this(in.readLong(), in.readLong());


    }





    /**


     * Create an empty checksum.


     */


    protected Checksum() {}





    /**


     * Get the first 64 bits of the checksum.


     */


    public final long getChecksum1() {


	return checksum1;


    }





    /**


     * Get the second 64 bits of the checksum.


     */


    public final long getChecksum2() {


	return checksum2;


    }





    /**


     * Check if the checksum is not 0.


     */


    public boolean valid() {


	return (checksum1 != 0 && checksum2 != 0);


    }





    /**


     * Compute a hash code for the checksum.


     */


    public int hashCode() {


	return (int) ((checksum1 >>> 32) ^ (checksum1 & 0xFFFFFFFF) ^


		      (checksum2 >>> 32) ^ (checksum2 & 0xFFFFFFFF));


    }





    /**


     * Check if a checksum is equal to two longs.


     */


    public boolean equals(long checksum1, long checksum2) {


	return (this.checksum1 == checksum1 && this.checksum2 == checksum2);


    }





    /**


     * Check if a checksum is equal to another object.


     */


    public boolean equals(Object o) {


	if (o == this) {


	    return true;


	} else if (o instanceof Checksum) {


	    Checksum c = (Checksum) o;


	    return c.checksum1 == checksum1 && c.checksum2 == checksum2;


	}


	return false;


    }





    public Checksum combine(Checksum other) {


	return new Checksum((checksum1 * 37) ^ (other.checksum1 * 53),


			    (checksum2 * 37) ^ (other.checksum2 * 53));


    }





    /**


     * Set the checksum to the value of another checksum.


     * Use with care.


     */


    protected void setChecksum0(Checksum cs) {


	checksum1 = cs.checksum1;


	checksum2 = cs.checksum2;


    }





    /**


     * Return an array of two longs.


     */


    public long[] toArray() {


	long data[] = {checksum1, checksum2};


	return data;


    }





    /**


     * Write the checksum to an output stream.


     */


    public void writeChecksum(FastOutputStream out) {


	out.writeLong(checksum1);


	out.writeLong(checksum2);


    }





    final static char map[] = {


	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',


	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',


	'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',


	'U', 'V'


    };





    /**


     * Convert the checksum to a 27 character string.


     */


    public String toString() {


	char str[] = new char[27];





	long v = checksum1;


	for (int i = 13 ; i-- > 0 ; v >>>= 5) {


	    str[i] = map[(int)(v & 0x1F)];


	}


	str[13] = 'x';


	v = checksum2;


	for (int i = 27 ; i-- > 14 ; v >>>= 5) {


	    str[i] = map[(int)(v & 0x1F)];


	}


	return new String(str);


    }





    /**


     * Convert the checksum to a hexadecimal string.


     */


    public String toHexString() {


	char str[] = new char[32];





	long v = checksum1;


	for (int i = 0 ; i < 16 ; i += 2, v >>>= 8) {


	    str[i] = map[(int)((v >> 4) & 0xF)];


	    str[i+1] = map[(int)(v & 0xF)];


	}





	v = checksum2;


	for (int i = 16 ; i < 32 ; i += 2, v >>>= 8) {


	    str[i] = map[(int)((v >> 4) & 0xF)];


	    str[i+1] = map[(int)(v & 0xF)];


	}





	return new String(str);


    }





    /**


     * Parse a 27 character string.


     */


    void parse(char str[], int off, int len) {


	if (len != 27) {


	    throw new IllegalArgumentException("length must be 27 chars");


	}


	if (str[off + 13] != 'x') {


	    throw new IllegalArgumentException("format exception");


	}





	long v = 0;


	for (int i = 0 ; i < 13 ; i++) {


	    int ch = str[i];


	    switch (ch) {


	      case '0': case '1': case '2': case '3': case '4':


	      case '5': case '6': case '7': case '8': case '9':


		v = (v << 5L) + (ch - '0');


		break;


	      case 'A': case 'B': case 'C': case 'D': case 'E':


	      case 'F': case 'G': case 'H': case 'I': case 'J':


	      case 'K': case 'L': case 'M': case 'N': case 'O':


	      case 'P': case 'Q': case 'R': case 'S': case 'T':


	      case 'U': case 'V':


		v = (v << 5L) + (ch - 'A') + 10;


		break;


	      default:


		throw new IllegalArgumentException("number format exception");


	    }


	}


	checksum1 = v;





	v = 0;


	for (int i = 14 ; i < 27 ; i++) {


	    int ch = str[i];


	    switch (ch) {


	      case '0': case '1': case '2': case '3': case '4':


	      case '5': case '6': case '7': case '8': case '9':


		v = (v << 5L) + (ch - '0');


		break;


	      case 'A': case 'B': case 'C': case 'D': case 'E':


	      case 'F': case 'G': case 'H': case 'I': case 'J':


	      case 'K': case 'L': case 'M': case 'N': case 'O':


	      case 'P': case 'Q': case 'R': case 'S': case 'T':


	      case 'U': case 'V':


		v = (v << 5L) + (ch - 'A') + 10;


		break;


	      default:


		throw new IllegalArgumentException("number format exception");


	    }


	}


	checksum2 = v;


    }





    // Constant multipliers


    private static final int S11 = 7;


    private static final int S12 = 12;


    private static final int S13 = 17;


    private static final int S14 = 22;


    private static final int S21 = 5;


    private static final int S22 = 9;


    private static final int S23 = 14;


    private static final int S24 = 20;


    private static final int S31 = 4;


    private static final int S32 = 11;


    private static final int S33 = 16;


    private static final int S34 = 23;


    private static final int S41 = 6;


    private static final int S42 = 10;


    private static final int S43 = 15;


    private static final int S44 = 21;





    /**


     * Create a new digest


     */


    public static int[] MD5Digest() {


	int digest[] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};


	return digest;


    }





    /**


     * Add 64 bytes to the digest. The digest (the checksum) consists


     * of 4 integers.


     */


    public static void MD5Add(byte buf[], int i, int digest[]) {


	if ((Environment.desktop != null) && Environment.desktop.MD5Add(buf, i, digest)) {


	    return;


	}





	// Decode the 64 bytes into 16 integers.


	int x0  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);





	int x1  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);





	int x2  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x3  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);





	int x4  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x5  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x6  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x7  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);





	int x8  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x9  = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x10 = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)


	    |((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x11 = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)


	    |((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);





	int x12 = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x13 = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x14 = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);


	int x15 = (buf[i++] & 0xFF) | ((buf[i++] & 0xFF) << 8)|


	    ((buf[i++] & 0xFF) << 16) | ((buf[i++] & 0xFF) << 24);





	// Get a local copy of the digest


	int a = digest[0];


	int b = digest[1];


	int c = digest[2];


	int d = digest[3];





	// Round 1


	a += ((b & c) | ((~b) & d)) + x0 + 0xd76aa478;


	a = ((a << S11) | (a >>>(32 - S11))) + b; 


	d += ((a & b) | ((~a) & c)) + x1 + 0xe8c7b756;


	d = ((d << S12) | (d >>>(32 - S12))) + a; 


	c += ((d & a) | ((~d) & b)) + x2 + 0x242070db;


	c = ((c << S13) | (c >>>(32 - S13))) + d; 


	b += ((c & d) | ((~c) & a)) + x3 + 0xc1bdceee;


	b = ((b << S14) | (b >>>(32 - S14))) + c; 





	a += ((b & c) | ((~b) & d)) + x4 + 0xf57c0faf;


	a = ((a << S11) | (a >>>(32 - S11))) + b; 


	d += ((a & b) | ((~a) & c)) + x5 + 0x4787c62a;


	d = ((d << S12) | (d >>>(32 - S12))) + a; 


	c += ((d & a) | ((~d) & b)) + x6 + 0xa8304613;


	c = ((c << S13) | (c >>>(32 - S13))) + d; 


	b += ((c & d) | ((~c) & a)) + x7 + 0xfd469501;


	b = ((b << S14) | (b >>>(32 - S14))) + c; 





	a += ((b & c) | ((~b) & d)) + x8 + 0x698098d8;


	a = ((a << S11) | (a >>>(32 - S11))) + b; 


	d += ((a & b) | ((~a) & c)) + x9 + 0x8b44f7af;


	d = ((d << S12) | (d >>>(32 - S12))) + a; 


	c += ((d & a) | ((~d) & b)) + x10 + 0xffff5bb1;


	c = ((c << S13) | (c >>>(32-S13))) + d; 


	b += ((c & d) | ((~c) & a)) + x11 + 0x895cd7be;


	b = ((b << S14) | (b >>>(32-S14))) + c; 





	a += ((b & c) | ((~b) & d)) + x12 + 0x6b901122;


	a = ((a << S11) | (a >>>(32-S11))) + b; 


	d += ((a & b) | ((~a) & c)) + x13 + 0xfd987193;


	d = ((d << S12) | (d >>>(32-S12))) + a; 


	c += ((d & a) | ((~d) & b)) + x14 + 0xa679438e;


	c = ((c << S13) | (c >>>(32-S13))) + d; 


	b += ((c & d) | ((~c) & a)) + x15 + 0x49b40821;


	b = ((b << S14) | (b >>>(32-S14))) + c; 





	// Round 2


	a += ((b & d) | (c & (~d))) + x1 + 0xf61e2562;


	a = ((a << S21) | (a >>>(32 - S21))) + b; 


	d += ((a & c) | (b & (~c))) + x6 + 0xc040b340;


	d = ((d << S22) | (d >>>(32 - S22))) + a; 


	c += ((d & b) | (a & (~b))) + x11 + 0x265e5a51;


	c = ((c << S23) | (c >>>(32-S23))) + d; 


	b += ((c & a) | (d & (~a))) + x0 + 0xe9b6c7aa;


	b = ((b << S24) | (b >>>(32 - S24))) + c; 





	a += ((b & d) | (c & (~d))) + x5 + 0xd62f105d;


	a = ((a << S21) | (a >>>(32 - S21))) + b; 


	d += ((a & c) | (b & (~c))) + x10 + 0x02441453;


	d = ((d << S22) | (d >>>(32-S22))) + a; 


	c += ((d & b) | (a & (~b))) + x15 + 0xd8a1e681;


	c = ((c << S23) | (c >>>(32-S23))) + d; 


	b += ((c & a) | (d & (~a))) + x4 + 0xe7d3fbc8;


	b = ((b << S24) | (b >>>(32 - S24))) + c; 





	a += ((b & d) | (c & (~d))) + x9 + 0x21e1cde6;


	a = ((a << S21) | (a >>>(32 - S21))) + b; 


	d += ((a & c) | (b & (~c))) + x14 + 0xc33707d6;


	d = ((d << S22) | (d >>>(32-S22))) + a; 


	c += ((d & b) | (a & (~b))) + x3 + 0xf4d50d87;


	c = ((c << S23) | (c >>>(32 - S23))) + d; 


	b += ((c & a) | (d & (~a))) + x8 + 0x455a14ed;


	b = ((b << S24) | (b >>>(32 - S24))) + c; 





	a += ((b & d) | (c & (~d))) + x13 + 0xa9e3e905;


	a = ((a << S21) | (a >>>(32-S21))) + b; 


	d += ((a & c) | (b & (~c))) + x2 + 0xfcefa3f8;


	d = ((d << S22) | (d >>>(32 - S22))) + a; 


	c += ((d & b) | (a & (~b))) + x7 + 0x676f02d9;


	c = ((c << S23) | (c >>>(32 - S23))) + d; 


	b += ((c & a) | (d & (~a))) + x12 + 0x8d2a4c8a;


	b = ((b << S24) | (b >>>(32-S24))) + c; 





	// Round 3


	a += ((b ^ c) ^ d) + x5 + 0xfffa3942;


	a = ((a << S31) | (a >>>(32 - S31))) + b; 


	d += ((a ^ b) ^ c) + x8 + 0x8771f681;


	d = ((d << S32) | (d >>>(32 - S32))) + a; 


	c += ((d ^ a) ^ b) + x11 + 0x6d9d6122;


	c = ((c << S33) | (c >>>(32-S33))) + d; 


	b += ((c ^ d) ^ a) + x14 + 0xfde5380c;


	b = ((b << S34) | (b >>>(32-S34))) + c; 





	a += ((b ^ c) ^ d) + x1 + 0xa4beea44;


	a = ((a << S31) | (a >>>(32 - S31))) + b; 


	d += ((a ^ b) ^ c) + x4 + 0x4bdecfa9;


	d = ((d << S32) | (d >>>(32 - S32))) + a; 


	c += ((d ^ a) ^ b) + x7 + 0xf6bb4b60;


	c = ((c << S33) | (c >>>(32 - S33))) + d; 


	b += ((c ^ d) ^ a) + x10 + 0xbebfbc70;


	b = ((b << S34) | (b >>>(32-S34))) + c; 





	a += ((b ^ c) ^ d) + x13 + 0x289b7ec6;


	a = ((a << S31) | (a >>>(32-S31))) + b; 


	d += ((a ^ b) ^ c) + x0 + 0xeaa127fa;


	d = ((d << S32) | (d >>>(32 - S32))) + a; 


	c += ((d ^ a) ^ b) + x3 + 0xd4ef3085;


	c = ((c << S33) | (c >>>(32 - S33))) + d; 


	b += ((c ^ d) ^ a) + x6 + 0x04881d05;


	b = ((b << S34) | (b >>>(32 - S34))) + c; 





	a += ((b ^ c) ^ d) + x9 + 0xd9d4d039;


	a = ((a << S31) | (a >>>(32 - S31))) + b; 


	d += ((a ^ b) ^ c) + x12 + 0xe6db99e5;


	d = ((d << S32) | (d >>>(32-S32))) + a; 


	c += ((d ^ a) ^ b) + x15 + 0x1fa27cf8;


	c = ((c << S33) | (c >>>(32-S33))) + d; 


	b += ((c ^ d) ^ a) + x2 + 0xc4ac5665;


	b = ((b << S34) | (b >>>(32 - S34))) + c; 





	// Round 4


	a += (c ^ (b | (~d))) + x0 + 0xf4292244;


	a = ((a << S41) | (a >>>(32 - S41))) + b; 


	d += (b ^ (a | (~c))) + x7 + 0x432aff97;


	d = ((d << S42) | (d >>>(32 - S42))) + a; 


	c += (a ^ (d | (~b))) + x14 + 0xab9423a7;


	c = ((c << S43) | (c >>>(32-S43))) + d; 


	b += (d ^ (c | (~a))) + x5 + 0xfc93a039;


	b = ((b << S44) | (b >>>(32 - S44))) + c; 





	a += (c ^ (b | (~d))) + x12 + 0x655b59c3;


	a = ((a << S41) | (a >>>(32-S41))) + b; 


	d += (b ^ (a | (~c))) + x3 + 0x8f0ccc92;


	d = ((d << S42) | (d >>>(32 - S42))) + a; 


	c += (a ^ (d | (~b))) + x10 + 0xffeff47d;


	c = ((c << S43) | (c >>>(32-S43))) + d; 


	b += (d ^ (c | (~a))) + x1 + 0x85845dd1;


	b = ((b << S44) | (b >>>(32 - S44))) + c; 





	a += (c ^ (b | (~d))) + x8 + 0x6fa87e4f;


	a = ((a << S41) | (a >>>(32 - S41))) + b; 


	d += (b ^ (a | (~c))) + x15 + 0xfe2ce6e0;


	d = ((d << S42) | (d >>>(32-S42))) + a; 


	c += (a ^ (d | (~b))) + x6 + 0xa3014314;


	c = ((c << S43) | (c >>>(32 - S43))) + d; 


	b += (d ^ (c | (~a))) + x13 + 0x4e0811a1;


	b = ((b << S44) | (b >>>(32-S44))) + c; 





	a += (c ^ (b | (~d))) + x4 + 0xf7537e82;


	a = ((a << S41) | (a >>>(32 - S41))) + b; 


	d += (b ^ (a | (~c))) + x11 + 0xbd3af235;


	d = ((d << S42) | (d >>>(32-S42))) + a; 


	c += (a ^ (d | (~b))) + x2 + 0x2ad7d2bb;


	c = ((c << S43) | (c >>>(32 - S43))) + d; 


	b += (d ^ (c | (~a))) + x9 + 0xeb86d391;


	b = ((b << S44) | (b >>>(32 - S44))) + c;  





	// Add to the digest


	digest[0] += a;


	digest[1] += b;


	digest[2] += c;


	digest[3] += d;


    }





    /**


     * Finish computation of the digest. Must pass the remaining bytes (always


     * fewer than 64). This method return the checksum.


     */


    public static Checksum MD5Finish(byte data[], int off, int len, long total, int digest[]) {


	byte buf[] = new byte[128];


	System.arraycopy(data, off, buf, 0, len);


	buf[len++] = (byte)0x80;


	len = (len < 56) ? 56: 120;


	total *= 8L;





	// append the length in bits


	buf[len++] = (byte)((total >>> 0) & 0xFF);


	buf[len++] = (byte)((total >>> 8) & 0xFF);


	buf[len++] = (byte)((total >>> 16) & 0xFF);


	buf[len++] = (byte)((total >>> 24) & 0xFF);


	buf[len++] = (byte)((total >>> 32) & 0xFF);


	buf[len++] = (byte)((total >>> 40) & 0xFF);


	buf[len++] = (byte)((total >>> 48) & 0xFF);


	buf[len++] = (byte)((total >>> 56) & 0xFF);





	// finish computation of the digest


	MD5Add(buf, 0, digest);


	if (len > 64) {


	    MD5Add(buf, 64, digest);


	}





	// Convert the digest to a checksum


	return new Checksum((digest[1] << 32L) | (digest[0] & 0xFFFFFFFFL),


			    (digest[3] << 32L) | (digest[2] & 0xFFFFFFFFL));


    }





    /**


     * Compute the MD5 checksum for a buffer of bytes.


     */


    public static Checksum MD5(byte data[], int offset, int len) {


	int digest[] = MD5Digest();


	int length = len;





	while (true) {


	    if (length < 64) {


		return MD5Finish(data, offset, length, len, digest);


	    }


	    MD5Add(data, offset, digest);


	    offset += 64;


	    length -= 64;


	}


    }





    /**


     * Compute the MD5 checksum for an input stream. The stream is


     * read 64 bytes at a time until the EOF is reached.


     */


    public static Checksum MD5(InputStream in) throws IOException {


	int digest[] = MD5Digest();


	byte buf[] = new byte[64];


	long total = 0;





	while (true) {


	    for (int n = 0, m ; n < 64 ; n += m) {


		if ((m = in.read(buf, n, 64-n)) <= 0) {


		    return MD5Finish(buf, 0, n, total + n, digest);


		}


	    }


	    total += 64;


	    MD5Add(buf, 0, digest);


	}


    }





    /**


     * For testing purposes only.


     */


    public static void main(String argv[]) throws IOException {


	byte buf[] = new byte[10*1024 - 1231];


	for (int i = 0 ; i < argv.length ; i++) {


	    FastInputStream fin = new FastInputStream(argv[i]);


	    Checksum sum = Checksum.MD5(fin);


	    System.out.println("CHECKSUM1: " + sum.toString());


	    System.out.println("CHECKSUM2: " + sum.toHexString());


	    fin.close();


	}


    }


}


