#pragma attribute("aid", "f0 00 00 02 00 00 01")
#pragma attribute("name", "TC")

// 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
verifier_identifier_t ve_identifier = {
        0x16, 0x2d, 0x79, 0x36, 0xac, 0x32, 0x87, 0xbd,
        0x7e, 0x3b, 0xfa, 0x3b, 0xa0, 0xab, 0x00, 0xa9
};
uint8_t ve_keys[AES_BLOCK_SIZE];
verifier_nonce_t ve_nonce[NONCE_LENGTH] = {0x00};

/// Session values - RAM (Dynamic memory)
#pragma melsession
uint8_t ve_cipher_output[5 * AES_BLOCK_SIZE] = {0};
uint8_t ve_cipher_data[5 * AES_BLOCK_SIZE];

uint8_t ue_id[USER_MAX_ID_LENGTH];
uint8_t ue_nonce[NONCE_LENGTH];
uint8_t ue_cipher_output[4 * AES_BLOCK_SIZE];
uint8_t ue_decipher_output[4 * AES_BLOCK_SIZE];

uint8_t k_vi_ui[AES_BLOCK_SIZE] = {0};
uint8_t ukey[AES_BLOCK_SIZE] = {0};
uint8_t vkey[AES_BLOCK_SIZE] = {0};
uint8_t sessionKey[AES_BLOCK_SIZE];

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

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

            // send verifier identifier
            memcpy(apdu_data, (const void *)&ve_identifier, VERIFIER_MAX_ID_LENGTH);

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

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

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

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

            ExitSW(ISO7816_SW_NO_ERROR);
            break;
        }
        case INS_CYPHER:
        {
            if (!CheckCase(3))
            {
                ExitSW(ISO7816_SW_CLA_NOT_SUPPORTED);
                break;
            }
            //copy cypher
            memcpy(&ue_cipher_output, (const void *) apdu_data, 64);

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

            multosBlockEncipherECB(ALGORITHM_AES, AES_BLOCK_SIZE, (BYTE *)&ue_id,(BYTE *)k_vi_ui, 16,(BYTE *)ve_keys);
            multosBlockEncipherECB(ALGORITHM_AES, AES_BLOCK_SIZE, (BYTE *)"000000000000User",(BYTE *)ukey, 16,(BYTE *)k_vi_ui);
            multosBlockEncipherECB(ALGORITHM_AES, AES_BLOCK_SIZE, (BYTE *)"00000000Verifier",(BYTE *)vkey, 16,(BYTE *)k_vi_ui);

            multosBlockDecipherCBC(ALGORITHM_AES, 4 * AES_BLOCK_SIZE, (BYTE *)&ue_cipher_output, (BYTE *)&ue_decipher_output, AES_IV_LENGTH, (BYTE *)"0000000000000000", AES_BLOCK_SIZE, (BYTE *)ukey);

            if(memcmp(&ue_decipher_output[0], ue_id, USER_MAX_ID_LENGTH) || memcmp(&ue_decipher_output[16], &ve_identifier, VERIFIER_MAX_ID_LENGTH) ||
               memcmp(&ue_decipher_output[32], ue_nonce, NONCE_LENGTH) || memcmp(&ue_decipher_output[48], &ve_nonce, NONCE_LENGTH)){

                ExitSW(ERR_SECURITY);
            }
            //generates random session key
            set_by_csprng((uint8_t *)&sessionKey);

            //cpy vkey data
            memcpy(&ve_cipher_data[0],&ve_identifier,AES_BLOCK_SIZE);
            memcpy(&ve_cipher_data[16],ue_id,AES_BLOCK_SIZE);
            memcpy(&ve_cipher_data[32],&ve_nonce,AES_BLOCK_SIZE);
            memcpy(&ve_cipher_data[48],ue_nonce,AES_BLOCK_SIZE);
            memcpy(&ve_cipher_data[64],sessionKey,AES_BLOCK_SIZE);

            /// AES (vkey; ID_vi,ID_ui, vnonce, unonce, Ks)
            multosBlockEncipherCBC(ALGORITHM_AES, 5 * AES_BLOCK_SIZE, (BYTE *)&ve_cipher_data,(BYTE *)ve_cipher_output, AES_IV_LENGTH, (BYTE *)"0000000000000000", AES_BLOCK_SIZE,(BYTE *)vkey);
            //cpy to apdu
            memcpy(apdu_data, (const void *)&ve_cipher_output, 5 * AES_BLOCK_SIZE);

            ExitSWLa(ISO7816_SW_NO_ERROR, 5 * AES_BLOCK_SIZE);
            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);
}
