//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.provider;
import java.security.*;

/**
 * <p>Implements the SHA-1 Message Digest algorithm.
 *
 * @see java.security.MessageDigest
 *
 * @version 0.98, 98/07/01
 * @author Shelina Gorain
 * @author Ming Yung
 */
public final class SHA1 extends MessageDigestSpi implements Cloneable
{
  private static int blockSize = 64; //in bytes
  private byte inputBuffer[];
  private int inputBufferOffset;
  private int mdBuffer[];
  private int plainTextLength;

  public SHA1() 
  {
    //Initialize
    inputBuffer = new byte[blockSize];
    inputBufferOffset = 0;
    plainTextLength = 0;
    mdBuffer = new int[5];
    mdBuffer[0] =  0x67452301;
    mdBuffer[1] =  0xefcdab89;
    mdBuffer[2] =  0x98badcfe;
    mdBuffer[3] =  0x10325476;
    mdBuffer[4] =  0xc3d2e1f0;
  }

  protected byte[] engineDigest() 
  {
    int temp =  56 - (plainTextLength & 0x3f);
    int extra = temp > 0 ? temp : 64 + temp;

    //NB: There are either one or two blocks to encrypt

    //Step 1: Add padding
    inputBuffer[inputBufferOffset] = (byte)0x80;
    int start = 1;

    if (inputBufferOffset + extra > blockSize) 
    {
      //Two blocks to encrypt: Do first block
      start = blockSize - inputBufferOffset;
      for (int i=1; i<start; i++) 
      {
        inputBuffer[inputBufferOffset + i] = 0x00;
      }
      doDecodeBlock(mdBuffer, bytesToInts (inputBuffer, 0, blockSize));
      inputBufferOffset = -start;
    }
      
    for (int i=start; i<extra; i++) 
    {
      inputBuffer[inputBufferOffset + i] = 0x00;
    }
    inputBufferOffset += extra;

    //Step 2: Append length in bits
    long lb = (long)(plainTextLength*8);
    for (int i=0; i<8; i++) 
    {
      inputBuffer[inputBufferOffset+i] = (byte)((lb >>> 8*(7-i)) & 0xff);
    }

    //Last block to doDecode
    doDecodeBlock(mdBuffer, bytesToInts(inputBuffer, 0, blockSize));
    
    byte retval[] = intsToBytes(mdBuffer);
    doReset();
    return retval;
  }

  protected void engineReset() 
  {
    doReset();
  }

  private final void doReset() 
  {
    inputBufferOffset = 0;
    mdBuffer[0] =  0x67452301;
    mdBuffer[1] =  0xefcdab89;
    mdBuffer[2] =  0x98badcfe;
    mdBuffer[3] =  0x10325476;
    mdBuffer[4] =  0xc3d2e1f0;
    plainTextLength = 0;
  }

  protected void engineUpdate(byte input)
  {
    byte in[] = new byte[1];
    in[0] = input;
    engineUpdate(in, 0, 1);
  }

  protected void engineUpdate(byte input[], int inputOffset, int length) 
  {
    int len;
    int left = length;
    int inputUsed = 0;
    while (true) 
    {
      len = blockSize - inputBufferOffset;
      if (len <= left) 
      {
        //Enough input for block
        System.arraycopy(input, inputUsed + inputOffset, inputBuffer,
                          inputBufferOffset, len);
        doDecodeBlock(mdBuffer, bytesToInts(inputBuffer, 0, blockSize));
        left -= len;
        inputBufferOffset = 0;
        inputUsed += len;
      } 
      else 
      {
        //Buffer the remaining input
        System.arraycopy(input, inputUsed + inputOffset, inputBuffer,
                         inputBufferOffset, left);
        inputBufferOffset += left;
        break;
      }
    }
    plainTextLength += length;
  }

  public Object clone() throws CloneNotSupportedException
  {
    SHA1 retval = new SHA1();
    retval.inputBufferOffset = this.inputBufferOffset;
    retval.plainTextLength = this.plainTextLength;
    System.arraycopy(this.mdBuffer, 0, retval.mdBuffer, 0, 5);
    System.arraycopy(this.inputBuffer, 0, retval.inputBuffer, 0, blockSize);
    return retval;
  }

