#pragma attribute("aid", "f0 00 00 01")
#pragma attribute("dir", "61 0d 4f 04 f0 00 00 01 50 05 72 6b 76 61 63")

// ISO codes
#include <ISO7816.h>
// SmartDeck comms support
#include <multoscomms.h>
// SmartDeck crypto support
#include <multoscrypto.h>

// Standard libraries
#include <stdint.h>
#include <string.h>

// ECC support
#include "ecc/multosecc.h"

#include "helpers/mem_helper.h"
#include "helpers/random_helper.h"

#include "models/verifier.h"
#include "models/multos.h"

#include "apdu.h"
#include "types.h"

/// Global values - RAM (Public memory)
#pragma melpublic
uint8_t apdu_data[APDU_L_MAX];

/// Static values - RAM (Static memory)
#pragma melstatic
// verifier identifier and attributes
user_identifier_t ue_identifier = {
        0x02, 0x0F, 0x84, 0x31, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0x1A, 0x2B, 0x3C, 0x4D,
        0x5E, 0x6F
};
uint8_t k_vi_ui[AES_BLOCK_SIZE] = {0};
uint8_t sessionKey[AES_BLOCK_SIZE];

uint8_t name[36];
uint8_t email[36];
uint8_t phone[12];
uint8_t trustcore[21];
uint8_t time[16];
uint8_t password[21];

user_nonce_t ue_nonce[NONCE_LENGTH] = {0x00};

/// Session values - RAM (Dynamic memory)
#pragma melsession
uint8_t ve_cipher_input[5 * AES_BLOCK_SIZE] = {0}; //zašifrovaný hlavní obsah od ověřovatele
uint8_t ve_decipher_input[5 * AES_BLOCK_SIZE]; // dešifrovaný hlavní obsah od ověřovatele

uint8_t ve_id[USER_MAX_ID_LENGTH];
uint8_t ve_nonce[NONCE_LENGTH];
uint8_t ue_cipher_output[4 * AES_BLOCK_SIZE]; //poslaný zašifrovaný blok
uint8_t ue_data_output[4 * AES_BLOCK_SIZE]; // ještě nezašifrovaný blok

uint8_t ukey[AES_BLOCK_SIZE] = {0};
uint8_t vkey[AES_BLOCK_SIZE] = {0};
uint8_t i = 0;
uint8_t separator = 0;
uint8_t auxiliary = 0;

