#include "Open_Secure_Channel.h"

tciReturnCode_t process_open_secure_channel(
    tl_dk_ctx_t *ctx,
    tz_dk_open_secure_channel_payload_t *sendmsg,
    tz_dk_open_secure_channel_payload_t *respmsg) 
{

    DK_Result rc;

    byte byte_cpdu[CPDU_MAX_SIZE] = {0};
    size_t byte_cpdu_len = CPDU_MAX_SIZE;

    secEse_7816_rpdu_t rpdu = {0};
    rpdu.pdata = dk_malloc(MAX_RAPDU_SIZE);
    if (NULL == rpdu.pdata) {
        DK_LOG_ERR("failed to allocate mem for rpdu, aborting");
        return DK_ERROR_OUT_OF_MEMORY;
    }
    
    byte full_response[MAX_RESPONSE_SIZE];
    uint16_t full_response_len = 0;

    byte byte_rpdu[RPDU_MAX_SIZE] = {0};
    size_t byte_rpdu_len = RPDU_MAX_SIZE;

    byte wrapped_apdu[CPDU_MAX_SIZE] = {0};
    size_t wrapped_apdu_len = CPDU_MAX_SIZE;

    byte kvn = 0x30; // default value set by HQ

    byte open_channel_attempts = 0;

    if (ctx->channel.is_open) {
        DK_LOG_WARN("channel is open");
        // TODO: should we close the channel or return?
        // close_channel(&ctx->channel); 
        rc = TZ_DIGITALKEY_CHANNEL_CLOSED;
        goto catch_error;
    }
    
    // TODO: this issues an error when executed too quickly after another
    // open_channel call. Needs further investigation
    scp_init(&ctx->scp_ctx);
    rc = open_channel(&ctx->channel, &rpdu);
    if (rc) {
        DK_LOG_ERR("error opening channel");
        goto catch_error;
    }

    // TODO: this ifdef is to remain here and must not be moved to
    // a mock function since this code might be actually be used in
    // the final solution. For now, this is only used for test purposes
    // since key provisioning is not inplace yet, but when it becomes
    // clear how the keys are obtained, stored and manipulated, parts 
    // of this section of code might be relevant for the product
// #ifdef DK_DEBUG
    if (sendmsg != NULL) {
        uint8_t *enc, *mac/*, *dek */;
        size_t key_len = sendmsg->payload.cmd.buffer_len;
        switch (key_len * 8) {
        case 128:
            enc = sendmsg->payload.cmd.buffer.kenc.b_128;
            mac = sendmsg->payload.cmd.buffer.kmac.b_128;
            // dek = sendmsg->payload.cmd.scp_key_dek.b_128;
            break;
        case 192:
            enc = sendmsg->payload.cmd.buffer.kenc.b_192;
            mac = sendmsg->payload.cmd.buffer.kmac.b_192;
            // dek = sendmsg->payload.cmd.scp_key_dek.b_192;
            break;
        case 256:
            enc = sendmsg->payload.cmd.buffer.kenc.b_256;
            mac = sendmsg->payload.cmd.buffer.kmac.b_256;
            // dek = sendmsg->payload.cmd.scp_key_dek.b_256;
            break;
        default:
            DK_LOG_ERR("invalid key size: %d bytes", key_len);
            rc = DK_ERROR_BAD_PARAM;
            goto catch_error;
        }
        ctx->scp_ctx.key_enc = dk_malloc(key_len);
        ctx->scp_ctx.key_mac = dk_malloc(key_len);
        // ctx->scp_ctx.key_dek = dk_malloc(key_len);

        dk_memcpy(ctx->scp_ctx.key_enc, enc, key_len);
        dk_memcpy(ctx->scp_ctx.key_mac, mac, key_len);
        // dk_memcpy(ctx->scp_ctx.key_enc, dek, key_len);

        ctx->scp_ctx.key_enc_len = key_len;
        ctx->scp_ctx.key_mac_len = key_len;
        kvn = sendmsg->payload.cmd.kvn;
        // ctx->scp_ctx.key_dek_len = key_len;
    }
    else {
        DK_LOG_ERR("process_open_secure_channel: no keys received as parameter");
        rc = DK_ERROR_BAD_PARAM;
        goto catch_error;
    }
// #endif

    // send INITIALIZE UPDATE APDU
    rc = generate_initialize_update_cpdu(&ctx->scp_ctx, SCP03_INIT_UPDATE_CLA_CHANNEL_0, kvn, byte_cpdu, &byte_cpdu_len);
    if (rc) {
        DK_LOG_ERR("error generate_initialize_update_cpdu()");
        goto catch_error;
    }

    rc = transmit(byte_cpdu, byte_cpdu_len, &rpdu, &ctx->channel);
    if (rc) {
        DK_LOG_ERR("error transmit init update: %x", rc);
        goto catch_error;
    }
    if (RPDU_MAX_SIZE < rpdu.len) {
        goto catch_error;
    }
    rc = rpdu_to_barray(byte_rpdu, &byte_rpdu_len, &rpdu);
    if (rc) {
        DK_LOG_ERR("rpdu2barray(): error");
        goto catch_error;
    }

    rc = handle_initialize_update_rpdu(&ctx->scp_ctx, byte_rpdu, byte_rpdu_len);
    if (rc) {
        DK_LOG_ERR("error handle_initialize_update_rpdu()");
        goto catch_error;
    }

    // TODO: Key info is the KVN and other informations, perhaps there is
    // a way to obtain this from the applet?
    // byte[] keyInfo = getKeyInformation();
    // if (SCP03_ID != keyInfo[INDEX_SCP_ID]) {
    //     throw new ScpException("wrong SCP id");
    // }

    // send EXTERNAL AUTH APDU
    rpdu_clear(&rpdu);
    barray_clear(byte_cpdu, byte_cpdu_len);

    rc = gen_apdu_scp_03_external_authenticate(&ctx->scp_ctx, byte_cpdu, &byte_cpdu_len, wrapped_apdu, &wrapped_apdu_len, SCP03_EXT_AUTHENTICATE_CLA_CHANNEL_0);
    if (rc) {
        DK_LOG_ERR("error gen_apdu_scp_03_external_authenticate()");
        goto catch_error;
    }

    rc = transmit(wrapped_apdu, wrapped_apdu_len, &rpdu, &ctx->channel);
    if (rc) {
        DK_LOG_ERR("error transmit external auth");
        goto catch_error;
    }

    barray_clear(byte_rpdu, byte_rpdu_len);
    byte_rpdu_len = RPDU_MAX_SIZE;

    if (RPDU_MAX_SIZE < rpdu.len) {
        goto catch_error;
    }
    rc = rpdu_to_barray(byte_rpdu, &byte_rpdu_len, &rpdu);
    if (rc) {
        DK_LOG_ERR("rpdu2barray(): error");
        goto catch_error;
    }
    rc = handle_external_authenticate_rpdu(byte_rpdu, &byte_rpdu_len);
    if (rc) {
        DK_LOG_ERR("error ()");
        goto catch_error;
    }

    ctx->scp_ctx.security_level |= SCP03_SEC_AUTHENTICATED;
    
    // TODO all handle() functions should have input validator
    // parseConfiguration(mCurrentSecurityLevel);

    if (is_cdecryption_on(ctx->scp_ctx.security_level) && is_cmac_on(ctx->scp_ctx.security_level)) {
        ctx->scp_ctx.encryption_counter = 1;
    }

    rc = RET_TL_TZ_DIGITALKEY_OK;
    goto success;

catch_error:
    close_channel(&ctx->channel);

success:
    if (rpdu.pdata) dk_free(rpdu.pdata);

    return rc;
}
