/*
 * Copyright 2018-2019 NXP
 *
 * License: NXP LA_OPT_NXP_Software_License
 *
 * NXP Confidential. This software is owned or controlled by NXP and may
 * only be used strictly in accordance with the applicable license terms.
 * By expressly accepting such terms or by downloading, installing,
 * activating and/or otherwise using the software, you are agreeing that
 * you have read, and that you agree to comply with and are bound by,
 * such license terms.  If you do not agree to be bound by the applicable
 * license terms, then you may not retain, install, activate or otherwise
 * use the software.  This code may only be used in a microprocessor,
 * microcontroller, sensor or digital signal processor ("NXP Product")
 * supplied directly or indirectly from NXP.  See the full NXP Software
 * License Agreement in license/LA_OPT_NXP_Software_License.pdf
 *
 * FreeMASTER Communication Driver - protocol implementation
 */

/*
 * This code is based on original implementation by Brad Conte. Extended to
 * support big-endian platforms for FreeMASTER driver needs.
 * Origin: https://github.com/B-Con/crypto-algorithms
 *
 * Author:     Brad Conte (brad AT bradconte.com)
 * Copyright:
 * Disclaimer: This code is presented "as is" without any guarantees.
 * Details:    Implementation of the SHA1 hashing algorithm.
               Algorithm specification can be found here:
                * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
               This implementation uses little endian byte order.
 */

#include "freemaster.h"
#include "freemaster_private.h"

#if !(FMSTR_DISABLE)

/*****************************************************************************
* Local Functions
******************************************************************************/

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

static void FMSTR_Sha1Transform(FMSTR_SHA1_CTX *ctx, const FMSTR_U8* data)
{
	// save 352 bytes on stack by making these static, we never run more instances than one anyway
	static FMSTR_U32 a, b, c, d, e, i, j, t, m[80];

	for (i = 0, j = 0; i < 16; ++i, j += 4) {
		a = ((FMSTR_U32)data[j]) << 24;
		a += ((FMSTR_U32)data[j + 1]) << 16;
		a += ((FMSTR_U32)data[j + 2]) << 8;
		a += data[j + 3];
		m[i] = a;
	}
	for ( ; i < 80; ++i) {
		m[i] = (m[i - 3] ^ m[i - 8] ^ m[i - 14] ^ m[i - 16]);
		m[i] = (m[i] << 1) | (m[i] >> 31);
	}

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

	for (i = 0; i < 20; ++i) {
		t = ROTLEFT(a, 5) + ((b & c) ^ (~b & d)) + e + ctx->k[0] + m[i];
		e = d;
		d = c;
		c = ROTLEFT(b, 30);
		b = a;
		a = t;
	}
	for ( ; i < 40; ++i) {
		t = ROTLEFT(a, 5) + (b ^ c ^ d) + e + ctx->k[1] + m[i];
		e = d;
		d = c;
		c = ROTLEFT(b, 30);
		b = a;
		a = t;
	}
	for ( ; i < 60; ++i) {
		t = ROTLEFT(a, 5) + ((b & c) ^ (b & d) ^ (c & d))  + e + ctx->k[2] + m[i];
		e = d;
		d = c;
		c = ROTLEFT(b, 30);
		b = a;
		a = t;
	}
	for ( ; i < 80; ++i) {
		t = ROTLEFT(a, 5) + (b ^ c ^ d) + e + ctx->k[3] + m[i];
		e = d;
		d = c;
		c = ROTLEFT(b, 30);
		b = a;
		a = t;
	}

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

/*****************************************************************************
* API Functions
******************************************************************************/

void FMSTR_Sha1Init(FMSTR_SHA1_CTX *ctx)
{
	ctx->datalen = 0;
	ctx->bitlen = 0;
	ctx->state[0] = 0x67452301;
	ctx->state[1] = 0xEFCDAB89;
	ctx->state[2] = 0x98BADCFE;
	ctx->state[3] = 0x10325476;
	ctx->state[4] = 0xc3d2e1f0;
	ctx->k[0] = 0x5a827999;
	ctx->k[1] = 0x6ed9eba1;
	ctx->k[2] = 0x8f1bbcdc;
	ctx->k[3] = 0xca62c1d6;
}

void FMSTR_Sha1Update(FMSTR_SHA1_CTX *ctx, const FMSTR_U8* data, size_t len)
{
	size_t i;

	for (i = 0; i < len; ++i) {
		ctx->data[ctx->datalen] = data[i];
		ctx->datalen++;
		if (ctx->datalen == 64) {
			FMSTR_Sha1Transform(ctx, ctx->data);
			ctx->bitlen += 512;
			ctx->datalen = 0;
		}
	}
}

void FMSTR_Sha1Final(FMSTR_SHA1_CTX *ctx, FMSTR_U8* hash)
{
	FMSTR_U32 i;

	i = ctx->datalen;

	// Pad whatever data is left in the buffer.
	if (ctx->datalen < 56) {
		ctx->data[i++] = 0x80;
		while (i < 56)
			ctx->data[i++] = 0x00;
	}
	else {
		ctx->data[i++] = 0x80;
		while (i < 64)
			ctx->data[i++] = 0x00;
		FMSTR_Sha1Transform(ctx, ctx->data);
		memset(ctx->data, 0, 56);
	}

	// Append to the padding the total message's length in bits and transform.
	ctx->bitlen += ctx->datalen * 8;
	ctx->data[63] = (FMSTR_U8)(ctx->bitlen);
	ctx->data[62] = (FMSTR_U8)(ctx->bitlen >> 8);
	ctx->data[61] = (FMSTR_U8)(ctx->bitlen >> 16);
	ctx->data[60] = (FMSTR_U8)(ctx->bitlen >> 24);
	ctx->data[59] = (FMSTR_U8)(ctx->bitlen >> 32);
	ctx->data[58] = (FMSTR_U8)(ctx->bitlen >> 40);
	ctx->data[57] = (FMSTR_U8)(ctx->bitlen >> 48);
	ctx->data[56] = (FMSTR_U8)(ctx->bitlen >> 56);
	FMSTR_Sha1Transform(ctx, ctx->data);

#if FMSTR_PLATFORM_BIG_ENDIAN
	// copy state to output buffer
	for (i = 0; i < 5; ++i) {
		*(FMSTR_U32*)(hash+i*4) = ctx->state[i];
	}
#else
	// copy while reversing byte order
	for (i = 0; i < 4; ++i) {
		int shift = 24 - i * 8;
		hash[i]      = (FMSTR_U8)((ctx->state[0] >> shift) & 0xff);
		hash[i + 4]  = (FMSTR_U8)((ctx->state[1] >> shift) & 0xff);
		hash[i + 8]  = (FMSTR_U8)((ctx->state[2] >> shift) & 0xff);
		hash[i + 12] = (FMSTR_U8)((ctx->state[3] >> shift) & 0xff);
		hash[i + 16] = (FMSTR_U8)((ctx->state[4] >> shift) & 0xff);
	}
#endif
}

#endif /* !FMSTR_DISABLE */
