// Copyright (c) 2021 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// Implementation was off Steve Reid's SHA-1 C implementation

module std::hash::sha1;
import std::hash::hmac;
import std::bits;

const BLOCK_BYTES = 64;
const HASH_BYTES = 20;

struct Sha1
{
	uint[5] state;
	uint[2] count;
	char[BLOCK_BYTES] buffer;
}

alias HmacSha1 = Hmac{Sha1, HASH_BYTES, BLOCK_BYTES};
alias hmac = hmac::hash{Sha1, HASH_BYTES, BLOCK_BYTES};
alias pbkdf2 = hmac::pbkdf2{Sha1, HASH_BYTES, BLOCK_BYTES};

fn char[HASH_BYTES] hash(char[] data)
{
	Sha1 sha1 @noinit;
	sha1.init();
	sha1.update(data);
	return sha1.final();
}

fn void Sha1.init(&self)
{
	// SHA1 initialization constants
	*self = {
		.state = {
			0x67452301,
			0xEFCDAB89,
			0x98BADCFE,
			0x10325476,
			0xC3D2E1F0
		}
	};
}

<*
 @param [in] data
 @require data.len <= uint.max
*>
fn void Sha1.update(&self, char[] data)
{
	uint j = self.count[0];
	uint len = data.len;
	if ((self.count[0] += len << 3) < j) self.count[1]++;
	self.count[1] += len >> 29;
	j = (j >> 3) & 63;
	uint i;
	if (j + len > 63)
	{
		i = 64 - j;
		self.buffer[j..] = data[:i];
		sha1_transform(&self.state, &self.buffer);
		for (; i + 63 < len; i += 64)
		{
			sha1_transform(&self.state, &data[i]);
		}
		j = 0;
	}
	self.buffer[j:len - i] = data[i..];
}


