/**
  * Copyright (c) 2020 by Qualcomm Technologies, Inc. 
  * All Rights Reserved. 
  * Confidential and Proprietary - Qualcomm Technologies, Inc.
  */

#include <stringl.h>
#include <stdbool.h>
#include "qsee_log.h"
#include "qsee_services.h"
#include "sampleapp.h"

/** send a LISTENER_FREE_BUFF cmd to listener service. */
static int32_t send_free_buff_cmd(void)
{
  uint32_t cmdId        = LISTENER_FREE_BUFF;
  uint32_t cmdStatus    = SAMPLEAPP_SUCCESS;
  int qseeRes = SAMPLEAPP_SUCCESS;

  qseeRes = qsee_request_service(LISTENER_ALLOC_TEST_SERVICE, &cmdId, sizeof(uint32_t), &cmdStatus, sizeof(int32_t));

  if (qseeRes) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: qsee_request_service generic error :%d", __func__, qseeRes);
    cmdStatus = SAMPLEAPP_FAILED;
  }

  return ((int32_t)cmdStatus);
}

/* check if listener is present */
static bool is_listener_present(void) 
{
  uintptr_t buf;
  uint32_t retval = SAMPLEAPP_SUCCESS;
  struct listener_buff_req req;
  struct listener_buff_res res;

  req = (struct listener_buff_req) {
    .cmdId       = LISTENER_ALLOC_BUFF,
    .bufLen      = SIZE_2MB,
    .is64        = LISTENER_BUFF_ARCH64
  };

  retval = qsee_request_service(LISTENER_ALLOC_TEST_SERVICE, &req, sizeof(struct listener_buff_req), &res, sizeof(struct listener_buff_res));
  if (retval) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: qsee_request_service generic error :%d", __func__, retval);
  }
  else {
    retval = send_free_buff_cmd();
  }

  if (retval) {
    return false;
  }
  else {
    return true;
  }
}

/** receive a shared buffer from listener, validate and register it */
static int32_t listener_buff_alloc(uint32_t len, uintptr_t* app_buf)
{
  int32_t ret = SAMPLEAPP_SUCCESS;
  int qseeRes = SAMPLEAPP_SUCCESS ;
  struct listener_buff_req req;
  struct listener_buff_res res;
  
  /* overflow check for buffer len not needed since len is not exposed outside this file */
  req = (struct listener_buff_req) {
      .cmdId       = LISTENER_ALLOC_BUFF,
      .bufLen      = len,
      .is64        = LISTENER_BUFF_ARCH64
  };

  /* send the command to the listener */
  qseeRes = qsee_request_service(LISTENER_ALLOC_TEST_SERVICE, &req, sizeof(struct listener_buff_req), &res, sizeof(struct listener_buff_res));

  if (qseeRes) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: qsee_request_service generic error :%d", __func__, qseeRes);
    ret = SAMPLEAPP_FAILED;
    goto bail;
  }

  /* validate size of returned buffer */
  ret = (res.bufSize < len);
  if (ret) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: wrong buffer size: %u, expected: %u", __func__, res.bufSize, len);
    goto bail;
  }

  /* validate if buffer is 2MB aligned*/
  ret = !(IS_ALIGNED(res.buffPtr, SIZE_2MB));
  if (ret) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: buffer address 0x%08X is not 2MB aligned", __func__, res.buffPtr);
    goto bail;
  }

  *app_buf = res.buffPtr;

  /* register the buffer */
  ret = qsee_register_shared_buffer((void *)*app_buf, len);
  if (ret) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: qsee_register_shared_buffer: 0x%08X", __func__, ret);
    goto bail;
  }

  /* prepare the shared buffer before secure side reads it */
  ret = qsee_prepare_shared_buf_for_secure_read((void *)(*app_buf), len);
  if (ret) {
    qsee_deregister_shared_buffer((void *)(*app_buf));
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: qsee_prepare_shared_buf_for_secure_read: 0x%08X", __func__, ret);
    goto bail;
  }

  secure_memset((void *)(*app_buf), 0, len);

bail:
  if (ret) {
    if (!qseeRes) {
      send_free_buff_cmd();
    }
    ret = SAMPLEAPP_FAILED;
  }

  return ret;
}

/* free the allocated buffer */
static int32_t listener_buff_free(uint32_t len, uintptr_t* app_buf)
{
  int32_t ret = SAMPLEAPP_SUCCESS;

  ret = qsee_prepare_shared_buf_for_nosecure_read((void *)*app_buf, len);
  if (ret) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: buffer non secure read fail");
    goto bail;
  }

  ret = qsee_deregister_shared_buffer((void *)*app_buf);
  if (ret) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: qsee_deregister_shared_buffer: 0x%08X", ret);
    goto bail;
  }

  ret = send_free_buff_cmd();

bail:
  if (ret) {
    ret = SAMPLEAPP_FAILED;
  }

  return (ret);
}

/* allocate a buffer of given length and then free it */
static int32_t test_listener_buff_alloc(uint32_t buf_len)
{
  uintptr_t buf;

  if (listener_buff_alloc(buf_len, &buf)) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: FAILED at listener_buff_alloc", __func__);
    return SAMPLEAPP_FAILED;
  }
  
  if (listener_buff_free(buf_len, &buf)) {
    qsee_log(QSEE_LOG_MSG_ERROR, "%s: FAILED to free buffer", __func__);
    return SAMPLEAPP_FAILED;
  }
  return SAMPLEAPP_SUCCESS;
}

/* main function. If listener not found, test is skipped and SAMPLEAPP_SUCCESS returned*/
int run_listener_buff_test(void)
{
  /* is_listener_present returns 1 if listener present and 0 if not */
  if (!(is_listener_present())) {
    qsee_log(QSEE_LOG_MSG_DEBUG, "****Listener not found. Skipping test");
    return SAMPLEAPP_SUCCESS;
  }
  else {
    return test_listener_buff_alloc(SIZE_2MB);
  }
}