/*
@file app_service.c
*/

#include <stdio.h>
#include <string.h>

#include <tee_internal_api.h>
#include <tees_extension.h>
#include <tees_kdf.h>

#include "grdm_common.h"
#include "grdm_app.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_allowlist.h"

#include "icccOperations_grdm.h"

/* DEBUG */
#if DEBUG_ICCC
// dump memory
void dbg_dump(uint8_t * data, uint32_t data_len)
{
    uint32_t i;
    char buf[50] = { 0 };
    printf(TAG "dump data with len : %d \n", data_len);
    printf(TAG "=============================================== \n");

    for (i = 0; i < data_len; i++) {
        sprintf(buf + 3 * (i % 16), "%02X ", *(data + i));
        if (i % 16 == 15) {
            printf("%s", buf);
            TEE_MemFill(buf, 0, sizeof(buf));
        }
    }
    printf("%s", buf);
    printf("\n");
}

// 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));
            printf(TAG "os_version %s \n", buffer);
            break;
        case PATCHMONTHYEAR_FLAG:
            sprintf(buffer, "%d.%d", (value & 0x0F), (value >> 4));
            printf(TAG "patch_month_year %s \n", buffer);
            break;
        case BOOTPATCHLEVEL_FLAG:
            sprintf(buffer, "%d.%d.%d", ((0x7F & (value >> 4)) + 2000), (0x0F & value), (0x1F & (value >> 11)));
            printf(TAG "boot_patch_level %s \n", buffer);
            break;
        case VENDORPATCHLEVEL_FLAG:
            sprintf(buffer, "%d.%d.%d", ((0x7F & (value >> 4)) + 2000), (0x0F & value), (0x1F & (value >> 11)));
            printf(TAG "vendor_patch_level %s \n", buffer);
            break;
        default:
            break;
    }
}
#endif
/* DEBUG */

TEE_Result digest_sha256(uint8_t* messageData, uint32_t messageLen, uint8_t* digest, uint32_t* pDigestLen)
{
    TEE_Result ret = TEE_SUCCESS;
    TEE_OperationHandle opHandle = TEE_HANDLE_NULL;

    printf(TAG "digest_sha256 \n");

    if (messageData == NULL || messageLen == 0 || digest == NULL || pDigestLen == NULL) {
        printf(TAG "invalid input/output data buffer \n");
        return TZ_GRDM_ICCC_FAILURE;
    }

    if (*pDigestLen < SHA256_DIGEST_LENGTH) {
        printf(TAG "pDigestLen < SHA256_DIGEST_LENGTH \n");
        return TZ_GRDM_ICCC_FAILURE;
    }

    ret = TEE_AllocateOperation(&opHandle, TEE_ALG_SHA256, TEE_MODE_DIGEST, TEE_MAX_KEY_SIZE);
    if (ret != TEE_SUCCESS) {
        printf(TAG "TEE_AllocateOperation failed with ret = %d \n", ret);
        goto exit;
    }

    ret = TEE_DigestDoFinal(opHandle, messageData, messageLen, digest, pDigestLen);
    if (ret != TEE_SUCCESS) {
        printf(TAG "TEE_DigestDoFinal failed with ret = %d \n", ret);
        goto exit;
    }

    // TEE_DigestDoFinal() has a bug with which pDigestLen is not returned correctly, so pDigestLen is set manually.
    printf(TAG "TEE_DigestDoFinal() success digest len = %d \n", *pDigestLen);
    *pDigestLen = SHA256_DIGEST_LENGTH;

exit:
    if (opHandle != TEE_HANDLE_NULL) {
        TEE_FreeOperation(opHandle);
    }

    return ret;
}

