/**
 *
 * \file  m2m_crypto.c
 *
 * \brief WINC Crypto module.
 *
 * Copyright (c) 2014 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */

/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
INCLUDES
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/

#include "driver/include/m2m_crypto.h"
#include "driver/source/nmbus.h"
#include "driver/source/nmasic.h"

#ifdef CONF_CRYPTO_HW

/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
MACROS
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/

/*======*======*======*======*======*=======*
 * WINC SHA256 HW Engine Register Definition *
 *======*======*======*======*======*========*/

#define SHA_BLOCK_SIZE (64)

#define SHARED_MEM_BASE (0xd0000)

#define SHA256_MEM_BASE (0x180000UL)
#define SHA256_ENGINE_ADDR (0x180000ul)

/* SHA256 Registers */
#define SHA256_CTRL (SHA256_MEM_BASE + 0x00)
#define SHA256_CTRL_START_CALC_MASK (NBIT0)
#define SHA256_CTRL_START_CALC_SHIFT (0)
#define SHA256_CTRL_PREPROCESS_MASK (NBIT1)
#define SHA256_CTRL_PREPROCESS_SHIFT (1)
#define SHA256_CTRL_HASH_HASH_MASK (NBIT2)
#define SHA256_CTRL_HASH_HASH_SHIFT (2)
#define SHA256_CTRL_INIT_SHA256_STATE_MASK (NBIT3)
#define SHA256_CTRL_INIT_SHA256_STATE_SHIFT (3)
#define SHA256_CTRL_WR_BACK_HASH_VALUE_MASK (NBIT4)
#define SHA256_CTRL_WR_BACK_HASH_VALUE_SHIFT (4)
#define SHA256_CTRL_FORCE_SHA256_QUIT_MASK (NBIT5)
#define SHA256_CTRL_FORCE_SHA256_QUIT_SHIFT (5)

#define SHA256_REGS_SHA256_CTRL_AHB_BYTE_REV_EN (NBIT6)
#define SHA256_REGS_SHA256_CTRL_RESERVED (NBIT7)
#define SHA256_REGS_SHA256_CTRL_CORE_TO_AHB_CLK_RATIO (NBIT8 + NBIT9 + NBIT10)
#define SHA256_REGS_SHA256_CTRL_CORE_TO_AHB_CLK_RATIO_MASK (NBIT2 + NBIT1 + NBIT0)
#define SHA256_REGS_SHA256_CTRL_CORE_TO_AHB_CLK_RATIO_SHIFT (8)
#define SHA256_REGS_SHA256_CTRL_RESERVED_11 (NBIT11)
#define SHA256_REGS_SHA256_CTRL_SHA1_CALC (NBIT12)
#define SHA256_REGS_SHA256_CTRL_PBKDF2_SHA1_CALC (NBIT13)

#define SHA256_START_RD_ADDR (SHA256_MEM_BASE + 0x04UL)
#define SHA256_DATA_LENGTH (SHA256_MEM_BASE + 0x08UL)
#define SHA256_START_WR_ADDR (SHA256_MEM_BASE + 0x0cUL)
#define SHA256_COND_CHK_CTRL (SHA256_MEM_BASE + 0x10)
#define SHA256_COND_CHK_CTRL_HASH_VAL_COND_CHK_MASK (NBIT1 | NBIT0)
#define SHA256_COND_CHK_CTRL_HASH_VAL_COND_CHK_SHIFT (0)
#define SHA256_COND_CHK_CTRL_STEP_VAL_MASK (NBIT6 | NBIT5 | NBIT4 | NBIT3 | NBIT2)
#define SHA256_COND_CHK_CTRL_STEP_VAL_SHIFT (2)
#define SHA256_COND_CHK_CTRL_COND_CHK_RESULT_MASK (NBIT7)
#define SHA256_COND_CHK_CTRL_COND_CHK_RESULT_SHIFT (7)

#define SHA256_MOD_DATA_RANGE (SHA256_MEM_BASE + 0x14)
#define SHA256_MOD_DATA_RANGE_ST_BYTE_2_ADD_STP_MASK (NBIT24 - 1)
#define SHA256_MOD_DATA_RANGE_ST_BYTE_2_ADD_STP_SHIFT (0)
#define SHA256_MOD_DATA_RANGE_MOD_DATA_LEN_MASK (NBIT24 | NBIT25 | NBIT26)
#define SHA256_MOD_DATA_RANGE_MOD_DATA_LEN_SHIFT (24)

#define SHA256_COND_CHK_STS_1 (SHA256_MEM_BASE + 0x18)
#define SHA256_COND_CHK_STS_2 (SHA256_MEM_BASE + 0x1c)
#define SHA256_DONE_INTR_ENABLE (SHA256_MEM_BASE + 0x20)
#define SHA256_DONE_INTR_STS (SHA256_MEM_BASE + 0x24)
#define SHA256_TARGET_HASH_H1 (SHA256_MEM_BASE + 0x28)
#define SHA256_TARGET_HASH_H2 (SHA256_MEM_BASE + 0x2c)
#define SHA256_TARGET_HASH_H3 (SHA256_MEM_BASE + 0x30)
#define SHA256_TARGET_HASH_H4 (SHA256_MEM_BASE + 0x34)
#define SHA256_TARGET_HASH_H5 (SHA256_MEM_BASE + 0x38)
#define SHA256_TARGET_HASH_H6 (SHA256_MEM_BASE + 0x3c)
#define SHA256_TARGET_HASH_H7 (SHA256_MEM_BASE + 0x40)
#define SHA256_TARGET_HASH_H8 (SHA256_MEM_BASE + 0x44)

