#include "crypto_module.h"

#ifdef USE_MOBICORE
#include <TlApi/TlApi.h>
#endif

#ifdef USE_QSEE
#include "qsee_kdf.h"
#endif

#ifdef USE_BLOWFISH
#include <tee_internal_api.h>
#ifdef USE_TEEGRIS
#include <tees_kdf.h>
#endif
#endif

#ifdef USE_TRUSTY_UNISOC
#include <tee_api.h>
#endif

#include "tz_platform.h"
#include "sec_EseStatus.h"
#include "tz_debug.h"
#include "tz_utils.h"
#include "ccm.h"
#include "sse.h"
#include "ssp_util.h"


#define BERTLV_DATA_BORDER_CHECK( curIndex, dataLen ) \
    if ( curIndex >= dataLen ) { \
        LOGE("ERROR length : WRONG BERTLV data. curIndex=0x%x, dataLen=0x%x", curIndex, dataLen); \
        return -1; \
    }

#define BERTLV_DATA_ENOUGH_CHECK( dataLen, valueLen, curIndex ) \
    if (dataLen < valueLen + curIndex) { \
        LOGE("ERROR length : NOT ENOUGH  BERTLV DATA PROVIDED . dataLen=0x%x, length=0x%x, curIndex0x%x", dataLen, valueLen, curIndex); \
        return -1; \
    }


uint8_t kvn31_key_enc[AES_128_KEY_SIZE] = {0,};
uint8_t kvn31_key_mac[AES_128_KEY_SIZE] = {0,};
uint8_t kvn31_key_dek[AES_128_KEY_SIZE] = {0,};

// SALT : <<== random32byte -> last 3bytes are ENC, MAC, DEK for each salt
//static uint8_t ISD_AID[8] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00};
//static uint8_t AMSD_AID[14] = {0xA0,0x00,0x00,0x01,0x51,0x53,0x50,0x41,0x4C,0x43,0x43,0x4D,0x41,0x4D};
static uint8_t DMSD_AID[14] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x53, 0x50, 0x41, 0x4C, 0x43, 0x43, 0x4D, 0x44, 0x4D};
static uint8_t ARAC_AID[16] = {0xA0, 0x00, 0x00, 0x02, 0x20, 0x15, 0x03, 0x01, 0x03, 0x00, 0x00, 0x00, 0x41, 0x52, 0x41, 0x43};
static uint8_t sTID_SEM[16] = {0xff, 0xff, 0xff, 0xff, 0xf0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1b};

static uint8_t SALT_AES_ENC[] = {0x01,0x26,0x6D,0x5F,0x42,0xE5,0xCE,0x36,0xFA,0xDB,0x4E,0x1A,0xE0,0x9D,0x64,0x91,0xF9,0xEA,0xFD,0x69,0xA8,0xF9,0x77,0xD7,0x7E,0x86,0x1D,0x01,0xFD,0x45,0x4E,0x43};
static uint8_t SALT_AES_MAC[] = {0x4E,0x8F,0xC0,0x52,0xDC,0x7A,0x11,0x0E,0xFE,0x32,0x5D,0x50,0x2A,0xC6,0x4B,0x04,0x23,0xE4,0x5B,0x7A,0x23,0xFF,0x5C,0xF5,0x98,0x7C,0x1B,0x11,0x90,0x44,0x45,0x4B};
static uint8_t SALT_AES_DEK[] = {0x70,0x53,0xF5,0xFD,0x59,0xA9,0x75,0xEF,0x69,0x1D,0x8C,0xAF,0x3B,0x2A,0x85,0x7C,0xAD,0xE9,0x82,0x75,0x9D,0x40,0xF0,0x48,0x17,0xC4,0x78,0x2B,0xD2,0x4D,0x31,0x43};

//static int StoreScenarioValue[2] = {0, 0};

enum {
    NEW_VERSION = 0,
    MAJOR_UP_VERSION = 1,
    MINOR_UP_VERSION = 2,
    RELEASE_UP_VERSION = 3,
    IDENTICAL_VERSION = 4,
    MAJOR_DOWN_VERSION = 5,
    MINOR_DOWN_VERSION = 6,
    RELEASE_DOWN_VERSION = 7,
};

