/**
 * @file teec_common_qsee.c
 * @brief Common client interface implementation based on qsee API
 * @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 <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <custom_alloc.h>
#include <teec_callback_handler.h>
#include <protocol.h>
#include <pthread.h>
#include <QSEEComAPI.h>
#include <teec_param_utils.h>
#include <tees_log.h>

#define MAX_QSEE_UUID_LEN  17
#define PROV_TA_PATH       "/firmware/image/"
#define PROV_TA_PATH_ALT   "/vendor/firmware_mnt/image/"
#define QSEE_SUCCESS       0
#define MAX_RETRIES        100 //should be more than num of parallel processes
#define SHUTDOWN_WAIT_TIME 2 //seconds

#ifdef QSEE_DISABLE_INTERNAL_LISTENER
static const bool use_listener;
#else
static const bool use_listener = true;
#endif

typedef struct QseeSessionStruct {
  struct QSEECom_handle *qsee_com_handle;
  struct QSEECom_handle *listener_handle;
  void *shmem_start;
  void *parent_context;
  Memmngr memmgr;
  pthread_t listener_thread;
  uint32_t should_stop;
  uint32_t listener_id;
  TEEC_UUID uuid;
} QseeSession;

void *listener_thread(void *data) {
  int32_t ret;
  QseeSession *session = (QseeSession *)data;
  struct QSEECom_handle *qsee_com_handle = session->listener_handle;
  PersObjectCmd *command = (PersObjectCmd *)qsee_com_handle->ion_sbuffer;

  while (!session->should_stop) {
    // We need intermediate variables to avoid compilation warning when passing
    // a pointer to the field of packed structure.
    uint32_t data_len;
    U64 teec_oper;

    ret = QSEECom_receive_req(qsee_com_handle,
                              qsee_com_handle->ion_sbuffer,
                              sizeof(PersObjectCmd));
    if (ret) {
      MB_LOGW("Failed to receive PO request!\n");
      break;
    }

    if (session->should_stop) {
      break;
    }

    data_len = command->data_len;
    teec_oper = command->teec_oper;

    command->cmd_id = (uint32_t)MbHandleSWdCommand(command->cmd_id,
                                                   &session->uuid,
                                                   command->id,
                                                   command->id_len,
                                                   command->data,
                                                   &data_len,
                                                   &teec_oper);
    command->data_len = data_len;
    command->teec_oper = teec_oper;

    ret = QSEECom_send_resp(qsee_com_handle,
                            qsee_com_handle->ion_sbuffer,
                            sizeof(PersObjectCmd));
    if (ret) {
      MB_LOGW("Failed to send response to PO!\n");
      break;
    }
  }

  return NULL;
}

static TEEC_Result TeecUuidToQseeUuid(const TEEC_UUID *uuid, char *qsee_uuid) {
  uint32_t i = 0;
  uint32_t j = 0;
  TEEC_Result res = TEEC_SUCCESS;

  if (!uuid || !qsee_uuid) {
    res = TEEC_ERROR_BAD_PARAMETERS;
    goto exit;
  }

  qsee_uuid[i++] = (uuid->timeLow & 0xFF000000) >> 24;
  qsee_uuid[i++] = (uuid->timeLow & 0xFF0000) >> 16;
  qsee_uuid[i++] = (uuid->timeLow & 0xFF00) >> 8;
  qsee_uuid[i++] = (uuid->timeLow & 0xFF);

  qsee_uuid[i++] = (uuid->timeMid & 0xFF00) >> 8;
  qsee_uuid[i++] = (uuid->timeMid & 0xFF);

  qsee_uuid[i++] = (uuid->timeHiAndVersion & 0xFF00) >> 8;
  qsee_uuid[i++] = (uuid->timeHiAndVersion & 0xFF);

  for (j = 0; j < sizeof(uuid->clockSeqAndNode); j++) {
    qsee_uuid[i++] = uuid->clockSeqAndNode[j];
  }

exit:
  return res;
}

/**
 * @brief Wrapper for QSEECom_shutdown_app.
 * Repeats shutdown after delay on error.
 *
 * @param[in] handle The device handle
 * @return TEEC_SUCCESS on success, TEEC_ERROR_GENERIC on failure.
 */
static TEEC_Result QseeShutdownApp(struct QSEECom_handle **handle) {
  int ret;

  ret = QSEECom_shutdown_app(handle);

  if (ret && *handle) {
    sleep(SHUTDOWN_WAIT_TIME);
    if (QSEE_SUCCESS != QSEECom_shutdown_app(handle)) {
      MB_LOGE("Failed to unload secure application!\n");
      return TEEC_ERROR_GENERIC;
    }
  }

  return TEEC_SUCCESS;
}

