/**
 * @file teec_common_qsee.c
 * @brief Common client interface implementation for TA emulation on 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 <teec_common.h>

#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <teec_callback_handler.h>


#include <protocol.h>
#include <socket_utils.h>
#include <teec_param_utils.h>

#define CMD_LEN     256
#define TA_STR_LEN  64

typedef struct ListenerInfoStruct {
  int sock_;
  uint32_t sock_id_;
  pthread_t thread_;
} ListenerInfo;

typedef struct EmulSessionStruct {
  char ta_str_[TA_STR_LEN];
  TEEC_UUID ta_uuid;
  int sock_;
  void *parent_context;
  ListenerInfo listener;
  bool should_stop;
} EmulSession;

// todo - remove it to some
// shared place
static uint32_t GenRandomSocketId() {
  FILE *f_dev_urandom = fopen("/dev/urandom", "r");
  uint32_t retval = 0;

  if (f_dev_urandom) {
    uint32_t sz = fread(&retval, 1, sizeof(retval), f_dev_urandom);
    if (sz != sizeof(retval)) {
      printf("Invalid random size!\n");
    }
    fclose(f_dev_urandom);
  }
  return retval;
}


static TEEC_Result InitListener(ListenerInfo *li) {
  TEEC_Result res = TEEC_ERROR_COMMUNICATION;
  struct sockaddr_un addr = { 0 };

  if (!li) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  li->sock_ = socket(AF_UNIX, SOCK_STREAM, 0);
  if (li->sock_ < 0) {
    goto exit;
  }

  li->sock_id_ = GenRandomSocketId();

  if (!li->sock_id_) {
    goto exit;
  }
  addr.sun_family = AF_UNIX;
  snprintf(addr.sun_path, sizeof(addr.sun_path) - 1, "%u", li->sock_id_);

  if (bind(li->sock_, (struct sockaddr *)&addr, sizeof(addr))) {
    goto exit;
  }
  if (listen(li->sock_, 1)) {
    goto exit;
  }
  res = TEEC_SUCCESS;
exit:
  if (res != TEEC_SUCCESS) {
    if (li->sock_ >= 0) {
      close(li->sock_);
      if (addr.sun_path[0]) {
        unlink(addr.sun_path);
      }
    }
    li->sock_ = -1;
  }

  return res;
}

static void HandleSocketConnection(int sock, EmulSession *session) {

  PersObjectCmd command = { 0 };

  if (SocketStatusOk != ReadPersObj(sock, &command)) {
    printf("Failed to read command!\n");
    return;
  }

  command.cmd_id = (uint32_t)MbHandleSWdCommand(command.cmd_id,
                                                &session->ta_uuid,
                                                command.id,
                                                command.id_len,
                                                command.data,
                                                &command.data_len,
                                                &command.teec_oper);

// write response
  if (SocketStatusOk != WritePersObj(sock, &command)) {
    printf("Failed to write command!\n");
  }

}


void *listener_thread(void *data) {
  EmulSession *ses = (EmulSession *)data;
  int msgsock = -1;

  while (!ses->should_stop) {

    if (!CheckSocketInput(ses->listener.sock_, 0)) {
      continue;
    }

    msgsock = accept(ses->listener.sock_, 0, 0);

    if (msgsock == -1) {
      continue;
    }

    HandleSocketConnection(msgsock, ses);
    close(msgsock);
  }
exit:
  return NULL;
}


static void CloseTASocket(EmulSession *ctx) {
  if (ctx->sock_ >= 0) {
    close(ctx->sock_);
  }
}

static SocketStatus OpenTASocket(EmulSession *ctx) {
  struct sockaddr_un server;

  if (!ctx) {
    return SocketStatusError;
  }

  memset(&server, 0x00, sizeof(server));
  server.sun_family = AF_UNIX;
  strncpy(server.sun_path, ctx->ta_str_, sizeof(ctx->ta_str_));

  return OpenSocket(&server, &ctx->sock_);
}

TEEC_Result EmulSendCommand(void *ta_session,
                            uint32_t command_id,
                            TEEC_Operation *operation,
                            uint32_t *returnOrigin,
                            int32_t timeout) {
  SocketStatus status = SocketStatusOk;
  TEEC_Result ret = TEEC_ERROR_BAD_PARAMETERS;
  ProtocolCmd command;
  EmulSession *emul_session = (EmulSession *)ta_session;

  memset(&command, 0x00, sizeof(command));

  // Return origin: "API" is rewritten always at the exit condition.
  command.return_origin = TEEC_ORIGIN_API;

  if (!emul_session) {
    goto exit;
  }

  // Return origin: "COMMS" by default because of possible communication errors.
  command.return_origin = TEEC_ORIGIN_COMMS;

  status = OpenTASocket(emul_session);
  if (SocketStatusOk != status) {
    ret = TEEC_ERROR_COMMUNICATION;
    goto exit;
  }

  command.cmd_id = command_id;
  command.cmd_ret = TEEC_ERROR_COMMUNICATION;
  command.param_types = 0;
  command.service_data = emul_session->listener.sock_id_;
  if (operation) {
    PtrToU64(operation,&command.teec_oper);
    command.param_types = operation->paramTypes;
    ret = FillCommandArgs(&command, operation, NULL);

    if (TEEC_SUCCESS != ret) {
      command.return_origin = TEEC_ORIGIN_API;
      goto exit;
    }
  }

  status = WriteCommand(emul_session->sock_, &command);
  if (SocketStatusOk != status) {
    ret = TEEC_ERROR_COMMUNICATION;
    goto exit;
  }

  status = ReadCommand(emul_session->sock_, &command);
  if (SocketStatusOk != status) {
    ret = TEEC_ERROR_COMMUNICATION;
    goto exit;
  }

  // Return origin: "TEE" or "TRUSTED_APP" after communication with TEE.

  ret = command.cmd_ret;

  if (TEEC_SUCCESS == ret && operation) {
    ret = FillOperationArgs(operation, &command);
    if (TEEC_SUCCESS != ret) {
      command.return_origin = TEEC_ORIGIN_API;
    }
  }

exit:
  if (emul_session) {
    CloseTASocket(emul_session);
  }

  if (returnOrigin) {
    *returnOrigin = command.return_origin;
  }

  return ret;
}


TEEC_Result EmulLoadTA(void *ta_session, const TEEC_UUID *uuid) {
  char cmd_str[CMD_LEN] = { 0 };
  uint32_t return_origin = 0;
  uint32_t i = 0;

  EmulSession *emul_session = (EmulSession *)ta_session;
  TEEC_Result res = InitListener(&emul_session->listener);

  if (TEE_SUCCESS != res) {
    goto exit;
  }

  emul_session->should_stop = false;

  if (pthread_create(&emul_session->listener.thread_,
                     NULL, listener_thread, emul_session)) {
    res = TEEC_ERROR_GENERIC;
    goto exit;
  }

  int write_offset = snprintf(emul_session->ta_str_,
                              sizeof(emul_session->ta_str_), "%08x%04x%04x",
                              uuid->timeLow, uuid->timeMid,
                              uuid->timeHiAndVersion);

  for (i = 0; i < sizeof(uuid->clockSeqAndNode); i++) {
    write_offset += snprintf(emul_session->ta_str_ + write_offset,
                             sizeof(emul_session->ta_str_) - write_offset,
                             "%02x", uuid->clockSeqAndNode[i]);
  }
  memcpy(&emul_session->ta_uuid, uuid, sizeof(TEEC_UUID));
  snprintf(cmd_str, sizeof(cmd_str), "./emul_%s &", emul_session->ta_str_);
  system(cmd_str);

exit:
  return res;
}

TEEC_Result EmulUnloadTA(void *ta_session) {
  EmulSession *emul_session = (EmulSession *)ta_session;
  ListenerInfo *li = NULL;

  if (!ta_session) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  li = &emul_session->listener;

  char sock_name[108] = { 0 };
  emul_session->should_stop = true;
  if (li->thread_) {
    pthread_join(li->thread_, NULL);
  }
  if (li->sock_ >= 0) {
    close(li->sock_);
  }
  snprintf(sock_name, sizeof(sock_name) - 1, "%u", li->sock_id_);
  unlink(sock_name);
  li->sock_ = -1;
  return TEEC_SUCCESS;
}

PlatformClientImpl g_emulator_platform_impl = {
  EmulLoadTA,
  EmulUnloadTA,
  EmulSendCommand,
  NULL,
  NULL,
  NULL,
  NULL,
};

PlatformClientImpl *GetPlatformClientImpl(void) {
  return &g_emulator_platform_impl;
}

size_t GetPlatformClientImplSize(void) {
  return sizeof(EmulSession);
}

void ClientImplSetParentContext(void *session, void *context) {
  EmulSession *emul_session = (EmulSession *)session;

  if (emul_session) {
    emul_session->parent_context = context;
  }
}

void *ClientImplGetParentContext(void *session) {
  void *ret = NULL;
  EmulSession *emul_session = (EmulSession *)session;

  if (emul_session) {
    ret = emul_session->parent_context;
  }
  return ret;
}
