// 6.857 Problem Set 1 
// hash function implementation in Java
// abhi shelat [abhi@mit.edu] 
//
// this file borrows code from:
//
// RC5_32 implementation
// Ronald L. Rivest
// November 30, 1997

import java.lang.Exception;
import java.lang.Math;

public class rc5hash 
{

	// the notation  /*w*/int  denotes a declaration for a "word"
	// such a declaration must be modifed to move to another word size

    // constants for 32-bit words
    private final static int w1 = 16;                // number of bits in word
    private final static int w8 = 2;                 // number of 8-bit bytes in a word
    private final static /*w*/short P = (short)0xb7e1;    // magic constant P
    private final static /*w*/short Q = (short)0x9e37;    // magic constant Q

    // now the data members
    private int rounds = 12;        // number of rounds
    private /*w*/short[] S;         // subkey words
    private int Sn;                 // # of subkey words
    private /*w*/short[] L;         // intermediate words for subkey computation
    private int Ln;                 // # of words for subkey computation

    // rotate a word left by b bits
    private static final /*w*/short rol(/*w*/short x, int b)
    {
		short j = (short)(x<<b);
		short k = (short)((x&0xffff)>>>(w1-b));
		short l = (short)(j | k);
		short t = (short)((x<<b) | ((x&0xffff)>>>(w1-b)));
		if (t!=l) { System.out.println(t + " != " + l + " ERROR"); 
		System.exit(1);
		}
		
        return  l;
    }

	private int can(short x) { return (x<0)? x+65536 : x; }

    // rotate a word right by b bits
    private static final /*w*/short ror(/*w*/short x, int b)
    {
		short j = (short)((x&0xffff)>>>b);
		short k = (short)(x << (w1-b));
		short l = (short)(j | k);
		return l;
    }

    // setup [[ required for BlockCipher ]]
    public void setup(byte[] key, int nrounds)
    throws Exception
    {
        if (rounds<0 || rounds > 255)
        {
            throw new Exception("rc5 setup: illegal number of rounds");
        }
        if (key.length > 255)
        {
            throw new Exception("rc5 setup: key is too long");
        }

        rounds = nrounds;
        Ln = (key.length+(w8-1))/w8;            // number of words in L
        if (L == null || L.length != Ln) { 
			L = new /*w*/short [Ln];           // working storage L for setup
		}
        Sn = 2*rounds + 2;                      // number of words in S
        if (S == null || S.length != Sn)   {
			S = new /*w*/short [Sn];           // subkey array S
		}
        // initialize L
        L[Ln-1] = 0;
        for (int i=key.length-1; i>=0; i--)
        {
            L[i/w8] = (short)((L[i/w8]<<8) + (0xff & (/*w*/short)key[i]));
        }

        // initialize S
        S[0] = P;
        for (int i=1;i<Sn;i++)
        {
            S[i] = (short)(S[i-1] + Q);
        }

        // now scramble
        /*w*/short A = 0, B = 0, C;
        int i = 0, j = 0,k;
		int t = 2*rounds+2;

		for(i=j=k=0; k<3*t; k++, i=(i+1)%t, j=(j+1)%Ln) 
        {

			C = (short)(S[i] + A + B);
            A = S[i] = rol(C,3);
            int r = (int)((A+B) & (w1-1));
			C = (short)(L[j] + (A+B));
            B = L[j] = rol(C,r);
        }


    } 

    // encrypt [[required for BlockCipher]]
    // w8 bytes of plaintext pt encrypted in w8 bytes of ciphertext ct
    public void encrypt(short[] ct, short[] pt)
    throws Exception
    {   

        /*w*/short A =  pt[0];
        /*w*/short B =  pt[1]; 
        A += S[0];
        B += S[1];
        for (int i = 0; i<rounds; i++)
        {
            A ^= B;
            int r = (int)(B & (w1-1));
            A = (short)(rol(A,r) + S[2*i+2]);
            B ^= A;
            r = (int)(A & (w1-1));
            B = (short)(rol(B,r) + S[2*i+3]);
        }
		ct[0] = A;
		ct[1] = B;

    }

    // decrypt [[required for BlockCipher]]
    // w8 bytes of ciphertext ct decrypted into w8 bytes of plaintext pt
    public void decrypt(short[] pt, short[] ct)
    throws Exception
    {   

        /*w*/short A =  ct[0];
        /*w*/short B =  ct[1];
        for (int i = rounds-1; i>=0; i--)
        {
            B = (short)(B - S[2*i+3]);
            int r = (int)(A & (w1-1));
            B = (short)(A ^ ror(B,r));
            A = (short)(A - S[2*i+2]);
            r = (int)(B & (w1-1));
            A = (short)(B ^ ror(A,r));
        }
        B -= S[1];
        A -= S[0];
        pt[0] = A;
		pt[1] = B;
    }
	
	public void hash(String s, short out[]) throws Exception
	{
		hash(s.getBytes(), out);
	}

	public void hash(byte[] msg, short out[]) throws Exception
	{
		int i=0,j,n;
		short tmp[] = {1,2};
		byte  key[] = new byte[8];

		n = msg.length;

		
		while(i<n) {
			for(j=0; j<8 && i<n; i++, j++) {
				key[j] = msg[i];
			}
			for(; j<8; j++) { key[j] = 0; } // pad on the right

			setup(key, 12);
			encrypt(tmp, tmp);
		}

		out[0] = tmp[0]; out[1] = tmp[1];

	}

	public static void main(String args[])
	{
		if (args.length != 1) {
			System.out.println("Usage: java rc5_32 <msg>");
			System.exit(1);
		}

		short digest[] = new short[2];

		rc5hash r = new rc5hash();

		try {
			r.hash(args[0], digest);
			
			System.out.println(Integer.toHexString(digest[0]&0xffff) + " " +
							   Integer.toHexString(digest[1]&0xffff));
		} catch (Exception e){
			e.printStackTrace();
		}
		
	}

}