TEE_Result derive_key(uint8_t* label, uint32_t labelLen, uint8_t* salt, uint32_t saltLen, uint8_t* encKey, uint32_t encKeyLen)
{
    TEE_Result ret = TEE_SUCCESS;
    TEE_ObjectHandle opHandle = TEE_HANDLE_NULL;
    uint32_t byte_size = AUTH_KEY_SIZE;
    const uint32_t bit_size = 8 * byte_size;

    printf(TAG "derive_key \n");

    if (salt == NULL || saltLen == 0) {
        printf(TAG "invalid salt \n");
        return TZ_GRDM_ICCC_FAILURE;
    }

    if (encKey == NULL || encKeyLen == 0 || encKeyLen != AUTH_KEY_SIZE) {
        printf(TAG "invalid key buffer \n");
        return TZ_GRDM_ICCC_FAILURE;
    }

    if (label == NULL) {
        printf(TAG "label is NULL, labelLen is 0 \n");
        labelLen = 0;
    }

    ret = TEE_AllocateTransientObject(TEE_TYPE_GENERIC_SECRET, bit_size, &opHandle);
    if (ret == TEE_SUCCESS) {
        printf(TAG "TEE_AllocateTransientObject success and encKeyLen is = %d \n", encKeyLen);
    } else {
        printf(TAG "TEE_AllocateTransientObject failed with ret = %d \n", ret);
        goto exit;
    }

    ret = TEES_DeriveKeyKDF(label, labelLen, salt, saltLen, encKeyLen, opHandle);
    if (ret != TEE_SUCCESS) {
        printf(TAG "TEES_DeriveKeyKDF failed with ret = %d \n", ret);
        goto exit;
    }

    ret = TEE_GetObjectBufferAttribute(opHandle, TEE_ATTR_SECRET_VALUE, encKey, &byte_size);
    if (ret != TEE_SUCCESS) {
        printf(TAG "TEE_GetObjectBufferAttribute failed with ret = %d \n", ret);
        goto exit;
    }

exit:
    if (opHandle != TEE_HANDLE_NULL) {
        TEE_CloseObject(opHandle);
    }

    return ret;
}

GRDM_RESULT grdm_ICCC_auth_key_init(uint8_t* auth_key)
{
    GRDM_RESULT grdm_ret = GRDM_NO_ERROR; // grdm APIs
    uint32_t ret; // teegris APIs

    uint8_t ICCC_keyflag = 0;

    TEE_UUID ICCC_UUID = {0,0,0,{0,0,0x49,0x63,0x43,0x47,0x52,0x44}};
    uint8_t salt[SALT_SIZE] = {0x0, };
    uint8_t digest[SHA256_DIGEST_LENGTH] = {0x0, };
    uint32_t digestLen = sizeof(digest);
    TEE_UUID unique_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) {
        printf(TAG "grdm_getinfo error : %d \n", grdm_ret);
        return grdm_ret;
    }
    printf(TAG "grdm_getinfo \n");

#if DEBUG_ICCC
    printf(TAG "grdm_status : \n");
    DBG_DUMP(grdm_status, sizeof(grdm_status));
#endif
    if (chipid_len == 0) {
        printf(TAG "chipid is empty \n");
    } else {
#if DEBUG_ICCC
        printf(TAG "chipid : \n");
        DBG_DUMP(chipid, chipid_len);
#endif
    }
    if (fwVersion_len == 0) {
        printf(TAG "fwVersion is empty \n");
    } else {
#if DEBUG_ICCC
        printf(TAG "fwVersion : \n");
        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) {
        printf(TAG "grdm_domain_putkey_init error : %d \n", grdm_ret);
        return grdm_ret;
    }
    printf(TAG "grdm_domain_putkey_init : %d \n", ICCC_keyflag);

    // Create auth_key (TZ_APP_UUID + gpd.tee.deviceID + grdm chip id)
    // TO-DO : Need to review the safety of auth_key
    TEE_MemFill(salt, 0xFF, SALT_SIZE);
