#include "querymanager.hh"

static const uint32 SHA256IV[8] = {
	0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
	0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
};

static const uint32 SHA256K[64] = {
	0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
	0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
	0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
	0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
	0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
	0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
	0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
	0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
	0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
	0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
	0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
	0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
	0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
	0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
	0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
	0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
};

static uint32 RotR32(uint32 Value, int N){
	ASSERT(N >= 0 && N < 32);
	return (Value >> N) | (Value << (32 - N));
}

static void SHA256Compress(uint32 *H, const uint8 *Block){
	uint32 W[64];

	for(int i = 0; i < 16; i += 1){
		W[i] = BufferRead32BE(&Block[i * 4]);
	}

	for(int i = 16; i < 64; i += 1){
		uint32 S0 = RotR32(W[i - 15],  7) ^ RotR32(W[i - 15], 18) ^ (W[i - 15] >>  3);
		uint32 S1 = RotR32(W[i -  2], 17) ^ RotR32(W[i -  2], 19) ^ (W[i -  2] >> 10);
		W[i] = W[i - 16] + S0 + W[i - 7] + S1;
	}

	uint32 Aux[8];
	memcpy(Aux, H, sizeof(uint32) * 8);
	for(int i = 0; i < 64; i += 1){
		uint32 S1 = RotR32(Aux[4], 6) ^ RotR32(Aux[4], 11) ^ RotR32(Aux[4], 25);
		uint32 Ch = (Aux[4] & Aux[5]) ^ (~Aux[4] & Aux[6]);
		uint32 T1 = Aux[7] + S1 + Ch + SHA256K[i] + W[i];

		uint32 S0 = RotR32(Aux[0], 2) ^ RotR32(Aux[0], 13) ^ RotR32(Aux[0], 22);
		uint32 Maj = (Aux[0] & Aux[1]) ^ (Aux[0] & Aux[2]) ^ (Aux[1] & Aux[2]);
		uint32 T2 = S0 + Maj;

		Aux[7] = Aux[6];
		Aux[6] = Aux[5];
		Aux[5] = Aux[4];
		Aux[4] = Aux[3] + T1;
		Aux[3] = Aux[2];
		Aux[2] = Aux[1];
		Aux[1] = Aux[0];
		Aux[0] = T1 + T2;
	}

	H[0] += Aux[0];
	H[1] += Aux[1];
	H[2] += Aux[2];
	H[3] += Aux[3];
	H[4] += Aux[4];
	H[5] += Aux[5];
	H[6] += Aux[6];
	H[7] += Aux[7];
}

void SHA256(const uint8 *Input, int InputBytes, uint8 *Digest){
	ASSERT(Input != NULL && InputBytes >= 0 && Digest != NULL);
	uint32 H[8];
	memcpy(H, SHA256IV, sizeof(uint32) * 8);

	const uint8 *InputPtr = Input;
	int InputRem = InputBytes;
	while(InputRem >= 64){
		SHA256Compress(H, InputPtr);
		InputPtr += 64;
		InputRem -= 64;
	}

	ASSERT(InputRem < 64);
	uint8 Block[64] = {};
	memcpy(Block, InputPtr, InputRem);
	BufferWrite8(&Block[InputRem], 0x80);
	if(InputRem > 55){
		SHA256Compress(H, Block);
		memset(Block, 0, sizeof(Block));
	}
	BufferWrite64BE(&Block[56], ((uint64)InputBytes * 8));
	SHA256Compress(H, Block);

	BufferWrite32BE(&Digest[ 0], H[0]);
	BufferWrite32BE(&Digest[ 4], H[1]);
	BufferWrite32BE(&Digest[ 8], H[2]);
	BufferWrite32BE(&Digest[12], H[3]);
	BufferWrite32BE(&Digest[16], H[4]);
	BufferWrite32BE(&Digest[20], H[5]);
	BufferWrite32BE(&Digest[24], H[6]);
	BufferWrite32BE(&Digest[28], H[7]);
}

