/*
 * Copyright (c) 2019 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Brief: Kinibi trustlet entry point.
 */

#include "TzwMacros.h"
#include "TzwMemory.h"
#include "TzwString.h"
#include "ifaa_ta_common.h"
#include "ifaa_ta_vendor.h"
#include "ifaa_wsbuff.h"
#include "ifaa_config.h"
#include "ta_banner_print.h"

#include <TlApi/TlApiMcSystem.h>
#include <TlApi/TlApiCom.h>

#include <inttypes.h>

DECLARE_TRUSTED_APPLICATION_MAIN_STACK(0x80000)

#define TA_PROP_VERSION                "slsi-Kinibi"
#define TA_PROP_DESCRIPTION            "IFAA"

/**
 * Termination codes
 */
#define EXIT_ERROR                      ((uint32_t)(-1))


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__ */

void __CreateEntryPoint(void) {

    /*LOG_I("VERSION: %s", taAppVersion);*/

#ifdef __DEV_DEBUG__
    start_time_of_entry_point = systemTime();
#endif /* __DEV_DEBUG__ */

    print_banner(TA_PROP_DESCRIPTION, TA_PROP_VERSION);

    session_counter +=1;

    IFAA_TaAddEntry(IFAA_BIO_FINGERPRINT, IFAA_ENTRY_LAST_IDENTIFIED_RESULT_GETTER, IFAA_GetFpLastIdentifiedResult);
    IFAA_TaAddEntry(IFAA_BIO_FINGERPRINT, IFAA_ENTRY_AUTHENTICATOR_VERSION_GETTER, IFAA_GetFpAuthenticatorVersion);
    IFAA_TaAddEntry(IFAA_BIO_FINGERPRINT, IFAA_ENTRY_ID_LIST_GETTER, IFAA_GetFpEnrolledIdList);
    IFAA_TaAddEntry(IFAA_BIO_FINGERPRINT, IFAA_ENTRY_EQUATOR, IFAA_FpIdCompare);
    IFAA_TaAddEntry(IFAA_BIO_FINGERPRINT, IFAA_ENTRY_IS_ROOT, IFAA_CheckRoot);

    LOG_Raw("session counter: %" PRIu64, session_counter);

}

void __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__ */

    session_counter -=1;
    LOG_Raw("session counter: %" PRIu64, session_counter);
}

/**
 * Trustlet entry.
 **/
