/**
 * \file gatekeeper_ta.c
**/

#include "gatekeeper_ta.h"
#include "gatekeeper_common.h"
#include "gatekeeper_records.h"
#include "gatekeeper_log.h"
#include "communication.h"
#include "hal/gatekeeper_hal.h"

#include <tee_internal_api.h>
#include <driver.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

#ifdef PROCA
#include <pa_handler.h>
#include <pa_tz_api.h>
#include <tees_extension.h>
#endif 

#define _ROUND_MASK(a)      ((a) - 1)
#define ROUND_PGUP(x, a)    (((x) + _ROUND_MASK(a)) & ~_ROUND_MASK(a))
#define ARRAY_SIZE(a)       (sizeof(a) / sizeof((a)[0]))
#define HMAC_KEY_SIZE 32
#define UUID_LEN 16

enum cmd_ioctl {
    CMD_IOCTL_GET_HMAC_KEY = 1,
    CMD_IOCTL_MAX
};

static struct usr_drv_info *drv_info = NULL;
static char drv_name[] = "gatekeeper_driver";

static const TEEC_UUID allowed_uuids[] = {
    /* Keymaster TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x4b, 0x45, 0x59, 0x4d, 0x53, 0x54},
    },
    /* Fingerprint TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x46, 0x49, 0x4E, 0x47, 0x45, 0x52},
    },
    /* Fingerprint TA 2 */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x46, 0x49, 0x4E, 0x47, 0x45, 0x02},
    },
    /* Fingerprint TA 3 SOTER */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x54, 0x49, 0x47, 0x45, 0x52, 0x46},
    },
    /* Face recognition TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x53, 0x45, 0x43, 0x5F, 0x46, 0x52},
    },
    /* StrongBox KM TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x53, 0x42, 0x58, 0x50, 0x58, 0x59},
    },
    /* hwvault TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x48, 0x76, 0x41, 0x55, 0x74, 0x57},
    },
#ifdef WINGTECH
    /* Fingerprint (Egis) TA */
    {
        .timeLow = 0x00000000,
		.timeMid = 0x5769,
		.timeHiAndVersion = 0x4e67,
		.clockSeqAndNode = {0x54, 0x4b, 0x46, 0x4e, 0x45, 0x67, 0x69, 0x73},
    },
    /* Fingerprint (Chipone) TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x5769,
	    .timeHiAndVersion = 0x4e67,
	    .clockSeqAndNode = {0x54, 0x4b, 0x46, 0x4e, 0x43, 0x68, 0x69, 0x70},
    },
    /* Fingerprint (Focal) TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x5769,
        .timeHiAndVersion = 0x4e67,
        .clockSeqAndNode = {0x54, 0x4b, 0x46, 0x4e, 0x46, 0x6f, 0x63, 0x61},
    },
    /* Fingerprint (Silead) TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x5769,
        .timeHiAndVersion = 0x4e67,
        .clockSeqAndNode = {0x54, 0x4b, 0x46, 0x4e, 0x53, 0x69, 0x6c, 0x65},
    },
    /* Face */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x5769,
        .timeHiAndVersion = 0x4e67,
        .clockSeqAndNode = {0x54, 0x4b, 0x46, 0x63, 0x65, 0x55, 0x6c, 0x6b},
    },
    /* AlipayIFAA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x5769,
        .timeHiAndVersion = 0x4e67,
        .clockSeqAndNode = {0x54, 0x4b, 0x41, 0x6c, 0x69, 0x50, 0x49, 0x41},
    },
    /* WechatpaySoter */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x5769,
        .timeHiAndVersion = 0x4e67,
        .clockSeqAndNode = {0x54, 0x4b, 0x57, 0x63, 0x74, 0x50, 0x53, 0x74},
    },
