/*
@file app_service.c
*/

#include "grdm_common.h"
#include "grdm_app.h"

#include "IICCCGrdmROTCred_invoke.h"
#include "IICCCGrdmBLCred_invoke.h"
#include "IICCCGrdmICCCCred_invoke.h"
#include "IICCCGrdmDefaultCred_invoke.h"

#include "CICCCGrdmROTCred.h"
#include "CICCCGrdmBLCred.h"
#include "CICCCGrdmICCCCred.h"
#include "CICCCGrdmDefaultCred.h"

#include "CICCCGrdmROTCred_open.h"
#include "CICCCGrdmBLCred_open.h"
#include "CICCCGrdmICCCCred_open.h"
#include "CICCCGrdmDefaultCred_open.h"
#include "object.h"

#include "app_main.h"
#include "app_service.h"
#include "app_credential_rot.h"
#include "app_credential_bl.h"
#include "app_credential_iccc.h"
#include "app_credential_default.h"
#include "app_allowlist.h"

#include "tz_iccc_grdm_api.h"

/* DEBUG */
#if DEBUG_ICCC
// dump memory
void dbg_dump(uint8_t * data, uint32_t data_len)
{
    uint32_t i;
    char buf[50] = { 0 };
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : dump data with len : %d", TAG, data_len);
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : ===============================================", TAG);

    for (i = 0; i < data_len; i++) {
        sprintf(buf + 3 * (i % 16), "%02X ", *(data + i));
        if (i % 16 == 15) {
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s", buf);
            memset(buf, 0, sizeof(buf));
        }
    }
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s", buf);
}

// print log for os version and patch level
void getStringValueFromROT(uint32_t type, uint32_t value)
{
    char buffer[256];
    switch (type) {
        case OSVERSION_FLAG:
            sprintf(buffer, "%d.%d.%d", ((value >> 14) & 0x7F), ((value >> 7) & 0x7F), (value & 0x7F));
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : os_version %s", TAG, buffer);
            break;
        case PATCHMONTHYEAR_FLAG:
            sprintf(buffer, "%d.%d", (value & 0x0F), (value >> 4));
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : patch_month_year %s", TAG, buffer);
            break;
        case BOOTPATCHLEVEL_FLAG:
            sprintf(buffer, "%d.%d.%d", ((0x7F & (value >> 4)) + 2000), (0x0F & value), (0x1F & (value >> 11)));
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : boot_patch_level %s", TAG, buffer);
            break;
        case VENDORPATCHLEVEL_FLAG:
            sprintf(buffer, "%d.%d.%d", ((0x7F & (value >> 4)) + 2000), (0x0F & value), (0x1F & (value >> 11)));
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : vendor_patch_level %s", TAG, buffer);
            break;
        default:
            break;
    }
}
#endif
/* DEBUG */

/* ICCC GRDM Service */
// Null pointer global for use to alleviate build warnings seen when
// intentionally using a null pointer. Static assignment and use will result in a compiler warning.
void *null_ptr = NULL;

static inline int32_t CICCCGrdmROTCred_getCred(void *cxt, uint32_t *ret, uint32_t *value, size_t value_len, size_t *value_outLen, 
                                                          const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmROTCred_getCred()", TAG);

    // cmd allowlist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_permission(ta_name, caller_ta_name_size, CICCCGrdmROTCred_UID);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command permission failed to TA %s on execute command %d, error %d", TAG, ta_name, CICCCGrdmROTCred_UID, *ret);
        goto error;
    }

    *ret = grdm_ICCC_get_ROT_credential(value);

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmROTCred_getCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmBLCred_getCred(void *cxt, uint32_t *ret, uint32_t *value, size_t value_len, size_t *value_outLen, 
                                                         const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmBLCred_getCred()", TAG);

    // cmd allowlist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_permission(ta_name, caller_ta_name_size, CICCCGrdmBLCred_UID);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command permission failed to TA %s on execute command %d, error %d", TAG, ta_name, CICCCGrdmBLCred_UID, *ret);
        goto error;
    }

    *ret = grdm_ICCC_get_BL_credential(value);

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmBLCred_getCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmICCCCred_getCred(void *cxt, uint32_t *ret, uint32_t type, uint32_t *value, size_t value_len, size_t *value_outLen, 
                                                           const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmICCCCred_getCred()", TAG);

    // cmd/type allowlist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_type_permission(ta_name, caller_ta_name_size, CICCCGrdmICCCCred_UID, type);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command type permission failed to TA %s on execute command %d on type %d, error %d", TAG, ta_name, CICCCGrdmICCCCred_UID, type, *ret);
        goto error;
    }

    // get credential index with type flag
    uint8_t credential_index = type_to_credential_map(type);