_TLAPI_ENTRY void tlMain( const addr_t tciBuffer, const uint32_t tciBufferLen ) {
    __CreateEntryPoint();

    LOG_D("trustlet is starting");
#ifdef __DEV_DEBUG__
    {
        uint32_t tlApiVersion;
        tlApiResult_t ret;
        mcVersionInfo_t versionInfo;

        ret = tlApiGetVersion(&tlApiVersion);
        if (TLAPI_OK != ret) {
            LOG_E("tlApiGetVersion failed with ret=0x%08X, exit", ret);
            tlApiExit(ret);
        }
        LOG_D("tlApi version 0x%08X, exit", tlApiVersion);

        ret = tlApiGetMobicoreVersion(&versionInfo);
        if (TLAPI_OK != ret) {
            LOG_E("tlApiGetMobicoreVersion failed with ret=0x%08X, exit", ret);
            tlApiExit(ret);
        }
        LOG_D("productId        = %s",     versionInfo.productId);
        LOG_D("versionMci       = 0x%08X", versionInfo.versionMci);
        LOG_D("versionSo        = 0x%08X", versionInfo.versionSo);
        LOG_D("versionMclf      = 0x%08X", versionInfo.versionMclf);
        LOG_D("versionContainer = 0x%08X", versionInfo.versionContainer);
        LOG_D("versionMcConfig  = 0x%08X", versionInfo.versionMcConfig);
        LOG_D("versionTlApi     = 0x%08X", versionInfo.versionTlApi);
        LOG_D("versionDrApi     = 0x%08X", versionInfo.versionDrApi);
        LOG_D("versionCmp       = 0x%08X", versionInfo.versionCmp);
    }
#endif

    /* Check if the size of the given TCI is sufficient */
    if ((NULL == tciBuffer) || ( sizeof(cmd_req_t) + sizeof(cmd_rsp_t) != tciBufferLen)) {
          LOG_E("TCI error");
          LOG_E("TCI buffer: 0x%08x", (uint32_t)tciBuffer);
          LOG_E("TCI buffer actual length:0x%04x, expected length: 0x%04x",
                              tciBufferLen, sizeof(cmd_rsp_t) + sizeof(cmd_req_t));
          tlApiExit(EXIT_ERROR);
    }

    LOG_DEV("Trusted Application is processing the command");

    TEE_Result status = TEE_SUCCESS;
    cmd_req_p request = NULL;
    cmd_rsp_p response = NULL;

    do {

        LOG_DEV("Trusted Application is waiting for a notification to arrive");
        /* Wait for a notification to arrive */
        tlApiResult_t ret;
        ret = tlApiWaitNotification(TLAPI_INFINITE_TIMEOUT);
        if(TLAPI_OK != ret){
            LOG_E("tlApiWaitNotification failed with ret=0x%08X, exit", ret);
        }
        /* Dereference commandId once for further usage */
        LOG_D("Trusted Application received command");

        /* Process command message */
        LOG_DEV("tciBufferLen: 0x%04x", tciBufferLen);

        cmd_req_p request = tzwMalloc(sizeof(cmd_req_t));
        if(!request){
            LOG_E("Fail to tzwMalloc", request);
            status = TEE_ERROR_OUT_OF_MEMORY;
            break;
        }

        cmd_rsp_p response = tzwMalloc(sizeof(cmd_rsp_t));
        if(!request){
            LOG_E("Fail to tzwMalloc", request);
            tzwFree(request);
            status = TEE_ERROR_OUT_OF_MEMORY;
            break;
        }

        tzwMemMove(request, tciBuffer, sizeof(cmd_req_t));
        tzwMemMove(response, (uint8_t*)tciBuffer + sizeof(cmd_req_t), sizeof(cmd_rsp_t));

#ifdef __DEV_DEBUG__
        logRawByteArrayHex((uint8_t*)request, sizeof(cmd_req_t)/2, "request bytes");
#endif
        LOG_D("request: %p, sizeof request: 0x%04x", request->input, request->in_len);
        LOG_D("response: %p, sizeof response: 0x%04x", response->output , response->out_len);

        uint32_t ret_size = sizeof(cmd_rsp_t);

        LOG_I_PERF_BEGIN
        status = IFAA_TaInvokeCmd((uint8_t*)request->input, request->in_len, (uint8_t*)response, &ret_size);
        LOG_I_PERF_END

        LOG_D("size of response: 0x%04x( %d )", response->out_len, response->out_len);

        if(request){
            tzwFree(request);
            request = NULL;
        }

        LOG_D("Trusted Application is exiting with status: 0x%08x ", status);

        //--------------------------------------------------------------
        //Transmit TA log to Nwd
        uint32_t total_log_size = 0;
#if defined(TRANSMIT_LOG_TO_CA)
#ifdef TA_RELEASE
    if (IFAA_ERR_SUCCESS != status)
    {
#endif /*TA_RELEASE*/
        do{
            int len = 0;
            LOG_I_AM_HERE
            cmd_rsp_p logOut = response;
            char *ptr = (char*)logOut->log_buf;
            logOut->log_len= 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);

#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__ */
#ifdef TA_RELEASE
    }
#endif
#endif /*TRANSMIT_LOG_TO_CA*/

        //--------------------------------------------------------------
        // Copy Response from SWD memory back to NWD
        //--------------------------------------------------------------
        tzwMemMove((uint8_t*)tciBuffer + sizeof(cmd_req_t), response, sizeof(cmd_rsp_t));

        if(response){
            tzwFree(response);
            response = NULL;
        }

        /* Notify back the TLC */
        tlApiNotify();
    }while(1);

    if(request){
        tzwFree(request);
        request = NULL;
    }

    if(response){
        tzwFree(response);
        response = NULL;
    }

    LOG_FUNC_END

    __DestroyEntryPoint();

    tlApiExit(TEE_SUCCESS);
}

/////////////////////////////////////////////////////////////////////////////////////
/*
* https://embdev.net/topic/linker-error-undefined-reference-to-_sbrk
*/
/////////////////////////////////////////////////////////////////////////////////////

#include <sys/types.h>

extern caddr_t _sbrk (int incr);
extern int  __HEAP_START;
extern __attribute__ ((weak)) int  end;

__attribute__ ((weak)) caddr_t _sbrk ( int incr )
{
       static unsigned char *heap = NULL;
       unsigned char *prev_heap;

       LOG_D("++++++++++ _sbrk is called finally +++++++++++++");
       LOG_Raw("++++++++++ _sbrk is called finally +++++++++++++");

       if (heap == NULL) {
               heap = (unsigned char *)&__HEAP_START;
       }
       prev_heap = heap;
       /* check removed to show basic approach */

       heap += incr;
       return (caddr_t) prev_heap;
}