/*======*======*======*======*======*=======*
 * WINC BIGINT HW Engine Register Definition *
 *======*======*======*======*======*========*/

#define BIGINT_ENGINE_ADDR (0x180080ul)
#define BIGINT_VERSION (BIGINT_ENGINE_ADDR + 0x00)

#define BIGINT_MISC_CTRL (BIGINT_ENGINE_ADDR + 0x04)
#define BIGINT_MISC_CTRL_CTL_START (NBIT0)
#define BIGINT_MISC_CTRL_CTL_RESET (NBIT1)
#define BIGINT_MISC_CTRL_CTL_MSW_FIRST (NBIT2)
#define BIGINT_MISC_CTRL_CTL_SWAP_BYTE_ORDER (NBIT3)
#define BIGINT_MISC_CTRL_CTL_FORCE_BARRETT (NBIT4)
#define BIGINT_MISC_CTRL_CTL_M_PRIME_VALID (NBIT5)

#define BIGINT_M_PRIME (BIGINT_ENGINE_ADDR + 0x08)

#define BIGINT_STATUS (BIGINT_ENGINE_ADDR + 0x0C)
#define BIGINT_STATUS_STS_DONE (NBIT0)

#define BIGINT_CLK_COUNT (BIGINT_ENGINE_ADDR + 0x10)
#define BIGINT_ADDR_X (BIGINT_ENGINE_ADDR + 0x14)
#define BIGINT_ADDR_E (BIGINT_ENGINE_ADDR + 0x18)
#define BIGINT_ADDR_M (BIGINT_ENGINE_ADDR + 0x1C)
#define BIGINT_ADDR_R (BIGINT_ENGINE_ADDR + 0x20)
#define BIGINT_LENGTH (BIGINT_ENGINE_ADDR + 0x24)

#define BIGINT_IRQ_STS (BIGINT_ENGINE_ADDR + 0x28)
#define BIGINT_IRQ_STS_DONE (NBIT0)
#define BIGINT_IRQ_STS_CHOOSE_MONT (NBIT1)
#define BIGINT_IRQ_STS_M_READ (NBIT2)
#define BIGINT_IRQ_STS_X_READ (NBIT3)
#define BIGINT_IRQ_STS_START (NBIT4)
#define BIGINT_IRQ_STS_PRECOMP_FINISH (NBIT5)

#define BIGINT_IRQ_MASK (BIGINT_ENGINE_ADDR + 0x2C)
#define BIGINT_IRQ_MASK_CTL_IRQ_MASK_START (NBIT4)

#define ENABLE_FLIPPING 1

#define GET_UINT32(BUF, OFFSET)                                                                                        \
	(((uint32)((BUF)[OFFSET])) | ((uint32)(((BUF)[OFFSET + 1]) << 8)) | ((uint32)(((BUF)[OFFSET + 2]) << 16))          \
	 | ((uint32)(((BUF)[OFFSET + 3]) << 24)))

#define PUTU32(VAL32, BUF, OFFSET)                                                                                     \
	do {                                                                                                               \
		(BUF)[OFFSET]     = BYTE_3((VAL32));                                                                           \
		(BUF)[OFFSET + 1] = BYTE_2((VAL32));                                                                           \
		(BUF)[OFFSET + 2] = BYTE_1((VAL32));                                                                           \
		(BUF)[OFFSET + 3] = BYTE_0((VAL32));                                                                           \
	} while (0)

/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
DATA TYPES
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/

/*!
@struct	\
    tstrHashContext

@brief
*/
typedef struct {
	uint32 au32HashState[M2M_SHA256_DIGEST_LEN / 4];
	uint8  au8CurrentBlock[64];
	uint32 u32TotalLength;
	uint8  u8InitHashFlag;
} tstrSHA256HashCtxt;

/*======*======*======*======*======*=======*
 *           SHA256 IMPLEMENTATION           *
 *======*======*======*======*======*========*/

sint8 m2m_crypto_sha256_hash_init(tstrM2mSha256Ctxt *pstrSha256Ctxt)
{
	tstrSHA256HashCtxt *pstrSHA256 = (tstrSHA256HashCtxt *)pstrSha256Ctxt;
	if (pstrSHA256 != NULL) {
		m2m_memset((uint8 *)pstrSha256Ctxt, 0, sizeof(tstrM2mSha256Ctxt));
		pstrSHA256->u8InitHashFlag = 1;
	}
	return 0;
}

