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

#include "smci_example_app.h"
#include "CAppLoader.h"
#include "CCallbackObjectExample_open.h"
#include "IAppController.h"
#include "IAppLoader.h"
#include "IClientEnv.h"
#include "ICallbackObjectExample.h"
#include "IMemRegion.h"
#include "BufferAllocatorWrapperSim.h"
#include "ISMCIExampleApp.h"
#include "MemObj.h"
#include "TZCom.h"
#include "alog.h"
#include "map_trusted_application.h"
#include "object.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#define ION_BUFFER_ALIGNMENT 0x1000

/* Similar to Android LOG_TAG, define a tag that appears when logging from this
 * application. */
static char const LOG_TAG[] = "SMCInvoke_Example_CA";

/* Helper function to build a printable string from an array of bytes, used to print
 * hashes. */
static const char *stringFromHash(const uint8_t *inHash, size_t inHashLen)
{
  static char outString[(2 * ISMCIExampleApp_HASH_SHA256_SIZE) + 1];
  size_t index = 0;

  for (size_t i = 0; i < inHashLen; i++) {
    index += snprintf(outString + index, sizeof(outString) - index, "%02X", inHash[i]);
  }

  return outString;
}

static int32_t run_shared_memory_example(Object *appObj)
{
  int32_t ret = Object_OK;
  Object sharedMemObj = Object_NULL;
  BufferAllocatorSimHandle *ionHandle = NULL;

  ionHandle = CreateDmabufHeapBufferAllocatorSim();

  /* Allocate an Ion buffer for sharing memory. */
  size_t allocSize = sysconf(_SC_PAGESIZE);
  int ion_fd = DmabufHeapAllocSim(ionHandle, allocSize, ION_BUFFER_ALIGNMENT);

  if (ion_fd < 0) {
    ALOGE("Ion buffer allocation failed!");
    return Object_ERROR;
  }

  /* Put a string into this memory. Generate a "random" number for the message. */
  char const message[] = "A random number from the CA:";
  srand(time(0));
  int randNum = rand();
  snprintf(mapGetBuffer(ionHandle),mapGetBufferSize(ionHandle), "%s %d", message, randNum);

  /* Call MemObj_new which will create a memory object that represents our ion buffer, with
   * read/write permission - allowing the TA-side to write data to the shared memory.
   */
  sharedMemObj = MemObj_new(mapGetBuffer(ionHandle), mapGetBufferSize(ionHandle), IMemRegion_PERM_RW, true);
  if (Object_isNull(sharedMemObj)) {
    ALOGE("Mapping sharedMemObj failed!");
    ret = Object_ERROR;
    goto sharedMemoryExample_cleanup;
  }

  /* Call into ISMCIExampleApp and pass it our memory object. */
  ret = ISMCIExampleApp_sharedMemoryExample(*appObj, sharedMemObj);
  if (Object_isERROR(ret)) {
    ALOGE("ISMCIExampleApp_sharedMemoryExample failed with error: %d!", ret);
  }

  /* Ensure that the last character of what we're about to print is \0. */
  ((char *)mapGetBuffer(ionHandle))[mapGetBufferSize(ionHandle) - 1] = '\0';

  /* Print the buffer to show that the TA has modified it. */
  ALOGD("%s", (char *)mapGetBuffer(ionHandle));

sharedMemoryExample_cleanup:
  Object_ASSIGN_NULL(sharedMemObj);
  freeSim(ion_fd);
  DmabufHeapBufferFreeSim(ionHandle);
  return ret;
}

static int32_t run_hash_example(Object *appObj)
{
  int32_t ret;

  const char stringToHash[] = "String to hash";

  /* We use -1 for the size, since we don't want to hash the trailing '\0' character. */
  const size_t strLen = sizeof(stringToHash) - 1;

  uint8_t digest[ISMCIExampleApp_HASH_SHA256_SIZE];
  const size_t digestLen = sizeof(digest);
  size_t digestLenOut;
  const char *printHashString;

  ret = ISMCIExampleApp_computeHash(
    *appObj, &stringToHash, strLen, ISMCIExampleApp_HASH_SHA256, &digest, digestLen, &digestLenOut);

  if (Object_isERROR(ret)) {
    ALOGE("Computing hash failed with error: %d", ret);
    goto hashExampleCleanup;
  }

  if (digestLenOut == 0) {
    ALOGE("Returned hash had a length of zero!");
    ret = Object_ERROR;
    goto hashExampleCleanup;
  }
  printHashString = stringFromHash(digest, digestLenOut);
  ALOGI("Hash: %s", printHashString);

hashExampleCleanup:
  return ret;
}

/* The implementation for this example resides primarily in CSMCIExampleApp and
 * CAdder. This example demonstrates the usage of TA services. Services allow a
 * caling TA to use an interface exposed by a service TA, so long as the calling
 * TA has the privilege to access it defined in its SConscript. */
static int32_t run_service_example(Object *appObj)
{
  int32_t ret;

  ret = ISMCIExampleApp_serviceExample(*appObj);

  if (Object_isERROR(ret)) {
    ALOGE("Running service example failed with error: %d!", ret);
  }

  return ret;
}