#endif
#ifdef HUAQIN
	/* Face */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x4875,
        .timeHiAndVersion = 0x4171,
        .clockSeqAndNode = {0x49, 0x6e, 0x46, 0x61, 0x63, 0x65, 0x68, 0x71},
    },
	/* Fingerprint TA 1 */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x4875,
        .timeHiAndVersion = 0x4171,
        .clockSeqAndNode = {0x49, 0x6e, 0x46, 0x70, 0x53, 0x69, 0x6c, 0x64},
    },
	/* Fingerprint TA 2 */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x4875,
        .timeHiAndVersion = 0x4171,
        .clockSeqAndNode = {0x49, 0x6e, 0x46, 0x70, 0x43, 0x48, 0x70, 0x6e},
    },
	/* Fingerprint TA 3 */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x4875,
        .timeHiAndVersion = 0x4171,
        .clockSeqAndNode = {0x49, 0x6e, 0x46, 0x70, 0x43, 0x64, 0x66, 0x50},
    },
	/* Fingerprint TA 4 */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x4875,
        .timeHiAndVersion = 0x4171,
        .clockSeqAndNode = {0x49, 0x6e, 0x46, 0x70, 0x45, 0x67, 0x73, 0x54},
    },
#endif
#ifdef DEBUG
    /* test_TA_driver_client TA */
    {
        .timeLow = 0x00000000,
        .timeMid = 0x0000,
        .timeHiAndVersion = 0x0000,
        .clockSeqAndNode = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08},
    },
#endif /* DEBUG */
};

static int gk_drv_open(struct drv_info *info, const char *drv_path, ...);
static int gk_drv_close(struct drv_info *info);
static int gk_drv_ioctl(struct drv_info *info, int ioctl_cmd, unsigned long ioctl_data);
static struct fops file_ops = {
    .open  = gk_drv_open,
    .close = gk_drv_close,
    .ioctl = gk_drv_ioctl,
    .read = 0,
    .write = 0,
    .mmap = 0
};

static TEE_Result gk_reg_drv()
{
    int ret = 0;

    ret = TEES_InitDriver(drv_name, &file_ops, ACC_PERM_SEC_DRV, &drv_info);
    if (ret != TEE_SUCCESS) {
        LOGE("TEES_RegisterDriver failed, ret = %d\n", ret);
        return TEE_ERROR_GENERIC;
    }
    LOGD("TEES_RegisterDriver OK, ret = %d\n", ret);

    return TEE_SUCCESS;
}

static int gk_drv_open(struct drv_info *info, const char *drv_path, ...)
{
    (void) drv_path;
    size_t i;

    for (i = 0; i < ARRAY_SIZE(allowed_uuids); i++) {
        if (!memcmp(allowed_uuids + i, &info->uuid, UUID_LEN))
            break;
    }
    if (i == ARRAY_SIZE(allowed_uuids)) {
        LOGE("Access is not permitted\n");
        return -EPERM;
    }
    LOGD("drv_open success\n");
    return 0;
}

static int gk_drv_close(struct drv_info *info)
{
    (void) info;
    return 0;
}

static int gk_drv_ioctl(struct drv_info *info, int ioctl_cmd, unsigned long ioctl_data)
{
    int retval = 0;

    LOGD("drv_gatekeeper_ioctl\n");
    void *vaddr = TEES_AcquireUserBuffer(info, (uint64_t)ioctl_data, HMAC_KEY_SIZE, PROT_READ | PROT_WRITE);
    if (vaddr == ((void *) -1)) {
        return -EFAULT;
    }

    switch (ioctl_cmd)
    {
    case CMD_IOCTL_GET_HMAC_KEY:
    {
        if (!gk_cp_sess_key(vaddr, HMAC_KEY_SIZE)) {
            retval = -EFAULT;
        }
        break;
    }
    default:
        retval = -EINVAL;
    }

    TEES_ReleaseUserBuffer(vaddr, HMAC_KEY_SIZE);

    return retval;
}

TEE_Result TA_CreateEntryPoint(void)
{
    return TEE_SUCCESS;
}

void TA_DestroyEntryPoint(void)
{
}