#if DEBUG_ICCC
    // ICCC_UUID (16 bytes) - 00 00 00 00 00 00 00 00 00 00 49 63 43 47 52 44
    printf(TAG "ICCC_UUID.timeLow %08X \n", ICCC_UUID.timeLow);
    printf(TAG "ICCC_UUID.timeMid %04X \n", ICCC_UUID.timeMid);
    printf(TAG "ICCC_UUID.timeHiAndVersion %04X \n", ICCC_UUID.timeHiAndVersion);
    printf(TAG "ICCC_UUID.clockSeqAndNode %02X%02X%02X%02X%02X%02X%02X%02X \n", ICCC_UUID.clockSeqAndNode[0],
                                                                                ICCC_UUID.clockSeqAndNode[1],
                                                                                ICCC_UUID.clockSeqAndNode[2],
                                                                                ICCC_UUID.clockSeqAndNode[3],
                                                                                ICCC_UUID.clockSeqAndNode[4],
                                                                                ICCC_UUID.clockSeqAndNode[5],
                                                                                ICCC_UUID.clockSeqAndNode[6],
                                                                                ICCC_UUID.clockSeqAndNode[7]);
#endif
    TEE_MemMove(salt, (uint8_t *)&ICCC_UUID, sizeof(ICCC_UUID));

    // TEE Internal Core API Specification Version 1.1.2.50 (Target v1.2)
    // It is acceptable to derive this device identifier from statistically unique secret or public information, such as a Hardware Unique Key, die identifiers, etc. 
    // However, note that this property is intended to be public and exposed to any software running on the device, not only to Trusted Applications.
    // The derivation SHALL therefore be carefully designed so that it does not compromise secret information.
    // TEE Internal Core API Specification
    ret = TEE_GetPropertyAsUUID(TEE_PROPSET_TEE_IMPLEMENTATION, "gpd.tee.deviceID", &unique_num);
    if (ret != TEE_SUCCESS) {
        printf(TAG "TEE_GetPropertyAsUUID error : %d \n", ret);
        return ret;
    }
#if DEBUG_ICCC
    // TEE_UUID (16 bytes)
    printf(TAG "unique_num.timeLow %08X \n", unique_num.timeLow);
    printf(TAG "unique_num.timeMid %04X \n", unique_num.timeMid);
    printf(TAG "unique_num.timeHiAndVersion %04X \n", unique_num.timeHiAndVersion);
    printf(TAG "unique_num.clockSeqAndNode %02X%02X%02X%02X%02X%02X%02X%02X \n", unique_num.clockSeqAndNode[0],
                                                                                 unique_num.clockSeqAndNode[1],
                                                                                 unique_num.clockSeqAndNode[2],
                                                                                 unique_num.clockSeqAndNode[3],
                                                                                 unique_num.clockSeqAndNode[4],
                                                                                 unique_num.clockSeqAndNode[5],
                                                                                 unique_num.clockSeqAndNode[6],
                                                                                 unique_num.clockSeqAndNode[7]);
#endif
    TEE_MemMove(salt + sizeof(ICCC_UUID), (uint8_t *)&unique_num, sizeof(unique_num));

    // Use chipid which is retrived from grdm_getinfo
#if DEBUG_ICCC
    printf(TAG "chipid : \n");
    DBG_DUMP(chipid, chipid_len);
#endif
    TEE_MemMove(salt + sizeof(ICCC_UUID) + sizeof(unique_num), chipid, chipid_len);

#if DEBUG_ICCC
    printf(TAG "salt : \n");
    DBG_DUMP(salt, sizeof(salt));
#endif

    // sha256 digest
    ret = digest_sha256(salt, SALT_SIZE, digest, &digestLen);
    if (ret != TEE_SUCCESS) {
        printf(TAG "digest_sha256 error : %d \n", ret);
        return ret;
    }
#if DEBUG_ICCC
    printf(TAG "digest : \n");
    DBG_DUMP(digest, SHA256_DIGEST_LENGTH);
#endif

    // auth_key derivation using digest and context
    ret = derive_key(digest, SHA256_DIGEST_LENGTH, (uint8_t *) auth_key_context, strlen(auth_key_context), auth_key, AUTH_KEY_SIZE);
    if (ret != TEE_SUCCESS) {
        printf(TAG "derive_key error : %d \n", ret);
        return ret;
    }
#if DEBUG_ICCC
    printf(TAG "auth_key : \n");
    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) {
            printf(TAG "grdm_ICCC_putKey error : %d \n", grdm_ret);
        }
#if DEBUG_ICCC
        printf(TAG "grdm_ICCC_putKey \n");
#endif
    }

    return grdm_ret;
}