void handleCCMDataPart1(p_cmd_t cmd, p_rsp_t rsp) {
    CRYPTO_STATUS cryptoret = CRYPTO_STATUS_FAILED;
    int32_t ret = RET_SUCCESS;

    void* pCardMetadata = NULL;
    card_meta_struct_t_v1x cardMeta_v1x;
    card_meta_struct_t_v20 cardMeta_v20;
    card_meta_struct_t_v31 cardMeta_v31;
    int32_t ccmVersion;

    uint8_t otp_key_blob[SSP_MAX_WRAPPED_OTP_KEY_SIZE] = {0,};
    uint32_t otp_key_blob_size;
    uint32_t ccmDataLen = 0;
    uint32_t current_pos = 0;

    uint8_t* message = NULL;
    int32_t messageSize = 0;

    uint8_t storeData[MAX_CAPDU_DATA_SIZE] = {0,};
    uint8_t receipt[MAX_CAPDU_DATA_SIZE] = {0,};

    uint8_t signatureFromBin[SIZE_ECC256_SIGNATURE] = {0,};
    uint32_t sigLen = SIZE_ECC256_SIGNATURE;

    //uint8_t* tmpCertData = NULL;
    //StoreScenarioValue[0] = 0;
    //StoreScenarioValue[1] = 0;

    LOGD("handleCCMDataPart1 Start");

    // separate ccmData and otp_key_blob from data
    memcpy(&ccmVersion, cmd->data + current_pos, 4);
    current_pos += 4;
    memcpy(&otp_key_blob_size, cmd->data + current_pos, 4);
    current_pos += 4;
    LOGD("otp_key_blob_size: %d, SSP_MAX_WRAPPED_OTP_KEY_SIZE: %d ", otp_key_blob_size, SSP_MAX_WRAPPED_OTP_KEY_SIZE);
    if (otp_key_blob_size > SSP_MAX_WRAPPED_OTP_KEY_SIZE) {
        LOGE("Buffer overflow could be happen.");
        rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
        return;
    }
    memcpy(otp_key_blob, cmd->data + current_pos, otp_key_blob_size);
    current_pos += otp_key_blob_size;
    memcpy(&ccmDataLen, cmd->data + current_pos, 4);
    current_pos += 4;

    if ( (current_pos + ccmDataLen) > (cmd->dataLen) ) {
        LOGE( "Input data incorrect." );
        return;
    }

    if ( MAX_DATA_SIZE <= cmd->dataLen ) {
        LOGE("Buffer overflow");
        rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
        return;
    }

    rsp->dataLen = 0;
    LOGD("ccmVersion = %d, otp_key_blob_size = %u, ccmDataLen = %u, cmd->dataLen = %u", ccmVersion, otp_key_blob_size, ccmDataLen, cmd->dataLen);
    hex_print_tag_debug("otp_key_blob", otp_key_blob, otp_key_blob_size);
    LOGD("start point of lccm bin %08X", current_pos);

    if (ccmVersion == CCM_V10 || ccmVersion == CCM_V11) {
        card_meta_struct_t_v1x *pData = NULL;
        LOGD("START LCCM V1X");
        pCardMetadata = &cardMeta_v1x;
        pData = (card_meta_struct_t_v1x *)pCardMetadata;
        pCardMetadata = &cardMeta_v1x;
        initCCMMetaStruct(ccmVersion, pCardMetadata);
        // parse metadata
        ret = parseMetadataFromBin(ccmVersion, cmd->data + current_pos, ccmDataLen, pCardMetadata, &sigLen, signatureFromBin);
        if (ret != 0 || sigLen < 1) {
            LOGE("card metadata parsing failed %d, %u", ret, sigLen);
            rsp->ret = RET_ERR_CCM_INVALID_METADATA;
            goto error;
        }

        // verify signature '37tag message sha256' <-> '38'   // message : cmd->data except signature, and otp_key
        LOGD("ccmDataLen %08X", ccmDataLen);
        message = tz_malloc(ccmDataLen);
        if (message == NULL) {
            LOGD("message allocation was failed");
            rsp->ret = RET_ERR_CCM_OUT_OF_MEMORY;
            goto error;
        } else {
            LOGD("message allocation was succeeded");
        }

        if ( (pData->signatureTLV.dataLen + 2) > ccmDataLen ) {
            LOGD( "incorrect dataLen" );
            rsp->ret = RET_ERR_BUFFER_OVERFLOW;
            goto error;
        }
        messageSize = ccmDataLen - (pData->signatureTLV.dataLen + 2);
        LOGD("message pos and size %08X, %08X", current_pos, messageSize);
        memcpy(message, cmd->data + current_pos, messageSize);

        hex_print_tag_debug("message", message, messageSize);
        hex_print_tag_debug("message", signatureFromBin, 32);
        hex_print_tag_debug("message", signatureFromBin + 32, 32);
        cryptoret = metadataSigVerification(message, messageSize, signatureFromBin, 32, signatureFromBin + 32, 32);
        LOGD("handleCCMDataPart1 result of verification: %d", cryptoret);
        memset(message, 0, messageSize);
        tz_free(message);

        if (cryptoret != CRYPTO_STATUS_SUCCESS) {
            LOGE("card metadata signature verification failed");
            rsp->ret = RET_ERR_CCM_METADATA_SIG_VERIFICATION;
            goto error;
        }

        // access control, if it exists
        if (ccmVersion == CCM_V11) {
            if (pData->DeviceInfoTlv.dataLen > 0) {
                LOGD("LCCM scripts are for specific devices");
                if (RET_SUCCESS != enforceDeviceAccessControl(cmd->data + current_pos + pData->DeviceInfoTlv.dataOffset,
                        pData->DeviceInfoTlv.dataLen)) {
                    LOGE("access denied");
                    rsp->ret = RET_ERR_CCM_ACCESS_DENIED;
                    goto error;
                }
                LOGD("access granted");
            }
        }
        if ((pData->StoreDataFieldTLV.dataLen > MAX_CAPDU_DATA_SIZE) || (pData->ReceiptTLV.dataLen > MAX_CAPDU_DATA_SIZE)) {
            LOGE("Buffer overflow could be happen");
            rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
            return;
        }

        // scp03 protocol
        memcpy(storeData, cmd->data + current_pos + pData->StoreDataFieldTLV.dataOffset, pData->StoreDataFieldTLV.dataLen);
        memcpy(receipt, cmd->data + current_pos + pData->ReceiptTLV.dataOffset, pData->ReceiptTLV.dataLen);

        ret = doScenario3((uint8_t) pData->StoreDataFieldTLV.dataLen, storeData,
                (uint8_t) pData->ReceiptTLV.dataLen, receipt, otp_key_blob, otp_key_blob_size);
        if (ret != 0) {
            LOGE("doScenario3 Failed");
            rsp->ret = ret;
            goto error;
        }
        if (ccmVersion == CCM_V10) {
            if (pData->AraAppInfoForAddTLV.dataLen > 0) {
                uint8_t fakeAraAppInfo[FAKE_ARA_ARR_MAX_SIZE] = {0,}; // 3 + 1 + 16 + 1 + 20 + alpha
                uint8_t offset = 0;
                fakeAraAppInfo[offset++] = (uint8_t)0xF2;
                fakeAraAppInfo[offset++] = 1;
                fakeAraAppInfo[offset++] = 1;
                fakeAraAppInfo[offset++] = (uint8_t)0xF3;
                fakeAraAppInfo[offset++] = (uint8_t)(pData->AppAidTLV.dataLen) + (uint8_t)(pData->AraAppInfoForAddTLV.dataLen) + 2;  // aid len
                fakeAraAppInfo[offset++] = (uint8_t)(pData->AppAidTLV.dataLen); // aid len
                LOGD("build app aid: %d %X %X %X", offset, current_pos, pData->AppAidTLV.dataOffset, pData->AppAidTLV.dataLen);
                hex_print_tag_debug("app aid", cmd->data + current_pos + pData->AppAidTLV.dataOffset, pData->AppAidTLV.dataLen);
                LOGD("pData->AppAidTLV.dataLen = %d, FAKE_ARA_ARR_MAX_SIZE - offset = %d", pData->AppAidTLV.dataLen, FAKE_ARA_ARR_MAX_SIZE - offset);
                if (pData->AppAidTLV.dataLen > ((uint32_t)FAKE_ARA_ARR_MAX_SIZE - offset)) {
                    LOGE("Buffer overflow could be happen");
                    rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
                    return;
                }
                memcpy(fakeAraAppInfo + offset, cmd->data + current_pos + pData->AppAidTLV.dataOffset, pData->AppAidTLV.dataLen); // aid
                offset += pData->AppAidTLV.dataLen;
                fakeAraAppInfo[offset++] = (uint8_t)(pData->AraAppInfoForAddTLV.dataLen); // app info len
                LOGD("build app id:%d %X %X %X", offset, current_pos, pData->AraAppInfoForAddTLV.dataOffset, pData->AraAppInfoForAddTLV.dataLen);
                hex_print_tag_debug("app id", cmd->data + current_pos + pData->AraAppInfoForAddTLV.dataOffset, pData->AraAppInfoForAddTLV.dataLen);
                LOGD("pData->AraAppInfoForAddTLV.dataLen = %d, FAKE_ARA_ARR_MAX_SIZE - offset = %d", pData->AraAppInfoForAddTLV.dataLen, FAKE_ARA_ARR_MAX_SIZE - offset);
                if (pData->AraAppInfoForAddTLV.dataLen > ((uint32_t)FAKE_ARA_ARR_MAX_SIZE - offset)) {
                    LOGE("Buffer overflow could be happen");
                    rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
                    return;
                }
                memcpy(fakeAraAppInfo + offset, cmd->data + current_pos + pData->AraAppInfoForAddTLV.dataOffset, pData->AraAppInfoForAddTLV.dataLen); // app id
                offset += pData->AraAppInfoForAddTLV.dataLen;
                hex_print_tag_debug("fakeAraAppInfo", fakeAraAppInfo, offset);
                ret = manageAracRules(TAG_V1X_ARA_ADD_INFO, fakeAraAppInfo, offset);
                if (ret != 0) {
                    LOGE("manageAracRules Failed");
                    rsp->ret = ret;
                    goto error;
                }
            }
        }
        else {
            ret = manageAracRules(TAG_V1X_ARA_ADD_INFO, cmd->data + current_pos + pData->AraAppInfoForAddTLV.dataOffset, pData->AraAppInfoForAddTLV.dataLen);
        }

        LOGI("handleCCMDataPart1 generating scripts");
        if (pData->ApduScriptTLV.dataLen > MAX_DATA_SIZE) {
            LOGE("Buffer overflow could be happen");
            rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
            return;
        }
        memcpy(rsp->data, cmd->data + current_pos + pData->ApduScriptTLV.dataOffset, pData->ApduScriptTLV.dataLen);
        rsp->dataLen = pData->ApduScriptTLV.dataLen;
    } else if (ccmVersion == CCM_V20) {
//start CCM V20
        card_meta_struct_t_v20 *pData = NULL;
        LOGD("START LCCM V20");

        pCardMetadata = &cardMeta_v20;
        pData = (card_meta_struct_t_v20 *)pCardMetadata;
        pCardMetadata = &cardMeta_v20;
        initCCMMetaStruct(ccmVersion, pCardMetadata);
        // parse metadata
        ret = parseMetadataFromBin(ccmVersion, cmd->data + current_pos, ccmDataLen, pCardMetadata, &sigLen, signatureFromBin);
        if (ret != 0 || sigLen < 1) {
            LOGE("card metadata parsing failed %d, %u", ret, sigLen);
            rsp->ret = RET_ERR_CCM_INVALID_METADATA;
            goto error;
        }

        //verify signature '37tag message sha256' <-> '38'   // message : cmd->data except signature, and otp_key
        LOGD("ccmDataLen %08X", ccmDataLen);
        message = tz_malloc(ccmDataLen);
        if (message == NULL) {
            LOGD("message allocation was failed");
            rsp->ret = RET_ERR_CCM_OUT_OF_MEMORY;
            goto error;
        } else {
            LOGD("message allocation was succeeded");
        }

        if ( (pData->signatureTLV.dataLen + 2) > ccmDataLen ) {
            LOGD( "incorrect dataLen" );
            rsp->ret = RET_ERR_BUFFER_OVERFLOW;
            goto error;
        }
        messageSize = ccmDataLen - (pData->signatureTLV.dataLen + 2);
        LOGD("message pos and size %08X, %08X", current_pos, messageSize);
        memcpy(message, cmd->data + current_pos, messageSize);

        hex_print_tag_debug("message", message, messageSize);
        hex_print_tag_debug("message", signatureFromBin, 32);
        hex_print_tag_debug("message", signatureFromBin + 32, 32);
        cryptoret = metadataSigVerification(message, messageSize, signatureFromBin, 32, signatureFromBin + 32, 32);
        LOGD("handleCCMDataPart1 result of verification: %d", cryptoret);
        memset(message, 0, messageSize);
        tz_free(message);

        if (cryptoret != CRYPTO_STATUS_SUCCESS) {
            LOGE("card metadata signature verification failed");
            rsp->ret = RET_ERR_CCM_METADATA_SIG_VERIFICATION;
            goto error;
        }

        // access control, if it exists
        if (pData->DeviceInfoTlv.dataLen > 0) {
            LOGD("LCCM scripts are for specific devices");
            if (RET_SUCCESS != enforceDeviceAccessControl(cmd->data + current_pos + pData->DeviceInfoTlv.dataOffset,
                    pData->DeviceInfoTlv.dataLen)) {
                LOGE("access denied");
                rsp->ret = RET_ERR_CCM_ACCESS_DENIED;
                goto error;
            }
            LOGD("access granted");
        }
        if ((pData->StoreDataFieldTLV.dataLen > MAX_CAPDU_DATA_SIZE) || (pData->ReceiptTLV.dataLen > MAX_CAPDU_DATA_SIZE)) {
            LOGE("Buffer overflow could be happen");
            rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
            return;
        }

        // scp03 protocol
        memcpy(storeData, cmd->data + current_pos + pData->StoreDataFieldTLV.dataOffset, pData->StoreDataFieldTLV.dataLen);
        memcpy(receipt, cmd->data + current_pos + pData->ReceiptTLV.dataOffset, pData->ReceiptTLV.dataLen);

        ret = doScenario3((uint8_t) pData->StoreDataFieldTLV.dataLen, storeData,
                (uint8_t) pData->ReceiptTLV.dataLen, receipt, otp_key_blob, otp_key_blob_size);
        if (ret != 0) {
            LOGE("doScenario3 Failed");
            rsp->ret = ret;
            goto error;
        }

        // add ara rule
        ret = manageAracRules(TAG_V20_ARA_DEL_INFO, cmd->data + current_pos + pData->AraAppInfoForDelTLV.dataOffset, pData->AraAppInfoForDelTLV.dataLen);
        ret = manageAracRules(TAG_V20_ARA_ADD_INFO, cmd->data + current_pos + pData->AraAppInfoForAddTLV.dataOffset, pData->AraAppInfoForAddTLV.dataLen);

        LOGI("handleCCMDataPart1 generating scripts");
        LOGD("pData->ApduScriptTLV.dataLen: %d, MAX_DATA_SIZE: %d ", pData->ApduScriptTLV.dataLen, MAX_DATA_SIZE);
        if (pData->ApduScriptTLV.dataLen > MAX_DATA_SIZE) {
            LOGE("Buffer overflow could be happen");
            rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
            return;
        }
        memcpy(rsp->data, cmd->data + current_pos + pData->ApduScriptTLV.dataOffset, pData->ApduScriptTLV.dataLen);
        rsp->dataLen = pData->ApduScriptTLV.dataLen;
//end CCM V20
    }
     else if (ccmVersion == CCM_V31) {
//start CCM V31
        card_meta_struct_t_v31 *pData = NULL;
        LOGD("START LCCM V31");

        pCardMetadata = &cardMeta_v31;
        pData = (card_meta_struct_t_v31 *)pCardMetadata;
        pCardMetadata = &cardMeta_v31;
        initCCMMetaStruct(ccmVersion, pCardMetadata);
        // parse metadata
        ret = parseMetadataFromBin(ccmVersion, cmd->data + current_pos, ccmDataLen, pCardMetadata, &sigLen, signatureFromBin);
        if (ret != 0 || sigLen < 1) {
            LOGE("card metadata parsing failed %d, %u", ret, sigLen);
            rsp->ret = RET_ERR_CCM_INVALID_METADATA;
            goto error;
        }

        //verify signature '37tag message sha256' <-> '38'   // message : cmd->data except signature, and otp_key
        LOGD("ccmDataLen %08X", ccmDataLen);
        message = tz_malloc(ccmDataLen);
        if (message == NULL) {
            LOGD("message allocation was failed");
            rsp->ret = RET_ERR_CCM_OUT_OF_MEMORY;
            goto error;
        } else {
            LOGD("message allocation was succeeded");
        }

        if ( (pData->signatureTLV.dataLen + 2) > ccmDataLen ) {
            LOGD( "incorrect dataLen" );
            rsp->ret = RET_ERR_BUFFER_OVERFLOW;
            goto error;
        }
        messageSize = ccmDataLen - (pData->signatureTLV.dataLen + 2);
        LOGD("message pos and size %08X, %08X", current_pos, messageSize);
        memcpy(message, cmd->data + current_pos, messageSize);

        hex_print_tag_debug("message", message, messageSize);
        hex_print_tag_debug("message", signatureFromBin, 32);
        hex_print_tag_debug("message", signatureFromBin + 32, 32);
        cryptoret = metadataSigVerification(message, messageSize, signatureFromBin, 32, signatureFromBin + 32, 32);
        LOGD("handleCCMDataPart1 result of verification: %d", cryptoret);
        memset(message, 0, messageSize);
        tz_free(message);

        if (cryptoret != CRYPTO_STATUS_SUCCESS) {
            LOGE("card metadata signature verification failed");
            rsp->ret = RET_ERR_CCM_METADATA_SIG_VERIFICATION;
            goto error;
        }

        // access control, if it exists
        if (pData->DeviceInfoTlv.dataLen > 0) {
            LOGD("LCCM scripts are for specific devices");
            if (RET_SUCCESS != enforceDeviceAccessControl(cmd->data + current_pos + pData->DeviceInfoTlv.dataOffset,
                    pData->DeviceInfoTlv.dataLen)) {
                LOGE("access denied");
                rsp->ret = RET_ERR_CCM_ACCESS_DENIED;
                goto error;
            }
            LOGD("access granted");
        }
        /*
        if ((pData->StoreDataFieldTLV.dataLen > MAX_CAPDU_DATA_SIZE) || (pData->ReceiptTLV.dataLen > MAX_CAPDU_DATA_SIZE)) {
            LOGE("Buffer overflow could be happen");
            rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
            return;
        }

        // scp03 protocol
        memcpy(storeData, cmd->data + current_pos + pData->StoreDataFieldTLV.dataOffset, pData->StoreDataFieldTLV.dataLen);
        memcpy(receipt, cmd->data + current_pos + pData->ReceiptTLV.dataOffset, pData->ReceiptTLV.dataLen);

        ret = doScenario3((uint8_t) pData->StoreDataFieldTLV.dataLen, storeData,
                (uint8_t) pData->ReceiptTLV.dataLen, receipt, otp_key_blob, otp_key_blob_size);
        if (ret != 0) {
            LOGE("doScenario3 Failed");
            rsp->ret = ret;
            goto error;
        }

        // add ara rule
        ret = manageAracRules(TAG_V31_ARA_DEL_INFO, cmd->data + current_pos + pData->AraAppInfoForDelTLV.dataOffset, pData->AraAppInfoForDelTLV.dataLen);
        ret = manageAracRules(TAG_V31_ARA_ADD_INFO, cmd->data + current_pos + pData->AraAppInfoForAddTLV.dataOffset, pData->AraAppInfoForAddTLV.dataLen);
        */

        //TAG 3F51 Check => Certificate or Recepipt return
        if(pData->StoreScenarioFieldTLV.dataLen > 0) {
            LOGI("Check Scenario Data");
            if(pData->StoreScenarioFieldTLV.tag == 0x01) {
                //only 1
                LOGI("Start Scenario 1");
                //getCertificate(tmpCertData);
            } else if(pData->StoreScenarioFieldTLV.tag == 0x03) {
                //only 3
                LOGI("Start Scenario 3");
                //getReceipt(tmpReceiptData);
            } else if(pData->StoreScenarioFieldTLV.tag == 0x0F) {
                // 1 & 3
                LOGI("Start Scenario 1 && 3");
                //getCertificate(tmpCertData);
                //getReceipt(tmpReceiptData);
            } else {
                LOGE("Scenario Check Fail");
            }
        }

        LOGI("handleCCMDataPart1 generating scripts");
        LOGD("pData->ApduScriptTLV.dataLen: %d, MAX_DATA_SIZE: %d ", pData->ApduScriptTLV.dataLen, MAX_DATA_SIZE);
        if (pData->ApduScriptTLV.dataLen > MAX_DATA_SIZE) {
            LOGE("Buffer overflow could be happen");
            rsp->ret =  RET_ERR_BUFFER_OVERFLOW;
            return;
        }

        //return 01, 03 Data
        memcpy(rsp->data, cmd->data + current_pos + pData->ApduScriptTLV.dataOffset, pData->ApduScriptTLV.dataLen);
        rsp->dataLen = pData->ApduScriptTLV.dataLen;
//end CCM V31
    }

    else {
        LOGE("Wrong CCM version");
        rsp->ret = RET_ERR_CCM_NOT_IMPLEMENTED;
        goto error;
    }
    LOGI("handleCCMDataPart1 [success]");
    rsp->ret =  RET_SUCCESS;
    return ;
error:
    if ( NULL != message ) {
        tz_free( message );
    }
    releaseCCMMetaStruct(ccmVersion);
    LOGE("handleCCMDataPart1 [ error END]");
}
#ifdef LCCM_SPI
void handleCCMDataPart2(p_cmd_t cmd, p_rsp_t rsp) {
    uint8_t cApdu[MAX_C_APDU_LEN] = {0,};
    uint8_t expSw[MAX_R_APDU_LEN] = {0,};
    uint8_t rApduData[MAX_R_APDU_LEN] = {0,};
    int32_t offset = 0, cLen =0, expSwLen = 0;
    secEse_7816_rpdu_t rApdu;
    int32_t lastSw = 0, inputSize;
    int32_t respOffset = 0;

    LOGD("handleCCMDataPart2 Start");

    inputSize = cmd->dataLen;
    LOGD("%d, %d", inputSize, MAX_DATA_SIZE);
    if (inputSize < 1 || inputSize >= MAX_DATA_SIZE) {
        LOGE("Invalid parameter:: dataLen");
        return ;
    }

    rsp->ret = RET_ERR_CCM_FAIL_PART2;

    offset = nextApduOffset(cmd->data, offset, inputSize, cApdu, &cLen, expSw, &expSwLen);
    LOGD("handleCCMDataPart2 offset: %d %d %d", offset, inputSize, expSwLen);
    while (offset > 0 && offset <= inputSize) {
        memset(&rApdu, 0, sizeof(secEse_7816_rpdu_t));
        memset(rApduData, 0, MAX_R_APDU_LEN);
        rApdu.pdata = rApduData;

        LOGD("offset = %d", offset);
        if (expSwLen == 0) { // no expected responses
            if (ESESTATUS_SUCCESS != secEseAPDUTransmit(0, cApdu, cLen, &rApdu)) {
                LOGE("secEseAPDUTransmit failed");
                lastSw = -2;
                break;
            } else {
                LOGD("secEseAPDUTransmit return : %02x %02x",rApdu.sw1,rApdu.sw2);
                if (rApdu.sw1 == 0x6A && rApdu.sw2 == 0x88 && cApdu[1] == 0xE4) {
                    LOGD("handleCCMDataPart2 possible sw");
                } else if (rApdu.sw1 != 0x90 || rApdu.sw2 != 0x00) {
                    // fail
                    LOGE("secEseAPDUTransmit failed");
                    lastSw = ((rApdu.sw1&0xff)<<8) + (rApdu.sw2&0xff);;
                    break;
                }
            }
        } else if (expSwLen > 0) { // expected responses
            if (ESESTATUS_SUCCESS != secEseAPDUTransmit(0, cApdu, cLen, &rApdu)) {
                LOGE("secEseAPDUTransmit failed");
                lastSw = -1002;
                break;
            } else {
                int32_t i = 0, flag = 0;;

                //if 0000000000000001 0000000000000003
                if(expSwLen < 7) {
                    LOGI("expSwLen : %d", expSwLen);
                } else if(expSw[0] == 0x00 && expSw[1] == 0x00 && expSw[2] == 0x00 && expSw[3] == 0x00 && expSw[4] == 0x00 && expSw[5] == 0x00 &&
                    expSw[6] == 0x00 ) {
                    if(expSw[7] == 0x01) {
                        //save response
                        if(MAX_DATA_SIZE > expSwLen + respOffset) {
                            memcpy(rsp->data + respOffset, expSw, expSwLen);
                            respOffset += expSwLen;
                            rsp->dataLen += expSwLen;
                        } else {
                            LOGE("EXP Copy Size error");
                            break;
                        }

                        if(MAX_DATA_SIZE > rApdu.len + respOffset) {
                            rsp->dataLen += 2;
                            memcpy(rsp->data + respOffset, &rApdu.len, 2);
                            respOffset += 2;
                        } else {
                            LOGE("EXP Copy Size error");
                            break;
                        }

                        if(MAX_DATA_SIZE > rApdu.len + respOffset) {
                            rsp->dataLen += rApdu.len;
                            memcpy(rsp->data + respOffset, rApdu.pdata, rApdu.len);
                            respOffset += rApdu.len;
                        } else {
                            LOGE("EXP Copy Size error");
                            break;
                        }

                        flag = 1;
                    } else if(expSw[7] == 0x03) {
                        //save response
                        if(MAX_DATA_SIZE > expSwLen + respOffset) {
                            memcpy(rsp->data + respOffset, expSw, expSwLen);
                            respOffset += expSwLen;
                            rsp->dataLen += expSwLen;
                        } else {
                            LOGE("EXP Copy Size error");
                            break;
                        }

                        if(MAX_DATA_SIZE > rApdu.len + respOffset) {
                            rsp->dataLen += 2;
                            memcpy(rsp->data + respOffset, &rApdu.len, 2);
                            respOffset += 2;
                        } else {
                            LOGE("EXP Copy Size error");
                            break;
                        }

                        if(MAX_DATA_SIZE > rApdu.len + respOffset) {
                            rsp->dataLen += rApdu.len;
                            memcpy(rsp->data + respOffset, rApdu.pdata, rApdu.len);
                            respOffset += rApdu.len;
                        } else {
                            LOGE("EXP Copy Size error");
                            break;
                        }

                        flag = 1;
                    } else {
                        LOGI("0103 Error");
                    }                        
                }

                for ( i = 0 ; (i + 1) < expSwLen ; i += 2) {
                    if (rApdu.sw1 == expSw[i] && rApdu.sw2 == expSw[i+1]) {
                        LOGE("MATCHED %02X, %02X", expSw[i], expSw[i+1]);
                        flag = 1;
                        break;
                    }
                }
                if (flag != 1) {
                    // fail
                    LOGE("not expected sw");
                    lastSw = ((rApdu.sw1&0xff)<<8) + (rApdu.sw2&0xff);;
                    break;
                }
            }
        } else {
            LOGE("wrong expSwLen");
            lastSw = -1001;
            break;
        }

        memset(cApdu, 0, MAX_C_APDU_LEN);
        cLen =0;
        memset(expSw, 0, MAX_R_APDU_LEN);
        expSwLen =0;
        offset = nextApduOffset(cmd->data, offset, inputSize, cApdu, &cLen, expSw, &expSwLen);
    }

    if (lastSw == 0)
        rsp->ret = RET_SUCCESS ;
    else {
        rsp->ret = lastSw;
        LOGE("last error: %8X", lastSw);
    }
}
//short apdu only!!
int32_t nextApduOffset(uint8_t* inData, int32_t start, int32_t inLen, uint8_t* outData, int32_t *outLen, uint8_t* expSw, int32_t *expSwLen) {
    int32_t totalLen = 0, apduLen = 0, apduOffset = start, expOffset = 0;
    int32_t lc = 0, expLen = 0;
    LOGI("nextApduOffset start from : %2X",  start);

    if (inData == NULL || start < 0 || start >= inLen || outData == NULL || expSw == NULL || expSwLen == NULL) {
        LOGE("INVALID PARAM");
        return -1;
    }

    if (inData[apduOffset] != 0xF1) {
        LOGE("INVALID PARAM2");
        return -2;
    }

    apduOffset++;
    totalLen = inData[apduOffset];
    LOGD("nextApduOffset totalLen: %2X, inlen:%2X", totalLen, inLen);
    hex_print_tag_debug("INDATA", inData + start, inLen - start);

    if (totalLen > 0x7F) {
        if(inData[apduOffset] == 0x81) {
            totalLen = (inData[apduOffset+1]&0xff);
            //LOGI("81 LEN = %02X", totalLen);
            apduOffset ++;
        } else if(inData[apduOffset] == 0x82) {
            totalLen = ((inData[apduOffset+1]&0xff)<<8) + (inData[apduOffset+2]&0xff);
            apduOffset += 2;
            //LOGI("82 LEN = %02X", totalLen);
        } else if(inData[apduOffset] == 0x83) {
            totalLen = ((inData[apduOffset+1]&0xff)<<16) + ((inData[apduOffset+2]&0xff)<<8) + (inData[apduOffset+3]&0xff);
            apduOffset += 3;
            //LOGI("83 LEN = %02X", totalLen);
        } else {
            LOGE("WRONG VALUE LEN");
            return -4;
        }
        //LOGD("start: %d, [%02X, %02X] offset: %d, LEN : %X (%d)\n", inData[start], inData[start+1], start, offset, len, len);
    } else if (totalLen < 1) {
        LOGE("WRONG VALUE LEN2");
        return -5;
    } else if (inLen <= (totalLen + apduOffset)) {
        LOGE("OEVERFLOW");
        return -6;
    } else {
        LOGI("80 LEN = %02X", totalLen);
    }
    apduOffset ++;
    lc = inData[apduOffset + 4]; //header size - 1
    if(lc == 0 && totalLen > 0) {
        //LOGI("***** LC 00 - extended");
        //hex_print_tag_debug("[extended 1]", &inData[apduOffset + 5], 1);
        //hex_print_tag_debug("[extended 2]", &inData[apduOffset + 6], 1);

        lc = ((inData[apduOffset+5]&0xff)<<8) + (inData[apduOffset+6]&0xff);
        //LOGI("***** Extended LC : %02X", lc);
        apduLen += 3;
    }
    expOffset = apduOffset + 4;
    LOGD(" apduOffset %02X, lc %02X, expOffset: %02X",  apduOffset, lc, expOffset);
    apduLen += 5;
    if (lc == 0) { // CASE 2
        // do nothing. remain this for understanding
    } else {
        if (inLen < expOffset + lc + 1) {
            // wrong data format
            LOGE("WRONG VALUE");
            return -11;
        }
        expOffset += lc + 1;
        LOGD(" expOffset %02X",  expOffset);
        uint8_t tmp = inData[expOffset];

        if (tmp == 0xF1) { // CASE3
            apduLen += lc + 0;
            LOGD(" CASE3 : %02X",  apduLen);
        } else if (tmp != 0x5A) { // CASE 4
            apduLen += lc + 1;
            LOGD(" CASE4 : %02X",  apduLen);
        } else {
            LOGI("else");
            if (inLen == expOffset) { // EOL, CASE3, NO EXP
                apduLen += lc;
                LOGD(" CASE3 : %02X, apduLen: %02X",  apduLen, apduLen);
            } else if (inLen == expOffset + 1) {
                apduLen += lc + 1;
                LOGD(" CASE4 : %02X, apduLen: %02X",  apduLen, apduLen);
            } else if (inData[expOffset+1] == 0x4F) { // CASE 3
                apduLen += lc;
                LOGD(" CASE3 : %02X",  apduLen);
            } else if (inData[expOffset+1] == 0x5A && inData[expOffset+2]) { // case 4
                apduLen += lc + 1;
                LOGD(" CASE4 : %02X",  apduLen);
            } else {
                LOGE("WRONG VALUE");
                return -12;
            }
        }
    }

    if (apduLen == totalLen ) {
        // expOffset absent!
        LOGD("NO EXP FIELD");
        memcpy(outData, inData + apduOffset, apduLen);
        *outLen = apduLen;
        *expSwLen = 0;
        hex_print_tag_debug("[APDU]", outData, apduLen);
    } else if (totalLen >= apduLen + 5) {
        expOffset = apduOffset + apduLen + 2;
        LOGD(" expOffset %02X",  expOffset);
        if (inLen < expOffset + 3) {
            LOGE("WRONG VALUE LEN3");
            return -20;
        }
        expLen = inData[expOffset];
        LOGD(" expOffset %02X, %2X",  expOffset, expLen);

        if (expLen < 0 || expLen % 2 == 1) {
            LOGE("WRONG EXP LEN [%02X]", expLen);
            return -21;
        }
        expOffset ++;
        LOGD(" expOffset %02X, %2X",  expOffset, expLen);
        if (inLen < expOffset + expLen) {
            LOGE("WRONG VALUE LEN4");
            return -22;
        }
        LOGD(" start: %02X, apduOffset %02X, apduLen: %02X",  start, apduOffset, apduLen);
        memcpy(outData, inData + apduOffset, apduLen);
        LOGD(" start: %02X, expOffset %02X, expLen: %02X",  start, expOffset, expLen);
        memcpy(expSw, inData + expOffset, expLen);
        *outLen = apduLen;
        *expSwLen = expLen;
        hex_print_tag_debug("[APDU]", outData, apduLen);
        hex_print_tag_debug("[ESW]", expSw, expLen);
    } else {
        LOGE("WRONG VAL");
        return -23;
    }
    return (totalLen + apduOffset);
}