sint8 m2m_crypto_sha256_hash_update(tstrM2mSha256Ctxt *pstrSha256Ctxt, uint8 *pu8Data, uint16 u16DataLength)
{
	sint8               s8Ret      = M2M_ERR_FAIL;
	tstrSHA256HashCtxt *pstrSHA256 = (tstrSHA256HashCtxt *)pstrSha256Ctxt;
	if (pstrSHA256 != NULL) {
		uint32 u32ReadAddr;
		uint32 u32WriteAddr = SHARED_MEM_BASE;
		uint32 u32Addr      = u32WriteAddr;
		uint32 u32ResidualBytes;
		uint32 u32NBlocks;
		uint32 u32Offset;
		uint32 u32CurrentBlock = 0;
		uint8  u8IsDone        = 0;

		/* Get the remaining bytes from the previous update (if the length is not block aligned). */
		u32ResidualBytes = pstrSHA256->u32TotalLength % SHA_BLOCK_SIZE;

		/* Update the total data length. */
		pstrSHA256->u32TotalLength += u16DataLength;

		if (u32ResidualBytes != 0) {
			if ((u32ResidualBytes + u16DataLength) >= SHA_BLOCK_SIZE) {
				u32Offset = SHA_BLOCK_SIZE - u32ResidualBytes;
				m2m_memcpy(&pstrSHA256->au8CurrentBlock[u32ResidualBytes], pu8Data, u32Offset);
				pu8Data += u32Offset;
				u16DataLength -= u32Offset;

				nm_write_block(u32Addr, pstrSHA256->au8CurrentBlock, SHA_BLOCK_SIZE);
				u32Addr += SHA_BLOCK_SIZE;
				u32CurrentBlock = 1;
			} else {
				m2m_memcpy(&pstrSHA256->au8CurrentBlock[u32ResidualBytes], pu8Data, u16DataLength);
				u16DataLength = 0;
			}
		}

		/* Get the number of HASH BLOCKs and the residual bytes. */
		u32NBlocks       = u16DataLength / SHA_BLOCK_SIZE;
		u32ResidualBytes = u16DataLength % SHA_BLOCK_SIZE;

		if (u32NBlocks != 0) {
			nm_write_block(u32Addr, pu8Data, (uint16)(u32NBlocks * SHA_BLOCK_SIZE));
			pu8Data += (u32NBlocks * SHA_BLOCK_SIZE);
		}

		u32NBlocks += u32CurrentBlock;
		if (u32NBlocks != 0) {
			uint32 u32RegVal = 0;

			nm_write_reg(SHA256_CTRL, u32RegVal);
			u32RegVal |= SHA256_CTRL_FORCE_SHA256_QUIT_MASK;
			nm_write_reg(SHA256_CTRL, u32RegVal);

			if (pstrSHA256->u8InitHashFlag) {
				pstrSHA256->u8InitHashFlag = 0;
				u32RegVal |= SHA256_CTRL_INIT_SHA256_STATE_MASK;
			}

			u32ReadAddr = u32WriteAddr + (u32NBlocks * SHA_BLOCK_SIZE);
			nm_write_reg(SHA256_DATA_LENGTH, (u32NBlocks * SHA_BLOCK_SIZE));
			nm_write_reg(SHA256_START_RD_ADDR, u32WriteAddr);
			nm_write_reg(SHA256_START_WR_ADDR, u32ReadAddr);

			u32RegVal |= SHA256_CTRL_START_CALC_MASK;

			u32RegVal &= ~(0x7 << 8);
			u32RegVal |= (2 << 8);

			nm_write_reg(SHA256_CTRL, u32RegVal);

			/* 5.	Wait for done_intr */
			while (!u8IsDone) {
				u32RegVal = nm_read_reg(SHA256_DONE_INTR_STS);
				u8IsDone  = u32RegVal & NBIT0;
			}
		}
		if (u32ResidualBytes != 0) {
			m2m_memcpy(pstrSHA256->au8CurrentBlock, pu8Data, u32ResidualBytes);
		}
		s8Ret = M2M_SUCCESS;
	}
	return s8Ret;
}

sint8 m2m_crypto_sha256_hash_finish(tstrM2mSha256Ctxt *pstrSha256Ctxt, uint8 *pu8Sha256Digest)
{
	sint8               s8Ret      = M2M_ERR_FAIL;
	tstrSHA256HashCtxt *pstrSHA256 = (tstrSHA256HashCtxt *)pstrSha256Ctxt;
	if (pstrSHA256 != NULL) {
		uint32 u32ReadAddr;
		uint32 u32WriteAddr = SHARED_MEM_BASE;
		uint32 u32Addr      = u32WriteAddr;
		uint16 u16Offset;
		uint16 u16PaddingLength;
		uint16 u16NBlocks = 1;
		uint32 u32RegVal  = 0;
		uint32 u32Idx, u32ByteIdx;
		uint32 au32Digest[M2M_SHA256_DIGEST_LEN / 4];
		uint8  u8IsDone = 0;

		nm_write_reg(SHA256_CTRL, u32RegVal);
		u32RegVal |= SHA256_CTRL_FORCE_SHA256_QUIT_MASK;
		nm_write_reg(SHA256_CTRL, u32RegVal);

		if (pstrSHA256->u8InitHashFlag) {
			pstrSHA256->u8InitHashFlag = 0;
			u32RegVal |= SHA256_CTRL_INIT_SHA256_STATE_MASK;
		}

		/* Calculate the offset of the last data byte in the current block. */
		u16Offset = (uint16)(pstrSHA256->u32TotalLength % SHA_BLOCK_SIZE);

		/* Add the padding byte 0x80. */
		pstrSHA256->au8CurrentBlock[u16Offset++] = 0x80;

		/* Calculate the required padding to complete
		one Hash Block Size.
		*/
		u16PaddingLength = SHA_BLOCK_SIZE - u16Offset;
		m2m_memset(&pstrSHA256->au8CurrentBlock[u16Offset], 0, u16PaddingLength);

		/* If the padding count is not enough to hold 64-bit representation of
		the total input message length, one padding block is required.
		*/
		if (u16PaddingLength < 8) {
			nm_write_block(u32Addr, pstrSHA256->au8CurrentBlock, SHA_BLOCK_SIZE);
			u32Addr += SHA_BLOCK_SIZE;
			m2m_memset(pstrSHA256->au8CurrentBlock, 0, SHA_BLOCK_SIZE);
			u16NBlocks++;
		}

		/* pack the length at the end of the padding block */
		PUTU32(pstrSHA256->u32TotalLength << 3, pstrSHA256->au8CurrentBlock, (SHA_BLOCK_SIZE - 4));

		u32ReadAddr = u32WriteAddr + (u16NBlocks * SHA_BLOCK_SIZE);
		nm_write_block(u32Addr, pstrSHA256->au8CurrentBlock, SHA_BLOCK_SIZE);
		nm_write_reg(SHA256_DATA_LENGTH, (u16NBlocks * SHA_BLOCK_SIZE));
		nm_write_reg(SHA256_START_RD_ADDR, u32WriteAddr);
		nm_write_reg(SHA256_START_WR_ADDR, u32ReadAddr);

		u32RegVal |= SHA256_CTRL_START_CALC_MASK;
		u32RegVal |= SHA256_CTRL_WR_BACK_HASH_VALUE_MASK;
		u32RegVal &= ~(0x7UL << 8);
		u32RegVal |= (0x2UL << 8);

		nm_write_reg(SHA256_CTRL, u32RegVal);

		/* 5.	Wait for done_intr */
		while (!u8IsDone) {
			u32RegVal = nm_read_reg(SHA256_DONE_INTR_STS);
			u8IsDone  = u32RegVal & NBIT0;
		}
		nm_read_block(u32ReadAddr, (uint8 *)au32Digest, 32);

		/* Convert the output words to an array of bytes.
		 */
		u32ByteIdx = 0;
		for (u32Idx = 0; u32Idx < (M2M_SHA256_DIGEST_LEN / 4); u32Idx++) {
			pu8Sha256Digest[u32ByteIdx++] = BYTE_3(au32Digest[u32Idx]);
			pu8Sha256Digest[u32ByteIdx++] = BYTE_2(au32Digest[u32Idx]);
			pu8Sha256Digest[u32ByteIdx++] = BYTE_1(au32Digest[u32Idx]);
			pu8Sha256Digest[u32ByteIdx++] = BYTE_0(au32Digest[u32Idx]);
		}
		s8Ret = M2M_SUCCESS;
	}
	return s8Ret;
}

