/*
 * Copyright (c) 2017 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Created in Samsung Ukraine R&D Center (SRK) under a contract between
 * LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine)
 * and "Samsung Electronics Co", Ltd (Seoul, Republic of Korea)
 *
 * Created on: Mar 31, 2017
 * Author: Konstyantyn Volobuyev <k.volobuyev@samsung.com>
 * Brief: Blowfish trustlet entry point.
 */

#include <tees_extension.h>

#include "TigerMacroses.h"
#include "TigerCore.h"
#include "TigerMbedTlsHooks.h"

#include "TzwMemory.h"
#include "TzwString.h"

#include "TigerTaConfig.h"
#include "TigerUtils.h"
#include "TigerTci.h"

#include "ta_banner_print.h"

#include <inttypes.h>

// Few defines below describes TA prorperties.
#define TA_PROP_UUID                   tlTigerBlowfish_UUID
#define TA_PROP_SINGLE_INSTANCE        FALSE
#define TA_PROP_MULTISESSION           FALSE
#define TA_PROP_INSTANCE_KEEPALIVE     FALSE
#define TA_PROP_DATASIZE               RLIM_INFINITY
#define TA_PROP_STACKSIZE              0x8000
#define TA_PROP_THREAD_COUNT           1
#define TA_PROP_NUMINSTANCES           0
#define TA_PROP_INITIAL_PRIORITY       RLIM_DEF_PRIORITY
#define TA_PROP_MAX_PRIORITY           RLIM_MAX_PRIORITY
#define TA_PROP_GROUP_ID               "samsung_ta"
#define TA_PROP_VERSION                "slsi-v4"
#define TA_PROP_DESCRIPTION            "TIGER"
#define TA_PROP_DBG_DLM_DATA_AVAILABLE TA_DBG_DLM_BLOCKED
#define TA_PROP_DBG_PMR_DATA_AVAILABLE TA_DBG_PMR_BLOCKED

#include <ta_property.h>

uint64_t perf_time_begin;
uint64_t perf_time_end;

static uint64_t session_counter = 0;

#ifdef __DEV_DEBUG__
static uint64_t start_time_of_entry_point = 0;
static uint64_t end_time_of_entry_point = 0;

static uint64_t start_time_of_session_entry = 0;
static uint64_t end_time_of_session_entry = 0;
#endif /* __DEV_DEBUG__ */
/**
 *    The function is the service constructor, which the system calls when it creates a new instance of the service.
 **/
TEE_Result TA_EXPORT TA_CreateEntryPoint(void) {
    LOG_I("VERSION: %s", taAppVersion);

    setupMbedTlsHooks();
#ifdef __DEV_DEBUG__
    start_time_of_entry_point = systemTime();
#endif /* __DEV_DEBUG__ */
    return TEE_SUCCESS;
}

/**
 *    The function is the service destructor, which the system calls when the instance is being destroyed.
 **/
void TA_EXPORT TA_DestroyEntryPoint(void) {
    LOG_I("TA is unloaded");

#ifdef __DEV_DEBUG__
    end_time_of_entry_point = systemTime();
    LOG_DEV("elapsed time(entry point): %"PRIu64"ms", end_time_of_entry_point - start_time_of_entry_point);
#endif /* __DEV_DEBUG__ */
}

/**
 *    The system calls this function when a new client connects to the service instance.
 **/
TEE_Result TA_EXPORT TA_OpenSessionEntryPoint(uint32_t nParamTypes, IN OUT TEE_Param pParams[4], OUT void** ppSessionContext) {
    S_VAR_NOT_USED(pParams);
    LOG_D("enter func: %s", __func__);

#ifdef __DEV_DEBUG__
    start_time_of_session_entry = systemTime();
#endif /* __DEV_DEBUG__ */

    if (!ppSessionContext) {
        return TEE_ERROR_GENERIC;
    }

    if (TEE_PARAM_TYPE_GET(nParamTypes, 0) != TEE_PARAM_TYPE_VALUE_INPUT) {
        LOG_E("Bad parameter at index 0: expected value input");
        return TEE_ERROR_BAD_PARAMETERS;
    }

    print_banner(TA_PROP_DESCRIPTION, TA_PROP_VERSION);

    session_counter +=1;
    LOG_Raw("session counter: %"PRIu64, session_counter);

    return TEE_SUCCESS;
}

/**
 *  The system calls this function to indicate that a session is being closed.
 **/
void TA_EXPORT TA_CloseSessionEntryPoint(IN OUT void* pSessionContext) {
    LOG_D("enter func: %s", __func__);

    session_counter -=1;
    LOG_Raw("session counter: %"PRIu64, session_counter);

    if (pSessionContext) {
        //freeContext((TigerSessionContext_t*) pSessionContext);
        tzwFree(pSessionContext);
        pSessionContext = NULL;
    }

#ifdef __DEV_DEBUG__
    end_time_of_session_entry = systemTime();
    LOG_DEV("elapsed time(session entry): %"PRIu64"ms", end_time_of_session_entry- start_time_of_session_entry);
#endif /* __DEV_DEBUG__ */
}

/**
 *  This function calls when the client invokes a command within the given session.
 **/