#endif
void initCCMMetaStruct(uint32_t ccmVersion, void* pCardMetadata) {
    if (ccmVersion == CCM_V10 || ccmVersion == CCM_V11) {
        card_meta_struct_t_v1x * pData = (card_meta_struct_t_v1x *)pCardMetadata;
        memset(&pData->headerTLV, 0, sizeof(tlv_t));
        memset(&pData->pkgInfoTLV, 0, sizeof(tlv_t));
        memset(&pData->ElfAidTLV, 0, sizeof(tlv_t));
        memset(&pData->SdAidTLV, 0, sizeof(tlv_t));
        memset(&pData->AppAidTLV, 0, sizeof(tlv_t));
        memset(&pData->ElfVersionTLV, 0, sizeof(tlv_t));
        memset(&pData->extrainfoTLV, 0, sizeof(tlv_t));
        memset(&pData->DateTLV, 0, sizeof(tlv_t));
        memset(&pData->ProfileIdTLV, 0, sizeof(tlv_t));
        memset(&pData->LccmVerTLV, 0, sizeof(tlv_t));
        memset(&pData->SigAlgoTLV, 0, sizeof(tlv_t));
        memset(&pData->PurposeTLV, 0, sizeof(tlv_t));
        memset(&pData->ApduScriptTLV, 0, sizeof(tlv_t));
        memset(&pData->StoreDataFieldTLV, 0, sizeof(tlv_t));
        memset(&pData->ReceiptTLV, 0, sizeof(tlv_t));
        memset(&pData->AraAppInfoForAddTLV, 0, sizeof(tlv_t));
        memset(&pData->DeviceInfoTlv, 0, sizeof(tlv_t));
        memset(&pData->signatureTLV, 0, sizeof(tlv_t));
    } else if (ccmVersion == CCM_V20) {
        card_meta_struct_t_v20 * pData = (card_meta_struct_t_v20 *)pCardMetadata;
        memset(&pData->headerTLV, 0, sizeof(tlv_t));
        memset(&pData->scriptInfoTLV, 0, sizeof(tlv_t));
        memset(&pData->DateTLV, 0, sizeof(tlv_t));
        memset(&pData->ApduScriptTLV, 0, sizeof(tlv_t));
        memset(&pData->StoreDataFieldTLV, 0, sizeof(tlv_t));
        memset(&pData->ReceiptTLV, 0, sizeof(tlv_t));
        memset(&pData->AraAppInfoForAddTLV, 0, sizeof(tlv_t));
        memset(&pData->DeviceInfoTlv, 0, sizeof(tlv_t));
        memset(&pData->AraAppInfoForDelTLV, 0, sizeof(tlv_t));
        memset(&pData->signatureTLV, 0, sizeof(tlv_t));
    } 
    else if(ccmVersion == CCM_V31) {
        card_meta_struct_t_v31 * pData = (card_meta_struct_t_v31 *)pCardMetadata;
        memset(&pData->headerTLV, 0, sizeof(tlv_t));
        memset(&pData->scriptInfoTLV, 0, sizeof(tlv_t));
        memset(&pData->DateTLV, 0, sizeof(tlv_t));
        memset(&pData->ApduScriptTLV, 0, sizeof(tlv_t));
        memset(&pData->DeviceInfoTlv, 0, sizeof(tlv_t));
        memset(&pData->StoreScenarioFieldTLV, 0, sizeof(tlv_t));
        memset(&pData->signatureTLV, 0, sizeof(tlv_t));
    }
    else {
        LOGE("Wrong CCM version");
    }
}

