/**
 * \file gatekeeper_hal.c
**/
#include "hal/gatekeeper_hal.h"
#include "gatekeeper_log.h"
#include "tee_internal_api.h"
#include "tees_kdf.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>

#define GK_FAIL_COUNTER_FILE_NAME "gk_fail_counter"
#define TEES_STORAGE_PRIVATE_EFS        0x80000000  /* Custom constant for using efs partition on trusted storage */

#ifdef USE_EFS_STORAGE
	#define STORAGE_TYPE TEES_STORAGE_PRIVATE_EFS
#else
	#define STORAGE_TYPE TEE_STORAGE_PRIVATE
#endif

int gk_store_fail_counter(uint32_t uid,
                          const void *trusted_data_buffer,
                          size_t trusted_data_size)
{
    TEE_Result res;
    TEE_ObjectHandle obj;
    char object_name[30] = {0,};

    sprintf(object_name, "%s%u", GK_FAIL_COUNTER_FILE_NAME, uid);
    LOGD("[store]storage name is %s. length is %zd\n", object_name, strlen(object_name));
    res = TEE_OpenPersistentObject(STORAGE_TYPE,
                                   object_name,
                                   strlen(object_name),
                                   TEE_DATA_FLAG_ACCESS_READ |
                                   TEE_DATA_FLAG_ACCESS_WRITE |
                                   TEE_DATA_FLAG_ACCESS_WRITE_META,
                                   &obj);
    if (res == TEE_ERROR_ITEM_NOT_FOUND) {
        LOGD("[store]creating fail counter file\n");

        res = TEE_CreatePersistentObject(STORAGE_TYPE,
                                         object_name,
                                         strlen(object_name),
                                         TEE_DATA_FLAG_ACCESS_READ |
                                         TEE_DATA_FLAG_ACCESS_WRITE |
                                         TEE_DATA_FLAG_ACCESS_WRITE_META |
                                         TEE_DATA_FLAG_EXCLUSIVE,
                                         TEE_HANDLE_NULL, "", 0,
                                         &obj);
        if (res != TEE_SUCCESS) {
            LOGE("[store]failed to create fail counter file res=%x\n", res);
            return -ENOENT;
        }
    }
    else if (res != TEE_SUCCESS)
    {
        LOGE("[store]failed to open fail counter file res=%x\n", res);
        return -EIO;
    }

    res = TEE_SeekObjectData(obj, 0, TEE_DATA_SEEK_SET);
    if (res != TEE_SUCCESS) {
        TEE_CloseObject(obj);
        LOGE("[store]failed to seek fail counter file res=%x\n", res);
        return -EIO;
    }

    res = TEE_WriteObjectData(obj,
                              trusted_data_buffer,
                              trusted_data_size);
    if (res != TEE_SUCCESS) {
        LOGE("[store]failed to write fail counter file res=%x\n", res);
        if (res == TEE_ERROR_CORRUPT_OBJECT || res == TEE_ERROR_CORRUPT_OBJECT_2) {
        /* we do not call TEE_CloseObject,
         * because corrupted data will be closed and deleted in Trusted Storage API */
            return -EBADFD;
        }
        TEE_CloseObject(obj);
        return -EIO;
    }

    TEE_CloseObject(obj);
    return 0;
}

int gk_restore_fail_counter(uint32_t uid,
                            void *trusted_data_buffer,
                            size_t trusted_data_size)
{
    TEE_Result res;
    TEE_ObjectHandle obj;
    uint32_t result_data_size;
    char object_name[30] = {0,};

    sprintf(object_name, "%s%u", GK_FAIL_COUNTER_FILE_NAME, uid);
    LOGD("[restore]storage name is %s. length is %zd\n", object_name, strlen(object_name));
    res = TEE_OpenPersistentObject(STORAGE_TYPE,
                                   object_name,
                                   strlen(object_name),
                                   TEE_DATA_FLAG_ACCESS_READ |
                                   TEE_DATA_FLAG_ACCESS_WRITE |
                                   TEE_DATA_FLAG_ACCESS_WRITE_META,
                                   &obj);
    if (res == TEE_ERROR_ITEM_NOT_FOUND) {
        LOGE("[restore]fail counter file doesn't exist res=%x\n", res);
        return -ENOENT;
    }

    if (res == TEE_ERROR_CORRUPT_OBJECT || res == TEE_ERROR_CORRUPT_OBJECT_2) {
        LOGE("[restore]fail counter file is corrupted. res=%x\n", res);
        return -EBADFD;
    }

    if (res != TEE_SUCCESS)
    {
        LOGE("[restore]failed to open fail counter file res=%x\n", res);
        return -EIO;
    }

    res = TEE_SeekObjectData(obj, 0, TEE_DATA_SEEK_SET);
    if (res != TEE_SUCCESS) {
        TEE_CloseObject(obj);
        LOGE("[restore]failed to seek fail counter file res=%x\n", res);
        return -EIO;
    }

    res = TEE_ReadObjectData(obj,
                             trusted_data_buffer,
                             trusted_data_size,
                             &result_data_size);
    if (res != TEE_SUCCESS) {
        LOGE("[restore]failed to read fail counter file res=%x\n", res);
        if (res == TEE_ERROR_CORRUPT_OBJECT || res == TEE_ERROR_CORRUPT_OBJECT_2) {
        /* we do not call TEE_CloseObject,
         * because corrupted data will be closed and deleted in Trusted Storage API */
            return -EBADFD;
        }
        TEE_CloseObject(obj);
        return -EIO;
    }

    if (result_data_size != trusted_data_size) {
        LOGE("[restore]Read data size mismatch\n");
        goto delete_file;
    }

    TEE_CloseObject(obj);
    return 0;

delete_file:
    res = TEE_CloseAndDeletePersistentObject1(obj);
    if (res != TEE_SUCCESS) {
        LOGE("[restore]failed to close and delete object res=%x\n", res);
    }
    return -EBADFD;
}