void main(void)
{
    if (CLA != CLA_APPLICATION)
    {
        ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
    }

    switch (INS)
    {
        case INS_SEND_USER_ID:
        {
            if (!CheckCase(2))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }

            // send user identifier
            memcpy(apdu_data, (const void *)&ue_identifier, USER_MAX_ID_LENGTH);

            ExitSWLa(ISO7816_SW_NO_ERROR, VERIFIER_MAX_ID_LENGTH);
            break;
        }
        case INS_KEY_USER:
        {
            if (!CheckCase(3))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }
            //copy sk user key
            memcpy(&k_vi_ui, (const void *) apdu_data, USER_MAX_ID_LENGTH);

            ExitSW(ISO7816_SW_NO_ERROR);
            break;
        }
       case INS_UE_NONCE:
        {
            if (!CheckCase(2))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }
            //generates random user nonce
            set_by_csprng((uint8_t *)&ue_nonce);
            //cpy to apdu
            memcpy(apdu_data, (const void *)&ue_nonce, NONCE_LENGTH);

            ExitSWLa(ISO7816_SW_NO_ERROR, NONCE_LENGTH);
            break;
        }
        case INS_ID_VERIFIER:
        {
            if (!CheckCase(3))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }
            //copy id verifier
            memcpy(&ve_id, (const void *) apdu_data, USER_MAX_ID_LENGTH);

            ExitSW(ISO7816_SW_NO_ERROR);
            break;
        }
        case INS_NONCE_VERIFIER:
        {
            if (!CheckCase(3))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }
            //copy nonce verifier
            memcpy(&ve_nonce, (const void *) apdu_data, NONCE_LENGTH);

            ExitSW(ISO7816_SW_NO_ERROR);
            break;
        }
       case INS_CYPHER:
        {
            if (!CheckCase(2))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }
            //compute ukey
            multosBlockEncipherECB(ALGORITHM_AES, AES_BLOCK_SIZE, (BYTE *)"000000000000User",(BYTE *)ukey, 16,(BYTE *)k_vi_ui);

            //nezašifrovaná data
            memcpy(&ue_data_output[0],&ue_identifier,AES_BLOCK_SIZE);
            memcpy(&ue_data_output[16],ve_id,AES_BLOCK_SIZE);
            memcpy(&ue_data_output[32],&ue_nonce,AES_BLOCK_SIZE);
            memcpy(&ue_data_output[48],ve_nonce,AES_BLOCK_SIZE);

            /// AES (ukey; ID_ui,ID_vi, unonce, vnonce)
            multosBlockEncipherCBC(ALGORITHM_AES, 4 * AES_BLOCK_SIZE, (BYTE *)&ue_data_output,(BYTE *)&ue_cipher_output, AES_IV_LENGTH, (BYTE *)"0000000000000000", AES_BLOCK_SIZE,(BYTE *)ukey);

            //cpy to apdu
            memcpy(apdu_data, (const void *)&ue_cipher_output, 4 * AES_BLOCK_SIZE);

            ExitSWLa(ISO7816_SW_NO_ERROR, 4 * AES_BLOCK_SIZE);
            break;
        }
        case INS_VERIFY:
        {
            if (!CheckCase(3))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }

            ///copy AES (vkey; ID_vi,ID_ui, vnonce, unonce, Ks)
            memcpy(&ve_cipher_input, (const void *) apdu_data, 5 * AES_BLOCK_SIZE);

            multosBlockEncipherECB(ALGORITHM_AES, AES_BLOCK_SIZE, (BYTE *)"00000000Verifier",(BYTE *)vkey, 16,(BYTE *)k_vi_ui);


            multosBlockDecipherCBC(ALGORITHM_AES, 5 * AES_BLOCK_SIZE, (BYTE *)&ve_cipher_input, (BYTE *)&ve_decipher_input, AES_IV_LENGTH, (BYTE *)"0000000000000000", AES_BLOCK_SIZE, (BYTE *)vkey);

            if(memcmp(&ve_decipher_input[0], ve_id, VERIFIER_MAX_ID_LENGTH) || memcmp(&ve_decipher_input[16], &ue_identifier, USER_MAX_ID_LENGTH) ||
               memcmp(&ve_decipher_input[32], ve_nonce, NONCE_LENGTH) || memcmp(&ve_decipher_input[48], &ue_nonce, NONCE_LENGTH)){

                ExitSW(ERR_SECURITY);
            }

            /// cpy session key
            memcpy(sessionKey, &ve_decipher_input[64], AES_BLOCK_SIZE);

            ExitSW(ISO7816_SW_NO_ERROR);
            break;
        }
        case INS_RECEIVE_DATA:
        {
            if (!CheckCase(3))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }

            for (i = 0; i < 200; i++) {
                if(apdu_data[i] == 42){
                    password[i - auxiliary] = 42;
                    break;}
                if(apdu_data[i] != 44){
                    if (separator == 0)name[i] = apdu_data[i];
                    if (separator == 1)email[i - auxiliary] = apdu_data[i];
                    if (separator == 2)phone[i - auxiliary] = apdu_data[i];
                    if (separator == 3)trustcore[i - auxiliary] = apdu_data[i];
                    if (separator == 4)time[i - auxiliary] = apdu_data[i];
                    if (separator == 5)password[i - auxiliary] = apdu_data[i];
                } else{
                    if (separator == 0)name[i] = 42;
                    if (separator == 1)email[i - auxiliary] = 42;
                    if (separator == 2)phone[i - auxiliary] = 42;
                    if (separator == 3)trustcore[i - auxiliary] = 42;
                    if (separator == 4)time[i - auxiliary] = 42;
                    separator++;
                    auxiliary = i + 1;
                }
            }

            ExitSW(ISO7816_SW_NO_ERROR);
            break;
        }
        case INS_SEND_DATA:
        {
            if (!CheckCase(2))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }

            // send data
            memcpy(&apdu_data[0], (const void *)&name, 36);
            memcpy(&apdu_data[36], (const void *)&email, 36);
            memcpy(&apdu_data[72], (const void *)&phone, 12);
            memcpy(&apdu_data[84], (const void *)&trustcore, 21);
            memcpy(&apdu_data[105], (const void *)&time, 16);
            memcpy(&apdu_data[121], (const void *)&password, 21);

            ExitSWLa(ISO7816_SW_NO_ERROR, 142);
            break;
        }
        default:
        {
            ExitSW(ISO7816_SW_INS_NOT_SUPPORTED);
            break;
        }
    }
}

/**
 * Writes random bytes to address by cryptographically secure
 * pseudo random number generator.
 *
 * @param address pointer to where the random number will be written
 */
void set_by_csprng(unsigned char *address)
{
    __set_by_csprng(address);
}
