///////////////////////////////////////////////////////////////////////////////
/// @file sha1.cpp
///
/// @brief sha1 (fips-180-1 standard) secure hash
///
/// @author Lightning
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2004 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Sep 2004
/// @author Lightning
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

//written based on specs from, optimized where spotted
//http://www.itl.nist.gov/fipspubs/fip180-1.htm

#include "sha1.h"

#include <windows.h>
#include "deOS.hpp"
#include "deByteOrder.hpp"

//define the shift
#ifdef DE3D_CPU_I386
#define S(val, shift) {		\
	__asm rol val, shift	\
}
#else
#define S(val, shift) val = (((val) << shift) | ((val) >> (32-shift)))
#endif

//our internal function for hashing
void SHA1Hash(SHA1Instance *Inst, unsigned char *Buffer);

void SHA1Init(SHA1Instance *Inst)
{
	//clear the data for the instance
	memset((void *)Inst, 0, sizeof(SHA1Instance));

	//set the default values
	Inst->CurrentHash[0] = 0x67452301;
	Inst->CurrentHash[1] = 0xEFCDAB89;
	Inst->CurrentHash[2] = 0x98BADCFE;
	Inst->CurrentHash[3] = 0x10325476;
	Inst->CurrentHash[4] = 0xC3D2E1F0;
}

void SHA1Process(SHA1Instance *Inst, unsigned char *Buffer, long Length)
{
	long	OldData;
	long	TempLen;

	//process any data

	//if no data, exit
	if(!Length)
		return;

	//first, see if there is any data left from the last process call
	OldData = Inst->TotalLength & 0x3F;

	//add the length to the instance
	Inst->TotalLength = Inst->TotalLength + Length;
	
	//if we have old data, copy part of the buffer and process it
	TempLen = 0;
	if(OldData)
	{
		//figure out how many bytes to copy
		//first, set enough to fill the buffer, if length isnt long enough
		//then go with length
		TempLen = 64 - OldData;
		if(Length < TempLen)
			TempLen = Length;

		//copy in data that we can to process
		memcpy(&Inst->Buffer[OldData], Buffer, TempLen);

		//process it
		SHA1Hash(Inst, Inst->Buffer);

		//adjust the length and buffer pointer
		Length = Length - TempLen;
		Buffer = Buffer + TempLen;
	}

	//loop until no more data to process
	while(Length > 64)
	{
		//process a chunk
		SHA1Hash(Inst, (Buffer + TempLen));

		//adjust the length and buffer pointer
		Length = Length - 64;
		TempLen = TempLen + 64;
	};

	//if any data left, copy it into the buffer to process at the end
	if(Length)
		memcpy(Inst->Buffer, &(Buffer)[TempLen], Length);
}

void SHA1Finish(SHA1Instance *Inst, unsigned char *Hash, long HashLen)
{
	long	OldData;
	DWORD	TempLen[2];

	//finish the hash

	//if thte hash length is not 20 bytes, fail
	if(HashLen != 20)
		return;
	
	//first, pad the buffer, have to figure out where to start
	OldData = Inst->TotalLength & 0x3F;

	//have to set the next byte to 0x80 (high bit on)
	Inst->Buffer[OldData] = (unsigned char)0x80;

	//setup the length, shift as we need it as bits instead of bytes
	TempLen[0] = Inst->TotalLength >> 29;
	TempLen[1] = Inst->TotalLength << 3;
	//count for the high bit entry
	OldData++;

	//zero out what is left in the buffer
	memset(&Inst->Buffer[OldData], 0, 64-OldData);

	//if the buffer is not large enough to handle the last bit
	//and 8 bytes for bit size, then process the buffer so we can
	//get another 64 bytes to work on
	if(OldData >= 57)
	{
		//process the buffer
		SHA1Hash(Inst, Inst->Buffer);

		//zero out what is left in the buffer, minus 8 bytes for bit length
		memset(Inst->Buffer, 0, 56);
	}

	//redo the order of the bytes if need be
	TempLen[0] = deByteOrderBE32(TempLen[0]);
	TempLen[1] = deByteOrderBE32(TempLen[1]);

	//copy in the bytes for the length
	memcpy(&Inst->Buffer[56], TempLen, 8);

	//process the last buffer amount
	SHA1Hash(Inst, Inst->Buffer);

	//copy the final hash
	*((unsigned long *)&Hash[0]) = deByteOrderBE32(Inst->CurrentHash[0]);
	*((unsigned long *)&Hash[4]) = deByteOrderBE32(Inst->CurrentHash[1]);
	*((unsigned long *)&Hash[8]) = deByteOrderBE32(Inst->CurrentHash[2]);
	*((unsigned long *)&Hash[12]) = deByteOrderBE32(Inst->CurrentHash[3]);
	*((unsigned long *)&Hash[16]) = deByteOrderBE32(Inst->CurrentHash[4]);
	
	//clear the data for the instance
	memset((void *)Inst, 0, sizeof(Inst));
}