TEEC_Result QseeUnloadTA(void *ta_session) {
  TEEC_Result status = TEEC_ERROR_GENERIC;
  QseeSession *qsee_session = (QseeSession *)ta_session;

  if (!ta_session) {
    goto exit;
  }

  if (NULL == qsee_session->qsee_com_handle) {
    goto exit;
  }

  qsee_session->should_stop = 1;

  if (use_listener) {
    QSEECom_unregister_listener(qsee_session->listener_handle);
  }

  if (!QseeShutdownApp(&qsee_session->qsee_com_handle)) {
    goto exit;
  }

  qsee_session->qsee_com_handle = NULL;

  if (use_listener) {
    pthread_join(qsee_session->listener_thread, NULL);
  }

  status = TEEC_SUCCESS;
exit:
  return status;
}

static uint32_t QseeGenListenerId(QseeSession *qsee_session) {
  (void)qsee_session;

  static bool seeded = false;
  uint32_t retval = 0;

  // RNG should be seeded only once, thus rand() returns a new value every call
  // urandom is overkill for generating listener IDs - they are not critical security params
  if (!seeded) {
    srand(time(NULL));
    seeded = true;
  }

  retval = rand();

  // qsee listener id should not be zero.
  // recommended range for id is 0x00010000 ~ 0xffffffff
  if (retval < 0x00010000) {
    retval += 0x00010000;
  }

  return retval;
}

TEEC_Result QseeLoadTA(void *ta_session, const TEEC_UUID *uuid) {
  TEEC_Result status = TEEC_SUCCESS;
  char qsee_uuid[MAX_QSEE_UUID_LEN] = { 0 };
  QseeSession *qsee_session = (QseeSession *)ta_session;
  int ret;

  if (!ta_session || !uuid ||
      TEEC_SUCCESS != TeecUuidToQseeUuid(uuid, qsee_uuid)) {
    status = TEEC_ERROR_BAD_PARAMETERS;
    goto exit;
  }

  memcpy(&qsee_session->uuid, uuid, sizeof(TEEC_UUID));

  const char *ta_path;
  if (!access(PROV_TA_PATH, 0)) {
    ta_path = PROV_TA_PATH;
  } else {
    ta_path = PROV_TA_PATH_ALT;
  }

  if (QSEE_SUCCESS != QSEECom_start_app(&qsee_session->qsee_com_handle,
                                        ta_path,
                                        qsee_uuid,
                                        QSEECOM_ALIGN(sizeof(ProtocolCmd) +
                                                      ((ptrdiff_t)qsee_session->shmem_start) % MALLOC_ALIGN +
                                                      MAX_SHAREDMEM_SIZE)))
  {
    status = (errno == EINVAL ? TEEC_ERROR_TARGET_DEAD : TEEC_ERROR_GENERIC);
    goto exit;
  }

  qsee_session->shmem_start = qsee_session->qsee_com_handle->ion_sbuffer + sizeof(ProtocolCmd);
  qsee_session->shmem_start += ((ptrdiff_t)qsee_session->shmem_start) % MALLOC_ALIGN;

  if (!custom_alloc_init(&qsee_session->memmgr,
                         qsee_session->shmem_start,
                         MAX_SHAREDMEM_SIZE)) {
    MB_LOGW("Failed to initialize internal shared memory buffer!\n");
  }

  qsee_session->should_stop = 0;
  qsee_session->listener_id = 0;

  if (!use_listener) {
    MB_LOGE("Skip registration of listener!\n");
    goto exit;
  }

  int32_t guard = MAX_RETRIES;
  do {
    qsee_session->listener_id = QseeGenListenerId(qsee_session);
    ret = QSEECom_register_listener(&qsee_session->listener_handle,
                                    qsee_session->listener_id,
                                    sizeof(PersObjectCmd), 0);

    if (ret == 0)
      break;
    else if (ret != QSEECOM_LISTENER_ALREADY_REGISTERED) {
      status = (ENOMEM == errno ? TEEC_ERROR_OUT_OF_MEMORY : TEEC_ERROR_GENERIC);
      goto cleanup;
    }
  } while (ret && (guard-- > 0));

  if (ret != 0 || pthread_create(&qsee_session->listener_thread,
                      NULL, listener_thread, qsee_session)) {
    status = TEEC_ERROR_GENERIC;
  }

cleanup:
  if (TEEC_SUCCESS != status) {
    QseeShutdownApp(&qsee_session->qsee_com_handle);
  }

exit:
  return status;
}

