/*
** Copyright 2001 S. Varshavchik.
** See COPYING for distribution information.
*/

#define	SHA1_INTERNAL
#include	"sha1.h"

#include	<string.h>
#include	<stdlib.h>


#define	K0 0x5A827999
#define	K1 0x6ED9EBA1
#define	K2 0x8F1BBCDC
#define	K3 0XCA62C1D6

#define	K20(x)	x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x

static SHA1_WORD K[80] = { K20(K0), K20(K1), K20(K2), K20(K3) };

void sha1_context_init(struct SHA1_CONTEXT *c)
{
	if (sizeof(SHA1_WORD) != 4)
		abort();

	c->H[0] = 0x67452301;
	c->H[1] = 0xEFCDAB89;
	c->H[2] = 0x98BADCFE;
	c->H[3] = 0x10325476;
	c->H[4] = 0xC3D2E1F0;
	c->blk_ptr=0;
}

void sha1_context_hash(struct SHA1_CONTEXT *c,
		const unsigned char blk[SHA1_BLOCK_SIZE])
{
SHA1_WORD	A,B,C,D,E;
SHA1_WORD	TEMP;
SHA1_WORD	W[80];
unsigned	i, t;

#define f(t,B,C,D)	( \
	(t) < 20 ? ( (B) & (C) ) | ( (~(B)) & (D) ) : \
	(t) >= 40 && (t) < 60 ? ( (B) & (C) ) | ( (B) & (D) ) | ( (C) & (D) ):\
		(B) ^ (C) ^ (D) )

#define S(a,b) ( ((SHA1_WORD)(a) << (b)) | ((SHA1_WORD)(a) >> (32 - (b))))

	for (i=t=0; t<16; t++)
	{
		W[t]= blk[i]; i++;
		W[t] = (W[t] << 8) | blk[i]; i++;
		W[t] = (W[t] << 8) | blk[i]; i++;
		W[t] = (W[t] << 8) | blk[i]; i++;
	}

	for (t=16; t<80; t++)
	{
		TEMP= W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16];
		W[t]= S(TEMP, 1);
	}

	A=c->H[0];
	B=c->H[1];
	C=c->H[2];
	D=c->H[3];
	E=c->H[4];

	for (t=0; t<80; t++)
	{
		TEMP = S(A,5);
		TEMP += f(t, B, C, D);
		TEMP += E;
		TEMP += W[t];
		TEMP += K[t];

		E=D;
		D=C;
		C= S(B, 30);
		B=A;
		A=TEMP;
	}

	c->H[0] += A;
	c->H[1] += B;
	c->H[2] += C;
	c->H[3] += D;
	c->H[4] += E;
}

void sha1_context_hashstream(struct SHA1_CONTEXT *c, const void *p, unsigned l)
{
const unsigned char *cp=(const unsigned char *)p;
unsigned ll;

	while (l)
	{
		if (c->blk_ptr == 0 && l >= SHA1_BLOCK_SIZE)
		{
			sha1_context_hash(c, cp);
			cp += SHA1_BLOCK_SIZE;
			l -= SHA1_BLOCK_SIZE;
			continue;
		}

		ll=l;
		if (ll > SHA1_BLOCK_SIZE - c->blk_ptr)
			ll=SHA1_BLOCK_SIZE - c->blk_ptr;
		memcpy(c->blk + c->blk_ptr, cp, ll);
		c->blk_ptr += ll;
		cp += ll;
		l -= ll;
		if (c->blk_ptr >= SHA1_BLOCK_SIZE)
		{
			sha1_context_hash(c, c->blk);
			c->blk_ptr=0;
		}
	}
}

void sha1_context_endstream(struct SHA1_CONTEXT *c, unsigned long l)
{
	unsigned char buf[8];
	static const unsigned char zero[SHA1_BLOCK_SIZE-8];

	buf[0]=0x80;
	sha1_context_hashstream(c, &buf, 1);
	while (c->blk_ptr != SHA1_BLOCK_SIZE-8)
	{
		if (c->blk_ptr > SHA1_BLOCK_SIZE-8)
		{
			sha1_context_hashstream(c, zero,
				SHA1_BLOCK_SIZE - c->blk_ptr);
			continue;
		}
		sha1_context_hashstream(c, zero,
			SHA1_BLOCK_SIZE-8-c->blk_ptr);
	}

	l *= 8;
	buf[7] = l;
	buf[6] = (l >>= 8);
	buf[5] = (l >>= 8);
	buf[4] = (l >> 8);
	buf[3]=buf[2]=buf[1]=buf[0]=0;

	sha1_context_hashstream(c, buf, 8);
}

void sha1_context_digest(struct SHA1_CONTEXT *c, SHA1_DIGEST d)
{
unsigned char *dp=d + SHA1_DIGEST_SIZE;
unsigned i;

	for ( i=5; i; )
	{
	SHA1_WORD	w=c->H[--i];

		*--dp=w; w >>= 8;
		*--dp=w; w >>= 8;
		*--dp=w; w >>= 8;
		*--dp=w;
	}
}

void sha1_context_restore(struct SHA1_CONTEXT *c, const SHA1_DIGEST d)
{
const unsigned char *dp=d;
unsigned i;

	for (i=0; i<5; i++)
	{
	SHA1_WORD	w= *dp++;

		w=(w << 8) | *dp++;
		w=(w << 8) | *dp++;
		w=(w << 8) | *dp++;
		c->H[i]=w;
	}
	c->blk_ptr=0;
}

void sha1_digest(const void *msg, unsigned len, SHA1_DIGEST d)
{
struct SHA1_CONTEXT c;

	sha1_context_init( &c );
	sha1_context_hashstream(&c, msg, len);
	sha1_context_endstream(&c, len);
	sha1_context_digest( &c, d );
}