TEE_Result
TA_OpenSessionEntryPoint(uint32_t paramTypes,
                         TEE_Param params[4],
                         void **sessionContext)
{
    (void)paramTypes;
    (void)params;
    (void)sessionContext;

#ifdef PROCA
    PaHandler handler;
    PaTzResult result;
    TEE_Result res;
    struct TEES_ClientCredentials creds;
    PaTzRules rules;
    const char *allowed_process = "/vendor/bin/hw/android.hardware.gatekeeper@1.0-service";

    res = TEES_GetClientCredentials(&creds);
    if (res) {
        LOGE("TEES_GetClientCredentials failed\n");
        return res;
    }

    result = PaTzHandlerCreateFromPid(creds.pid, &handler);
    if (result != PA_TZ_SUCCESS) {
        LOGE("PaTzHandlerCreateFromPid failed ret : %x\n", result);
        return TEE_ERROR_COMMUNICATION;
    }

    rules = PaTzRulesCreate();
    result = PaTzRulesAddProcessName(rules, allowed_process);
    if (result != PA_TZ_SUCCESS) {
        LOGE("PaTzRulesAddProcessName failed ret : %x\n", result);
        res = TEE_ERROR_COMMUNICATION;
        goto freerules;
    }

    result = PaTzAuthenticateWithRules(handler, rules, NULL);
    if (result != PA_TZ_SUCCESS) {
        LOGE("PaTzAuthenticateWithRules failed ret : %x\n", result);
        res = TEE_ERROR_SECURITY;
        goto freerules;
    }
freerules:
    PaTzRulesDestroy(rules);
    if (res != TEE_SUCCESS) {
        return res;
    }
#endif

    if (gk_gen_sess_key() == false) {
        return TEE_ERROR_GENERIC;
    }

    printf("[gatekeeper] TEEGRIS H/W Gatekeeper TA version : %.3f\n", GATEKEEPER_VERSION);

    return gk_reg_drv();
}

void TA_CloseSessionEntryPoint(void *sessionContext)
{
    (void)sessionContext;
    gk_release_sess_key();

    if ((drv_info != NULL) && TEES_FiniDriver(drv_info) != 0) {
        LOGE("TEES_ReleaseDriver failed\n");
    }
}

static bool gk_init_enr_request(TEE_Param params[4], enroll_request* enr_request)
{
    bool result = true;
    TEE_MemFill(enr_request, 0x0, sizeof(*enr_request));
    result = gk_cp_to_sized_buffer(params[3].memref.buffer, params[3].memref.size, &enr_request->pwd);
    /* Current password and current password handle go together */
    if(params[1].memref.size == 0
            || (params[2].memref.size == 0)
            || (result == false)) {
        return result;
    }
    result = ((gk_cp_to_sized_buffer(params[1].memref.buffer, params[1].memref.size, &enr_request->pwd_hndl))
              &&  (gk_cp_to_sized_buffer(params[2].memref.buffer, params[2].memref.size, &enr_request->enr_pwd)));

    return result;
}

static void gk_relese_enr_request(enroll_request* enr_request)
{
    gk_free_sized_buffer(&enr_request->pwd_hndl);
    gk_free_sized_buffer(&enr_request->enr_pwd);
    gk_free_sized_buffer(&enr_request->pwd);
}

static bool gk_init_enr_responce(const cmd_enroll_t *cmd, enroll_responce_t* enr_resp)
{
    TEE_MemFill(enr_resp, 0x0, sizeof(*enr_resp));
    enr_resp->uid = cmd->uid;
    enr_resp->result = ERR_UNKNOWN;
    if (cmd->len < sizeof(enr_resp->handle))
        return false;

    return true;
}

static void gk_init_cmd_enroll(const enroll_responce_t* ver_resp, cmd_enroll_t *cmd)
{
    cmd->uid = ver_resp->uid;
    cmd->result = ver_resp->result;
    cmd->retry_timeout = ver_resp->retry_timeout;
    cmd->len = sizeof(ver_resp->handle);
    TEE_MemMove(cmd->buff, &ver_resp->handle, cmd->len);
}

