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

/* ----------------------------------------------------------------------------
 *   Includes
 * ---------------------------------------------------------------------------- */
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>

#include "tee_client_api.h"
#include "run_gp_time.h"
#include "common.h"

#define NSEC_PER_SEC 1000000000ULL
#define NSEC_PER_MSEC 1000000ULL

// This is defined externally in ca_main.c
extern TEEC_UUID example_gpapp_ta_uuid;

/* ----------------------------------------------------------------------------
 *   Example time Operation(s)
 * ---------------------------------------------------------------------------- */
/**
 * This function shows how to form the parameters to call TEEC_InvokeCommand()
 *  1. Set value parameters to TEEC_Operation struct
 *  2. Set corresponding parameter types
 *  3. Call TEEC_InvokeCommand with appropriate command
 */
static TEEC_Result Invoke_Wait(TEEC_Session *session,
                               TEEC_Operation *operation,
                               uint32_t waitTimeMs,
                               uint32_t cancellable,
                               uint32_t *returnOrigin)
{
  TEEC_Result result = TEEC_SUCCESS;

  // 1. Set value parameters to TEEC_Operation struct
  operation->params[0].value.a = waitTimeMs;
  operation->params[1].value.a = cancellable;

  // 2. Set corresponding parameter types
  operation->paramTypes =
    TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_VALUE_INPUT, TEEC_NONE, TEEC_NONE);

  // 3. Call TEEC_InvokeCommand with appropriate command
  result = TEEC_InvokeCommand(session, EXAMPLE_TEE_WAIT_CMD, operation, returnOrigin);
  return result;
}

/**
 * This function shows how to:
 *  1. Open a context
 *  2. Open a session to the GP TA
 *  3. Request a secure world to wait for 1000 ms, the request is not cancellable
 *  4. Close the session
 *  5. Finalize the context
 */
TEEC_Result run_tee_wait(void)
{
  TEEC_Context context = {0};
  TEEC_Session session = {0};
  TEEC_Operation operation = {0};
  TEEC_Result result = TEEC_ERROR_GENERIC;
  uint32_t returnOrigin = 0xFFFFFFFF;
  uint32_t waitTimeMs = 1000;
  uint32_t cancellable = 0;

  printf("+ Run Invoke TEE_Wait \n");

  do {
    printf("1. Open a context\n");
    result = TEEC_InitializeContext(NULL, &context);
    if (result != TEEC_SUCCESS) {
      printf("Initialize context failed, ret = 0x%X.\n", result);
      break;
    }

    printf("2. Open a session\n");
    result = TEEC_OpenSession(
      &context, &session, &example_gpapp_ta_uuid, TEEC_LOGIN_USER, NULL, NULL, &returnOrigin);
    if (result != TEEC_SUCCESS) {
      printf("Open a session failed, ret = 0x%X.\n", result);
      break;
    }

    printf("3. Request a secure world wait for %u ms, the request is not cancellable\n",
           waitTimeMs);

    struct timespec start = {0}, end = {0};
    clock_gettime(CLOCK_MONOTONIC, &start);
    result = Invoke_Wait(&session, &operation, waitTimeMs, cancellable, &returnOrigin);
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t sTimeMs = (start.tv_nsec + (start.tv_sec * NSEC_PER_SEC)) / NSEC_PER_MSEC;
    uint64_t eTimeMs = (end.tv_nsec + (end.tv_sec * NSEC_PER_SEC)) / NSEC_PER_MSEC;

    printf("* Waited for %" PRIu64 " ms\n", eTimeMs - sTimeMs);

    if (result != TEEC_SUCCESS) {
      printf("Invoking TA failed, ret = 0x%X.\n", result);
    }
  } while (0);

  // Free resources
  if (session.imp != NULL) {
    printf("4. Close the session\n");
    TEEC_CloseSession(&session);
  }
  if (context.imp != NULL) {
    printf("5. Finalize the context\n");
    TEEC_FinalizeContext(&context);
  }

  printf("- Run Invoke TEE_Wait \n\n");

  return result;
}