void releaseCCMMetaStruct(uint32_t ccmVersion) {
    if (ccmVersion == CCM_V10) {

    } else if (ccmVersion == CCM_V11) {

    } else if (ccmVersion == CCM_V20) {

    } else if (ccmVersion == CCM_V31) {
    
    } else {
        LOGE("Wrong CCM version");
    }
}

int32_t parseMetadataFromBin(uint32_t ccmVersion, uint8_t* ccmMetaDataBuf, uint32_t ccmMetaDataLen, void *pCardMetadata,
        uint32_t* signLen, uint8_t* signatureFromBin) {
    int32_t ret = -1;
    uint32_t bufOffset = 0;
    //int i = 0;
    LOGI("Parsing start %u", ccmVersion);
    if (ccmVersion == CCM_V10 || ccmVersion == CCM_V11) {
        card_meta_struct_t_v1x * pData = (card_meta_struct_t_v1x *)pCardMetadata;
        // find '37' tag : header of  meta data
        if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_HEADER, &pData->headerTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_HEADER);
            return ret;
        }

        // find '3f20' tag : package info
        if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_PACKAGE_INFO, &pData->pkgInfoTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_PACKAGE_INFO);
            return ret;
        }
        // find '3f21' tag : ELF aid
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_AID_ELF, &pData->ElfAidTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_AID_ELF);
            return ret;
        }

        // find '3f22' tag : SD aid - optional
        ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_AID_ASSOCIATED_SD, &pData->SdAidTLV);
        if (0 != ret) {
            if ( -2 == ret) {
                LOGE("It's ok TAG[%04X] is optional tag", TAG_V1X_AID_ASSOCIATED_SD);
                ret = -1;
            } else {
                LOGE("Wrong data: %04X", TAG_V1X_AID_ASSOCIATED_SD);
                return ret;
            }
        }
        // find '3f23' tag : Application aid
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_AID_APP, &pData->AppAidTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_AID_APP);
            return ret;
        }
        // find '3f24' tag : ElfVersionTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_VERSION_ELF, &pData->ElfVersionTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_VERSION_ELF);
            return ret;
        }

        // rearrange offset
        bufOffset = pData->pkgInfoTLV.dataOffset + pData->pkgInfoTLV.dataLen;
        // find '3f30' tag : extra info
        if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_EXTRA_INFO, &pData->extrainfoTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_EXTRA_INFO);
            return ret;
        }

        // find '3f31' tag : DateTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_DATE_SCRIPTS, &pData->DateTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_DATE_SCRIPTS);
            return ret;
        }

        // find '3f32' tag : ProfileIdTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_PROFILE, &pData->ProfileIdTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_PROFILE);
            return ret;
        }

        // find '3f33' tag : LccmVerTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_VERSION_LCCM, &pData->LccmVerTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_VERSION_LCCM);
            return ret;
        }

        // find '3f34' tag : SigAlgoTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_SIGNATURE_ALGO, &pData->SigAlgoTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_SIGNATURE_ALGO);
            return ret;
        }

        // find '3f35' tag : PurposeTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_PURPOSE, &pData->PurposeTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_PURPOSE);
            return ret;
        }
        // find '3f36' tag : ApduScriptTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_APDU_SCRIPT_INFO, &pData->ApduScriptTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_APDU_SCRIPT_INFO);
            return ret;
        }
        // find '3f37' tag : StoreDataFieldTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_DFIELD_STOREDATA, &pData->StoreDataFieldTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_DFIELD_STOREDATA);
            return ret;
        }
        // find '3f38' tag : ReceiptTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_RECEIPT, &pData->ReceiptTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_RECEIPT);
            return ret;
        }
        // find '3f39' tag : AraAppInfoForAddTLV
        ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_ARA_ADD_INFO, &pData->AraAppInfoForAddTLV);
        if (0 != ret) {
            if ( -2 == ret) {
                LOGE("It's ok TAG[%04X] is optional tag", TAG_V1X_ARA_ADD_INFO);
                ret = -1;
            } else {
                LOGE("Wrong data: %04X", TAG_V1X_ARA_ADD_INFO);
                return ret;
            }
        }
        if (ccmVersion == CCM_V11) {
            // find '3f40' tag : DeviceAccessInfo
            ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_DEVICE_ACCESS_INFO, &pData->DeviceInfoTlv);
            if (0 != ret) {
                if ( -2 == ret) {
                    LOGE("It's ok TAG[%04X] is optional tag", TAG_V1X_DEVICE_ACCESS_INFO);
                    ret = -1;
                } else {
                    LOGE("Wrong data: %04X", TAG_V1X_DEVICE_ACCESS_INFO);
                    return ret;
                }
            }
        }
        // find '38' tag : signature
        bufOffset = pData->headerTLV.dataOffset + pData->headerTLV.dataLen;
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V1X_SIGNATURE, &pData->signatureTLV)) {
            LOGE("Wrong data: %04X", TAG_V1X_SIGNATURE);
            return ret;
        }
        if(pData->signatureTLV.dataLen > *signLen) {
            LOGE("signatureTLV overflow");
            return -1;
        }
        *signLen = pData->signatureTLV.dataLen;

        if( SIZE_ECC256_SIGNATURE < pData->signatureTLV.dataLen )
        {
            LOGE("signatureTLV overflow");
            return -1;
        }
        memcpy(signatureFromBin, ccmMetaDataBuf + pData->signatureTLV.dataOffset, pData->signatureTLV.dataLen);

        ret = 0;
        LOGI("Parsing was ok");
    } else if (ccmVersion == CCM_V20) {
        card_meta_struct_t_v20 * pData = (card_meta_struct_t_v20 *)pCardMetadata;
    //start v2.0
        LOGI("LCCM v2.0 Parsing Start");
        // find '37' tag : header of  meta data
        if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_HEADER, &pData->headerTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_HEADER);
            return ret;
        }

        // find '3f50' tag : Script info
        if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_SCRIPT_INFO, &pData->scriptInfoTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_SCRIPT_INFO);
            return ret;
        }

        // find '3f31' tag : DateTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_DATE_SCRIPTS, &pData->DateTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_DATE_SCRIPTS);
            return ret;
        }

        // find '3f36' tag : ApduScriptTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_APDU_SCRIPT_INFO, &pData->ApduScriptTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_APDU_SCRIPT_INFO);
            return ret;
        }
        // find '3f37' tag : StoreDataFieldTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_DFIELD_STOREDATA, &pData->StoreDataFieldTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_DFIELD_STOREDATA);
            return ret;
        }
        // find '3f38' tag : ReceiptTLV
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_RECEIPT, &pData->ReceiptTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_RECEIPT);
            return ret;
        }
        // find '3f39' tag : AraAppInfoForAddTLV
        ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_ARA_ADD_INFO, &pData->AraAppInfoForAddTLV);
        if (0 != ret) {
            if ( -2 == ret) {
                LOGE("It's ok TAG[%04X] is optional tag", TAG_V20_ARA_ADD_INFO);
                //ret = -1;
            } else {
                LOGE("Wrong data: %04X", TAG_V20_ARA_ADD_INFO);
                return ret;
            }
        }

        // find '3f40' tag : DeviceAccessInfo
        ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_DEVICE_ACCESS_INFO, &pData->DeviceInfoTlv);
        if (0 != ret) {
            if ( -2 == ret) {
                LOGE("It's ok TAG[%04X] is optional tag", TAG_V20_DEVICE_ACCESS_INFO);
                //ret = -1;
            } else {
                LOGE("Wrong data: %04X", TAG_V20_DEVICE_ACCESS_INFO);
                return ret;
            }
        }

        // find '3f41' tag : AraAppInfoForDelTLV
        ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_ARA_DEL_INFO, &pData->AraAppInfoForDelTLV);
        if (0 != ret) {
            if ( -2 == ret) {
                LOGE("It's ok TAG[%04X] is optional tag", TAG_V20_ARA_DEL_INFO);
                ret = -1;
            } else {
                LOGE("Wrong data: %04X", TAG_V20_ARA_DEL_INFO);
                return ret;
            }
        }

        // find '38' tag : signature
        bufOffset = pData->headerTLV.dataOffset + pData->headerTLV.dataLen;
        if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_SIGNATURE, &pData->signatureTLV)) {
            LOGE("Wrong data: %04X", TAG_V20_SIGNATURE);
            return ret;
        }
        if(pData->signatureTLV.dataLen > *signLen) {
            LOGE("signatureTLV overflow");
            return -1;
        }
        *signLen = pData->signatureTLV.dataLen;

        if( SIZE_ECC256_SIGNATURE < pData->signatureTLV.dataLen )
        {
            LOGE("signatureTLV overflow");
            return -1;
        }
        memcpy(signatureFromBin, ccmMetaDataBuf + pData->signatureTLV.dataOffset, pData->signatureTLV.dataLen);
        ret = 0;
        LOGI("Parsing was ok");
    //end V2.0
    }
    else if (ccmVersion == CCM_V31) {
           card_meta_struct_t_v31 * pData = (card_meta_struct_t_v31 *)pCardMetadata;
    //start v3.1
           LOGI("LCCM v3.1 Parsing Start");
           // find '37' tag : header of  meta data
           if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_HEADER, &pData->headerTLV)) {
               LOGE("Wrong data: %04X", TAG_V31_HEADER);
               return ret;
           }
    
           // find '3f50' tag : Script info
           if (0 != parseBERTLV(FALSE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_SCRIPT_INFO, &pData->scriptInfoTLV)) {
               LOGE("Wrong data: %04X", TAG_V31_SCRIPT_INFO);
               return ret;
           }
    
           // find '3f31' tag : DateTLV
           if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_DATE_SCRIPTS, &pData->DateTLV)) {
               LOGE("Wrong data: %04X", TAG_V31_DATE_SCRIPTS);
               return ret;
           }

           // find '3f36' tag : ApduScriptTLV
           if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_APDU_SCRIPT_INFO, &pData->ApduScriptTLV)) {
               LOGE("Wrong data: %04X", TAG_V31_APDU_SCRIPT_INFO);
               return ret;
           }
           /*
           // find '3f37' tag : StoreDataFieldTLV
           if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_DFIELD_STOREDATA, &pData->StoreDataFieldTLV)) {
               LOGE("Wrong data: %04X", TAG_V20_DFIELD_STOREDATA);
               return ret;
           }
           // find '3f38' tag : ReceiptTLV
           if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_RECEIPT, &pData->ReceiptTLV)) {
               LOGE("Wrong data: %04X", TAG_V20_RECEIPT);
               return ret;
           }
           // find '3f39' tag : AraAppInfoForAddTLV
           ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V20_ARA_ADD_INFO, &pData->AraAppInfoForAddTLV);
           if (0 != ret) {
               if ( -2 == ret) {
                   LOGE("It's ok TAG[%04X] is optional tag", TAG_V20_ARA_ADD_INFO);
                   ret = -1;
               } else {
                   LOGE("Wrong data: %04X", TAG_V20_ARA_ADD_INFO);
                   return ret;
               }
           }
           */

           // find '3f40' tag : DeviceAccessInfo
           ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_DEVICE_ACCESS_INFO, &pData->DeviceInfoTlv);
           if (0 != ret) {
               if ( -2 == ret) {
                   LOGE("It's ok TAG[%04X] is optional tag", TAG_V31_DEVICE_ACCESS_INFO);
                   //ret = -1;
               } else {
                   LOGE("Wrong data: %04X", TAG_V31_DEVICE_ACCESS_INFO);
                   return ret;
               }
           }

           // find '3f51' tag : Requst eSE response
           ret = parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_SCENARIO_DFILED, &pData->StoreScenarioFieldTLV);
           if (0 != ret) {
               if ( -2 == ret) {
                   LOGE("It's ok TAG[%04X] is optional tag", TAG_V31_SCENARIO_DFILED);
                   ret = -1;
               } else {
                   LOGE("Wrong data: %04X", TAG_V31_SCENARIO_DFILED);
                   return ret;
               }
           }/* else {
           //parse the scenario data
           //set StoreScenarioValue
               //hex_print_tag_debug("Scenario 0103 Test ", pData->StoreScenarioFieldTLV.dataOffset, pData->StoreScenarioFieldTLV.dataLen);
               LOGI("***** 3F51 Len : %d", pData->StoreScenarioFieldTLV.dataLen);
               //LOGI("***** 3F51 TAG : %04X", pData->StoreScenarioFieldTLV.dataOffset);
               for(i = 0; i < pData->StoreScenarioFieldTLV.dataLen; i++) {
                   if(pData->StoreScenarioFieldTLV.dataLen < i+2) {
                       LOGE("Scenario Check len error");
                       break;
                   }

                   if(pData->StoreScenarioFieldTLV.dataOffset[i] == 0xE8) {
                       if(pData->StoreScenarioFieldTLV.dataOffset[i+2] == 0x01) {
                           StoreScenarioValue[0] = 1;
                       } else if(pData->StoreScenarioFieldTLV.dataOffset[i+2] == 0x03) {
                           StoreScenarioValue[1] = 3;
                       }
                   }
               }
           }
		   */

           // find '38' tag : signature
           bufOffset = pData->headerTLV.dataOffset + pData->headerTLV.dataLen;
           if (0 != parseBERTLV(TRUE, ccmMetaDataBuf, &bufOffset, ccmMetaDataLen, TAG_V31_SIGNATURE, &pData->signatureTLV)) {
               LOGE("Wrong data: %04X", TAG_V31_SIGNATURE);
               return ret;
           }
           if(pData->signatureTLV.dataLen > *signLen) {
               LOGE("signatureTLV overflow");
               return -1;
           }
           *signLen = pData->signatureTLV.dataLen;
    
           if( SIZE_ECC256_SIGNATURE < pData->signatureTLV.dataLen )
           {
               LOGE("signatureTLV overflow");
               return -1;
           }
           memcpy(signatureFromBin, ccmMetaDataBuf + pData->signatureTLV.dataOffset, pData->signatureTLV.dataLen);
           ret = 0;
           LOGI("Parsing was ok");
       //end V3.1
       }else {
        LOGE("Wrong CCM version");
    }
    printParseResult(ccmVersion, pCardMetadata);
    return ret;
}

