/*
 * sha1.c - Minimal SHA-1 implementation (FIPS 180-4).
 *
 * Used for the WebSocket handshake (Sec-WebSocket-Accept).
 */

#include "sha1.h"

#include <string.h>

static uint32_t rotl32(uint32_t x, unsigned n)
{
	return (x << n) | (x >> (32 - n));
}

static void sha1_store_be32(uint8_t *p, uint32_t v)
{
	p[0] = (uint8_t)(v >> 24);
	p[1] = (uint8_t)(v >> 16);
	p[2] = (uint8_t)(v >> 8);
	p[3] = (uint8_t)(v);
}

static uint32_t sha1_load_be32(const uint8_t *p)
{
	return ((uint32_t)p[0] << 24) |
	       ((uint32_t)p[1] << 16) |
	       ((uint32_t)p[2] << 8)  |
	       ((uint32_t)p[3]);
}

static void sha1_transform(uint32_t state[5], const uint8_t block[64])
{
	uint32_t w[80];
	uint32_t a, b, c, d, e;
	int i;

	for (i = 0; i < 16; i++)
		w[i] = sha1_load_be32(block + i * 4);

	for (i = 16; i < 80; i++)
		w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);

	a = state[0];
	b = state[1];
	c = state[2];
	d = state[3];
	e = state[4];

	for (i = 0; i < 80; i++) {
		uint32_t f, k, tmp;

		if (i < 20) {
			f = (b & c) | ((~b) & d);
			k = 0x5A827999;
		} else if (i < 40) {
			f = b ^ c ^ d;
			k = 0x6ED9EBA1;
		} else if (i < 60) {
			f = (b & c) | (b & d) | (c & d);
			k = 0x8F1BBCDC;
		} else {
			f = b ^ c ^ d;
			k = 0xCA62C1D6;
		}

		tmp = rotl32(a, 5) + f + e + k + w[i];
		e = d;
		d = c;
		c = rotl32(b, 30);
		b = a;
		a = tmp;
	}

	state[0] += a;
	state[1] += b;
	state[2] += c;
	state[3] += d;
	state[4] += e;
}

void sha1(const void *data, size_t len, uint8_t out[SHA1_DIGEST_SIZE])
{
	uint32_t state[5] = {
		0x67452301, 0xEFCDAB89, 0x98BADCFE,
		0x10325476, 0xC3D2E1F0
	};
	const uint8_t *src = data;
	uint8_t block[64];
	size_t i, remaining;

	/* Process full 64-byte blocks */
	for (i = 0; i + 64 <= len; i += 64)
		sha1_transform(state, src + i);

	/* Build the final padded block(s) */
	remaining = len - i;
	memset(block, 0, sizeof(block));
	memcpy(block, src + i, remaining);
	block[remaining] = 0x80;

	if (remaining >= 56) {
		/* Not enough room for the length, need two blocks */
		sha1_transform(state, block);
		memset(block, 0, sizeof(block));
	}

	/* Append bit length in big-endian */
	sha1_store_be32(block + 56, (uint32_t)(len >> 29));
	sha1_store_be32(block + 60, (uint32_t)(len << 3));
	sha1_transform(state, block);

	/* Write digest */
	for (i = 0; i < 5; i++)
		sha1_store_be32(out + i * 4, state[i]);
}