#if DEBUG_ICCC
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : credential_index %d", TAG, credential_index);
#endif
    switch (credential_index) {
        case ICCC_CREDENTIAL_1:
            *ret = grdm_ICCC_get_ICCC_credential_1(type, value);
            break;
        case ICCC_CREDENTIAL_2:
            *ret = grdm_ICCC_get_ICCC_credential_2(type, value);
            break;
        default:
            *ret = TZ_GRDM_ICCC_ERROR_NOT_SUPPORT;
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check credential index failed on execute command %d on type %d", TAG, CICCCGrdmICCCCred_UID, type);
            break;
    }

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmICCCCred_getCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmICCCCred_storeCred(void *cxt, uint32_t *ret, uint32_t type, const uint32_t *value, size_t value_len,
                                                             const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmICCCCred_storeCred()", TAG);

    // cmd/type allowlist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_type_permission(ta_name, caller_ta_name_size, CICCCGrdmICCCCred_UID, type);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command type permission failed to TA %s on execute command %d on type %d, error %d", TAG, ta_name, CICCCGrdmICCCCred_UID, type, *ret);
        goto error;
    }

    // get credential index with type flag
    uint8_t credential_index = type_to_credential_map(type);
#if DEBUG_ICCC
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : credential_index %d", TAG, credential_index);
#endif
    switch (credential_index) {
        case ICCC_CREDENTIAL_1:
            *ret = grdm_ICCC_store_ICCC_credential_1(type, (uint32_t*)value);
            break;
        case ICCC_CREDENTIAL_2:
            *ret = grdm_ICCC_store_ICCC_credential_2(type, (uint32_t*)value);
            break;
        default:
            *ret = TZ_GRDM_ICCC_ERROR_NOT_SUPPORT;
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check credential index failed on execute command %d on type %d", TAG, CICCCGrdmICCCCred_UID, type);
            break;
    }

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmICCCCred_storeCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmICCCCred_deleteCred(void *cxt, uint32_t *ret, uint32_t type,
                                                              const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmICCCCred_deleteCred()", TAG);

    // cmd/type allowlist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_type_permission(ta_name, caller_ta_name_size, CICCCGrdmICCCCred_UID, type);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command type permission failed to TA %s on execute command %d on type %d, error %d", TAG, ta_name, CICCCGrdmICCCCred_UID, type, *ret);
        goto error;
    }

    // get credential index with type flag
    uint8_t credential_index = type_to_credential_map(type);
#if DEBUG_ICCC
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : credential_index %d", TAG, credential_index);
#endif
    switch (credential_index) {
        case ICCC_CREDENTIAL_1:
            *ret = grdm_ICCC_delete_ICCC_credential_1(type);
            break;
        case ICCC_CREDENTIAL_2:
            *ret = grdm_ICCC_delete_ICCC_credential_2(type);
            break;
        default:
            *ret = TZ_GRDM_ICCC_ERROR_NOT_SUPPORT;
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check credential index failed on execute command %d on type %d", TAG, CICCCGrdmICCCCred_UID, type);
            break;
    }

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmICCCCred_deleteCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmDefaultCred_getROTCred(void *cxt, uint32_t *ret, uint32_t *value, size_t value_len, size_t *value_outLen, 
                                                                 const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmDefaultCred_getROTCred()", TAG);

    // cmd whitelist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_permission(ta_name, caller_ta_name_size, CICCCGrdmDefaultCred_UID);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command permission failed to TA %s on execute command %d, error %d", TAG, ta_name, CICCCGrdmDefaultCred_UID, *ret);
        goto error;
    }

    *ret = grdm_ICCC_get_default_ROT_credential(value);

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmDefaultCred_getROTCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmDefaultCred_getBLCred(void *cxt, uint32_t *ret, uint32_t *value, size_t value_len, size_t *value_outLen, 
                                                                const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmDefaultCred_getBLCred()", TAG);

    // cmd whitelist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_permission(ta_name, caller_ta_name_size, CICCCGrdmDefaultCred_UID);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command permission failed to TA %s on execute command %d, error %d", TAG, ta_name, CICCCGrdmDefaultCred_UID, *ret);
        goto error;
    }

    *ret = grdm_ICCC_get_default_BL_credential(value);

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmDefaultCred_getBLCred() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