void hal_gen_rand(uint8_t *dest, uint32_t len)
{
    TEE_GenerateRandom(dest, len);
}

uint64_t hal_get_time()
{
    TEE_Time time;
    TEE_GetSystemTime(&time);

    return ((uint64_t)time.seconds * 1000ull + time.millis);
}

bool
hal_hmac_sha256(const uint8_t *data, uint32_t data_len,
                   uint8_t *key, uint32_t key_len,
                   uint8_t *buf, uint32_t *dest_len)
{
    TEE_OperationHandle hndl = NULL;
    TEE_ObjectHandle obj_hndl = NULL;
    TEE_Attribute attrs[1];
    TEE_Result rc;

    char IV[32] = {0};

    do{
        rc = TEE_AllocateTransientObject(TEE_TYPE_HMAC_SHA256, key_len * 8, &obj_hndl);
        if (rc) {
            break;
        }

        TEE_InitRefAttribute(&attrs[0], TEE_ATTR_SECRET_VALUE, key, key_len);
        rc = TEE_PopulateTransientObject(obj_hndl, attrs, 1);
        if (rc) {
            break;
        }

        rc = TEE_AllocateOperation(&hndl, TEE_ALG_HMAC_SHA256, TEE_MODE_MAC, key_len * 8);
        if (rc) {
            break;
        }

        rc = TEE_SetOperationKey(hndl, obj_hndl);
        if (rc) {
            break;
        }

        TEE_MACInit(hndl, IV, 32);
        TEE_MACUpdate(hndl, data, 5);
        TEE_MACUpdate(hndl, data + 5, 15);
        rc = TEE_MACComputeFinal(hndl, data + 20, data_len - 20, buf, dest_len);
        if (rc) {
            break;
        }

#ifdef DEBUG
        unsigned int i;
        LOGD("============== Digest  =================");
        for (i = 0; i < *dest_len; i++) { 
            if (i % 16 == 0) printf("\n");
            printf("%02X ", (unsigned int)buf[i]);
        }
        printf("\n");
#endif
        rc = TEE_SUCCESS;
    } while(false);

    if (obj_hndl) {
        TEE_CloseObject(obj_hndl);
    }

    if (hndl) {
        TEE_FreeOperation(hndl);
    }

    if (rc == TEE_SUCCESS) {

        return true;
    } else {

        return false;
    }

}

static TEE_Result gk_compute_sha256(const unsigned char *d, size_t n, unsigned char *md, uint32_t *sizDgstLen)
{
    TEE_OperationHandle sha_hndl = NULL;
    TEE_Result rc;

    rc = TEE_AllocateOperation(&sha_hndl, TEE_ALG_SHA256, TEE_MODE_DIGEST, 0);
    if (TEE_SUCCESS != rc) {
        return rc;
    }

    rc = TEE_DigestDoFinal(sha_hndl, d, n, md, sizDgstLen);
    TEE_FreeOperation(sha_hndl);

    return rc;
}

void clear_signature(uint32_t signature_length, uint8_t *signature)
{
    TEE_MemFill(signature, 0x0, signature_length);
}

bool
hal_pwd_hmac(const uint8_t *password, uint32_t password_length, salt_t salt,
                       uint32_t signature_length, uint8_t *signature)
{
    uint8_t hash[32];
    uint32_t size         = sizeof(hash);
    bool retval         = true;
    uint32_t   out_size = signature_length;
    TEE_Result status;
    TEE_ObjectHandle tobj;

    status = gk_compute_sha256(password, password_length, hash, &size);
    if (TEE_SUCCESS != status)
      return false;
    
    status = TEE_AllocateTransientObject(TEE_TYPE_GENERIC_SECRET,
					signature_length * 8, &tobj);
    if (TEE_SUCCESS != status)
    {
        LOGE("failed to allocate object: %x", status);
        return false;
    }
    
    status = TEES_DeriveKeyKDF(hash,
                               size,
                               (const unsigned char *)&salt,
                               sizeof(salt),
                               signature_length, tobj);
    if (TEE_SUCCESS == status)
    {
        status = TEE_GetObjectBufferAttribute(tobj, TEE_ATTR_SECRET_VALUE, signature, &out_size);
        if (TEE_SUCCESS != status)
        {
            LOGE("failed to get object attribute: %x", status);
            retval = false;
            goto out;
        }
    } else {
        LOGE("failed to derive kdf key: %x", status);
        clear_signature(signature_length, signature);
        retval = false;
        goto out;
    }
    
out:
    TEE_CloseObject(tobj);
    return retval;
}


int hal_mem_cmp(const void *buffer1, const void *buffer2, uint32_t size)
{
    return TEE_MemCompare(buffer1, buffer2, size);
}

void hal_mem_fill(void *buffer, uint32_t  x, uint32_t size)
{
    TEE_MemFill(buffer, x, size);
}

void hal_mem_free(void *buffer)
{
    TEE_Free(buffer);
}

void hal_mem_move(void *dest, const void *src, uint32_t size)
{
    TEE_MemMove(dest, src, size);
}

void *hal_mem_malloc(uint32_t size)
{
    return TEE_Malloc(size, HINT_DONT_FILL_WITH_ZEROS);
}
