#include "SCP03_transceive.h"
#include "SCP03_cmdGenerator.h"
#include "SCP03_tools.h"
#include "SCP03_rspParser.h"

#include "dk_constants.h"
#include "dk_log.h"
#define LOGE DK_LOG_ERR

static uint8_t Key_ENC[KEY_LEN_BYTE];
static uint8_t Key_MAC[KEY_LEN_BYTE];
static uint8_t Key_DEK[KEY_LEN_BYTE];

static uint8_t S_ENC[KEY_LEN_BYTE];
static uint8_t S_MAC[KEY_LEN_BYTE];
static uint8_t S_RMAC[KEY_LEN_BYTE];

static uint8_t MAC_Chaining_Value[16];

static uint8_t security_level;
static uint8_t security_level_status[4] = {0,};

static int encryptionCounter = 0;

SCPSTATUS openSession(uint8_t channelId, uint8_t kvn, uint8_t* KeyENC, uint8_t* KeyMAC, uint8_t* KeyDEK, uint8_t* AID, int AIDLength, uint8_t securityLevel) {
    uint8_t cpduData[APDU_DATA_SIZE_MAX] = {0,};
    uint8_t rpduData[MAX_RAPDU_DATA_SIZE] = {0,};
    uint8_t hostChallenge[8] = {0,};
    uint8_t cardChallenge[8] = {0,};
    uint8_t hostCryptogram[8] = {0,};
    uint8_t cardCryptogram[8] = {0,};
    uint8_t sequenceCounter[3] = {0,};
    ESESTATUS result = ESESTATUS_SUCCESS;
    SCPSTATUS verifyResult = SCP_SUCCESS;
    secEse_7816_cpdu_t cpdu;
    secEse_7816_rpdu_t rpdu;

    // parse security level
    security_level = securityLevel;
    security_level_status[0] = securityLevel & 0x01; // C-MAC
    security_level_status[1] = securityLevel & 0x02; // C-DECRYPTION
    security_level_status[2] = securityLevel & 0x10; // R-MAC
    security_level_status[3] = securityLevel & 0x20; // R-ENCRYPTION

    // copy root key
    memcpy (Key_ENC, KeyENC, KEY_LEN_BYTE);
    memcpy (Key_MAC, KeyMAC, KEY_LEN_BYTE);
    memcpy (Key_DEK, KeyDEK, KEY_LEN_BYTE);

    //save current logical channel
    //current_channelId = channelId;

    // init cpdu pdata for initialupdate
    cpdu.pdata = hostChallenge;
    //hex_print_tag_debug ("Key_ENC", Key_ENC, KEY_LEN_BYTE);
    //hex_print_tag_debug ("Key_MAC", Key_MAC, KEY_LEN_BYTE);
    //hex_print_tag_debug ("Key_DEK", Key_DEK, KEY_LEN_BYTE);

    // get pdu datas for initialupdate
    verifyResult = getInitialUpdateCmd(&cpdu, kvn);
    if (verifyResult != SCP_SUCCESS) {
        LOGE("getInitialUpdateCmd Fail Error : %04x",verifyResult);
        return verifyResult;
    }

    // init rpdu pdata for initialupdate
    rpdu.pdata = rpduData;

    // send INITIALIZE UPDATE Command
    result = secEseTransmit(channelId, &cpdu, &rpdu);
    if (!(result == ESESTATUS_SUCCESS && (rpdu.sw1 == 0x90 && rpdu.sw2 == 0x00))) {
        LOGE("InitialUpdateCmd Fail");
        return SCP_INITIAL_UPDATE_FAIL;
    }
    // parse card Challenge from response PDU data field
    memcpy(cardChallenge, rpdu.pdata + 13, 8);
    //hex_print_tag_debug ("cardChallenge", cardChallenge, 8);
    // parse card Cryptogram from response PDU data field
    memcpy(cardCryptogram, rpdu.pdata + 21, 8);
    //hex_print_tag_debug ("cardCryptogram", cardCryptogram, 8);
    // parse sequence counter from response PDU data field
    memcpy(sequenceCounter, rpdu.pdata + 29, 3);
    //hex_print_tag_debug ("sequenceCounter", sequenceCounter, 3);
    //hex_print_tag_debug ("hostChallenge", hostChallenge, 8);

    // verify card challenge
    verifyResult = verifyCardChallenge(Key_ENC, cardChallenge, sequenceCounter, AID, AIDLength);
    if (verifyResult != SCP_SUCCESS) {
        LOGE("verifyCardChallenge Fail Error : %04x",verifyResult);
        return verifyResult;
    }
    // generate Session Key (S-MAC key)
    genSessionKey(Key_MAC, S_MAC, hostChallenge, cardChallenge, SMAC);
    //hex_print_tag_debug ("S_MAC", S_MAC, KEY_LEN_BYTE);

    // verify card cryptogram
    verifyResult = verifyCardCryptogram(S_MAC, cardCryptogram, hostChallenge, cardChallenge);
    if (verifyResult != SCP_SUCCESS) {
        LOGE("verifyCardCryptogram Fail Error : %04x",verifyResult);
        return verifyResult;
    }

    // generate Host Cryptogram
    genHostCryptogram(S_MAC, hostCryptogram, hostChallenge, cardChallenge);
    //hex_print_tag_debug ("hostCryptogram", hostCryptogram, 8);

    // init MAC chaining value & cpdu,rpdu
    memset(MAC_Chaining_Value, 0x00, 16);
    memset(&cpdu, 0x00, sizeof(secEse_7816_cpdu_t));
    memset(&rpdu, 0x00, sizeof(secEse_7816_rpdu_t));
    memset(rpduData, 0x00, MAX_RAPDU_DATA_SIZE);
    cpdu.pdata = cpduData;
    rpdu.pdata = rpduData;

    // generate EXTERNAL AUTHENTICATE Command
    getExternalAuthenticateCmd(S_MAC, &cpdu, security_level, hostCryptogram, MAC_Chaining_Value);
    //hex_print_tag_debug ("ExternalAuthenticate cmd data", cpdu.pdata, 16);
    // send EXTERNAL AUTHENTICATE Command
    result = secEseTransmit(channelId, &cpdu, &rpdu);
    if (!(result == ESESTATUS_SUCCESS && (rpdu.sw1 == 0x90 && rpdu.sw2 == 0x00))) {
        LOGE("ExternalAuthenticate Fail");
        return SCP_EXTERNAL_AUTHENTICATE_FAIL;
    }

    // generate Session Key (S-ENC key)
    genSessionKey(Key_ENC, S_ENC, hostChallenge, cardChallenge, SENC);
    //hex_print_tag_debug ("S_ENC", S_ENC, KEY_LEN_BYTE);

    // generate Session Key (S-RMAC key)
    genSessionKey(Key_MAC, S_RMAC, hostChallenge, cardChallenge, SRMAC);
    //hex_print_tag_debug ("S_RMAC", S_RMAC, KEY_LEN_BYTE);

    encryptionCounter = 0;

    return SCP_SUCCESS;
}