int32_t parseBERTLV(uint8_t isContainValue, uint8_t* data, uint32_t* offset, uint32_t dataLen, uint32_t tag, tlv_t* pTarget) {
    uint32_t curIndex = *offset;
    uint8_t tag_len = 0, len_len = 0;
    uint32_t valueLen = 0;
    uint8_t temp = 0;
    uint32_t tTag = 0;

    if (data == NULL || dataLen < 1){
        LOGE("ERROR : WRONG BERTLV data. data is NULL or empty : %u", dataLen);
        return -1;
    }

    // check tag
    BERTLV_DATA_BORDER_CHECK( curIndex, dataLen )
    if (tag <= 255) { // 1 byte tag
        tTag = data[curIndex++] & 0xff;
        tag_len = 1;
    } else  { // 2 bytes tag
        tTag = data[curIndex++] & 0xff;
        tTag = (tTag << 8) | (data[curIndex++] & 0xff);
        tag_len = 2;
    }
    if ( (tTag & 0xFFFF) != tag) {
        LOGE("ERROR : WRONG TAG %04X : %04X", tag, tTag);
        return -2;
    }
    LOGD("TAG : %04X", tag);

    BERTLV_DATA_BORDER_CHECK( curIndex, dataLen )
    temp = data[curIndex++] & 0xff;
    if (temp < 0x80) {
        valueLen = temp;
        len_len = 1;
    } else if (temp == 0x81) {
        BERTLV_DATA_BORDER_CHECK( curIndex, dataLen )
        valueLen = data[curIndex++] & 0xff;
        len_len = 2;
        BERTLV_DATA_ENOUGH_CHECK( dataLen, valueLen, curIndex )
    } else if (temp == 0x82) {
        BERTLV_DATA_BORDER_CHECK( (curIndex + 1), dataLen )
        valueLen = ((data[curIndex] & 0xff) << 8) | (data[curIndex +1] & 0xff);
        len_len = 3;
        curIndex += 2;
        BERTLV_DATA_ENOUGH_CHECK( dataLen, valueLen, curIndex )
    } else if (temp == 0x83) {
        BERTLV_DATA_BORDER_CHECK( (curIndex + 2), dataLen )
        valueLen = ((data[curIndex] & 0xff) << 16) | ((data[curIndex + 1] & 0xff) << 8) | (data[curIndex + 2] & 0xff);
        len_len = 4;
        curIndex += 3;
        BERTLV_DATA_ENOUGH_CHECK( dataLen, valueLen, curIndex )
    }
    pTarget->tag= tag;
    pTarget->dataLen= valueLen;
    pTarget->dataOffset = *offset + tag_len + len_len;
    if (isContainValue == TRUE) {
        *offset = pTarget->dataOffset + valueLen;
    }
    else {
        *offset = pTarget->dataOffset;
    }
    return 0;
}