/*======*======*======*======*======*=======*
 *             RSA IMPLEMENTATION            *
 *======*======*======*======*======*========*/

static void FlipBuffer(uint8 *pu8InBuffer, uint8 *pu8OutBuffer, uint16 u16BufferSize)
{
	uint16 u16Idx;
	for (u16Idx = 0; u16Idx < u16BufferSize; u16Idx++) {
#if ENABLE_FLIPPING == 1
		pu8OutBuffer[u16Idx] = pu8InBuffer[u16BufferSize - u16Idx - 1];
#else
		pu8OutBuffer[u16Idx] = pu8InBuffer[u16Idx];
#endif
	}
}

void BigInt_ModExp(uint8 *pu8X, uint16 u16XSize, uint8 *pu8E, uint16 u16ESize, uint8 *pu8M, uint16 u16MSize,
                   uint8 *pu8R, uint16 u16RSize)
{
	uint32 u32Reg;
	uint8  au8Tmp[780] = {0};
	uint32 u32XAddr    = SHARED_MEM_BASE;
	uint32 u32MAddr;
	uint32 u32EAddr;
	uint32 u32RAddr;
	uint8  u8EMswBits = 32;
	uint32 u32Mprime  = 0x7F;
	uint16 u16XSizeWords, u16ESizeWords;
	uint32 u32Exponent;

	u16XSizeWords = (u16XSize + 3) / 4;
	u16ESizeWords = (u16ESize + 3) / 4;

	u32MAddr = u32XAddr + (u16XSizeWords * 4);
	u32EAddr = u32MAddr + (u16XSizeWords * 4);
	u32RAddr = u32EAddr + (u16ESizeWords * 4);

	/* Reset the core.
	 */
	u32Reg = 0;
	u32Reg |= BIGINT_MISC_CTRL_CTL_RESET;
	u32Reg = nm_read_reg(BIGINT_MISC_CTRL);
	u32Reg &= ~BIGINT_MISC_CTRL_CTL_RESET;
	u32Reg = nm_read_reg(BIGINT_MISC_CTRL);

	nm_write_block(u32RAddr, au8Tmp, u16RSize);

	/* Write Input Operands to Chip Memory.
	 */
	/*------- X -------*/
	FlipBuffer(pu8X, au8Tmp, u16XSize);
	nm_write_block(u32XAddr, au8Tmp, u16XSizeWords * 4);

	/*------- E -------*/
	m2m_memset(au8Tmp, 0, sizeof(au8Tmp));
	FlipBuffer(pu8E, au8Tmp, u16ESize);
	nm_write_block(u32EAddr, au8Tmp, u16ESizeWords * 4);
	u32Exponent = GET_UINT32(au8Tmp, (u16ESizeWords * 4) - 4);
	while ((u32Exponent & NBIT31) == 0) {
		u32Exponent <<= 1;
		u8EMswBits--;
	}

	/*------- M -------*/
	m2m_memset(au8Tmp, 0, sizeof(au8Tmp));
	FlipBuffer(pu8M, au8Tmp, u16XSize);
	nm_write_block(u32MAddr, au8Tmp, u16XSizeWords * 4);

	/* Program the addresses of the input operands.
	 */
	nm_write_reg(BIGINT_ADDR_X, u32XAddr);
	nm_write_reg(BIGINT_ADDR_E, u32EAddr);
	nm_write_reg(BIGINT_ADDR_M, u32MAddr);
	nm_write_reg(BIGINT_ADDR_R, u32RAddr);

	/* Mprime.
	 */
	nm_write_reg(BIGINT_M_PRIME, u32Mprime);

	/* Length.
	 */
	u32Reg = (u16XSizeWords & 0xFF);
	u32Reg += ((u16ESizeWords & 0xFF) << 8);
	u32Reg += (u8EMswBits << 16);
	nm_write_reg(BIGINT_LENGTH, u32Reg);

	/* CTRL Register.
	 */
	u32Reg = nm_read_reg(BIGINT_MISC_CTRL);
	u32Reg ^= BIGINT_MISC_CTRL_CTL_START;
	u32Reg |= BIGINT_MISC_CTRL_CTL_FORCE_BARRETT;
	// u32Reg |= BIGINT_MISC_CTRL_CTL_M_PRIME_VALID;
#if ENABLE_FLIPPING == 0
	u32Reg |= BIGINT_MISC_CTRL_CTL_MSW_FIRST;
#endif
	nm_write_reg(BIGINT_MISC_CTRL, u32Reg);

	/* Wait for computation to complete. */
	while (1) {
		u32Reg = nm_read_reg(BIGINT_IRQ_STS);
		if (u32Reg & BIGINT_IRQ_STS_DONE) {
			break;
		}
	}
	nm_write_reg(BIGINT_IRQ_STS, 0);
	m2m_memset(au8Tmp, 0, sizeof(au8Tmp));
	nm_read_block(u32RAddr, au8Tmp, u16RSize);
	FlipBuffer(au8Tmp, pu8R, u16RSize);
}

