/**
 * 
 * @file sha256.c
 * 
 * @ingroup generic_bootloader_8bit 
 * 
 * @brief This source file provides the implementation of the SHA256 cryptographic hash algorithm 
 *
 * @version BOOTLOADER Driver Version 3.0.0
*/

${disclaimer}

#include <stdint.h>
#include <string.h>
#include "../sha256.h"
#include "../../system/system.h"


#define SHA2_ROTL(x,n)      ((x << n) | (x >> (32-n)))   
#define SHA2_ROTR(x,n)      ((x >> n) | (x << (32-n)))
#define SHA2_SHR(x,n)       (x >> n)

#define SHA2_SIG_U0(x)      (SHA2_ROTR(x,2) ^ SHA2_ROTR(x,13) ^ SHA2_ROTR(x,22))
#define SHA2_SIG_U1(x)      (SHA2_ROTR(x,6) ^ SHA2_ROTR(x,11) ^ SHA2_ROTR(x,25))
#define SHA2_SIG_L0(x)      (SHA2_ROTR(x,7) ^ SHA2_ROTR(x,18) ^ SHA2_SHR(x,3))
#define SHA2_SIG_L1(x)      (SHA2_ROTR(x,17) ^ SHA2_ROTR(x,19) ^ SHA2_SHR(x,10))

#define SHA2_CH(x,y,z)      ((x & y) ^ ((~x) & z))
#define SHA2_MAJ(x,y,z)     ((x & y) ^ (x & z) ^ (y & z))

#define MAX_BYTES_IN_SHA256_BLOCK           (64)
#define LAST_BYTE_LOCATION_IN_SHA256_ARRAY  (0x3F)


const uint32_t SHA256_K[] = { \
    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_t hash[8];
static uint32_t workingBuffer[MAX_BYTES_IN_SHA256_BLOCK];
static uint32_t totalBytes;
uint8_t buffer[MAX_BYTES_IN_SHA256_BLOCK];


static void SHA256_OutputData (uint8_t * output, uint32_t value);


void SHA256_DataAdd (uint8_t *data)
{
    uint32_t a, b, c, d, e, f, g, h;
    uint8_t i;
    uint32_t t1, t2;
    
    for (i = 0; i < 16; i++)
    {
        t1 = *data++;
        t1 <<= 8;
        t1 += *data++;
        t1 <<= 8;
        t1 += *data++;
        t1 <<= 8;
        t1 += *data++;
        workingBuffer[i] = t1;
    }

    for (i = 16; i < MAX_BYTES_IN_SHA256_BLOCK; i++)
    {
        workingBuffer[i] = SHA2_SIG_L1(workingBuffer[i-2]) + workingBuffer[i-7] + SHA2_SIG_L0(workingBuffer[i-15]) + workingBuffer[i-16];
    }

    a = hash[0];
    b = hash[1];
    c = hash[2];
    d = hash[3];
    e = hash[4];
    f = hash[5];
    g = hash[6];
    h = hash[7];

    for (i = 0; i < MAX_BYTES_IN_SHA256_BLOCK; i++)
    {        
        t1 = h + SHA2_SIG_U1(e) + SHA2_CH(e,f,g) + SHA256_K[i] + workingBuffer[i];
        t2 = SHA2_SIG_U0(a) + SHA2_MAJ(a,b,c);
        h = g;
        g = f;
        f = e;
        e = d + t1;
        d = c;
        c = b;
        b = a;
        a = t1 + t2;
    }

    hash[0] += a;
    hash[1] += b;
    hash[2] += c;
    hash[3] += d;
    hash[4] += e;
    hash[5] += f;
    hash[6] += g;
    hash[7] += h;
}


static void SHA256_OutputData (uint8_t * output, uint32_t value)
{
    *(output++) = (uint8_t)(value >> 24);
    *(output++) = (uint8_t)(value >> 16);
    *(output++) = (uint8_t)(value >> 8);
    *(output++) = (uint8_t)value;
}


void SHA256_Initialize (void)
{  
    // Initialize context to produce 256-bit digest
    hash[0] = 0x6a09e667;
    hash[1] = 0xbb67ae85;
    hash[2] = 0x3c6ef372;
    hash[3] = 0xa54ff53a;
    hash[4] = 0x510e527f;
    hash[5] = 0x9b05688c;
    hash[6] = 0x1f83d9ab;
    hash[7] = 0x5be0cd19;
    
    totalBytes = 0;
}


void SHA256_Calculate (uint8_t *result)
{
    uint8_t *blockPtr = buffer + ((uint8_t)totalBytes & LAST_BYTE_LOCATION_IN_SHA256_ARRAY);
    uint8_t *endPtr;
    uint8_t i;

    // Append a single 1
    *blockPtr++ = 0x80;

    // Pad with 0's until data is a multiple of (512 - 64) bits = 448 bits (56 bytes)
    endPtr = buffer + 56;

    if(blockPtr > endPtr)
    {
        endPtr += 8;
        while (blockPtr < endPtr)
        {
            *blockPtr++ = 0x00;
        }
        SHA256_DataAdd(buffer);
        blockPtr = buffer;
        endPtr -= 8;
    }

    // Set the offset to 5 bytes before the end of the block
    endPtr += 3;
    while (blockPtr < endPtr)
    {
        *blockPtr++ = 0x00;
    }

    *blockPtr++ = (uint8_t)(totalBytes >> 29);
    *blockPtr++ = (uint8_t)(totalBytes >> 21);
    *blockPtr++ = (uint8_t)(totalBytes >> 13);
    *blockPtr++ = (uint8_t)(totalBytes >> 5);
    *blockPtr++ = (uint8_t)(totalBytes << 3);

    // Calculate a hash on this final block and add it to the sum
    SHA256_DataAdd(buffer);

    // Format the result in big-endian format
    for (i = 0; i < 7; i++)
    {
        SHA256_OutputData (result + (i << 2), hash[i]);
    }

    SHA256_OutputData (result + 28, hash[7]);
}


void SHA256(const uint32_t startAddress, const uint32_t endAddress, uint8_t* resultBuffer)
{      
    uint32_t byteCount = endAddress - startAddress + 1;
    uint32_t currentAddress = startAddress;
    uint32_t index;
    uint32_t blockCount = byteCount >> 6;

    SHA256_Initialize();
    
    while(blockCount--)
    {
        for(index = 0; index < MAX_BYTES_IN_SHA256_BLOCK; index++)
        {
#ifdef PIC            
            buffer[index] = FLASH_Read(currentAddress++);
#elif AVR
            buffer[index] = FLASH_Read(currentAddress++);
#endif            
        }
        SHA256_DataAdd(buffer);
    }
    
     /* calculate on the remainder */
    index = 0;
    
    while(currentAddress <= endAddress)
    {
#ifdef PIC
		buffer[index++] = FLASH_Read(currentAddress++);
#elif AVR
		buffer[index++] = FLASH_Read(currentAddress++);
#endif
	
	
        
    }
    
    totalBytes = byteCount;

    SHA256_Calculate(resultBuffer);
    
}

