/**
 * @file tl_main.c
 * @brief TA main loop implementation for host
 * @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 <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <protocol.h>
#include <setjmp.h>
#include <tees_log.h>
#include <tee_internal_api.h>
#include <tee_param_utils.h>
#include <socket_utils.h>

#define KEEP_WORKING    0
#define MUST_UNLOAD_TA  1

extern jmp_buf g_panic_jmp_buf;

static int InitServer() {
  int sock = -1;
  struct sockaddr_un server;

  sock = socket(AF_UNIX, SOCK_STREAM, 0);
  if (sock < 0) {
    goto exit;
  }

  memset(&server, 0x00, sizeof(server));

  server.sun_family = AF_UNIX;
  strncpy(server.sun_path, TA_UUID, strlen(TA_UUID) + 1);

  if (bind(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un))) {
    close(sock);
    sock = -1;
    goto exit;
  }

exit:
  return sock;
}

void ExitServer(int sock) {
  if (sock >= 0) {
    close(sock);
  }
  unlink(TA_UUID);
}

void *g_session_context = NULL;
int g_ta_created = 0;

void InitSubsystems(void);

ProtocolCmd g_cmd_buffer = { 0 };

static int HandleSocketConnection(int sock) {
  int retval = KEEP_WORKING;
  TEE_Param params[4];
  int panic_reason = 0;

  memset(params, 0x00, sizeof(params));

  // Return origin: must be "TEE" by default.
  g_cmd_buffer.return_origin = TEE_ORIGIN_TEE;

  if (SocketStatusOk != ReadCommand(sock, &g_cmd_buffer)) {
    MB_LOGE("Failed to read command!\n");
    goto exit;
  }

  panic_reason = setjmp(g_panic_jmp_buf);

  if (panic_reason) {
    retval = MUST_UNLOAD_TA;
    g_cmd_buffer.cmd_ret = TEE_ERROR_TARGET_DEAD;
    goto write_back;
  }

  if (PROTOCOL_COMMAND_UNLOAD == g_cmd_buffer.cmd_id) {
    retval = MUST_UNLOAD_TA;
    TA_CloseSessionEntryPoint(g_session_context);
    goto exit;
  }

  g_cmd_buffer.cmd_ret = FillParamValues(g_cmd_buffer.param_types, params,
                                        &g_cmd_buffer);
  if (TEE_SUCCESS != g_cmd_buffer.cmd_ret) {
    MB_LOGE("Failed to read command params!\n");
    goto write_back;
  }

  if (PROTOCOL_COMMAND_LOAD == g_cmd_buffer.cmd_id) {
    if (!g_ta_created) {
      InitSubsystems();
      TA_CreateEntryPoint();
      g_ta_created++;
    }

    g_cmd_buffer.cmd_ret = TA_OpenSessionEntryPoint(g_cmd_buffer.param_types,
                                                    params,
                                                    &g_session_context);
  } else {
    g_cmd_buffer.cmd_ret = TA_InvokeCommandEntryPoint(g_session_context,
                                                      g_cmd_buffer.cmd_id,
                                                      g_cmd_buffer.param_types,
                                                      params);
  }

  if (TEE_SUCCESS !=
      FillCommandArgs(g_cmd_buffer.param_types, &g_cmd_buffer, params)) {
    MB_LOGE("Failed to write command params!\n");
    g_cmd_buffer.cmd_ret = TEE_ERROR_COMMUNICATION;
    goto write_back;
  }

  g_cmd_buffer.return_origin = TEE_ORIGIN_TRUSTED_APP;

write_back:
  if (SocketStatusOk != WriteCommand(sock, &g_cmd_buffer)) {
    MB_LOGE("Failed to write command!\n");
  }

exit:
  return retval;
}

int main() {
  int sock, msgsock;
  int must_unload = KEEP_WORKING;

  // Unlink socket file after interruption.
  ExitServer(0);

  sock = InitServer();
  if (sock < 0) {
    MB_LOGE("Failed to initialize TA!\n");
    goto exit;
  }

  if (listen(sock, 1)) {
    MB_LOGE("Failed to listen socket, exiting\n");
    goto exit;
  }

  while (MUST_UNLOAD_TA != must_unload) {
    msgsock = accept(sock, 0, 0);

    if (msgsock == -1) {
      continue;
    }

    must_unload = HandleSocketConnection(msgsock);
    close(msgsock);
  }

exit:
  ExitServer(sock);
  TA_DestroyEntryPoint();
}