#define MD5_DIGEST_SIZE (16)
#define SHA1_DIGEST_SIZE (20)

static const uint8 au8TEncodingMD5[]
    = {0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04};
/*!< Fixed part of the Encoding T for the MD5 hash algorithm.
 */

static const uint8 au8TEncodingSHA1[]
    = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04};
/*!< Fixed part of the Encoding T for the SHA-1 hash algorithm.
 */

static const uint8 au8TEncodingSHA2[]
    = {0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04};
/*!< Fixed part of the Encoding T for the SHA-2 hash algorithm.
 */

sint8 m2m_crypto_rsa_sign_verify(uint8 *pu8N, uint16 u16NSize, uint8 *pu8E, uint16 u16ESize, uint8 *pu8SignedMsgHash,
                                 uint16 u16HashLength, uint8 *pu8RsaSignature)
{
	sint8 s8Ret = M2M_RSA_SIGN_FAIL;

	if ((pu8N != NULL) && (pu8E != NULL) && (pu8RsaSignature != NULL) && (pu8SignedMsgHash != NULL)) {
		uint16 u16TLength, u16TEncodingLength;
		uint8 *pu8T;
		uint8  au8EM[512];

		/* Selection of correct T Encoding based on the hash size.
		 */
		if (u16HashLength == MD5_DIGEST_SIZE) {
			pu8T               = (uint8 *)au8TEncodingMD5;
			u16TEncodingLength = sizeof(au8TEncodingMD5);
		} else if (u16HashLength == SHA1_DIGEST_SIZE) {
			pu8T               = (uint8 *)au8TEncodingSHA1;
			u16TEncodingLength = sizeof(au8TEncodingSHA1);
		} else {
			pu8T               = (uint8 *)au8TEncodingSHA2;
			u16TEncodingLength = sizeof(au8TEncodingSHA2);
		}
		u16TLength = u16TEncodingLength + 1 + u16HashLength;

		/* If emLen < tLen + 11.
		 */
		if (u16NSize >= (u16TLength + 11)) {
			uint32 u32PSLength, u32Idx = 0;

			/*
			RSA verification
			*/
			BigInt_ModExp(pu8RsaSignature, u16NSize, pu8E, u16ESize, pu8N, u16NSize, au8EM, u16NSize);

			u32PSLength = u16NSize - u16TLength - 3;

			/*
			The calculated EM must match the following pattern.
			*======*======*======*======*======*
			* 0x00 || 0x01 || PS || 0x00 || T  *
			*======*======*======*======*======*
			Where PS is all 0xFF
			T is defined based on the hash algorithm.
			*/
			if ((au8EM[0] == 0x00) && (au8EM[1] == 0x01)) {
				for (u32Idx = 2; au8EM[u32Idx] == 0xFF; u32Idx++)
					;
				if (u32Idx == (u32PSLength + 2)) {
					if (au8EM[u32Idx++] == 0x00) {
						if (!m2m_memcmp(&au8EM[u32Idx], pu8T, u16TEncodingLength)) {
							u32Idx += u16TEncodingLength;
							if (au8EM[u32Idx++] == u16HashLength)
								s8Ret = m2m_memcmp(&au8EM[u32Idx], pu8SignedMsgHash, u16HashLength);
						}
					}
				}
			}
		}
	}
	return s8Ret;
}