int32_t get_max_rpdu_size() {
    if (security_level_status[3] != 0) { // R-ENCRYPTION is enabled
        return SCP03LIB_MAX_ENCRYPTED_APDU_DATA_SIZE;
    } else {
        return SCP03LIB_MAX_PLAIN_APDU_DATA_SIZE;
    }
}

void closeSession() {
    security_level = 0;
    security_level_status[0] = 0; // C-MAC
    security_level_status[1] = 0; // C-DECRYPTION
    security_level_status[2] = 0; // R-MAC
    security_level_status[3] = 0; // R-ENCRYPTION
}

void print_log(p_secEse_7816_cpdu_t pCpdu) {
    uint8_t length = 0;
    uint8_t log[256];
    memcpy(log+length++, &pCpdu->cla, 1);
    memcpy(log+length++, &pCpdu->ins, 1);
    memcpy(log+length++, &pCpdu->p1, 1);
    memcpy(log+length++, &pCpdu->p2, 1);
    memcpy(log+length++, &pCpdu->lc, 1);
    for (int i=0; i<pCpdu->lc; i++) {
        memcpy(log+length++, pCpdu->pdata+i, 1);
    }
    memcpy(log+length++, &pCpdu->le, 1);

    //hex_print_tag_debug ("apdu", log, length-1);
}

void print_rpdu(p_secEse_7816_rpdu_t pRpdu) {
    uint8_t length = 0;
    uint8_t log[258];
    memcpy(log+length++, &pRpdu->sw1, 1);
    memcpy(log+length++, &pRpdu->sw2, 1);
    for (int i=0; i<pRpdu->len; i++) {
        memcpy(log+length++, pRpdu->pdata+i, 1);
    }
    //hex_print_tag_debug ("sw+rpdu", log, length);
}

SCPSTATUS apduTransceive (uint8_t channelId, p_secEse_7816_cpdu_t pCpdu, p_secEse_7816_rpdu_t pRpdu) {
    ESESTATUS result = ESESTATUS_SUCCESS;
    SCPSTATUS verifyResult = 0;
    uint8_t ICV[16] = {0,};
    uint8_t encrypted_data[MAX_CIPHERED_TEXT_SIZE] = {0,};

    if (!(security_level_status[0] == 0 &&
        security_level_status[1] == 0 &&
        security_level_status[2] == 0 &&
        security_level_status[3] == 0)) {
        // Global platform CLA for Secure messaging
        pCpdu->cla = pCpdu->cla | 0x04;
    }

    if ((security_level_status[1] != 0/*) && (pCpdu->lc != 0*/)) { // C-DECRYPTION security level enabled and data is exist
        encryptionCounter = encryptionCounter+1;
        // generate ICV for data encryption
        genICV(ICV, S_ENC, encryptionCounter, ICVCMD);
    }

    // generate SCP03 command (encryption and MACing)
    getSCP03Cmd(pCpdu, encrypted_data, S_ENC, S_MAC, ICV, MAC_Chaining_Value, security_level_status);

    print_log(pCpdu);
    // transceive to ISO
    result = secEseTransmit(channelId, pCpdu, pRpdu);
    if ((pRpdu->sw1 == 0x90 && pRpdu->sw2 == 0x00) || pRpdu->sw1 == 0x62 || pRpdu->sw1 == 0x63) { // status byte is 9000 or 62xx or 63xx
        if ((security_level_status[2] != 0) && (pRpdu->len >= 8)) { // R-MAC security level enabled
            // verify R-MAC
            verifyResult = verifyRMAC(MAC_Chaining_Value, S_RMAC, pRpdu);
            if (verifyResult != SCP_SUCCESS) {
                return verifyResult;
            }

        }
        if ((security_level_status[3] != 0) && (pRpdu->len != 0)) { // R-ENCRYPTION security level enabled and response data is exist
            decryptRspDataField(S_ENC, encryptionCounter, pRpdu); // decrypt Data field
        }
    }

    if ((security_level_status[2] != 0) && (pRpdu->len >= 8)
        && (security_level_status[3] == 0)) { // R-MAC security level enabled and encryption is not enabled
        // sangj added to remove rmac length
        pRpdu->len -= 8;
    }


    return result;
}

void requestSensitiveDataEncryption(uint8_t* sensitiveData, int dataLength, uint8_t* encrypted_data) {
    genEncSensitiveData(Key_DEK, sensitiveData, dataLength, encrypted_data);
    return;
}