TEE_Result TA_EXPORT TA_InvokeCommandEntryPoint(void* sessionContext, uint32_t commandID, uint32_t paramTypes,
        TEE_Param params[4]) {
    S_VAR_NOT_USED(sessionContext);
    S_VAR_NOT_USED(commandID);

    LOG_FUNC_BEGIN

    LOG_D("enter func: %s, paramTypes: 0x%08x", __func__, paramTypes);

    TEE_Result status = TEE_SUCCESS;
    cmd_req_t request = {0,};
    cmd_rsp_t response = {0,};
    TciRequest_p tciRequest = (TciRequest_p)(&request);
    uint32_t total_log_size = 0;

    do{

         uint32_t  _paramTypes = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
                                        TEE_PARAM_TYPE_MEMREF_OUTPUT,
                                        TEE_PARAM_TYPE_MEMREF_OUTPUT,
                                        TEE_PARAM_TYPE_NONE);

        if(paramTypes != _paramTypes){
            status = TEE_ERROR_BAD_PARAMETERS;

            LOG_E("func: %s, paramTypes(in): 0x%08x, paramTypes(TA): 0x%08x",
                        __func__, paramTypes, _paramTypes);
            LOG_E("func: %s: Bad parameters", __func__);

            break;
        }

		/* param[0]: INPUT */
		if (TEES_IsREESharedMemory (TEE_MEMORY_ACCESS_READ,
			params[0].memref.buffer, params[0].memref.size) != TEE_SUCCESS) {
			LOG_E("Memory access check error\n");
			status = TEE_ERROR_ACCESS_DENIED;
			break;
		}

		/* param[1]: OUTPUT */
		if (TEES_IsREESharedMemory (TEE_MEMORY_ACCESS_WRITE,
			params[1].memref.buffer, params[1].memref.size) != TEE_SUCCESS) {
			status = TEE_ERROR_ACCESS_DENIED;
			break;
		}

		/* param[2]: OUTPUT */
		if (TEES_IsREESharedMemory (TEE_MEMORY_ACCESS_WRITE,
			params[2].memref.buffer, params[2].memref.size) != TEE_SUCCESS) {
			status = TEE_ERROR_ACCESS_DENIED;
			break;
		}

        if(sizeof(cmd_req_t) < (uint32_t)params[0].memref.size
                && MAX_RESPONSE_MSG_SIZE == (uint32_t)params[1].memref.size
                && LOGGER_BUF_LEN == (uint32_t)params[2].memref.size){
            status = TEE_ERROR_OUT_OF_MEMORY;

            LOG_E("%s:OOM, Too many bytes from NWD or Shared Memory Size does not match", __func__);

            break;
        }

        memset(&request, 0, sizeof(cmd_req_t));

        uint8_t *req = (uint8_t*)&request;

        response.out_len = params[1].memref.size;

#ifdef __ARM_ARCH_64__
        LOG_D("cmd_req_t: %08lx", sizeof(cmd_req_t));
#else
        LOG_D("cmd_req_t: %08x", sizeof(cmd_req_t));
#endif
        LOG_D("size of input: %08x", (uint32_t)params[0].memref.size);
        LOG_D("size of expect output: %08x", (uint32_t)params[1].memref.size);

        tzwMemMove(req, (uint8_t*)params[0].memref.buffer, (uint32_t)params[0].memref.size);
        uint32_t req_size = (uint32_t)params[0].memref.size;

#ifdef __DEV_DEBUG__
        logRawByteArrayHex(req, req_size, "request bytes");
#endif

        LOG_I_PERF_BEGIN
        status = processCommand(tciRequest, req_size, response.output, response.out_len);
        LOG_I_PERF_END

        TciCommandId_t commandId = tciRequest->TciCommandId;
        if (status != TEE_SUCCESS) {
            LOG_E("Failure while %s: %s", getTciCommandIdStr(commandId), getTeeErrorText(status));
        }else{
            LOG_I("%s: %s", getTciCommandIdStr(commandId), getTeeErrorText(status));
        }
        tzwMemMove(params[1].memref.buffer, response.output, params[1].memref.size);

#ifdef __DEV_DEBUG__
        do{
            size_t __size = response.out_len > 0x100 ? 0x100 : response.out_len;
            logRawByteArrayHex(response.output, __size, "response bytes(size < 0x100)");
        }while(0);
#endif

    }while(0);

#if defined(TRANSMIT_LOG_TO_CA)
#ifdef TA_RELEASE
    if (TEE_SUCCESS != status)
#endif /*TA_RELEASE*/
    do{
        int len = 0;
        LOG_I_AM_HERE
        qsc_send_cmd_rsp_t *logOut = (qsc_send_cmd_rsp_t*)(&response);
        char *ptr = (char*)logOut->log_buf;
        logOut->log_len= 0;
        total_log_size = 0;
        while(!isStackLogEmpty()) {
            char *str = popStackLog(&len);
            total_log_size += (len + 4);
            if(LOGGER_BUF_LEN < total_log_size ){
                /*LOG_Raw("missing log:%s", str);*/
                tzwFree(str);
                continue;
            }

            *((uint32_t*)ptr) = len;
            ptr += 4;

            strncpy(ptr, str, len);
            ptr += len;
            logOut->log_len += (len + 4);
            tzwFree(str);
        }
    }while(0);

    uint32_t transmit_size = params[2].memref.size > response.log_len + 4
                            ? response.log_len + 4 : params[2].memref.size;
    tzwMemMove(params[2].memref.buffer, response.buff_log, transmit_size);

#ifdef __DEV_DEBUG__
    LOG_Raw("len of transmit Ta log: %u", response.log_len);
    LOG_Raw("len of all Ta log: %d", total_log_size);
#endif /* __DEV_DEBUG__ */
#endif /*TRANSMIT_LOG_TO_CA*/

    LOG_FUNC_END

    return status;
}