sint8 m2m_crypto_rsa_sign_gen(uint8 *pu8N, uint16 u16NSize, uint8 *pu8d, uint16 u16dSize, uint8 *pu8SignedMsgHash,
                              uint16 u16HashLength, uint8 *pu8RsaSignature)
{
	sint8 s8Ret = M2M_RSA_SIGN_FAIL;

	if ((pu8N != NULL) && (pu8d != NULL) && (pu8RsaSignature != NULL) && (pu8SignedMsgHash != NULL)) {
		uint16 u16TLength, u16TEncodingLength;
		uint8 *pu8T;
		uint8  au8EM[512];

		/* Selection of correct T Encoding based on the hash size.
		 */
		if (u16HashLength == MD5_DIGEST_SIZE) {
			pu8T               = (uint8 *)au8TEncodingMD5;
			u16TEncodingLength = sizeof(au8TEncodingMD5);
		} else if (u16HashLength == SHA1_DIGEST_SIZE) {
			pu8T               = (uint8 *)au8TEncodingSHA1;
			u16TEncodingLength = sizeof(au8TEncodingSHA1);
		} else {
			pu8T               = (uint8 *)au8TEncodingSHA2;
			u16TEncodingLength = sizeof(au8TEncodingSHA2);
		}
		u16TLength = u16TEncodingLength + 1 + u16HashLength;

		/* If emLen < tLen + 11.
		 */
		if (u16NSize >= (u16TLength + 11)) {
			uint16 u16PSLength = 0;
			uint16 u16Offset   = 0;

			/*
			The calculated EM must match the following pattern.
			*======*======*======*======*======*
			* 0x00 || 0x01 || PS || 0x00 || T  *
			*======*======*======*======*======*
			Where PS is all 0xFF
			T is defined based on the hash algorithm.
			*/
			au8EM[u16Offset++] = 0;
			au8EM[u16Offset++] = 1;
			u16PSLength        = u16NSize - u16TLength - 3;
			m2m_memset(&au8EM[u16Offset], 0xFF, u16PSLength);
			u16Offset += u16PSLength;
			au8EM[u16Offset++] = 0;
			m2m_memcpy(&au8EM[u16Offset], pu8T, u16TEncodingLength);
			u16Offset += u16TEncodingLength;
			au8EM[u16Offset++] = u16HashLength;
			m2m_memcpy(&au8EM[u16Offset], pu8SignedMsgHash, u16HashLength);

			/*
			RSA Signature Generation
			*/
			BigInt_ModExp(au8EM, u16NSize, pu8d, u16dSize, pu8N, u16NSize, pu8RsaSignature, u16NSize);
			s8Ret = M2M_RSA_SIGN_OK;
		}
	}
	return s8Ret;
}

#endif /* CONF_CRYPTO */

#ifdef CONF_CRYPTO_SOFT

typedef struct {
	tpfAppCryproCb pfAppCryptoCb;
	uint8 *        pu8Digest;
	uint8 *        pu8Rsa;
	uint8          u8CryptoBusy;
} tstrCryptoCtxt;

typedef struct {
	uint8  au8N[M2M_MAX_RSA_LEN];
	uint8  au8E[M2M_MAX_RSA_LEN];
	uint8  au8Hash[M2M_SHA256_DIGEST_LEN];
	uint16 u16Nsz;
	uint16 u16Esz;
	uint16 u16Hsz;
	uint8  _pad16_[2];
} tstrRsaPayload;

static tstrCryptoCtxt gstrCryptoCtxt;

/**
 *	@fn			m2m_crypto_cb(uint8 u8OpCode, uint16 u16DataSize, uint32 u32Addr)
 *	@brief		WiFi call back function
 *	@param [in]	u8OpCode
 *					HIF Opcode type.
 *	@param [in]	u16DataSize
 *					HIF data length.
 *	@param [in]	u32Addr
 *					HIF address.
 *	@author
 *	@date
 *	@version	1.0
 */