static inline int32_t CICCCGrdmDefaultCred_getStat(void *cxt, uint32_t *ret, uint32_t *value, size_t value_len, size_t *value_outLen, 
                                                              const void *caller_ta_name, uint32_t caller_ta_name_size)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : Starting CICCCGrdmDefaultCred_getStat()", TAG);

    // cmd whitelist check
    char ta_name[32] = {0, };
    strncpy(ta_name, caller_ta_name, caller_ta_name_size);
    *ret = check_ta_cmd_permission(ta_name, caller_ta_name_size, CICCCGrdmDefaultCred_UID);
    if (*ret != TZ_GRDM_ICCC_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : check command permission failed to TA %s on execute command %d, error %d", TAG, ta_name, CICCCGrdmDefaultCred_UID, *ret);
        goto error;
    }

    *ret = grdm_ICCC_get_device_ROT_status(value);

    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmDefaultCred_getStat() grdm_ret = %d", TAG, *ret);

error:
    return Object_OK;
}

// This implementation does not require a context record, so `retain` and `release` are no-ops.
#define CICCCGrdmROTCred_release(ctx)   Object_OK
#define CICCCGrdmROTCred_retain(ctx)    Object_OK
#define CICCCGrdmBLCred_release(ctx)   Object_OK
#define CICCCGrdmBLCred_retain(ctx)    Object_OK
#define CICCCGrdmICCCCred_release(ctx)   Object_OK
#define CICCCGrdmICCCCred_retain(ctx)    Object_OK
#define CICCCGrdmDefaultCred_release(ctx)   Object_OK
#define CICCCGrdmDefaultCred_retain(ctx)    Object_OK

static IICCCGrdmROTCred_DEFINE_INVOKE(CICCCGrdmROTCred_invoke, CICCCGrdmROTCred_, void*)
static IICCCGrdmBLCred_DEFINE_INVOKE(CICCCGrdmBLCred_invoke, CICCCGrdmBLCred_, void*)
static IICCCGrdmICCCCred_DEFINE_INVOKE(CICCCGrdmICCCCred_invoke, CICCCGrdmICCCCred_, void*)
static IICCCGrdmDefaultCred_DEFINE_INVOKE(CICCCGrdmDefaultCred_invoke, CICCCGrdmDefaultCred_, void*)

int32_t CICCCGrdmROTCred_open(Object cred, Object *objOut)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmROTCred_open()", TAG);

    *objOut = (Object) { CICCCGrdmROTCred_invoke, NULL };
    return Object_OK;
}

int32_t CICCCGrdmBLCred_open(Object cred, Object *objOut)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmBLCred_open()", TAG);

    *objOut = (Object) { CICCCGrdmBLCred_invoke, NULL };
    return Object_OK;
}

int32_t CICCCGrdmICCCCred_open(Object cred, Object *objOut)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmICCCCred_open()", TAG);

    *objOut = (Object) { CICCCGrdmICCCCred_invoke, NULL };
    return Object_OK;
}

int32_t CICCCGrdmDefaultCred_open(Object cred, Object *objOut)
{
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : CICCCGrdmDefaultCred_open()", TAG);

    *objOut = (Object) { CICCCGrdmDefaultCred_invoke, NULL };
    return Object_OK;
}
/* ICCC GRDM Service */

GRDM_RESULT grdm_ICCC_auth_key_init(uint8_t* auth_key)
{
    GRDM_RESULT grdm_ret = GRDM_NO_ERROR; // grdm APIs
    uint32_t ret; // qsee APIs

    uint8_t ICCC_keyflag = 0;

    uint8_t salt[SALT_SIZE] = {0x0,};
    uint8_t digest[SHA256_DIGEST_LENGTH] = {0x0,};
    uint32_t serial_num = 0;

    uint8_t chipid[CHIPID_SIZE] = {0x0,};
    uint32_t chipid_len = CHIPID_SIZE;
    uint8_t fwVersion[FWVERSION_SIZE] = {0x0,};
    uint32_t fwVersion_len = FWVERSION_SIZE;
    uint8_t grdm_status[GRDM_STATUS_SIZE] = {0x0,};

    char auth_key_context[] = {"GRDM ICCC HW Crypto key derived from SHK"};

    uint8_t domain_index = DOMAIN_INDEX_ICCC;

    // GuardianM_API_Specification_v1.00.1 - 4.4.1 grdm_getinfo
    grdm_ret = grdm_getinfo(grdm_status, chipid, &chipid_len, fwVersion, &fwVersion_len);
    if (grdm_ret != GRDM_NO_ERROR) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_getinfo error : %d\n", TAG, grdm_ret);
        return grdm_ret;
    }
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_getinfo\n", TAG);

#if DEBUG_ICCC
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_status : ", TAG);
    DBG_DUMP(grdm_status, sizeof(grdm_status));