static void *cancellingThread_fn(void *operation)
{
  usleep(1000);  // To ensure main thread to request TEE_Wait() before requesting cancellation
  printf("** Request cancellation.\n");
  TEEC_RequestCancellation((TEEC_Operation *)operation);
  return NULL;
}

/**
 * This function shows how to:
 *  1. Open a context
 *  2. Open a session to the GP TA
 *    + A thread is created to disturb main thread's TEE_Wait request, this thread will send
 * cancellation of the TEE_Wait.
 *  3. Request a secure world to wait for 2000 ms, the request is cancellable
 *  4. Close the session
 *  5. Finalize the context
 *
 * By using a separate thread this code demonstrates how a entire GP process is being cancelled.
 * Spawn a thread that triggers request cancellation to cancel the main thread's TEE_Wait request.
 * As soon as the thread is created it waits for a short time, in which main thread triggers
 * TEE_Wait request. After that the cancellation request is being send.
 */
TEEC_Result run_tee_wait_cancel(void)
{
  TEEC_Context context = {0};
  TEEC_Session session = {0};
  TEEC_Operation operation = {0};
  TEEC_Result result = TEEC_ERROR_GENERIC;
  uint32_t returnOrigin = 0xFFFFFFFF;

  printf("+ Run Invoke TEE_Wait and Cancel it \n");

  do {
    printf("1. Open a context\n");
    result = TEEC_InitializeContext(NULL, &context);
    if (result != TEEC_SUCCESS) {
      printf("Initialize context failed, ret = 0x%X.\n", result);
      break;
    }

    printf("2. Open a session\n");
    result = TEEC_OpenSession(
      &context, &session, &example_gpapp_ta_uuid, TEEC_LOGIN_USER, NULL, &operation, &returnOrigin);
    if (result != TEEC_SUCCESS) {
      printf("Open a session failed, ret = 0x%X.\n", result);
      break;
    }

    // Thread for cancel request
    printf("*. Spawn a thread to trigger cancellation request.\n");
    int pthreadResult = 0;

    pthread_t cancellingThread;
    pthreadResult = pthread_create(&cancellingThread, NULL, cancellingThread_fn, (void *)&operation);
    if (pthreadResult) {
      printf("pthread_create() error\n");
      break;
    }

    uint32_t waitTimeMs = 6000;
    uint32_t cancellable = 1;

    printf("3. Request a secure world to wait for %u ms, the request is cancellable\n", waitTimeMs);

    struct timespec start = {0}, end = {0};
    clock_gettime(CLOCK_MONOTONIC, &start);
    result = Invoke_Wait(&session, &operation, waitTimeMs, cancellable, &returnOrigin);
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t sTimeMs = (start.tv_nsec + (start.tv_sec * NSEC_PER_SEC)) / NSEC_PER_MSEC;
    uint64_t eTimeMs = (end.tv_nsec + (end.tv_sec * NSEC_PER_SEC)) / NSEC_PER_MSEC;

    printf("* Waited for %" PRIu64 " ms\n", eTimeMs - sTimeMs);

    // Check the result 'TEEC_ERROR_CANCEL' indicates that the TEE_Wait request has been cancelled
    if (result == TEEC_ERROR_CANCEL) {
      printf("4. The TEE_Wait request has been cancelled, ret = 0x%X\n", result);
    } else if (result == TEEC_SUCCESS) {
      printf("!The TEE_Wait timed out before it is cancelled, ret = 0x%X\n", result);
    } else {
      printf("!Request failed, ret = 0x%X", result);
    }

    pthreadResult = pthread_join(cancellingThread, NULL);
    if (pthreadResult) {
      printf("pthread_join() error\n");
    }
  } while (0);

  // Free resources
  if (session.imp != NULL) {
    printf("5. Close the session\n");
    TEEC_CloseSession(&session);
  }
  if (context.imp != NULL) {
    printf("6. Finalize the context\n");
    TEEC_FinalizeContext(&context);
  }

  printf("- Run Invoke TEE_Wait and Cancel it \n\n");

  return result;
}