fn char[HASH_BYTES] Sha1.final(&self)
{
	char[8] finalcount;
	for (uint i = 0; i < 8; i++)
	{
		finalcount[i] = (char)((self.count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 0xFF);
	}
	self.update((char[]){ 0o200 });
	while ((self.count[0] & 504) != 448)
	{
		self.update((char[]){ 0 });
	}

	self.update(&finalcount);
	char[HASH_BYTES] digest;
	for (uint i = 0; i < HASH_BYTES; i++)
	{
		digest[i] = (char)((self.state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xFF);
	}

	// Clear mem
	*self = {};
	finalcount = {};
	return digest;
}

union Long16 @local
{
	char[BLOCK_BYTES] c;
	uint[16] l;
}

macro blk(Long16* block, i) @local
{
	return (block.l[i & 15] = (block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15]
		^ block.l[(i + 2) & 15] ^ block.l[i & 15]).rotl(1));
}

macro blk0(Long16* block, i) @local
{
	$if env::BIG_ENDIAN:
		return block.l[i];
	$else
		return block.l[i] = (block.l[i].rotl(24) & 0xFF00FF00)
			| (block.l[i].rotl(8) & 0x00FF00FF);
	$endif
}

macro r0(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
	var w = *wref;
	*z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + v.rotl(5);
	*wref = w.rotl(30);
}

macro r1(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
	var w = *wref;
	*z += ((w & (x ^ y)) ^ y) + blk(block, i) + 0x5A827999 + v.rotl(5);
	*wref = w.rotl(30);
}

macro r2(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
	var w = *wref;
	*z += (w ^ x ^ y) + blk(block, i) + 0x6ED9EBA1 + v.rotl(5);
	*wref = w.rotl(30);
}

macro r3(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
	var w = *wref;
	*z += (((w | x) & y) | (w & x)) + blk(block, i) + 0x8F1BBCDC + v.rotl(5);
	*wref = w.rotl(30);
}

macro r4(Long16* block, uint v, uint* wref, uint x, uint y, uint* z, uint i) @local
{
	var w = *wref;
	*z += (w ^ x ^ y) + blk(block, i) + 0xCA62C1D6 + v.rotl(5);
	*wref = w.rotl(30);
}

<*
 @param [&inout] state
 @param [&in] buffer
*>
fn void sha1_transform(uint[5]* state, char* buffer) @local
{
	Long16 block;
	block.c[..] = buffer[:64];
	uint a = (*state)[0];
	uint b = (*state)[1];
	uint c = (*state)[2];
	uint d = (*state)[3];
	uint e = (*state)[4];
	r0(&block, a, &b, c, d, &e, 0);
	r0(&block, e, &a, b, c, &d, 1);
	r0(&block, d, &e, a, b, &c, 2);
	r0(&block, c, &d, e, a, &b, 3);
	r0(&block, b, &c, d, e, &a, 4);
	r0(&block, a, &b, c, d, &e, 5);
	r0(&block, e, &a, b, c, &d, 6);
	r0(&block, d, &e, a, b, &c, 7);
	r0(&block, c, &d, e, a, &b, 8);
	r0(&block, b, &c, d, e, &a, 9);
	r0(&block, a, &b, c, d, &e, 10);
	r0(&block, e, &a, b, c, &d, 11);
	r0(&block, d, &e, a, b, &c, 12);
	r0(&block, c, &d, e, a, &b, 13);
	r0(&block, b, &c, d, e, &a, 14);
	r0(&block, a, &b, c, d, &e, 15);
	r1(&block, e, &a, b, c, &d, 16);
	r1(&block, d, &e, a, b, &c, 17);
	r1(&block, c, &d, e, a, &b, 18);
	r1(&block, b, &c, d, e, &a, 19);
	r2(&block, a, &b, c, d, &e, 20);
	r2(&block, e, &a, b, c, &d, 21);
	r2(&block, d, &e, a, b, &c, 22);
	r2(&block, c, &d, e, a, &b, 23);
	r2(&block, b, &c, d, e, &a, 24);
	r2(&block, a, &b, c, d, &e, 25);
	r2(&block, e, &a, b, c, &d, 26);
	r2(&block, d, &e, a, b, &c, 27);
	r2(&block, c, &d, e, a, &b, 28);
	r2(&block, b, &c, d, e, &a, 29);
	r2(&block, a, &b, c, d, &e, 30);
	r2(&block, e, &a, b, c, &d, 31);
	r2(&block, d, &e, a, b, &c, 32);
	r2(&block, c, &d, e, a, &b, 33);
	r2(&block, b, &c, d, e, &a, 34);
	r2(&block, a, &b, c, d, &e, 35);
	r2(&block, e, &a, b, c, &d, 36);
	r2(&block, d, &e, a, b, &c, 37);
	r2(&block, c, &d, e, a, &b, 38);
	r2(&block, b, &c, d, e, &a, 39);
	r3(&block, a, &b, c, d, &e, 40);
	r3(&block, e, &a, b, c, &d, 41);
	r3(&block, d, &e, a, b, &c, 42);
	r3(&block, c, &d, e, a, &b, 43);
	r3(&block, b, &c, d, e, &a, 44);
	r3(&block, a, &b, c, d, &e, 45);
	r3(&block, e, &a, b, c, &d, 46);
	r3(&block, d, &e, a, b, &c, 47);
	r3(&block, c, &d, e, a, &b, 48);
	r3(&block, b, &c, d, e, &a, 49);
	r3(&block, a, &b, c, d, &e, 50);
	r3(&block, e, &a, b, c, &d, 51);
	r3(&block, d, &e, a, b, &c, 52);
	r3(&block, c, &d, e, a, &b, 53);
	r3(&block, b, &c, d, e, &a, 54);
	r3(&block, a, &b, c, d, &e, 55);
	r3(&block, e, &a, b, c, &d, 56);
	r3(&block, d, &e, a, b, &c, 57);
	r3(&block, c, &d, e, a, &b, 58);
	r3(&block, b, &c, d, e, &a, 59);
	r4(&block, a, &b, c, d, &e, 60);
	r4(&block, e, &a, b, c, &d, 61);
	r4(&block, d, &e, a, b, &c, 62);
	r4(&block, c, &d, e, a, &b, 63);
	r4(&block, b, &c, d, e, &a, 64);
	r4(&block, a, &b, c, d, &e, 65);
	r4(&block, e, &a, b, c, &d, 66);
	r4(&block, d, &e, a, b, &c, 67);
	r4(&block, c, &d, e, a, &b, 68);
	r4(&block, b, &c, d, e, &a, 69);
	r4(&block, a, &b, c, d, &e, 70);
	r4(&block, e, &a, b, c, &d, 71);
	r4(&block, d, &e, a, b, &c, 72);
	r4(&block, c, &d, e, a, &b, 73);
	r4(&block, b, &c, d, e, &a, 74);
	r4(&block, a, &b, c, d, &e, 75);
	r4(&block, e, &a, b, c, &d, 76);
	r4(&block, d, &e, a, b, &c, 77);
	r4(&block, c, &d, e, a, &b, 78);
	r4(&block, b, &c, d, e, &a, 79);
	(*state)[0] += a;
	(*state)[1] += b;
	(*state)[2] += c;
	(*state)[3] += d;
	(*state)[4] += e;
	a = b = c = d = e = 0;
	block = {};
}