static int32_t run_callback_object_example(Object *appObj)
{
  int32_t ret;
  Object callbackObj;

  ret = CCallbackObjectExample_open(&callbackObj);
  if (Object_isERROR(ret)) {
    ALOGE("CCallbackObjectExample_open failed with error: %d!", ret);
    return ret;
  }

  static const char msg[] = "Local object invocation.";

  /* callbackObj resides in the client side. Invoke it locally first. */
  ret = ICallbackObjectExample_print(callbackObj, msg, sizeof(msg));
  if (Object_isERROR(ret)) {
    ALOGE("ICallbackObjectExample_print failed with error: %d!", ret);
    goto out;
  }

  /*
   * Now pass the local object as an argument in an ISMCIExampleApp invocation. The TA will make an
   * invocation on the callback object.
   */
  ret = ISMCIExampleApp_callbackObjectExample(*appObj, callbackObj);
  if (Object_isERROR(ret)) {
    ALOGE("ISMCIExampleApp_callbackObjectExample failed with error: %d!", ret);
  }

out:
  Object_ASSIGN_NULL(callbackObj);
  return ret;
}

static int32_t run_log_sink_example(Object *appObj)
{
  int32_t ret;

  ret = ISMCIExampleApp_logSinkExample(*appObj);

  if (Object_isERROR(ret)) {
    ALOGE("Running log sink example failed with error: %d!", ret);
  }

  return ret;
}

/* Examples showcasing SMCInvoke-based TA usage. */
int32_t run_smcinvoke_ta_example(ta_image_data *img_data, Object serviceTAMemObj)
{
  Object clientEnv = Object_NULL;  // A Client Environment that can be used to
                                   // get an IAppLoader object

  Object appLoader = Object_NULL;                        // IAppLoader object that allows us to load
                                                         // the TA in the trusted environment
  Object appController[2] = {Object_NULL, Object_NULL};  // AppController contains a reference to
                                                         // the app itself, after loading.
  Object appObj = Object_NULL;  // An interface to our TA that allows us to send
                                // commands to it.
  int32_t ret = Object_OK;

  ret = TZCom_getClientEnvObject(&clientEnv);

  if (Object_isERROR(ret)) {
    ALOGE("Failed to obtain clientenv from TZCom");
    clientEnv = Object_NULL;
    goto smci_cleanup;
  }

  /* Using the clientEnv object we retrieved, obtain an appLoader by
   * specifying its UID in the call to IClientEnv_open */
  ret = IClientEnv_open(clientEnv, CAppLoader_UID, &appLoader);
  if (Object_isERROR(ret)) {
    ALOGE("Failed to get apploader.");
    appLoader = Object_NULL;
    goto smci_cleanup;
  }

  ALOGD("Succeeded in getting apploader!");

  ret = IAppLoader_loadFromBuffer(
    appLoader, img_data->buffer, img_data->buffer_size, &appController[EXAMPLE_TA]);

  if (Object_isERROR(ret)) {
    ALOGE("Loading Example TA failed with error: %d!", ret);
    appController[EXAMPLE_TA] = Object_NULL;
    goto smci_cleanup;
  }

  ret = IAppLoader_loadFromRegion(appLoader, serviceTAMemObj, &appController[EXAMPLE_SERVICE_TA]);

  /*
   * Now that the TA has been loaded the memory object can be released.
   *
   * Object_ASSIGN_NULL releases the object and then sets it to Object_NULL. This means we can
   * safely call Object_ASSIGN_NULL or Object_RELEASE_IF again on the object and avoid a use after
   * free error.
   */
  Object_ASSIGN_NULL(serviceTAMemObj);

  if (Object_isERROR(ret)) {
    ALOGE("Loading Example Service TA failed with error: %d!", ret);
    appController[EXAMPLE_SERVICE_TA] = Object_NULL;
    goto smci_cleanup;
  }

  ALOGD("loading TAs succeeded!");

  /* For our example TA we need an AppObject, which will be our ISMCIExampleApp object.
   * since we're not using an interface from our service TA in our CA, we don't need to call
   * IAppController_getAppObject for that TA. */
  ret = IAppController_getAppObject(appController[EXAMPLE_TA], &appObj);
  if (Object_isERROR(ret)) {
    ALOGE("Getting app object for Example TA failed.");
    appObj = Object_NULL;
    goto smci_cleanup;
  }

  ALOGD("Getting app object for Example TA succeeded!");

  /* Run our examples. */
  ret = run_service_example(&appObj);
  if (Object_isERROR(ret)) {
    ALOGE("Running service example failed with error: %d!", ret);
    goto smci_cleanup;
  }

  ret = run_hash_example(&appObj);
  if (Object_isERROR(ret)) {
    ALOGE("Running hash example failed with error: %d!", ret);
    goto smci_cleanup;
  }

  ret = run_shared_memory_example(&appObj);
  if (Object_isERROR(ret)) {
    ALOGE("Running shared memory example failed with error: %d!", ret);
    goto smci_cleanup;
  }

  ret = run_callback_object_example(&appObj);
  if (Object_isERROR(ret)) {
    ALOGE("Running callback object example failed with error: %d!", ret);
    goto smci_cleanup;
  }

  ret = run_log_sink_example(&appObj);
  if (Object_isERROR(ret)) {
    ALOGE("Running log sink example failed with error: %d!", ret);
  }

smci_cleanup:
  Object_ASSIGN_NULL(appObj);
  Object_ASSIGN_NULL(serviceTAMemObj);
  Object_ASSIGN_NULL(appController[EXAMPLE_TA]);
  Object_ASSIGN_NULL(appController[EXAMPLE_SERVICE_TA]);
  Object_ASSIGN_NULL(appLoader);
  Object_ASSIGN_NULL(clientEnv);

  return ret;
}