  private static final byte[] intsToBytes(int ints[]) 
  {
    //Converts every int to 4 bytes
    byte bytes[] = new byte[4*ints.length];
    for (int j=0; j < bytes.length; j++) 
    {
      bytes[j] = (byte)((ints[j/4] >>> 8*((3-j)%4)) & 0xff);
    }
    return bytes;
  }

  private static final int[] bytesToInts(byte bytes[], int offset, int length) 
  {
    //Convert every 4 bytes to an int
    int retval[] = new int[(length + 3)/4];
    for (int j=0; j < length; j++) 
    {
      retval[j/4] += (int) (bytes[j + offset] & 0xff) << 8*((3-j)%4);
    }
    return retval;
  }

  private static void f1(int buf[], int x[], int a, int b, int c, int d, 
                         int e, int t) 
  {
     int temp = ((buf[a] << 5) | (buf[a] >>> 27)) + 
            ( (buf[b] & buf[c]) | ((~buf[b]) & buf[d]) ) + buf[e] 
              + x[t] + 0x5A827999;
     buf[e] = buf[d];
     buf[d] = buf[c];
     buf[c] = (buf[b] << 30) | (buf[b] >>> 2);
     buf[b] = buf[a];
     buf[a] = temp;
   }

  private static void f2(int buf[], int x[], int a, int b, int c, int d, 
                         int e, int t) 
  {
     int temp = ((buf[a] << 5) | (buf[a] >>> 27)) + 
             ((buf[b] ^ buf[c] ^ buf[d])) + buf[e] + x[t] + 0x6ED9EBA1;
     buf[e] = buf[d];
     buf[d] = buf[c];
     buf[c] = (buf[b] << 30) | (buf[b] >>> 2);
     buf[b] = buf[a];
     buf[a] = temp;
  }

  private static void f3(int buf[], int x[], int a, int b, int c, int d, 
                         int e, int t) 
  {
     int temp = ((buf[a] << 5) | (buf[a] >>> 27)) + 
            ((buf[b] & buf[c]) | (buf[b] & buf[d]) | (buf[c] & buf[d])) + 
                       buf[e] + x[t] + 0x8F1BBCDC;
     buf[e] = buf[d];
     buf[d] = buf[c];
     buf[c] = (buf[b] << 30) | (buf[b] >>> 2);
     buf[b] = buf[a];
     buf[a] = temp;
  }

  private static void f4(int buf[], int x[], int a, int b, int c, int d, 
                         int e, int t) 
  {
     int temp = ((buf[a] << 5) | (buf[a] >>> 27)) + 
             ((buf[b] ^ buf[c] ^ buf[d])) + buf[e] + x[t] + 0xCA62C1D6;
     buf[e] = buf[d];
     buf[d] = buf[c];
     buf[c] = (buf[b] << 30) | (buf[b] >>> 2);
     buf[b] = buf[a];
     buf[a] = temp;
  }

  private static void doDecodeBlock(int mdBuffer[], int block[]) 
  {
    int seq[] = expandBlock(block);
    int tmp[] = new int[5];

    System.arraycopy(mdBuffer, 0, tmp, 0, 5);

    for (int t=0; t<20; t++) 
    {
      f1(mdBuffer, seq, 0, 1, 2, 3, 4, t);
    }

    for (int t=20; t<40; t++) 
    {
      f2(mdBuffer, seq, 0, 1, 2, 3, 4, t);
    }
    
    for (int t=40; t<60; t++) 
    {
      f3(mdBuffer, seq, 0, 1, 2, 3, 4, t);
    }
    
    for (int t=60; t<80; t++) 
    {
      f4(mdBuffer, seq, 0, 1, 2, 3, 4, t);
    }

    for (int i=0; i<5; i++) 
    {
      mdBuffer[i] += tmp[i];
    }
  }
  
  private static int[] expandBlock(int block[]) 
  {
    int seq[]  = new int[80];
    System.arraycopy(block, 0, seq, 0, 16);

    for (int t=16; t<80; t++) 
    {
      seq[t] = seq[t-3] ^ seq[t-8] ^ seq[t-14] ^ seq[t-16];
      seq[t] = (seq[t] << 1) | (seq[t] >>> 31);
    }
    return seq;
  }
}
