/*
   This is a stripped-down and simplified "64-Bit only" version of BLAKE2s
   BLAKE2 reference source code package - reference C implementations
   Copyright 2012, Samuel Neves <sneves@dei.uc.pt>.  You may use this under the
   terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at
   your option.  The terms of these licenses can be found at:
   - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
   - OpenSSL license   : https://www.openssl.org/source/license.html
   - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
   More information about the BLAKE2 hash function can be found at
   https://blake2.net.
*/

#include "blake2.h"

/* -------------------------------------------------------------------------- */
/* blake2-impl.h                                                              */
/* -------------------------------------------------------------------------- */

#if defined(_MSC_VER)
#	define BLAKE2_PACKED(X) __pragma(pack(push, 1)) X __pragma(pack(pop))
#else
#	define BLAKE2_PACKED(X) X __attribute__((packed))
#endif

#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)
#	if defined(_MSC_VER)
#		define BLAKE2_INLINE __inline
#	elif defined(__GNUC__)
#		define BLAKE2_INLINE __inline__
#	else
#		define BLAKE2_INLINE
#	endif
#else
#	define BLAKE2_INLINE inline
#endif

static const uint32_t blake2s_IV[8] =
{
	0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
	0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
};

static const uint8_t blake2s_sigma[10][16] =
{
	{  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 },
	{ 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 },
	{ 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4 },
	{  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8 },
	{  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13 },
	{  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9 },
	{ 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11 },
	{ 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10 },
	{  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5 },
	{ 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0 },
};

static BLAKE2_INLINE uint32_t load32(const void *const src)
{
	const uint8_t *const p = (const uint8_t*)src;
	return
		((uint32_t)(p[0]) <<  0) |
		((uint32_t)(p[1]) <<  8) |
		((uint32_t)(p[2]) << 16) |
		((uint32_t)(p[3]) << 24);
}

static BLAKE2_INLINE uint64_t make64(const uint32_t a, const uint32_t b)
{
	return
		((uint64_t)(a) <<  0) |
		((uint64_t)(b) << 32);
}

static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c)
{
	return (w >> c) | (w << (32 - c));
}

/* -------------------------------------------------------------------------- */
/* blake2s-ref.c                                                              */
/* -------------------------------------------------------------------------- */

static void blake2s_set_lastnode(blake2s_t *const S)
{
	S->f[1] = (uint32_t)-1;
}

/* Some helper functions, not necessarily useful */
static int blake2s_is_lastblock(const blake2s_t *const S)
{
	return S->f[0] != 0;
}

static void blake2s_set_lastblock(blake2s_t *const S)
{
	if (S->last_node)
		blake2s_set_lastnode(S);
	S->f[0] = (uint32_t)-1;
}

static void blake2s_increment_counter(blake2s_t *const S, const uint32_t inc)
{
	S->t[0] += inc;
	S->t[1] += (S->t[0] < inc);
}

/* Sequential blake2s initialization */
void blake2s_init(blake2s_t *const S)
{
	size_t i;
	memset(S, 0, sizeof(blake2s_t));

	for (i = 0; i < 8; ++i)
		S->h[i] = blake2s_IV[i];

	/* IV XOR ParamBlock, assuming P.digest_length = sizeof(uint64_t) */
	S->h[0] ^= 0x01010008UL;
}

#define G(r,i,a,b,c,d) do \
{ \
	a = a + b + m[blake2s_sigma[r][2*i+0]]; \
	d = rotr32(d ^ a, 16);                  \
	c = c + d;                              \
	b = rotr32(b ^ c, 12);                  \
	a = a + b + m[blake2s_sigma[r][2*i+1]]; \
	d = rotr32(d ^ a, 8);                   \
	c = c + d;                              \
	b = rotr32(b ^ c, 7);                   \
} \
while(0)

#define ROUND(r) do \
{ \
	G(r, 0, v[0], v[4], v[ 8], v[12]); \
	G(r, 1, v[1], v[5], v[ 9], v[13]); \
	G(r, 2, v[2], v[6], v[10], v[14]); \
	G(r, 3, v[3], v[7], v[11], v[15]); \
	G(r, 4, v[0], v[5], v[10], v[15]); \
	G(r, 5, v[1], v[6], v[11], v[12]); \
	G(r, 6, v[2], v[7], v[ 8], v[13]); \
	G(r, 7, v[3], v[4], v[ 9], v[14]); \
} \
while(0)

static void blake2s_compress(blake2s_t *const S, const uint8_t in[BLAKE2S_BLOCKBYTES])
{
	uint32_t m[16];
	uint32_t v[16];
	size_t i;

	for (i = 0; i < 16; ++i)
	{
		m[i] = load32(in + i * sizeof(m[i]));
	}

	for (i = 0; i < 8; ++i)
	{
		v[i] = S->h[i];
	}

	v[ 8] = blake2s_IV[0];
	v[ 9] = blake2s_IV[1];
	v[10] = blake2s_IV[2];
	v[11] = blake2s_IV[3];
	v[12] = S->t[0] ^ blake2s_IV[4];
	v[13] = S->t[1] ^ blake2s_IV[5];
	v[14] = S->f[0] ^ blake2s_IV[6];
	v[15] = S->f[1] ^ blake2s_IV[7];

	ROUND(0);
	ROUND(1);
	ROUND(2);
	ROUND(3);
	ROUND(4);
	ROUND(5);
	ROUND(6);
	ROUND(7);
	ROUND(8);
	ROUND(9);

	for (i = 0; i < 8; ++i)
	{
		S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
	}
}

#undef G
#undef ROUND

void blake2s_update(blake2s_t *const S, const void *const pin, size_t inlen)
{
	const unsigned char* in = (const unsigned char*)pin;
	if (inlen > 0)
	{
		size_t left = S->buflen;
		size_t fill = BLAKE2S_BLOCKBYTES - left;
		if (inlen > fill)
		{
			S->buflen = 0;
			memcpy(S->buf + left, in, fill); /* Fill buffer */
			blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES);
			blake2s_compress(S, S->buf); /* Compress */
			in += fill; inlen -= fill;
			while (inlen > BLAKE2S_BLOCKBYTES)
			{
				blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES);
				blake2s_compress(S, in);
				in += BLAKE2S_BLOCKBYTES;
				inlen -= BLAKE2S_BLOCKBYTES;
			}
		}
		memcpy(S->buf + S->buflen, in, inlen);
		S->buflen += inlen;
	}
}

uint64_t blake2s_final(blake2s_t *const S)
{
	if (blake2s_is_lastblock(S))
		return 0U;

	blake2s_increment_counter(S, (uint32_t)S->buflen);
	blake2s_set_lastblock(S);
	memset(S->buf + S->buflen, 0, BLAKE2S_BLOCKBYTES - S->buflen); /* Padding */
	blake2s_compress(S, S->buf);

	return make64(S->h[0], S->h[1]);
}

uint64_t blake2s_compute(const void *const pin, const size_t inlen)
{
	blake2s_t state;

	if ((pin != NULL) && (inlen > 0U))
	{
		blake2s_init(&state);
		blake2s_update(&state, pin, inlen);
		return blake2s_final(&state);
	}

	return 0xCCF2B16074DF93CEull;
}
