/**
 * @file tl_main.c
 * @brief TA main loop implementation for qsee
 * @author Iaroslav Makarchuk (i.makarchuk@samsung.com)
 * @date Created Oct 3, 2016
 * @par In Samsung Ukraine R&D Center (SURC) under a contract between
 * @par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * @par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * @par Copyright: (c) Samsung Electronics Co, Ltd 2015. All rights reserved.
 *
 * This software is proprietary of Samsung Electronics.
 * No part of this software, either material or conceptual may be copied
 * or distributed, transmitted, transcribed, stored in a retrieval system
 * or translated into any human or computer language in any form by any means,
 * electronic, mechanical, manual or otherwise, or disclosed to third parties
 * without the express written permission of Samsung Electronics.
 */

#include <comdef.h>
#include <string.h>

#include <tees_log.h>
#include <protocol.h>

#include "tl_main.h"

#include <qsee_heap.h>
#include <qsee_log.h>
#include <qsee_services.h>
#include <qsee_timer.h>

#include <session_manager.h>
#include <tee_param_utils.h>
#include <tee_client_api.h>

char TZ_APP_NAME[] = {QSEE_APP_NAME};

void *g_session_context = NULL;
uint32_t g_ta_created = 0;
// g_listener_id == 0 is invalid value and means that
// listener is not registered on client side
uint32_t g_listener_id = 0;

ProtocolCmd *g_cmd_buffer = NULL;

int init_crypto(void);
void init_properties(void *start_TA_property);

static TEE_Result HandleOpenSession(ProtocolCmd *command, TEE_Param params[4]) {
  TEE_Result result = TEE_ERROR_GENERIC;

  TeeSession *session = NULL;

  if (TEE_SUCCESS == SessionManagerGetSession((void *)command, &session)) {
    MB_LOGE("Same session already opened! Reporting error.\n");
    goto exit;
  }

  session = TEE_Malloc(sizeof(TeeSession), HINT_FILL_WITH_ZEROS);
  if (!session) {
    result = TEE_ERROR_OUT_OF_MEMORY;
    goto exit;
  }

  result = TA_OpenSessionEntryPoint(command->param_types, params,
                                    &session->session_context);
  if (TEE_SUCCESS == result ) {
    session->handle = (void *)command;
    if (TEE_SUCCESS != SessionManagerAddSession(session)) {
      result = TEE_ERROR_GENERIC;
    }
  }

  if (TEE_SUCCESS != result) {
    TEE_Free(session);
  }

exit:
  return result;
}

static void HandleCloseSession(ProtocolCmd *command) {
  TeeSession *session = NULL;
  void *context = NULL;

  if (TEE_SUCCESS == SessionManagerGetSession((void *)command, &session)) {
    context = session->session_context;
  } else {
    MB_LOGW("Failed to obtain session context!\n");
  }

  TA_CloseSessionEntryPoint(context);

  if (TEE_SUCCESS != SessionManagerRemoveSession(session)) {
    MB_LOGW("Failed to remove session!\n");
  }

  TEE_Free(session);
}

static TEE_Result HandleInvokeCommand(ProtocolCmd *command,
                                      TEE_Param params[4]) {
  TEE_Result result = TEE_ERROR_GENERIC;
  TeeSession *session = NULL;
  void *context = NULL;

  if (TEE_SUCCESS == SessionManagerGetSession((void *)command, &session)) {
    context = session->session_context;
  } else {
    MB_LOGW("Failed to obtain session context!\n");
  }

  result = TA_InvokeCommandEntryPoint(context,
                                      command->cmd_id,
                                      command->param_types,
                                      params);
  return result;
}

void HandleCommand(ProtocolCmd *command) {
  TEE_Param params[4];

  // Return origin: it must be "TEE" be default.
  command->return_origin = TEE_ORIGIN_TEE;

  command->cmd_ret = FillParamValues(command->param_types, params, command);
  if (TEE_SUCCESS != command->cmd_ret) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed to read command params!\n");
    return;
  }

  switch (command->cmd_id) {
    case PROTOCOL_COMMAND_LOAD: {
      if (!g_ta_created) {
        TA_CreateEntryPoint();
        g_ta_created++;
        g_listener_id = command->service_data;
      }

      command->cmd_ret = HandleOpenSession(command, params);
      break;
    }
    case PROTOCOL_COMMAND_UNLOAD: {
      HandleCloseSession(command);
      break;
    }
    default: {
      command->cmd_ret = HandleInvokeCommand(command, params);
      break;
    }
  }

  if (TEE_SUCCESS != FillCommandArgs(command->param_types, command, params)) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed to write command params!\n");
    command->cmd_ret = TEE_ERROR_COMMUNICATION;
    return;
  }

  command->return_origin = TEE_ORIGIN_TRUSTED_APP;
}

void tz_app_init_ex(void) {
  /* cryptocore init */
  init_crypto();
  /* property init */
  init_properties((void *)&_TA_property);
}

#ifndef SCRYPTO_INIT
void tz_app_init(void) {
  tz_app_init_ex();
}
#endif

void tz_app_cmd_handler(void *cmd, uint32 cmdlen,
                        void *rsp, uint32 rsplen)
{
  ProtocolCmd *command = cmd;
  g_cmd_buffer = (ProtocolCmd *)cmd;

  (void) cmdlen;
  (void) rsp;
  (void) rsplen;

  if (!command ||
      cmdlen != sizeof(ProtocolCmd) + MAX_SHAREDMEM_SIZE) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Bad command buffer!\n");
    return;
  }

  HandleCommand(command);

  // When a request to a trustlet is performed from SWd,
  // then GetCancellationFlag should skip processing of g_cmd_buffer
  // which handles operations from NWd.
  g_cmd_buffer = NULL;
}


void tz_app_shutdown(void) {
  TA_DestroyEntryPoint();
}