void SHA1Hash(SHA1Instance *Inst, unsigned char *Buffer)
{
	unsigned long	NewHash[5];
	unsigned long	W[16];
	unsigned long	TempA;

#define W_GETLONG(x) W[x] = deByteOrderBE32(*(unsigned long *)(&Buffer[x*4]))
	//we have to read the bytes anyways for a copy, so read and shuffle if need be
	W_GETLONG(0);
	W_GETLONG(1);
	W_GETLONG(2);
	W_GETLONG(3);
	W_GETLONG(4);
	W_GETLONG(5);
	W_GETLONG(6);
	W_GETLONG(7);
	W_GETLONG(8);
	W_GETLONG(9);
	W_GETLONG(10);
	W_GETLONG(11);
	W_GETLONG(12);
	W_GETLONG(13);
	W_GETLONG(14);
	W_GETLONG(15);

	//setup a define so we can process the hash quicker by unrolling the loop
	
#define F(T) (W[(T + 13) & 0x0F] ^ W[(T + 8) & 0x0F] ^ W[(T + 2) & 0x0F] ^ W[T & 0x0F])

#define TEMP(A, B, C, D, E, WS) (A + ft(B, C, D) + E + WS + (unsigned long)K)

#define L(A, B, C, D, E, T)						\
{												\
	if(T > 16)									\
	{											\
		W[T & 0x0F] = F(T);						\
		S(W[T & 0x0F], 1);						\
	}											\
	TempA = A;									\
	S(TempA, 5);								\
	E = TEMP(TempA, B, C, D, E, W[T & 0x0F]);	\
	S(B, 30);									\
}



	//copy the old hash to the new
	memcpy(NewHash, Inst->CurrentHash, 20);

#define A	NewHash[0]
#define B	NewHash[1]
#define C	NewHash[2]
#define D	NewHash[3]
#define E	NewHash[4]
	
//define K and ft
//( 0 <= t <= 19)
#define K 0x5A827999
#define ft(B,C,D) ((B & C) | ((~B) & D))
	
	//rotate the hash entries around instead of swapping in memory
	L(A, B, C, D, E, 0);
	L(E, A, B, C, D, 1);
	L(D, E, A, B, C, 2);
	L(C, D, E, A, B, 3);
	L(B, C, D, E, A, 4);
	L(A, B, C, D, E, 5);
	L(E, A, B, C, D, 6);
	L(D, E, A, B, C, 7);
	L(C, D, E, A, B, 8);
	L(B, C, D, E, A, 9);
	L(A, B, C, D, E, 10);
	L(E, A, B, C, D, 11);
	L(D, E, A, B, C, 12);
	L(C, D, E, A, B, 13);
	L(B, C, D, E, A, 14);
	L(A, B, C, D, E, 15);

#define WS_SHIFT
	
	//t >= 16, use F from now on
	L(E, A, B, C, D, 16);
	L(D, E, A, B, C, 17);
	L(C, D, E, A, B, 18);
	L(B, C, D, E, A, 19);

//(20 <= t <= 39)
#undef K
#undef ft
#define K	0x6ED9EBA1
#define ft(B,C,D) (B ^ C ^ D)

	L(A, B, C, D, E, 20);
	L(E, A, B, C, D, 21);
	L(D, E, A, B, C, 22);
	L(C, D, E, A, B, 23);
	L(B, C, D, E, A, 24);
	L(A, B, C, D, E, 25);
	L(E, A, B, C, D, 26);
	L(D, E, A, B, C, 27);
	L(C, D, E, A, B, 28);
	L(B, C, D, E, A, 29);
	L(A, B, C, D, E, 30);
	L(E, A, B, C, D, 31);
	L(D, E, A, B, C, 32);
	L(C, D, E, A, B, 33);
	L(B, C, D, E, A, 34);
	L(A, B, C, D, E, 35);
	L(E, A, B, C, D, 36);
	L(D, E, A, B, C, 37);
	L(C, D, E, A, B, 38);
	L(B, C, D, E, A, 39);

//(40 <= t <= 59)
#undef K
#undef ft
#define K	0x8F1BBCDC
#define ft(B,C,D) ((B & (C | D)) | (C & D))

	L(A, B, C, D, E, 40);
	L(E, A, B, C, D, 41);
	L(D, E, A, B, C, 42);
	L(C, D, E, A, B, 43);
	L(B, C, D, E, A, 44);
	L(A, B, C, D, E, 45);
	L(E, A, B, C, D, 46);
	L(D, E, A, B, C, 47);
	L(C, D, E, A, B, 48);
	L(B, C, D, E, A, 49);
	L(A, B, C, D, E, 50);
	L(E, A, B, C, D, 51);
	L(D, E, A, B, C, 52);
	L(C, D, E, A, B, 53);
	L(B, C, D, E, A, 54);
	L(A, B, C, D, E, 55);
	L(E, A, B, C, D, 56);
	L(D, E, A, B, C, 57);
	L(C, D, E, A, B, 58);
	L(B, C, D, E, A, 59);

//(60 <= t <= 79)
#undef K
#undef ft
#define K	0xCA62C1D6
#define ft(B,C,D) (B ^ C ^ D)

	L(A, B, C, D, E, 60);
	L(E, A, B, C, D, 61);
	L(D, E, A, B, C, 62);
	L(C, D, E, A, B, 63);
	L(B, C, D, E, A, 64);
	L(A, B, C, D, E, 65);
	L(E, A, B, C, D, 66);
	L(D, E, A, B, C, 67);
	L(C, D, E, A, B, 68);
	L(B, C, D, E, A, 69);
	L(A, B, C, D, E, 70);
	L(E, A, B, C, D, 71);
	L(D, E, A, B, C, 72);
	L(C, D, E, A, B, 73);
	L(B, C, D, E, A, 74);
	L(A, B, C, D, E, 75);
	L(E, A, B, C, D, 76);
	L(D, E, A, B, C, 77);
	L(C, D, E, A, B, 78);
	L(B, C, D, E, A, 79);

	//now add in our results
	Inst->CurrentHash[0] += A;
	Inst->CurrentHash[1] += B;
	Inst->CurrentHash[2] += C;
	Inst->CurrentHash[3] += D;
	Inst->CurrentHash[4] += E;
}