void printParseResult (uint32_t ccmVersion, void* pCardMetadata) {
    if (ccmVersion == CCM_V10) {
        card_meta_struct_t_v1x * pData = (card_meta_struct_t_v1x *)pCardMetadata;
        LOGD("headerTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->headerTLV.tag, pData->headerTLV.dataLen, pData->headerTLV.dataOffset);
        LOGD("pkgInfoTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->pkgInfoTLV.tag, pData->pkgInfoTLV.dataLen, pData->pkgInfoTLV.dataOffset);
        LOGD("ElfAidTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ElfAidTLV.tag, pData->ElfAidTLV.dataLen, pData->ElfAidTLV.dataOffset);
        LOGD("SdAidTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->SdAidTLV.tag, pData->SdAidTLV.dataLen, pData->SdAidTLV.dataOffset);
        LOGD("AppAidTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->AppAidTLV.tag, pData->AppAidTLV.dataLen, pData->AppAidTLV.dataOffset);
        LOGD("ElfVersionTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ElfVersionTLV.tag, pData->ElfVersionTLV.dataLen, pData->ElfVersionTLV.dataOffset);
        LOGD("extrainfoTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->extrainfoTLV.tag, pData->extrainfoTLV.dataLen, pData->extrainfoTLV.dataOffset);
        LOGD("DateTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->DateTLV.tag, pData->DateTLV.dataLen, pData->DateTLV.dataOffset);
        LOGD("ProfileIdTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ProfileIdTLV.tag, pData->ProfileIdTLV.dataLen, pData->ProfileIdTLV.dataOffset);
        LOGD("LccmVerTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->LccmVerTLV.tag, pData->LccmVerTLV.dataLen, pData->LccmVerTLV.dataOffset);
        LOGD("SigAlgoTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->SigAlgoTLV.tag, pData->SigAlgoTLV.dataLen, pData->SigAlgoTLV.dataOffset);
        LOGD("PurposeTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->PurposeTLV.tag, pData->PurposeTLV.dataLen, pData->PurposeTLV.dataOffset);
        LOGD("ApduScriptTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ApduScriptTLV.tag, pData->ApduScriptTLV.dataLen, pData->ApduScriptTLV.dataOffset);
        LOGD("StoreDataFieldTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->StoreDataFieldTLV.tag, pData->StoreDataFieldTLV.dataLen, pData->StoreDataFieldTLV.dataOffset);
        LOGD("ReceiptTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ReceiptTLV.tag, pData->ReceiptTLV.dataLen, pData->ReceiptTLV.dataOffset);
        LOGD("AraAppInfoForAddTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->AraAppInfoForAddTLV.tag, pData->AraAppInfoForAddTLV.dataLen, pData->AraAppInfoForAddTLV.dataOffset);
        LOGD("signatureTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->signatureTLV.tag, pData->signatureTLV.dataLen, pData->signatureTLV.dataOffset);
        if (pData != NULL)
            pData = NULL;
    } else if (ccmVersion == CCM_V11) {

    } else if (ccmVersion == CCM_V20) {
        card_meta_struct_t_v20 * pData = (card_meta_struct_t_v20 *)pCardMetadata;
        LOGD("headerTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->headerTLV.tag, pData->headerTLV.dataLen, pData->headerTLV.dataOffset);
        LOGD("scriptInfoTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->scriptInfoTLV.tag, pData->scriptInfoTLV.dataLen, pData->scriptInfoTLV.dataOffset);
        LOGD("DateTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->DateTLV.tag, pData->DateTLV.dataLen, pData->DateTLV.dataOffset);
        LOGD("ApduScriptTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ApduScriptTLV.tag, pData->ApduScriptTLV.dataLen, pData->ApduScriptTLV.dataOffset);
        LOGD("StoreDataFieldTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->StoreDataFieldTLV.tag, pData->StoreDataFieldTLV.dataLen, pData->StoreDataFieldTLV.dataOffset);
        LOGD("ReceiptTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ReceiptTLV.tag, pData->ReceiptTLV.dataLen, pData->ReceiptTLV.dataOffset);
        LOGD("AraAppInfoForAddTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->AraAppInfoForAddTLV.tag, pData->AraAppInfoForAddTLV.dataLen, pData->AraAppInfoForAddTLV.dataOffset);
        LOGD("DeviceInfoTlv :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->DeviceInfoTlv.tag, pData->DeviceInfoTlv.dataLen, pData->DeviceInfoTlv.dataOffset);
        LOGD("AraAppInfoForDelTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->AraAppInfoForDelTLV.tag, pData->AraAppInfoForDelTLV.dataLen, pData->AraAppInfoForDelTLV.dataOffset);
        LOGD("signatureTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->signatureTLV.tag, pData->signatureTLV.dataLen, pData->signatureTLV.dataOffset);
        if (pData != NULL)
            pData = NULL;
    } 
    else if (ccmVersion == CCM_V31) {
       card_meta_struct_t_v31 * pData = (card_meta_struct_t_v31 *)pCardMetadata;
       LOGD("headerTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->headerTLV.tag, pData->headerTLV.dataLen, pData->headerTLV.dataOffset);
       LOGD("scriptInfoTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->scriptInfoTLV.tag, pData->scriptInfoTLV.dataLen, pData->scriptInfoTLV.dataOffset);
       LOGD("DateTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->DateTLV.tag, pData->DateTLV.dataLen, pData->DateTLV.dataOffset);
       LOGD("ApduScriptTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->ApduScriptTLV.tag, pData->ApduScriptTLV.dataLen, pData->ApduScriptTLV.dataOffset);
       LOGD("DeviceInfoTlv :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->DeviceInfoTlv.tag, pData->DeviceInfoTlv.dataLen, pData->DeviceInfoTlv.dataOffset);
       LOGD("StoreScenarioFieldTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->StoreScenarioFieldTLV.tag, pData->StoreScenarioFieldTLV.dataLen, pData->StoreScenarioFieldTLV.dataOffset);
       LOGD("signatureTLV :: tag= %04X, dataLen= %08X, dataOffset=%08X",pData->signatureTLV.tag, pData->signatureTLV.dataLen, pData->signatureTLV.dataOffset);
       if (pData != NULL)
           pData = NULL;
   } 
    else {
        LOGE("Wrong CCM version");
    }
}

void genAraCStoreDataApdu(uint32_t tag, uint8_t* appletAid, uint32_t appletAidLen, uint8_t* hash, uint32_t hashLen,
        uint8_t* araStoradataApdu, uint32_t *araStoradataApduLen) {
    uint8_t appletAidWithTag[20];
    uint32_t appletAidWithTag_size = 0;

    uint8_t hashWithTag[50];
    uint32_t hashWithTag_size = 0;

    uint8_t extraDataWithTag[8] = {0xE3, 0x06, 0xD0, 0x01, 0x01, 0xD1, 0x01, 0x00};
    uint8_t emptyDataWithTag[6] = {0xE3, 0x04, 0xD0, 0x00, 0xD1, 0x00};

    uint8_t e1WithTag[100];
    uint32_t e1WithTag_size = 0;

    uint8_t e2WithTag[100];
    uint32_t e2WithTag_size = 0;

    uint8_t f0WithTag[100];
    uint32_t f0WithTag_size = 0;

    uint8_t f1WithTag[100];
    uint32_t f1WithTag_size = 0;

    uint8_t tempBuffer[200];

    uint8_t temp_tlv_buf[3];
    uint8_t temp_tlv_buf_len;

    uint8_t tag_f0[1] = { 0xF0 };
    uint8_t tag_e1[1] = { 0xE1 };
    uint8_t tag_e2[1] = { 0xE2 };
    uint8_t tag_4f[1] = { 0x4F };
    uint8_t tag_c1[1] = { 0xC1 };
    uint8_t tag_f1[1] = { 0xF1 };

    if (appletAidLen > 18 || hashLen > 20) {
        LOGE("genAraCStoreDataApdu : Input parameter error");
        return ;
    }

    // Make 4F tag with data
    BER_TLV_LENGTH(appletAidLen, temp_tlv_buf, temp_tlv_buf_len);
    BUFFER_APPEND(appletAidWithTag, appletAidWithTag_size, appletAid, appletAidLen);
    BUFFER_APPEND_FRONT(tempBuffer, appletAidWithTag, appletAidWithTag_size, temp_tlv_buf, temp_tlv_buf_len);
    BUFFER_APPEND_FRONT(tempBuffer, appletAidWithTag, appletAidWithTag_size, tag_4f, sizeof(tag_4f));

    // Make C1 tag with data
    BER_TLV_LENGTH(hashLen, temp_tlv_buf, temp_tlv_buf_len);
    BUFFER_APPEND(hashWithTag, hashWithTag_size, hash, hashLen);
    BUFFER_APPEND_FRONT(tempBuffer, hashWithTag, hashWithTag_size, temp_tlv_buf, temp_tlv_buf_len);
    BUFFER_APPEND_FRONT(tempBuffer, hashWithTag, hashWithTag_size, tag_c1, sizeof(tag_c1));

    //Make E1 tag with data
    BUFFER_APPEND(e1WithTag, e1WithTag_size, appletAidWithTag, appletAidWithTag_size);
    BUFFER_APPEND(e1WithTag, e1WithTag_size, hashWithTag, hashWithTag_size);
    BER_TLV_LENGTH(e1WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
    BUFFER_APPEND_FRONT(tempBuffer, e1WithTag, e1WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
    BUFFER_APPEND_FRONT(tempBuffer, e1WithTag, e1WithTag_size, tag_e1, sizeof(tag_e1));

    if(tag == TAG_V1X_ARA_ADD_INFO || tag == TAG_V20_ARA_ADD_INFO){
        //Make E2 tag with data
        BUFFER_APPEND(e2WithTag, e2WithTag_size, e1WithTag, e1WithTag_size);
        BUFFER_APPEND(e2WithTag, e2WithTag_size, extraDataWithTag, sizeof(extraDataWithTag));
        BER_TLV_LENGTH(e2WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, e2WithTag, e2WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, e2WithTag, e2WithTag_size, tag_e2, sizeof(tag_e2));

        //Make F0 tag with data ,it is needed adding rules
        LOGD("genAraCStoreDataApdu : add ara rules");
        BUFFER_APPEND(f0WithTag, f0WithTag_size, e2WithTag, e2WithTag_size);
        BER_TLV_LENGTH(f0WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, f0WithTag, f0WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, f0WithTag, f0WithTag_size, tag_f0, sizeof(tag_f0));
        *araStoradataApduLen = f0WithTag_size;
        memcpy(araStoradataApdu, f0WithTag, *araStoradataApduLen);
    } else if(tag == TAG_V20_ARA_DEL_INFO) {
        //Make E2 tag with data, referenced by p.55 of GPD_SE_Access_Control_v1.1
        BUFFER_APPEND(e2WithTag, e2WithTag_size, e1WithTag, e1WithTag_size);
        BUFFER_APPEND(e2WithTag, e2WithTag_size, emptyDataWithTag, sizeof(emptyDataWithTag));
        BER_TLV_LENGTH(e2WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, e2WithTag, e2WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, e2WithTag, e2WithTag_size, tag_e2, sizeof(tag_e2));

        //Make F0 tag with data ,it is needed deleting rules
        LOGD("genAraCStoreDataApdu : delete ara rules");
        BUFFER_APPEND(f1WithTag, f1WithTag_size, e2WithTag, e2WithTag_size);
        BER_TLV_LENGTH(f1WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, f1WithTag, f1WithTag_size, temp_tlv_buf, temp_tlv_buf_len);
        BUFFER_APPEND_FRONT(tempBuffer, f1WithTag, f1WithTag_size, tag_f1, sizeof(tag_f1));
        *araStoradataApduLen = f1WithTag_size;
        memcpy(araStoradataApdu, f1WithTag, *araStoradataApduLen);
    } else {
        LOGE("genAraCStoreDataApdu : Input parameter error, incorrect tag value");
        return ;
    }
    return;
}
int32_t getCPLC4DeviceAccessControl(uint8_t *cplc) {
    int32_t ret = RET_ERR_TZ;
    uint8_t cn = 0;
    uint8_t isd_aid[] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00};
    uint8_t data[MAX_RAPDU_DATA_SIZE] = {0,};
    secEse_7816_cpdu_t cpdu;
    secEse_7816_rpdu_t rpdu;

    LOGD("getCPLC start");
    if (cplc == NULL) {
        LOGE("buffer is null");
        return ret;
    }

    if (ESESTATUS_SUCCESS != secEseOpen(&cn)) {
        LOGE("getCPLC channel open fail");
        ret = RET_ERR_GET_CPLC_FAIL;
        goto error;
    }

    memset(&rpdu, 0, sizeof(secEse_7816_rpdu_t));
    rpdu.pdata = data;
    if (cn < 1 || cn > 3 || ESESTATUS_SUCCESS != secEseSelect(cn, isd_aid, 0, 8, &rpdu) ) {
        LOGE("getCPLC failed to SELECT ISD");
        ret = RET_ERR_GET_CPLC_FAIL;
        goto error;
    }

    memset(&cpdu, 0, sizeof(secEse_7816_cpdu_t));
    memset(&rpdu, 0, sizeof(secEse_7816_rpdu_t));
    memset(data, 0, MAX_RAPDU_DATA_SIZE);
    rpdu.pdata = data;

    cpdu.cla = 0x80;
    cpdu.ins = 0xCA;
    cpdu.p1 = 0x9F;
    cpdu.p2 = 0x7F;
    cpdu.cpdu_type = 0;
    cpdu.le_type = 1;
    cpdu.le = 0;

    if (ESESTATUS_SUCCESS != secEseTransmit(cn, &cpdu, &rpdu)) {
        LOGE("getCPLC failed to send APDU GET CPLC");
        ret = RET_ERR_GET_CPLC_FAIL;
        goto error;
    }
    LOGD("status word %02X %02X %d", rpdu.sw1, rpdu.sw2, rpdu.len);
    if(rpdu.sw1 == 0x90 && rpdu.sw2 == 0x00 && rpdu.len > 0) {
        if (rpdu.len > 0) {
            memcpy(cplc, rpdu.pdata + 3, SIZE_CPLC);
            ret = RET_SUCCESS;
        }
    } else {
        LOGE("getCPLC failed. Invalid response : sw1 0x%2x,sw2 0x%2x,datalen %d",rpdu.sw1,rpdu.sw2,rpdu.len);
        ret = RET_ERR_GET_CPLC_FAIL;
        goto error;
    }

error:
    LOGE("getCPLC end %d", ret);
    if(cn != 0)
        secEseClose(cn);

    return ret;
}

int32_t enforceDeviceAccessControl(uint8_t* data, uint32_t dataLen) {
    //void getCPLC(p_rsp_t rsp) {
    int32_t ret = RET_ERR_CCM_ACCESS_DENIED;
    uint8_t* tmpCplc = NULL;
    uint8_t cplc[SIZE_CPLC] = {0,};
    int numOfDevices = 0;
    uint32_t offset = 2;
    int deviceIdLen = 0;
    uint32_t cplcLen = 42;
    int index = 0;

    tmpCplc = tz_malloc(SIZE_CPLC);
    if (tmpCplc == NULL || getCPLC4DeviceAccessControl(tmpCplc) != RET_SUCCESS) {
        LOGE("failed to get cplc");
        goto error;
    } else {
        hex_print_tag_debug("device info TLV", data, dataLen);
        hex_print_tag_debug("cplc from device", tmpCplc, SIZE_CPLC);

        numOfDevices = data[offset++]; // skip len of tag E6, cuz length is less than 255
        LOGD("numOfDevices: %d", numOfDevices);

        for ( ; index < numOfDevices && (offset + 2 <= dataLen) ; index++) {
            offset += 2; // skip next tag (0xE7) and length
            // parsing device information
            deviceIdLen = data[offset++];
            LOGD("deviceIdLen: %d", deviceIdLen);

            // current not implemented how to enforce script based on deviceId, TBD
            if (deviceIdLen != 0) {
                LOGE("Wrong device info: %d", deviceIdLen);
                ret = RET_ERR_CCM_INVALID_METADATA;
                goto error;
            }

            cplcLen = data[offset++];
            // current not implemented how to enforce script based on deviceId, TBD
            if (cplcLen != SIZE_CPLC) {
                LOGE("Wrong cplc info: %d", cplcLen);
                ret = RET_ERR_CCM_INVALID_METADATA;
                goto error;
            }

            if (offset + cplcLen <= dataLen) {
                if (memcpy(cplc, data + offset, SIZE_CPLC) != cplc) {
                    LOGE("failed to copy");
                    ret = RET_ERR_CCM_OUT_OF_MEMORY;
                    goto error;
                }
                hex_print_tag_debug("cplc from script", cplc, SIZE_CPLC);
                if (memcmp (cplc, tmpCplc, SIZE_CPLC) == 0) {
                    LOGD("matched cplc");
                    ret = RET_SUCCESS;
                    goto error;
                }
                LOGE("try with nex cplc");
                offset += cplcLen;
            } else {
                LOGE("invalid scripts. device access control error");
                ret = RET_ERR_CCM_INVALID_METADATA;
                goto error;
            }
        }
    }
error:
    if(tmpCplc != NULL) {
        tz_free(tmpCplc);
        tmpCplc = NULL;
    }
    LOGD("enforceDeviceAccessControl %d end", ret);
    return ret;
}
// NOTICE!!! : must be called after doScenario3 function
int32_t manageAracRules(uint32_t tag, uint8_t* data, uint32_t dataLen) {
    int32_t ret = 0;

    SCPSTATUS scpStatus = SCP_FAIL;
    ESESTATUS isoStatus = ESESTATUS_FAILED;

    secEse_7816_rpdu_t isoResponse;
    uint8_t r_data[MAX_RAPDU_DATA_SIZE] = {0,};

    uint8_t channelId = 0;

    uint8_t storedataP1 = 0x90;
    uint8_t storedataP2 = 0x00;

    uint8_t araStoradataApdu[500];
    uint32_t araStoradataApduLen = 0;

    uint8_t rspData[MAX_RSP_CIPHERED_TEXT_SIZE] = {0,};
    uint8_t appletAid[MAX_AID_SIZE] = {0,};
    uint8_t appInfo[20] = {0,};

    int numOfAccessRules = 0;
    int index = 0;
    uint32_t aidLen = 0, appInfoLen = 0;
    uint32_t offset = 2;

    if (data == NULL || dataLen < 2){
        LOGE("ERROR : WRONG BERTLV data. data is NULL or empty : %u", dataLen);
        return -1;
    }
    hex_print_tag_debug("manageAracRules", data, dataLen);
    memset(&isoResponse, 0, sizeof(secEse_7816_rpdu_t));
    isoResponse.pdata = r_data;

    // manage channel
    isoStatus = secEseOpen(&channelId);
    if (isoStatus != ESESTATUS_OK) {
        LOGE("Failed to open channel in araCBiding");
        ret = RET_ERR_CCM_MANAGE_CHANNEL;
        goto error;
    }
    LOGD("araCBiding - channel ID : %d",channelId);

    // select ARAC
    isoStatus = secEseSelect(channelId, ARAC_AID, 0, 16, &isoResponse);
    if (isoStatus != ESESTATUS_OK || isoResponse.sw1 != 0x90 || isoResponse.sw2 !=0x00) {
        LOGE("Failed to [SELECT ARAC] in araCBiding");
        ret = RET_ERR_CCM_SELECT_ARAC;
        goto error;
    }

    // SCP03 : open session [ARA-C rule update] via kvn31 session
    // securityLevel : External Authentication Reference Control Prameter P1 (page 29/36) <<===================== check DR. KWON
    scpStatus = openSession(channelId, 0x31, kvn31_key_enc, kvn31_key_mac, kvn31_key_dek, ARAC_AID, 16, 0x03);
    if (scpStatus != SCP_SUCCESS) {
        LOGE("Failed SCP03 KVN31 OPEN SESSION :: scpStatus : 0x%08x", scpStatus);
        ret = RET_ERR_CCM_OPENSESSION_KVN31;
        goto error;
    }

    // Generate Ara-C Binding Data;

    numOfAccessRules = data[offset++]; // skip len of tag F2, cuz length is less than 255
    LOGD("numOfAccessRules: %d", numOfAccessRules);

    for ( ; index < numOfAccessRules && (offset + 4 <= dataLen) ; index++) {
        offset += 2; // skip next tag (0xF3) and length
        // parsing applet information
        aidLen = data[offset++];
        LOGD("aidLen: %u", aidLen);
        if ((uint32_t) (offset + aidLen) <= dataLen) {
            memcpy(appletAid, data + offset, aidLen);
            offset += aidLen;
        } else {
            LOGE("invalid scripts. ara error, applet id");
            ret = RET_ERR_CCM_INVALID_METADATA;
            goto error;
        }

        // parsing app information
        appInfoLen = data[offset++];
        LOGD("appInfoLen: %u", appInfoLen);
        if ((uint32_t) (offset + appInfoLen) <= dataLen) {
            memcpy(appInfo, data + offset, appInfoLen);
            offset += appInfoLen;
        } else {
            LOGE("invalid scripts. ara error, app id");
            ret = RET_ERR_CCM_INVALID_METADATA;
            goto error;
        }

        hex_print_tag_debug("AID", appletAid, aidLen);
        hex_print_tag_debug("HASH", appInfo, appInfoLen);
        genAraCStoreDataApdu(tag, appletAid, aidLen, appInfo, appInfoLen, araStoradataApdu, &araStoradataApduLen);
        hex_print_tag_debug("DATA", araStoradataApdu, araStoradataApduLen);
        // scp03 [STORE DATA ARA-C rule] via kvn31 session
        // p1, p2, storeDataLen : Need to check test vector (GPcard page 167) <<===================================== check DR. KWON
        scpStatus = sendStoreDataCmd(channelId, storedataP1, storedataP2, (uint8_t)araStoradataApduLen, araStoradataApdu, rspData);
        if (scpStatus == SCP_FILE_ALREADY_EXIST) {
            LOGI("ARA-C rule has been already exist");
        } else if (scpStatus != SCP_SUCCESS) {
            LOGE("Failed SCP03 [ STORE DATA ARA-C rule ] :: scpStatus : 0x%08x", scpStatus);
            ret = RET_ERR_CCM_STORE_DATA;
            goto error;
        }
    }

error:
    secEseClose(channelId);
    return ret;
}

int32_t doScenario3(uint8_t storeDataLen, uint8_t *pStoredata, uint8_t receiptLen, uint8_t *pReceipt,
        uint8_t *otp_key_blob, uint32_t otp_key_blob_size) {
    int32_t ret = 0;

    SCPSTATUS scpStatus = SCP_FAIL;
    ESESTATUS isoStatus = ESESTATUS_FAILED;
    CRYPTO_STATUS cryptoStatus = CRYPTO_STATUS_FAILED;
    uint8_t kvn30keyset[48] = {0,}; // enc 16,mac 16, dek 16
    uint32_t kvn30keysetLen = 0;

    uint8_t kvn30_key_enc[AES_128_KEY_SIZE] = {0,};
    uint8_t kvn30_key_mac[AES_128_KEY_SIZE] = {0,};
    uint8_t kvn30_key_dek[AES_128_KEY_SIZE] = {0,};

    secEse_7816_rpdu_t isoResponse;
    uint8_t r_data[MAX_RAPDU_DATA_SIZE] = {0,};

    uint8_t channelId = 0;

    uint8_t storedataP1 = 0x89;
    uint8_t storedataP2 = 0x00;

    uint8_t receipt[50] = {0,};
    uint8_t receipt_value[RECEIPT_SIZE] = {0,};
    uint8_t receiptHashed[SHA256_DIGEST_SIZE] = {0,};
    LOGD("doScenario3 start");
    if (storeDataLen < 1 || pStoredata == NULL) {
        LOGE("Invalid store data field for scenario #3");
        ret = RET_ERR_CCM_STORE_DATA;
        goto error;
    }
    if (receiptLen < 1 || pReceipt == NULL) {
        LOGE("Invalid receipt for scenario #3");
        ret = RET_ERR_CCM_STORE_DATA;
        goto error;
    }

    hex_print_tag_debug("SEM", pStoredata, storeDataLen);
    hex_print_tag_debug("SEM", pReceipt, receiptLen);

    memset(&isoResponse, 0, sizeof(secEse_7816_rpdu_t));
    isoResponse.pdata = r_data;

    // manage channel
    isoStatus = secEseOpen(&channelId);
    if (isoStatus != ESESTATUS_OK) {
        LOGE("Failed to open channel in CCM. 1");
        ret = RET_ERR_CCM_MANAGE_CHANNEL;
        goto error;
    }
    LOGD("scp03Init - CCM channel ID : %d",channelId);

    // select DMSD
    isoStatus = secEseSelect(channelId, DMSD_AID, 0, 14, &isoResponse);
    if (isoStatus != ESESTATUS_OK || isoResponse.sw1 != 0x90 || isoResponse.sw2 !=0x00) {
        LOGE("Failed to [SELECT DMSD] in CCM");
        ret = RET_ERR_CCM_SELECT_DMSD;
        goto error;
    }

    // generate random keyset KVN#31
    ret = genKVN31keyset(kvn31_key_enc, kvn31_key_mac, kvn31_key_dek);
    if (ret != 0) {
        LOGE("Failed to generate KVN31 keyset");
        ret = RET_ERR_CCM_GENERATE_KVN31_KEYSET;
        goto error;
    }

    LOGD("********************* [ OPEN SESSION 31 TRIAL  #1 ] *******************");
    // SCP03 : open session KVN#31( incl. Initialize Update, External Authentication )
    // securityLevel : External Authentication Reference Control Prameter P1 (page 29/36) <<===================== check DR. KWON
    scpStatus = openSession(channelId, 0x31, kvn31_key_enc, kvn31_key_mac, kvn31_key_dek, DMSD_AID, 14, 0x11);
    //scpStatus = SCP_FAIL;
    if (scpStatus != SCP_SUCCESS) {
        // [[ If 'open session KVN#31' fails,
        // TODO : get kvn#30 keyset with ssp opensession with command function.
        LOGI("SCP03 KVN31 OPEN SESSION not available. Start to get kvn#30 to process PUT KEY scpStatus : 0x%08x", scpStatus);
        // close channel for scp03
        secEseClose(channelId);

        // manage channel
        isoStatus = secEseOpen(&channelId);
        if (isoStatus != ESESTATUS_OK) {
            LOGE("Failed to open channel in CCM. 2");
            ret = RET_ERR_CCM_MANAGE_CHANNEL;
            goto error;
        }
        LOGD("reopen channel to get kvn#30. channel ID : %d",channelId);

        // ssp kvn30 session open -> opensession with cmd -> close session in getKVN30Keyset
        ret = getKVN30Keyset(channelId, otp_key_blob, otp_key_blob_size, kvn30keyset, &kvn30keysetLen);
        if (0 != ret) {
            LOGE("Failed to get KVN30Keyset");
            ret = RET_ERR_CCM_OPENSESSION_KVN30;
            goto error;
        }

        // close channel for kvn30keyset
        secEseClose(channelId);

        // parse KVN30 keyset & save to buffers :: kvn30_key_enc, kvn30_key_mac, kvn30_key_dek each key length is 16byte
        if (kvn30keysetLen == 48) {
            memcpy(kvn30_key_enc, kvn30keyset, AES_128_KEY_SIZE);
            memcpy(kvn30_key_mac, kvn30keyset + AES_128_KEY_SIZE, AES_128_KEY_SIZE);
            memcpy(kvn30_key_dek, kvn30keyset + AES_128_KEY_SIZE + AES_128_KEY_SIZE, AES_128_KEY_SIZE);
        } else {
            LOGE("Failed to get KVN30Keyset. Invalid keyset length : kvn30keysetLen %u", kvn30keysetLen);
            ret = RET_ERR_CCM_OPENSESSION_KVN30;
            goto error;
        }

        // manage channel
        isoStatus = secEseOpen(&channelId);
        if (isoStatus != ESESTATUS_OK) {
            LOGE("Failed to open channel in CCM. 3");
            ret = RET_ERR_CCM_MANAGE_CHANNEL;
            goto error;
        }
        LOGD("CCM channel ID : %d",channelId);

        // select DMSD
        isoStatus = secEseSelect(channelId, DMSD_AID, 0, 14, &isoResponse);
        if (isoStatus != ESESTATUS_OK || isoResponse.sw1 != 0x90 || isoResponse.sw2 !=0x00) {
            LOGE("Failed to [SELECT DMSD] in CCM.");
            ret = RET_ERR_CCM_SELECT_DMSD;
            goto error;
        }

        LOGD("********************* [ OPEN SESSION 30 ] *******************");
        // SCP opensession KVN#30
        // securityLevel : External Authentication Reference Control Prameter P1 (page 29/36) <<===================== check DR. KWON
        scpStatus = openSession(channelId, 0x30, kvn30_key_enc, kvn30_key_mac, kvn30_key_dek, DMSD_AID, 14, 0x33);
        if (scpStatus != SCP_SUCCESS) {
            LOGE("Failed SCP03 KVN30 OPEN SESSION :: scpStatus : 0x%08x", scpStatus);
            ret = RET_ERR_CCM_OPENSESSION_KVN30;
            goto error;
        }

        LOGD("********************* [PUT KEY START] *******************");
        // SCP03 [PUT KEY] via kvn30 session
        LOGD("Try SCP03 [ PUT KVN31 KEY - ADD. ]");
        scpStatus = sendPutKeyCmd(channelId, 0x31, kvn31_key_enc, kvn31_key_mac, kvn31_key_dek, 1); // add : 1 => add new key, 0 => replace existing
        if (scpStatus != SCP_SUCCESS) {
            LOGE("Failed SCP03 [ PUT KVN31 KEY - ADD ] :: scpStatus : 0x%08x", scpStatus);
            if(scpStatus == SCP_FAIL) {
                // for 6a80 :: incorrect parameter in P1,P2 for Add new key command
                LOGD("Retry SCP03 [ PUT KVN31 KEY - Replace Exising. ]");
                scpStatus = sendPutKeyCmd(channelId, 0x31, kvn31_key_enc, kvn31_key_mac, kvn31_key_dek, 0); // add : 1 => add new key, 0 => replace existing
                if (scpStatus != SCP_SUCCESS) {
                    LOGE("Failed SCP03 [ PUT KVN31 KEY - Replace Existing. ] :: scpStatus : 0x%08x", scpStatus);
                    ret = RET_ERR_CCM_PUT_KEY;
                    goto error;
                }
            } else {
                ret = RET_ERR_CCM_PUT_KEY;
                goto error;
            }
        }

        LOGD("********************* [PUT KEY SUCCESS] *******************");
        hex_print_tag_debug("kvn30_key_enc", kvn30_key_enc, 16);
        hex_print_tag_debug("kvn30_key_mac", kvn30_key_mac, 16);
        hex_print_tag_debug("kvn30_key_dek", kvn30_key_dek, 16);

        // SCP03 : open session KVN#31 again
        // securityLevel : External Authentication Reference Control Prameter P1 (page 29/36) <<===================== check DR. KWON
        scpStatus = openSession(channelId, 0x31, kvn31_key_enc, kvn31_key_mac, kvn31_key_dek, DMSD_AID, 14, 0x11);
        if (scpStatus != SCP_SUCCESS) {
            LOGE("Failed SCP03 KVN31 OPEN SESSION :: scpStatus : 0x%08x", scpStatus);
            ret = RET_ERR_CCM_OPENSESSION_KVN31;
            goto error;
        }
        // ]]  'open session KVN#31' fails
    }

    LOGD("********************* [STORE DATA START] *******************");
    // scp03 [STORE DATA ePK.AP.ECKA] via kvn31 session
    // p1, p2, storeDataLen : Need to check test vector (GPcard page 167) <<===================================== check DR. KWON
    scpStatus = sendStoreDataCmd(channelId, storedataP1, storedataP2, storeDataLen, pStoredata, receipt);
    if (scpStatus != SCP_SUCCESS) {
        LOGE("Failed SCP03 [ STORE DATA ] :: scpStatus : 0x%08x", scpStatus);
        ret = RET_ERR_CCM_STORE_DATA;
        goto error;
    }

    hex_print_tag_debug("STORE DATA RECEIPT", receipt, RECEIPT_SIZE + 2);

    if (receipt[0] == 0x86 && receipt[1] == 0x10) {
        memcpy(receipt_value, receipt + 2, 0x10);
    } else {
        LOGE("STORE DATA Receipt format invalid! TAG = 0x%x, LEN = 0x%x", receipt[0], receipt[1]);
        ret = RET_ERR_CCM_RECEIPT_VERIFICATION;
        goto error;
    }

    // verify RECEIPT
        // STORE DATA returns receipt : Response data not present (Amend.E. page 32/44) <<===================================== check DR. KWON
    cryptoStatus = crypto_sha256( receiptHashed, receipt_value, RECEIPT_SIZE, NULL );
    if (CRYPTO_STATUS_SUCCESS != cryptoStatus) {
        LOGE("crypto_sha256 failed (error 0x%08X)", cryptoStatus);
        ret = RET_ERR_CCM_CRYPTO_FAIL;
        goto error;
    }

    LOGD("********************* [Receipt Verification] *******************");
    hex_print_tag_debug("scriptReceipt", pReceipt, SHA256_DIGEST_SIZE);
    hex_print_tag_debug("receiptHashed", receiptHashed, SHA256_DIGEST_SIZE);
    ret = compareReceipts(pReceipt, receiptHashed, SHA256_DIGEST_SIZE);
    if (ret != 0) {
        LOGE( "Failed STORE DATA Receipt Verification");
        ret = RET_ERR_CCM_RECEIPT_VERIFICATION;
        goto error;
    }
    ret = RET_SUCCESS;
error:
    secEseClose(channelId);
    return ret;
}

int32_t genKVN31keyset(uint8_t *out_key_enc, uint8_t *out_key_mac, uint8_t *out_key_dek) {

    if (tz_KDF_AES128(SALT_AES_ENC, 32, out_key_enc) != 0) {
        goto error;
    }
    hex_print_tag_debug("generated KVN#31 KEY_ENC", out_key_enc, AES_128_KEY_SIZE);

    if (tz_KDF_AES128(SALT_AES_MAC, 32, out_key_mac) != 0) {
        goto error;
    }
    hex_print_tag_debug("generated KVN#31 KEY_MAC", out_key_mac, AES_128_KEY_SIZE);

    if (tz_KDF_AES128(SALT_AES_DEK, 32, out_key_dek) != 0) {
        goto error;
    }
    hex_print_tag_debug("generated KVN#31 KEY_DEK", out_key_dek, AES_128_KEY_SIZE);

    return 0;

error:
    return -1;
}

int32_t tz_KDF_AES128( uint8_t *keySalt, uint16_t keySaltLen, uint8_t out[AES_128_KEY_SIZE] ) {
    int32_t res;
#ifdef USE_MOBICORE
    //uint8_t keySalt[] = "AESKDF_SALT";
    res = tlApiDeriveKey( keySalt, keySaltLen,
                          out, AES_128_KEY_SIZE,
                          MC_SO_CONTEXT_DEVICE,
                          MC_SO_LIFETIME_PERMANENT );

    if (TLAPI_OK != res) {
        LOGE( "tlApiDeriveKey failed with code %d", res );
        return -1;
    }
#endif

#ifdef USE_QSEE
    uint8_t keyLabel[] = "kvn31_keyset";

    res = qsee_kdf( NULL, AES_128_KEY_SIZE,
                    keyLabel, 12,
                    keySalt, keySaltLen,
                    out, AES_128_KEY_SIZE);
    if ( 0 != res ) {
        LOGE( "qsee_kdf failed with code %d", res );
        return -1;
    }
#endif

#ifdef USE_BLOWFISH
    TEE_ObjectHandle obj_hndl = TEE_HANDLE_NULL;
    uint32_t byte_size = AES_128_KEY_SIZE;
    const uint32_t bit_size = 8 * AES_128_KEY_SIZE;
    uint8_t keyLabel[] = "kvn31_keyset";

    res = TEE_AllocateTransientObject(TEE_TYPE_GENERIC_SECRET, bit_size, &obj_hndl);
    if (res != 0) {
        LOGE( "TEE_AllocateTransientObject failed with code %d", res );
        goto error;
    }
    res = TEES_DeriveKeyKDF(keyLabel, 12, keySalt, keySaltLen, byte_size, obj_hndl);
    if (res != 0) {
        LOGE( "TEES_DeriveKeyKDF failed with code %d", res );
        goto error;
    }
    res = TEE_GetObjectBufferAttribute(obj_hndl, TEE_ATTR_SECRET_VALUE, out, &byte_size);
    if (res != 0) {
        LOGE( "TEE_GetObjectBufferAttribute failed with code %d", res );
        goto error;
    }

    error:
    TEE_CloseObject(obj_hndl);
    if (res != 0) {
        LOGE( "tz_KDF_AES128 failed");
        return -1;
    }
#endif

#ifdef USE_TRUSTY_UNISOC

#endif

    return 0;
}

int32_t compareReceipts(uint8_t* receipt1, uint8_t* receipt2, uint8_t receiptLen) {
    int32_t len;
    for (len = receiptLen-1; -1 < len; len--) {
        if (receipt1[len] != receipt2[len]) {
            return -1;
        }
    }

    return 0;
}

/*
int32_t getCertificate(uint8_t* certData) {
    int32_t ret = RET_ERR_TZ;
    uint8_t cn = 0;
    uint8_t casd_aid[] = {0xA0, 0x00, 0x00, 0x01, 0x51, 0x53, 0x50, 0x43, 0x41, 0x53, 0x44, 0x00}; //A0 00 00 01 51 53 50 43 41 53 44 00
    uint8_t data[MAX_RAPDU_DATA_SIZE] = {0,};
    secEse_7816_cpdu_t cpdu;
    secEse_7816_rpdu_t rpdu;
    int certDataLen = 0;
    int i = 0;
    int offset = 0;
    ESESTATUS result = ESESTATUS_FAILED;
    
    LOGD("getCERT start");

    if (ESESTATUS_SUCCESS != secEseOpen(&cn)) {
        LOGE("getCERT channel open fail");
        ret = RET_ERR_GET_CPLC_FAIL;
        goto error;
    }


    memset(&rpdu, 0, sizeof(secEse_7816_rpdu_t));
    if (cn < 1 || cn > 3 || ESESTATUS_SUCCESS != secEseSelect(cn, casd_aid, 0, 12, &rpdu) ) {
        LOGE("getCERT failed to SELECT ISD");
        ret = ESESTATUS_FAILED;
        goto error;
    }

    memset(&cpdu, 0, sizeof(secEse_7816_cpdu_t));
    memset(&rpdu, 0, sizeof(secEse_7816_rpdu_t));
    memset(data, 0, MAX_RAPDU_DATA_SIZE);

    cpdu.cla = 0x80;
    cpdu.ins = 0xCA;
    cpdu.p1 = 0x7F;
    cpdu.p2 = 0x21;
    cpdu.cpdu_type = 0;
    cpdu.le_type = 1;
    cpdu.le = 0;

    for(i=0; i<20; i++) {
        result = secEseTransmit(cn, &cpdu, &rpdu);
        if (result != ESESTATUS_OK){
            LOGE("get CERT secEseTransmit fail %04x", result);
            result = ESESTATUS_FAILED;
            //error
            goto error;
        } else {
            LOGD("get CERT secEseTransmit success %04x", result);
        }

        if (result == ESESTATUS_OK) {
            if(((rpdu.sw1 == 0x90) && (rpdu.sw2 == 0x00)) || ((rpdu.sw1 == 0x63) && (rpdu.sw2 == 0x10)) ) {
                if((offset + rpdu.len) >= MAX_DATA_SIZE) {
                    LOGE("get CERT Data Overflow Error : %d", (offset + rpdu.len));
                    result = RET_ERR_BUFFER_OVERFLOW;
                    break;
                }
                memcpy(data + offset, rpdu.pdata, rpdu.len);

                offset = (offset+rpdu.len);
                certDataLen = offset;

                if((rpdu.sw1 == 0x90) && (rpdu.sw2 == 0x00)) {
                    LOGI("get CERT Check Finish");
                    result = RET_SUCCESS;
                    LOGD("get CERT return RET_SUCCESS");
                    break;
                } else if((rpdu.sw1 == 0x63) && (rpdu.sw2 == 0x10)) {
                    LOGI("get CERT Check again");
                    //continue;
                }
            }
            else {
                LOGE("get CERT Check Error");
                result = ESESTATUS_FAILED;
                break;
            }
        }
        else {
            LOGE("get CERT Check Error");
            result = ESESTATUS_FAILED;
            break;
        }
    }

    if (ESESTATUS_SUCCESS != secEseTransmit(cn, &cpdu, &rpdu)) {
        LOGE("getCERT failed to send APDU GET CERT");
        ret = ESESTATUS_FAILED;
        goto error;
    }
    
    LOGD("status word %02X %02X %d", rpdu.sw1, rpdu.sw2, rpdu.len);
    if(rpdu.sw1 == 0x90 && rpdu.sw2 == 0x00 && rpdu.len > 0) {
        if (rpdu.len > 0) {
            memcpy(certData, data + 3, certDataLen);
            ret = RET_SUCCESS;
        }
    } else {
        LOGE("getCERT failed. Invalid response : sw1 0x%2x,sw2 0x%2x,datalen %d", rpdu.sw1,rpdu.sw2,rpdu.len);
        ret = ESESTATUS_FAILED;
        goto error;
    }

    hex_print_tag_debug("Get CERT", certData, certDataLen);
error:
    LOGE("getCERT end %d", ret);
    if(cn != 0)
        secEseClose(cn);
    return ret;
}
*/

CRYPTO_STATUS metadataSigVerification(uint8_t* message, uint32_t messageLen, uint8_t* sigLeft, int32_t sigLeftLen, uint8_t* sigRight, int32_t sigRightLen) { // signature : 64byte, param 32 / 32
    CRYPTO_STATUS cryptoret = CRYPTO_STATUS_FAILED;
    ecc_key_t regen_eccKey;
    // real key 2015.06.05
    uint8_t pubkey[SSP_ECC_PUBKEY_SIZE] = {0x04, \
                            0x47,0x1e,0x70,0x7f,0x8b,0x9c,0x12,0x21,0x80,0x8d,0x52,0x66,0x90,0xd9,0x46,0x6a,0x05,0x2b,0x3c,0x02,0xa8,0x3e,0x55,0x39,0xe0,0x90,0x15,0x2d,0x8e,0x8d,0xf7,0x6e, \
                            0x12,0x0f,0xca,0xd5,0x04,0x7e,0xf1,0xa1,0xd5,0x6a,0x2b,0x1c,0x4e,0xab,0xe2,0x84,0xf0,0x6f,0xd8,0xd3,0x98,0x1d,0x19,0xaf,0xcc,0x22,0x75,0x64,0xc3,0xad,0x93,0x38};

    memset(&regen_eccKey, 0, sizeof(regen_eccKey));
    crypto_regen_ecc_pubkey(&regen_eccKey, pubkey, SSP_ECC_PUBKEY_SIZE);
    cryptoret = crypto_ecdsa_verify_with_sha256(regen_eccKey.ecc_keypair, message, messageLen, sigLeft, sigLeftLen, sigRight, sigRightLen);
    crypto_clear_ecc_context(&regen_eccKey);

    return cryptoret;
}

#define SESSION_WITHOUT_COMMAND
int32_t getKVN30Keyset(uint8_t channelId, const uint8_t *otp_key_blob, uint32_t otp_key_blob_size, uint8_t* outKeySet, uint32_t* outKeySetLen) {
    ESESTATUS ese_status = ESESTATUS_FAILED;
    SSPSTATUS sspStatus = SSPSTATUS_FAILED;

    uint32_t sessionId = 0;
    secEse_7816_rpdu_t response;
    uint8_t r_data[MAX_RAPDU_DATA_SIZE] = {0,};

#ifdef SESSION_WITHOUT_COMMAND
    secEse_7816_cpdu_t c_getKvn30;
    uint8_t getKVN30Data[8] = {0xE0, 0x06, 0xE2, 0x04, 0xAB, 0xF3, 0x63, 0x6E};
#else
    uint8_t getKVN30Apdu[13] = {0x80, 0xCA, 0x00, 0x00, 0x08, 0xE0, 0x06, 0xE2, 0x04, 0xAB, 0xF3, 0x63, 0x6E};
#endif

    memset(&response, 0, sizeof(secEse_7816_rpdu_t));
    response.pdata = r_data;

    ese_status = secEseSelect(channelId, (uint8_t*)SSE_AID, 0, 16, &response);
    if (ESESTATUS_SUCCESS != ese_status) {
        LOGE("getKVN30Keyset ESESTATUS = 0x%08X", ese_status);
        goto error;
    }

#ifdef SESSION_WITHOUT_COMMAND
    sspStatus = ssp_openSession(&sessionId, channelId, otp_key_blob, otp_key_blob_size, sTID_SEM, SSE_AID, 16);

    c_getKvn30.cla = 0x80;
    c_getKvn30.ins = 0xCA;
    c_getKvn30.p1 = 0x00;
    c_getKvn30.p2 = 0x00;
    c_getKvn30.lc = 0x08;
    c_getKvn30.pdata = getKVN30Data;
    c_getKvn30.le_type = 0x01;
    c_getKvn30.le = 0x00;
    c_getKvn30.cpdu_type = 0;

    sspStatus = ssp_transaction(sessionId, &c_getKvn30, &response);
    if (SSPSTATUS_SUCCESS != sspStatus) {
        goto error;
    }
#else
    sspStatus = ssp_openSession_with_command(&sessionId, channelId, otp_key_blob, otp_key_blob_size, sTID_SEM, SSE_AID, 16, getKVN30Apdu, sizeof(getKVN30Apdu), &response);
#endif

    if (sspStatus != SSPSTATUS_SUCCESS) {
        LOGE("Error : SSP ssp_openSession_with_command error while getting kvn30 :: sspStatus = 0x%x",sspStatus);
        goto error;
    }

    if (!(response.sw1 == 0x90 && response.sw2 == 0x00) && !(response.sw1 == 0x63 && response.sw2 == 0x10)) {
        LOGE("Error : getKVN30Keyset response is invalid :: 0x%x,0x%x", response.sw1, response.sw2);
        goto error;
    }

    hex_print_tag_debug("Rsp", response.pdata, response.len);

    memcpy(outKeySet, response.pdata, response.len);
    *outKeySetLen = response.len;

    sspStatus = ssp_closeSession(sessionId);
    if (sspStatus != SSPSTATUS_SUCCESS) {
        LOGE("Error : SSP close session error : 0x%x",sspStatus);
        goto error;
    }
    LOGD("******************* getkvn30keyset [end]   SUCCESS     ******************");
    return 0;

error:
    LOGE("******************* getkvn30keyset [end]   FAILED   ******************");
    return -1;
}