static void m2m_crypto_cb(uint8 u8OpCode, uint16 u16DataSize, uint32 u32Addr)
{
	sint8 ret                   = M2M_SUCCESS;
	gstrCryptoCtxt.u8CryptoBusy = 0;
	if (u8OpCode == M2M_CRYPTO_RESP_SHA256_INIT) {
		tstrM2mSha256Ctxt strCtxt;
		if (hif_receive(u32Addr, (uint8 *)&strCtxt, sizeof(tstrM2mSha256Ctxt), 0) == M2M_SUCCESS) {
			tstrCyptoResp strResp;
			if (hif_receive(u32Addr + sizeof(tstrM2mSha256Ctxt), (uint8 *)&strResp, sizeof(tstrCyptoResp), 1)
			    == M2M_SUCCESS) {
				if (gstrCryptoCtxt.pfAppCryptoCb)
					gstrCryptoCtxt.pfAppCryptoCb(u8OpCode, &strResp, &strCtxt);
			}
		}
	} else if (u8OpCode == M2M_CRYPTO_RESP_SHA256_UPDATE) {
		tstrM2mSha256Ctxt strCtxt;
		if (hif_receive(u32Addr, (uint8 *)&strCtxt, sizeof(tstrM2mSha256Ctxt), 0) == M2M_SUCCESS) {
			tstrCyptoResp strResp;
			if (hif_receive(u32Addr + sizeof(tstrM2mSha256Ctxt), (uint8 *)&strResp, sizeof(tstrCyptoResp), 1)
			    == M2M_SUCCESS) {
				if (gstrCryptoCtxt.pfAppCryptoCb)
					gstrCryptoCtxt.pfAppCryptoCb(u8OpCode, &strResp, &strCtxt);
			}
		}

	} else if (u8OpCode == M2M_CRYPTO_RESP_SHA256_FINSIH) {
		tstrCyptoResp strResp;
		if (hif_receive(u32Addr + sizeof(tstrM2mSha256Ctxt), (uint8 *)&strResp, sizeof(tstrCyptoResp), 0)
		    == M2M_SUCCESS) {
			if (hif_receive(u32Addr + sizeof(tstrM2mSha256Ctxt) + sizeof(tstrCyptoResp),
			                (uint8 *)gstrCryptoCtxt.pu8Digest,
			                M2M_SHA256_DIGEST_LEN,
			                1)
			    == M2M_SUCCESS) {
				if (gstrCryptoCtxt.pfAppCryptoCb)
					gstrCryptoCtxt.pfAppCryptoCb(u8OpCode, &strResp, gstrCryptoCtxt.pu8Digest);
			}
		}
	} else if (u8OpCode == M2M_CRYPTO_RESP_RSA_SIGN_GEN) {
		tstrCyptoResp strResp;
		if (hif_receive(u32Addr + sizeof(tstrRsaPayload), (uint8 *)&strResp, sizeof(tstrCyptoResp), 0) == M2M_SUCCESS) {
			if (hif_receive(u32Addr + sizeof(tstrRsaPayload) + sizeof(tstrCyptoResp),
			                (uint8 *)gstrCryptoCtxt.pu8Rsa,
			                M2M_MAX_RSA_LEN,
			                0)
			    == M2M_SUCCESS) {
				if (gstrCryptoCtxt.pfAppCryptoCb)
					gstrCryptoCtxt.pfAppCryptoCb(u8OpCode, &strResp, gstrCryptoCtxt.pu8Rsa);
			}
		}
	} else if (u8OpCode == M2M_CRYPTO_RESP_RSA_SIGN_VERIFY) {
		tstrCyptoResp strResp;
		if (hif_receive(u32Addr + sizeof(tstrRsaPayload), (uint8 *)&strResp, sizeof(tstrCyptoResp), 1) == M2M_SUCCESS) {
			if (gstrCryptoCtxt.pfAppCryptoCb)
				gstrCryptoCtxt.pfAppCryptoCb(u8OpCode, &strResp, NULL);
		}
	} else {
		M2M_ERR("u8Code %d ??\n", u8OpCode);
	}
}
/*!
@fn	\
    sint8 m2m_crypto_init();

@brief	crypto initialization

@param[in]	pfAppCryproCb

*/
sint8 m2m_crypto_init(tpfAppCryproCb pfAppCryproCb)
{
	sint8 ret = M2M_ERR_FAIL;
	m2m_memset((uint8 *)&gstrCryptoCtxt, 0, sizeof(tstrCryptoCtxt));
	if (pfAppCryproCb != NULL) {
		gstrCryptoCtxt.pfAppCryptoCb = pfAppCryproCb;
		ret                          = hif_register_cb(M2M_REQ_GROUP_CRYPTO, m2m_crypto_cb);
	}
	return ret;
}
/*!
@fn	\
    sint8 m2m_sha256_hash_init(tstrM2mSha256Ctxt *psha256Ctxt);

@brief	SHA256 hash initialization

@param[in]	psha256Ctxt
                Pointer to a sha256 context allocated by the caller.
*/
sint8 m2m_crypto_sha256_hash_init(tstrM2mSha256Ctxt *psha256Ctxt)
{
	sint8 ret = M2M_ERR_FAIL;
	if ((psha256Ctxt != NULL) && (!gstrCryptoCtxt.u8CryptoBusy)) {
		ret = hif_send(M2M_REQ_GROUP_CRYPTO,
		               M2M_CRYPTO_REQ_SHA256_INIT | M2M_REQ_DATA_PKT,
		               (uint8 *)psha256Ctxt,
		               sizeof(tstrM2mSha256Ctxt),
		               NULL,
		               0,
		               0);
	}
	return ret;
}

/*!
@fn	\
    sint8 m2m_sha256_hash_update(tstrM2mSha256Ctxt *psha256Ctxt, uint8 *pu8Data, uint16 u16DataLength);

@brief	SHA256 hash update

@param [in]	psha256Ctxt
                Pointer to the sha256 context.

@param [in]	pu8Data
                Buffer holding the data submitted to the hash.

@param [in]	u16DataLength
                Size of the data bufefr in bytes.
*/
sint8 m2m_crypto_sha256_hash_update(tstrM2mSha256Ctxt *psha256Ctxt, uint8 *pu8Data, uint16 u16DataLength)
{
	sint8 ret = M2M_ERR_FAIL;
	if ((!gstrCryptoCtxt.u8CryptoBusy) && (psha256Ctxt != NULL) && (pu8Data != NULL)
	    && (u16DataLength < M2M_SHA256_MAX_DATA)) {
		ret = hif_send(M2M_REQ_GROUP_CRYPTO,
		               M2M_CRYPTO_REQ_SHA256_UPDATE | M2M_REQ_DATA_PKT,
		               (uint8 *)psha256Ctxt,
		               sizeof(tstrM2mSha256Ctxt),
		               pu8Data,
		               u16DataLength,
		               sizeof(tstrM2mSha256Ctxt) + sizeof(tstrCyptoResp));
	}
	return ret;
}

/*!
@fn	\
    sint8 m2m_sha256_hash_finish(tstrM2mSha256Ctxt *psha256Ctxt, uint8 *pu8Sha256Digest);

@brief	SHA256 hash finalization

@param[in]	psha256Ctxt
                Pointer to a sha256 context allocated by the caller.

@param [in] pu8Sha256Digest
                Buffer allocated by the caller which will hold the resultant SHA256 Digest. It must be allocated no less
than M2M_SHA256_DIGEST_LEN.
*/
sint8 m2m_crypto_sha256_hash_finish(tstrM2mSha256Ctxt *psha256Ctxt, uint8 *pu8Sha256Digest)
{
	sint8 ret = M2M_ERR_FAIL;
	if ((!gstrCryptoCtxt.u8CryptoBusy) && (psha256Ctxt != NULL) && (pu8Sha256Digest != NULL)) {
		gstrCryptoCtxt.pu8Digest = pu8Sha256Digest;
		ret                      = hif_send(M2M_REQ_GROUP_CRYPTO,
                       M2M_CRYPTO_REQ_SHA256_FINSIH | M2M_REQ_DATA_PKT,
                       (uint8 *)psha256Ctxt,
                       sizeof(tstrM2mSha256Ctxt),
                       NULL,
                       0,
                       0);
	}
	return ret;
}