static TEE_Result gk_invoke_enroll(TEE_Param params[4])
{
    TEE_Result result = TEE_ERROR_GENERIC;
    fail_record_t failure_record;
    cmd_enroll_t cmd;
    enroll_request enr_request;
    enroll_responce_t enr_responce;

    int ret = 0;
    if (!( (params[1].memref.size == 0 && (params[1].memref.buffer == NULL))
        || ((params[1].memref.size == sizeof(passw_handle_t)) && (params[1].memref.buffer != NULL))
          )) {
        LOGE("param[1]: wrong parameter\n");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    if ((params[0].memref.buffer == NULL) || (params[0].memref.size != sizeof(cmd))) {
        LOGE("param[0]: wrong parameter\n");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    /* Copy to avoid TOCTOU issue */
    memcpy(&cmd, params[0].memref.buffer, sizeof(cmd));

    if (cmd.len > sizeof(cmd.buff)
            || cmd.len < sizeof(passw_handle_t)) {
        LOGE("param[0]: wrong parameter\n");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    ret = gk_restore_fail_counter(cmd.uid, &failure_record, sizeof(failure_record));
    if (ret != 0) {
        LOGE("load counter failed. return is 0x%08x\n", ret);
        if (ret == -ENOENT) { /* This error can occurs in enroll */
            failure_record.counter = 0;
            failure_record.user_id = 0; /* this value is tmp, real secure user id can't be 0 */
            failure_record.timestamp = hal_get_time();
        } else {
            /* initialize counter with 140 (max value of counter) in case of corrupted data.
             * initialize counter with 10 in other error cases */
            failure_record.counter = ret == -EBADFD ? 140 : 10;
            failure_record.user_id = 0; /* this value is tmp, real secure user id can't be 0 */
            failure_record.timestamp = hal_get_time();
        }
    }

    bool tmp_res = gk_init_enr_request(params, &enr_request);
    tmp_res &= gk_init_enr_responce(&cmd, &enr_responce);
    if (tmp_res) {
        gk_enroll(&enr_request, &enr_responce, &failure_record);
        result = TEE_SUCCESS;
    }

    gk_relese_enr_request(&enr_request);
    gk_init_cmd_enroll(&enr_responce, &cmd);
    LOGD("result_gatekeeper %d\n", cmd.result);

    memcpy(params[0].memref.buffer, &cmd, sizeof(cmd));

    return result;

}

static bool gk_init_ver_request(TEE_Param params[4], verify_request* ver_request)
{
    bool result = true;
    TEE_MemFill(ver_request, 0x0, sizeof(*ver_request));
    result = ((gk_cp_to_sized_buffer(params[1].memref.buffer,
                                     params[1].memref.size,
                                     &ver_request->pwd_hndl))
              &&  (gk_cp_to_sized_buffer(params[2].memref.buffer,
                                         params[2].memref.size,
                                         &ver_request->pwd)));

    return result;
}

static void gk_relese_ver_request(verify_request* ver_request)
{
    gk_free_sized_buffer(&ver_request->pwd_hndl);
    gk_free_sized_buffer(&ver_request->pwd);
}

static bool gk_init_ver_responce(const cmd_verify_t *cmd, verify_responce_t* ver_resp)
{
    TEE_MemFill(ver_resp, 0x0, sizeof(*ver_resp));
    ver_resp->challenge = cmd->challenge;
    ver_resp->uid = cmd->uid;
    ver_resp->result = ERR_UNKNOWN;
    if (cmd->len < sizeof(ver_resp->token)) {
        return false;
    }

    return true;
}

static void gk_init_cmd_verify(const verify_responce_t* ver_resp, cmd_verify_t *cmd)
{
    cmd->challenge = ver_resp->challenge;
    cmd->uid = ver_resp->uid;
    cmd->result = ver_resp->result;
    cmd->retry_timeout = ver_resp->retry_timeout;
    cmd->request_reenroll = ver_resp->request_reenroll;
    cmd->len = sizeof(ver_resp->token);
    TEE_MemMove(cmd->buff, &ver_resp->token, cmd->len);
}

static TEE_Result gk_invoke_verify(TEE_Param params[4])
{
    TEE_Result result = TEE_ERROR_GENERIC;
    fail_record_t failure_record;
    cmd_verify_t cmd;
    verify_request ver_request;
    verify_responce_t ver_responce;
    int ret = 0;

    if ((params[0].memref.buffer == NULL)
        || (params[0].memref.size != sizeof(cmd_verify_t)) ) {
        LOGE("param[0]: wrong parameter\n\n");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    if ((params[1].memref.buffer == NULL)
       || (params[1].memref.size != sizeof(passw_handle_t)) ) {
        LOGE("param[1]: wrong parameter\n");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    if (params[2].memref.buffer == NULL) {
        LOGE("param[1]: wrong length");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    /* Copy to avoid TOCTOU issue */
    memcpy(&cmd, params[0].memref.buffer, sizeof(cmd));

    ret = gk_restore_fail_counter(cmd.uid, &failure_record, sizeof(failure_record));
    if (ret != 0) {
        /* initialize counter with 140 (max value of counter) in case of corrupted data.
         * initialize counter with 10 in other error cases */
        LOGE("load counter failed. return = %d\n", ret);
        /*
         * this code runs only when failure record isn't loaded from trusted storage.
         * and if return vaule is -EBADFD or -ENOENT,
         * in this cases, failure record can never be loaded in the future.
         * so return 24 hours of penalty time for the purpose of preventing brute-force attack
         * and After 24 hours, it creates to new faileure record by using int overflow.
         * In the other case, we should not create a new failure record
         * becuase failure record can be loaded in the future. (eg, reboot .. )
         * Therefore it is reasonable to return error without verify
         */
        if (ret == -EBADFD || ret == -ENOENT) {
            failure_record.counter = FAILED_RECORD_STORAGE_ERROR;
            failure_record.user_id = 0;
            failure_record.timestamp = hal_get_time();
            if (gk_store_fail_counter(cmd.uid,
                                      &failure_record,
                                      sizeof(failure_record))) {
                LOGE("create new storage failed\n");
                return TEE_ERROR_STORAGE_NOT_AVAILABLE;
            }
        } else {
             return TEE_ERROR_STORAGE_NOT_AVAILABLE;
        }
    }

    bool tmp_res = gk_init_ver_request(params, &ver_request);
    tmp_res &= gk_init_ver_responce(&cmd, &ver_responce);
    if (tmp_res) {
        gk_verify(&ver_request, &ver_responce, &failure_record);
        result = TEE_SUCCESS;
    }

    gk_relese_ver_request(&ver_request);
    gk_init_cmd_verify(&ver_responce, &cmd);
    LOGD("result_gatekeeper %d \n", cmd.result);

    memcpy(params[0].memref.buffer, &cmd, sizeof(cmd));

    return result;
}

TEE_Result TA_InvokeCommandEntryPoint(void *sessionContext,
                                      uint32_t commandID,
                                      uint32_t paramTypes,
                                      TEE_Param params[4])
{
    (void)sessionContext;

    LOGD("GATEKEEPER_CMD %d\n", commandID);
    switch((gk_cmd_t)commandID)
    {
    case CMD_ENROLL:
    {
        LOGD("cmd ENROLL \n");
        if (paramTypes != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INOUT,/* command_enroll_t */
                                           TEE_PARAM_TYPE_MEMREF_INPUT,/* current pw handle */
                                           TEE_PARAM_TYPE_MEMREF_INPUT,/* current password */
                                           TEE_PARAM_TYPE_MEMREF_INPUT)) /* desired password */
        {
            LOGE("Wrong param types\n");

            return TEE_ERROR_BAD_PARAMETERS;
        }
        return gk_invoke_enroll(params);
    }
    case CMD_VERIFY:
    {
        LOGD("cmd VERIFY \n");

        if (paramTypes != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INOUT, /* cmd_verify_t */
                                          TEE_PARAM_TYPE_MEMREF_INPUT, /* enrolled pw handle */
                                          TEE_PARAM_TYPE_MEMREF_INPUT, /* provided password */
                                          TEE_PARAM_TYPE_NONE)) {
            LOGE("Wrong param types\n");
            return TEE_ERROR_BAD_PARAMETERS;
        }
        return gk_invoke_verify(params);
    }
    default:{
        LOGE("test_ta Wrong command 0x%x\n", commandID);
    }
    }

    return TEE_ERROR_GENERIC;
}
