#include "dk_cmd_initialize_update.h"
#include "dk_constants.h"
#include "dk_constants.h"
#include "dk_log.h"

DK_Result process_initialize_update()
{
    DK_Result rc;

    rc = DK_SUCCESS;

catch_error:
    return rc;
}

DK_Result generate_initialize_update_cpdu(scp_context *ctx, byte cla, byte key_version_number, byte* cpdu, size_t* cpdu_len)
{
    DK_Result rc;

    if (cla < SCP03_INIT_UPDATE_CLA_CHANNEL_0 || cla > SCP03_INIT_UPDATE_CLA_CHANNEL_3) {
        rc = DK_ERROR_BAD_PARAM;
        goto catch_error;
    }

    scp_generate_host_challenge(ctx);

    cpdu[APDU_OFFSET_CLA] = cla;
    cpdu[APDU_OFFSET_INS] = SCP03_INIT_UPDATE_INS;
    cpdu[APDU_OFFSET_P1] = key_version_number;
    cpdu[APDU_OFFSET_P2] = 0x00U;
    cpdu[APDU_OFFSET_LC] = SCP03_INIT_UPDATE_LC;
    dk_memcpy(cpdu + APDU_OFFSET_CDATA, ctx->host_challenge, SCP03_INIT_UPDATE_LC);
    cpdu[APDU_OFFSET_CDATA + SCP03_INIT_UPDATE_LC] = SCP03_INIT_UPDATE_LE;

    *cpdu_len = APDU_OFFSET_CDATA + SCP03_INIT_UPDATE_LC + 1;

    rc = DK_SUCCESS;

catch_error:
    return rc;
}

#ifdef DK_DEBUG
static byte __get_security_level_from_i(byte i)
#else
static byte get_security_level_from_i(byte i)
#endif
{
    if ((i & SCP_PARAM_i_RMAC_RENCRYPTION) == SCP_PARAM_i_RMAC_RENCRYPTION)
        return SCP_EXTERNAL_AUTH_CMAC_CDECRYPT_RMAC_RENCRYPT;
    else if ((i & SCP_PARAM_i_RMAC_NO_RENCRYPTION) == SCP_PARAM_i_RMAC_NO_RENCRYPTION)
        return SCP_EXTERNAL_AUTH_CMAC_CDECRYPT_RMAC;
    else
        return SCP_EXTERNAL_AUTH_CMAC_CDECRYPT;
}

#ifdef DK_DEBUG
static byte get_security_level_from_i(byte i)
{
    if (test_get_security_level_from_i) return test_get_security_level_from_i(i);
    return __get_security_level_from_i(i);
}

byte mock_get_security_level_from_i(byte i)
{
    return SCP03_SEC_C_MAC;
}

byte mock_get_security_level_from_i_full(byte i)
{
    return SCP03_SEC_C_MAC | SCP03_SEC_C_DECRYPTION | SCP03_SEC_AUTHENTICATED;
}
#endif

DK_Result handle_initialize_update_rpdu(scp_context *ctx, byte *rpdu, size_t rpdu_len)
{
    DK_Result rc;
    uint16_t sw = 0;
    size_t data_len = 0;
    size_t cursor = 0;

    byte key_diver_data[SCP03_INIT_UPDATE_KEY_DIV_DATA_LEN] = {0};
    byte sequence_counter[SCP03_INIT_UPDATE_SEQUENCE_COUNTER_LEN] = {0};
    byte security_level;

    if (rpdu_len < RPDU_MIN_SIZE || rpdu_len > RPDU_MAX_SIZE) {
        DK_LOG_ERR("handle_initialize_update_rpdu: bad format");
        rc = DK_ERROR_BAD_FORMAT;
        goto catch_error;
    }
    
    data_len = rpdu_len - APDU_SW_SIZE;
    dk_memcpy(&sw, rpdu + data_len, sizeof(sw));

    if (sw != RPDU_STATUS_SUCCESS && sw != RPDU_STATUS_SUCCESS >> 8) {
        DK_LOG_ERR("handle_initialize_update_rpdu: bad status");
        rc = DK_ERROR_GENERIC + sw;
        goto catch_error;
    }

    cursor = 0;
    dk_memcpy(key_diver_data, rpdu + cursor, SCP03_INIT_UPDATE_KEY_DIV_DATA_LEN);
    cursor += SCP03_INIT_UPDATE_KEY_DIV_DATA_LEN;
    dk_memcpy(ctx->key_information, rpdu + cursor, KEY_INFO_SIZE);
    cursor += SCP03_INIT_UPDATE_KEY_INFO_LEN;
    dk_memcpy(ctx->card_challenge, rpdu + cursor, CHALLENGE_SIZE);
    cursor += CHALLENGE_SIZE;
    dk_memcpy(ctx->card_cryptogram, rpdu + cursor, CRYPTOGRAM_SIZE);
    cursor += CRYPTOGRAM_SIZE;
    
    ctx->security_level = get_security_level_from_i(ctx->key_information[SCP_PARAM_i_INDEX]);

    scp_generate_session_keys(ctx);
    scp_generate_host_cryptogram(ctx, ctx->host_cryptogram);

    rc = scp_verify_card_cryptogram(ctx, ctx->card_cryptogram);
    if (rc) {
        DK_LOG_ERR("handle_initialize_update_rpdu: bad cryptogram");
        goto catch_error;
    }

    // Since the presence of the sequence counter byte array is
    // irrelevant for the state and operation of the OCE, its
    // presence in the response will be ignored for now.

    // if (cursor < data_len) {
    //     dk_memcpy(sequence_counter, rpdu + cursor, SCP03_INIT_UPDATE_SEQUENCE_COUNTER_LEN);
    //     cursor += SCP03_INIT_UPDATE_SEQUENCE_COUNTER_LEN;
    // }

    rc = DK_SUCCESS;

catch_error:
    return rc;
}
