/**
 *--------------------------------------------------------------------\n
 *          HSLU T&A Hochschule Luzern Technik+Architektur            \n
 *--------------------------------------------------------------------\n
 *
 * \brief         ASYD assignment crypto 05
 * \file
 * \author        Basil Estermann, basil.estermann@stud.hslu.ch
 *                Simon Frei, simon.frei@stud.hslu.ch
 *                Jonas Arnold, jonas.arnold@stud.hslu.ch
 * \date          23.03.2023
 *
 *--------------------------------------------------------------------
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define VALUE_LENGTH 32
#define MESSAGE_DIGEST_LENGTH 160
#define BLOCK_LENGTH (512/8)
#define INPUT_MESSAGE ("Testnachricht123")

#pragma region SHA1-Constants
// initial hash values
#define H0 0x67452301
#define H1 0xEFCDAB89
#define H2 0x98BADCFE
#define H3 0x10325476
#define H4 0xC3D2E1F0
// constants Kt
#define K1 0x5A827999
#define K2 0x6ED9EBA1
#define K3 0x8F1BBCDC
#define K4 0xCA62C1D6
#pragma endregion

void printBits(size_t const size, void const* const ptr);
uint32_t leftRotate(uint32_t n, uint32_t d);
uint32_t SHA1_f1(uint32_t b, uint32_t c, uint32_t d);
uint32_t SHA1_f2(uint32_t b, uint32_t c, uint32_t d);
uint32_t SHA1_f3(uint32_t b, uint32_t c, uint32_t d);
uint32_t SHA1_f4(uint32_t b, uint32_t c, uint32_t d);

void main(void) {

    int result = 0;

    printf("****** SHA1 preprocessing ******\n\n");
#pragma region Preprocessing

    printf("------ Set initial hash value ------\n\n");
    #pragma region Initial Hash values
    uint32_t a, b, c, d, e;
    uint32_t h[5] = { H0, H1, H2, H3, H4 };
    uint32_t k[5] = { 0,  K1, K2, K3, K4 }; // 5 so the array can be accessed by 1, 2, 3, 4
    a = h[0];
    b = h[1];
    c = h[2];
    d = h[3];
    e = h[4];

    #pragma endregion

    if (sizeof(INPUT_MESSAGE) * 8 > 447) {
        printf("Message too long!");
        return 1000002;
    }

    printf("------ Padding message ------\n\n");
    #pragma region Padding
    // print information about message without padding
    unsigned char messageNoPadding[] = INPUT_MESSAGE;
    int numBytesMessage = (sizeof(INPUT_MESSAGE) - 1);  // -1 because of '\0' at end of the string
    printf("Size of message without padding: %i bits\n", numBytesMessage*8);
    printf("Message without padding\n");
    printBits(sizeof(messageNoPadding), &messageNoPadding);

    unsigned char message[BLOCK_LENGTH] = "";
    // fill in message
    for (uint32_t i = 0; i < numBytesMessage; i++)
    {
        message[i] = INPUT_MESSAGE[i];
    }
    // set bit
    message[numBytesMessage] = 0b10000000;
    printf("\nMessage after adding 1: \n");
    printBits(sizeof(message), &message);
    // add length
    message[BLOCK_LENGTH - 1] = numBytesMessage * 8;
    printf("\nMessage after adding length: \n");
    printBits(sizeof(message), &message);
    #pragma endregion

#pragma endregion

    printf("\n\n****** Hash computation ******\n\n");
#pragma region Hash computation

    printf("\n------ Prepare message schedule ------\n\n");
    #pragma region Prepare message schedule
    uint32_t WordArray[80] = { 0 };
    for (uint32_t i = 0; i < 16; i++) {
        uint32_t currentbyte = message[i] << 24;
        uint32_t tmp = (uint32_t)(((uint8_t)(message[i * 4]) << 24) + ((uint8_t)(message[4 * i + 1]) << 16) + ((uint8_t)(message[4 * i + 2]) << 8) + (uint8_t)(message[4 * i + 3]));
        WordArray[i] = tmp;
    }
    for (uint32_t i = 16; i < 80; i++) {
        WordArray[i] = leftRotate((WordArray[i - 3] ^ WordArray[i - 8] ^ WordArray[i - 14] ^ WordArray[i - 16]), 1);
    }
    #pragma endregion

    printf("\n------ Stages ------\n\n");
    #pragma region Stages calculations
    uint32_t (*f_ptr)(uint32_t, uint32_t, uint32_t) = &SHA1_f1; // use: (*f_ptr)(b,c,d);
    uint8_t stage = 1;
    // print header row
    printf("\tA\t\tB\t\tC\t\tD\t\tE\n");

    // stage 1...4
    for (uint32_t t = 0; t < 80; t++) {
        uint32_t T = leftRotate(a, 5) + ((*f_ptr)(b, c, d)) + e + k[stage] + WordArray[t];
        e = d;
        d = c;
        c = leftRotate(b, 30);
        b = a;
        a = T;

        // print row
        printf("t= %02d:\t%08X\t%08X\t%08X\t%08X\t%08X\t\n", t, a, b, c, d, e);

        // switch stages
        if (t == 19) { f_ptr = SHA1_f2; stage = 2;          printf("Switched to stage 2.\n"); }
        else if (t == 39) { f_ptr = SHA1_f3; stage = 3;     printf("Switched to stage 3.\n"); }
        else if (t == 59) { f_ptr = SHA1_f4; stage = 4;     printf("Switched to stage 4.\n"); }
    }
    #pragma endregion

    printf("\n------ Intermediate hash calculation ------\n\n");
    #pragma region intermediate hash calculation
    uint32_t summand[5] = { a, b, c, d, e };
    for (uint8_t i = 0; i < 5; i++) {
        // calculate
        uint32_t h_new = summand[i] + h[i];
        // print
        printf("H[%i] = %08X + %08X = %08X\n", i, summand[i], h[i], h_new);
        // set value
        h[i] = h_new;
    }

    #pragma endregion
#pragma endregion

    printf("\n****** Resulting hash ******\n\n");
    for (uint8_t i = 0; i < 5; i++) {
        printf("%X ", h[i]);
    }
    printf("\n\n");

    return 0;
}

uint32_t SHA1_f1(uint32_t b, uint32_t c, uint32_t d) {
    return (b & c) | (~b & d);
}

uint32_t SHA1_f2(uint32_t b, uint32_t c, uint32_t d) {
    return b ^ c ^ d;
}

uint32_t SHA1_f3(uint32_t b, uint32_t c, uint32_t d) {
    return (b & c) | (b & d) | (c & d);
}

uint32_t SHA1_f4(uint32_t b, uint32_t c, uint32_t d) {
    return b ^ c ^ d;
}

uint32_t leftRotate(uint32_t n, uint32_t d)
{
    /* In n<<d, last d bits are 0. To put first 3 bits of n at
      last, do bitwise or of n<<d with n >>(INT_BITS - d) */
    return (n << d) | (n >> (VALUE_LENGTH - d));
}

// Assumes little endian
void printBits(size_t const size, void const* const ptr)
{
    unsigned char* b = (unsigned char*)ptr;
    unsigned char byte;
    int i, j;

    for (i = 0; i < size; i++) {
        for (j = 7; j >= 0; j--) {
            byte = (b[i] >> j) & 1;
            printf("%u", byte);
        }
    }
    /*original:
    for (i = size - 1; i >= 0; i--) {
        for (j = 7; j >= 0; j--) {
            byte = (b[i] >> j) & 1;
            printf("%u", byte);
        }
    }*/
    puts("");
}