bool TestPassword(const uint8 *Auth, int AuthSize, const char *Password){
	if(AuthSize != 64){
		LOG_ERR("Expected 64 bytes of authentication data (got %d)", AuthSize);
		return false;
	}

	// NOTE(fusion): Constant time comparison to check whether the authentication
	// data is set. I'm considering all zeros to be NOT set.
	bool IsSet = false;
	for(int i = 0; i < AuthSize; i += 1){
		if(Auth[i] != 0){
			IsSet = true;
		}
	}

	if(!IsSet){
		LOG_ERR("Authentication data not set");
		return false;
	}

	const uint8 *Hash = &Auth[ 0];
	const uint8 *Salt = &Auth[32];

	// TODO(fusion): It's probably not the best way to mix the salt but should
	// be better than using plaintext or non-salted hashing schemes.
	uint8 Digest[32];
	SHA256((const uint8*)Password, (int)strlen(Password), Digest);
	for(int i = 0; i < 32; i += 1){
		Digest[i] ^= Salt[i];
	}
	SHA256(Digest, 32, Digest);

	// NOTE(fusion): Constant time comparison.
	uint8 Result = 0;
	for(int i = 0; i < 32; i += 1){
		Result |= Digest[i] ^ Hash[i];
	}
	return Result == 0;;
}

// CheckSHA256
//==============================================================================
static int HexDigit(int Ch){
	if(Ch >= '0' && Ch <= '9'){
		return (Ch - '0');
	}else if(Ch >= 'A' && Ch <= 'F'){
		return (Ch - 'A') + 10;
	}else if(Ch >= 'a' && Ch <= 'f'){
		return (Ch - 'a') + 10;
	}else{
		return -1;
	}
}

static int ParseHexString(uint8 *Buffer, int BufferSize, const char *String){
	int StringLen = (int)strlen(String);
	if(StringLen % 2 != 0){
		LOG_ERR("Expected even number of characters");
		return -1;
	}

	int NumBytes = (StringLen / 2);
	if(NumBytes > BufferSize){
		LOG_ERR("Supplied buffer is too small (Size: %d, Required: %d)",
				BufferSize, NumBytes);
		return -1;
	}

	for(int i = 0; i < NumBytes; i += 1){
		int Digit0 = HexDigit(String[i * 2 + 0]);
		int Digit1 = HexDigit(String[i * 2 + 1]);
		if(Digit0 == -1 || Digit1 == -1){
			LOG_ERR("Invalid hex digit at offset %d", i * 2);
			return -1;
		}

		Buffer[i] = ((uint8)Digit0 << 4) | (uint8)Digit1;
	}

	return NumBytes;
}

bool CheckSHA256(void){
	// NOTE(fusion): We're using only a few NIST test vectors. This is to make
	// sure there are no blatant implementation errors but we'd ideally run it
	// against all of them to be sure.
	struct{
		const char *Input;
		const char *Expected;
	} Tests[] = {
		{
			"",
			"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
		},
		{
			"5738c929c4f4ccb6",
			"963bb88f27f512777aab6c8b1a02c70ec0ad651d428f870036e1917120fb48bf",
		},
		{
			"1b503fb9a73b16ada3fcf1042623ae7610",
			"d5c30315f72ed05fe519a1bf75ab5fd0ffec5ac1acb0daf66b6b769598594509",
		},
		{
			"09fc1accc230a205e4a208e64a8f204291f581a12756392da4b8c0cf5ef02b95",
			"4f44c1c7fbebb6f9601829f3897bfd650c56fa07844be76489076356ac1886a4",
		},
		{
			"03b264be51e4b941864f9b70b4c958f5355aac294b4b87cb037f11f85f07eb57"
			"b3f0b89550",
			"d1f8bd684001ac5a4b67bbf79f87de524d2da99ac014dec3e4187728f4557471",
		},
		{
			"d1be3f13febafefc14414d9fb7f693db16dc1ae270c5b647d80da8583587c1ad"
			"8cb8cb01824324411ca5ace3ca22e179a4ff4986f3f21190f3d7f3",
			"02804978eba6e1de65afdbc6a6091ed6b1ecee51e8bff40646a251de6678b7ef",
		}
	};

	uint8 Input[64];
	uint8 Expected[32];
	uint8 Digest[32];
	for(int i = 0; i < NARRAY(Tests); i += 1){
		int InputBytes = ParseHexString(Input, sizeof(Input), Tests[i].Input);
		int ExpectedBytes = ParseHexString(Expected, sizeof(Expected), Tests[i].Expected);
		if(InputBytes == -1 || ExpectedBytes != sizeof(Expected)){
			LOG_ERR("Invalid test vector %d", i);
			return false;
		}

		SHA256(Input, InputBytes, Digest);
		if(memcmp(Expected, Digest, 32) != 0){
			LOG_ERR("Test vector %d failed", i);
			return false;
		}
	}

	return true;
}