/*!
@fn	\
    sint8 m2m_rsa_sign_verify(uint8 *pu8N, uint16 u16NSize, uint8 *pu8E, uint16 u16ESize, uint8 *pu8SignedMsgHash, \
        uint16 u16HashLength, uint8 *pu8RsaSignature);

@brief	RSA Signature Verification

    The function shall request the RSA Signature verification from the WINC Firmware for the given message. The signed
message shall be compressed to the corresponding hash algorithm before calling this function. The hash type is
identified by the given hash length. For example, if the hash length is 32 bytes, then it is SHA256.

@param[in]	pu8N
                RSA Key modulus n.

@param[in]	u16NSize
                Size of the RSA modulus n in bytes.

@param[in]	pu8E
                RSA public exponent.

@param[in]	u16ESize
                Size of the RSA public exponent in bytes.

@param[in]	pu8SignedMsgHash
                The hash digest of the signed message.

@param[in]	u16HashLength
                The length of the hash digest.

@param[out] pu8RsaSignature
                Signature value to be verified.
*/

sint8 m2m_crypto_rsa_sign_verify(uint8 *pu8N, uint16 u16NSize, uint8 *pu8E, uint16 u16ESize, uint8 *pu8SignedMsgHash,
                                 uint16 u16HashLength, uint8 *pu8RsaSignature)
{
	sint8 ret = M2M_ERR_FAIL;
	if ((!gstrCryptoCtxt.u8CryptoBusy) && (pu8N != NULL) && (pu8E != NULL) && (pu8RsaSignature != NULL)
	    && (pu8SignedMsgHash != NULL) && (u16NSize != 0) && (u16ESize != 0) && (u16HashLength != 0)
	    && (pu8RsaSignature != NULL))

	{
		tstrRsaPayload strRsa = {0};

		m2m_memcpy(strRsa.au8N, pu8N, u16NSize);
		m2m_memcpy(strRsa.au8E, pu8E, u16ESize);
		m2m_memcpy(strRsa.au8Hash, pu8SignedMsgHash, u16HashLength);

		strRsa.u16Esz = u16ESize;
		strRsa.u16Hsz = u16HashLength;
		strRsa.u16Nsz = u16NSize;

		ret = hif_send(M2M_REQ_GROUP_CRYPTO,
		               M2M_CRYPTO_REQ_RSA_SIGN_VERIFY | M2M_REQ_DATA_PKT,
		               (uint8 *)&strRsa,
		               sizeof(tstrRsaPayload),
		               NULL,
		               0,
		               0);
	}
	return ret;
}

/*!
@fn	\
    sint8 m2m_rsa_sign_gen(uint8 *pu8N, uint16 u16NSize, uint8 *pu8d, uint16 u16dSize, uint8 *pu8SignedMsgHash, \
        uint16 u16HashLength, uint8 *pu8RsaSignature);

@brief	RSA Signature Generation

    The function shall request the RSA Signature generation from the WINC Firmware for the given message. The signed
message shall be compressed to the corresponding hash algorithm before calling this function. The hash type is
identified by the given hash length. For example, if the hash length is 32 bytes, then it is SHA256.

@param[in]	pu8N
                RSA Key modulus n.

@param[in]	u16NSize
                Size of the RSA modulus n in bytes.

@param[in]	pu8d
                RSA private exponent.

@param[in]	u16dSize
                Size of the RSA private exponent in bytes.

@param[in]	pu8SignedMsgHash
                The hash digest of the signed message.

@param[in]	u16HashLength
                The length of the hash digest.

@param[out] pu8RsaSignature
                Pointer to a user buffer allocated by teh caller shall hold the generated signature.
*/
sint8 m2m_crypto_rsa_sign_gen(uint8 *pu8N, uint16 u16NSize, uint8 *pu8d, uint16 u16dSize, uint8 *pu8SignedMsgHash,
                              uint16 u16HashLength, uint8 *pu8RsaSignature)
{
	sint8 ret = M2M_ERR_FAIL;
	if ((!gstrCryptoCtxt.u8CryptoBusy) && (pu8N != NULL) && (pu8d != NULL) && (pu8RsaSignature != NULL)
	    && (pu8SignedMsgHash != NULL) && (u16NSize != 0) && (u16dSize != 0) && (u16HashLength != 0)
	    && (pu8RsaSignature != NULL))

	{
		tstrRsaPayload strRsa = {0};

		m2m_memcpy(strRsa.au8N, pu8N, u16NSize);
		m2m_memcpy(strRsa.au8E, pu8d, u16dSize);
		m2m_memcpy(strRsa.au8Hash, pu8SignedMsgHash, u16HashLength);

		strRsa.u16Esz = u16dSize;
		strRsa.u16Hsz = u16HashLength;
		strRsa.u16Nsz = u16NSize;

		gstrCryptoCtxt.pu8Rsa = pu8RsaSignature;
		ret                   = hif_send(M2M_REQ_GROUP_CRYPTO,
                       M2M_CRYPTO_REQ_RSA_SIGN_GEN | M2M_REQ_DATA_PKT,
                       (uint8 *)&strRsa,
                       sizeof(tstrRsaPayload),
                       NULL,
                       0,
                       0);
	}
	return ret;
}

#endif