#endif
    if (chipid_len == 0) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : chipid is empty\n", TAG);
    } else {
#if DEBUG_ICCC
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : chipid : ", TAG);
        DBG_DUMP(chipid, chipid_len);
#endif
    }
    if (fwVersion_len == 0) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : fwVersion is empty\n", TAG);
    } else {
#if DEBUG_ICCC
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : fwVersion : ", TAG);
        DBG_DUMP(fwVersion, fwVersion_len);
#endif
    }

    // Get keyflag to check existing auth_key
    // GuardianM_API_Specification_v1.00.1 - 4.4.3 grdm_domain_putkey_init
    // BL auth_key should be injected in advance (grdm_BL_putkey)
    grdm_ret = grdm_domain_putkey_init(domain_index, &ICCC_keyflag);
    if (grdm_ret != GRDM_NO_ERROR) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_domain_putkey_init error : %d\n", TAG, grdm_ret);
        return grdm_ret;
    }
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_domain_putkey_init\n", TAG);

    // Create auth_key (TZ_APP_NAME + qsee serial number + grdm chip id)
    // TO-DO : Need to review the safety of auth_key 
    memset(salt, 0xFF, SALT_SIZE);
    // TZ_APP_NAME can be changed to device unique value later (refer to bl_secure_info->devInfoBuff)
    memcpy(salt, (uint8_t *)TZ_APP_NAME, strlen(TZ_APP_NAME));
     // QTEE Version 5.0 User Guide - Reads the serial number from the product test engineering (PTE) chain.
    serial_num = qsee_read_serial_num();
#if DEBUG_ICCC
    //qsee_log(QSEE_LOG_MSG_DEBUG, "%s : TZ_APP_NAME %s", TAG, TZ_APP_NAME);
    //qsee_log(QSEE_LOG_MSG_DEBUG, "%s : serial_num %#x", TAG, serial_num);
#endif
    memcpy(salt + strlen(TZ_APP_NAME), (uint8_t *)&serial_num, sizeof(serial_num));
    // Use chipid which is retrived from grdm_getinfo
    memcpy(salt + strlen(TZ_APP_NAME) + sizeof(serial_num), chipid, chipid_len);
#if DEBUG_ICCC
    //qsee_log(QSEE_LOG_MSG_DEBUG, "%s : salt : ", TAG);
    //DBG_DUMP(salt, sizeof(salt));
#endif
    // sha256 digest
    // QTEE Version 5.0 User Guide - Creates a message digest hash using the specified algorithm.
    ret = qsee_hash(QSEE_HASH_SHA256, salt, SALT_SIZE, digest, SHA256_DIGEST_LENGTH);
    if (ret != QSEE_HASH_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : qsee_hash error : %d\n", TAG, ret);
        return ret;
    }
#if DEBUG_ICCC
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : qsee_hash", TAG);
    //qsee_log(QSEE_LOG_MSG_DEBUG, "%s : digest : ", TAG);
    //DBG_DUMP(digest, SHA256_DIGEST_LENGTH);
#endif

    // auth_key derivation using digest and context
    // QTEE Version 5.0 User Guide - Key derivation function (KDF) key derivation algorithm.
    ret = qsee_kdf(NULL, 32, digest, SHA256_DIGEST_LENGTH,
                   auth_key_context, strlen(auth_key_context), auth_key, AUTH_KEY_SIZE);
    if (ret != QSEE_KDF_SUCCESS) {
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : qsee_kdf error : %d\n", TAG, ret);
        return ret;
    }
#if DEBUG_ICCC
    qsee_log(QSEE_LOG_MSG_DEBUG, "%s : qsee_kdf", TAG);
    //qsee_log(QSEE_LOG_MSG_DEBUG, "%s : auth_key : ", TAG);
    //DBG_DUMP(auth_key, AUTH_KEY_SIZE);
#endif

    if (ICCC_keyflag == 0) { // If auth_key exists, LSB set to 1, and for else, LSB set to 0.
        // put auth_key to GRDM
        // GuardianM_API_Specification_v1.00.1 - 4.4.5 grdm_ICCC_putKey
        grdm_ret = grdm_ICCC_putKey(auth_key, AUTH_KEY_SIZE);
        if (grdm_ret != 0) {
            qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_ICCC_putKey error : %d\n", TAG, grdm_ret);
        }
#if DEBUG_ICCC
        qsee_log(QSEE_LOG_MSG_DEBUG, "%s : grdm_ICCC_putKey\n", TAG);
#endif
    }

    return grdm_ret;
}