TEEC_Result QseeSendCommand(void *ta_session,
                            uint32_t command_id,
                            TEEC_Operation *operation,
                            uint32_t *return_origin,
                            int32_t timeout) {
  (void)timeout;

  int32_t qsee_res = QSEE_SUCCESS;
  QseeSession *qsee_session = (QseeSession *)ta_session;
  ProtocolCmd *command = NULL;
  TEEC_Result ret = TEEC_ERROR_BAD_PARAMETERS;

  if (!qsee_session) {
    goto exit;
  }

  command = (ProtocolCmd *)qsee_session->qsee_com_handle->ion_sbuffer;

  // Return origin: "COMMS" by default because of possible communication errors.
  command->return_origin = TEEC_ORIGIN_COMMS;

  command->cmd_id = command_id;
  command->param_types = 0;

  if (operation) {
    // We need intermediate variables to avoid compilation warning when passing
    // a pointer to the field of packed structure.
    U64 teec_oper = {0};

    PtrToU64(operation, &teec_oper);
    command->teec_oper = teec_oper;
    command->param_types = operation->paramTypes;
    ret = FillCommandArgs(command, operation, ta_session);
    if (TEEC_SUCCESS != ret) {
      command->return_origin = TEEC_ORIGIN_API;
      goto exit;
    }
  }

  /* Call */
  qsee_res = QSEECom_set_bandwidth(qsee_session->qsee_com_handle, true);

  if (QSEE_SUCCESS != qsee_res) {
    ret = TEEC_ERROR_COMMUNICATION;
    goto exit;
  }

  /* Send command to TA */
  command->cmd_ret = TEEC_ERROR_TARGET_DEAD; /* Prepare for silent TA death */
  command->service_data = qsee_session->listener_id;
  qsee_res = QSEECom_send_cmd(
    qsee_session->qsee_com_handle,
    qsee_session->qsee_com_handle->ion_sbuffer,
    sizeof(ProtocolCmd) + ((ptrdiff_t)qsee_session->shmem_start) % MALLOC_ALIGN + MAX_SHAREDMEM_SIZE,
    qsee_session->qsee_com_handle->ion_sbuffer,
    QSEECOM_ALIGN(sizeof(ProtocolCmd) +
                  MAX_SHAREDMEM_SIZE) -
    (sizeof(ProtocolCmd) +
     MAX_SHAREDMEM_SIZE));

  qsee_res |= QSEECom_set_bandwidth(qsee_session->qsee_com_handle, false);

  if (QSEE_SUCCESS != qsee_res) {
    ret = (errno == EINVAL ? TEEC_ERROR_TARGET_DEAD : 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 (return_origin && command) {
    *return_origin = command->return_origin;
  }

  return ret;
}

uint32_t QseeMapBuffer(void *session, void *ptr, size_t size) {
  uint32_t ret = 0;

  (void)size;

  QseeSession *qsee_session = (QseeSession *)session;

  if (!qsee_session) {
    goto exit;
  }

  if (ptr >= qsee_session->shmem_start &&
      (char *)ptr < ((char *)qsee_session->shmem_start + MAX_SHAREDMEM_SIZE)) {
    ret = (uint32_t)((unsigned char *)ptr - qsee_session->qsee_com_handle->ion_sbuffer);
  }

exit:
  return ret;
}

void *QseeAllocateForSession(void *session, size_t size) {
  void *ret = NULL;
  QseeSession *qsee_session = (QseeSession *)session;

  if (qsee_session) {
    ret = custom_malloc(&qsee_session->memmgr, size);
  }

  return ret;
}

void QseeFreeForSession(void *session, void *ptr) {
  QseeSession *qsee_session = (QseeSession *)session;

  if (ptr && qsee_session &&
      custom_is_ptr_in_range(&qsee_session->memmgr, ptr)) {
    custom_free(&qsee_session->memmgr, ptr);
  }
}

int QseeSessionCanReleaseBuffer(void *session, void *ptr) {
  int ret = 0;
  QseeSession *qsee_session = (QseeSession *)session;

  if (ptr && qsee_session) {
    ret = custom_is_ptr_in_range(&qsee_session->memmgr, ptr);
  }

  return ret;
}

PlatformClientImpl g_qsee_platform_impl = {
  QseeLoadTA,
  QseeUnloadTA,
  QseeSendCommand,
  QseeMapBuffer,
  QseeAllocateForSession,
  QseeFreeForSession,
  QseeSessionCanReleaseBuffer,
};

PlatformClientImpl *GetPlatformClientImpl(void) {
  return &g_qsee_platform_impl;
}

size_t GetPlatformClientImplSize(void) {
  return sizeof(QseeSession);
}

void ClientImplSetParentContext(void *session, void *context) {
  QseeSession *qsee_session = (QseeSession *)session;

  if (qsee_session) {
    qsee_session->parent_context = context;
  }
}

void *ClientImplGetParentContext(void *session) {
  void *ret = NULL;
  QseeSession *qsee_session = (QseeSession *)session;

  if (qsee_session) {
    ret = qsee_session->parent_context;
  }

  return ret;
}
