#include "pa_api.h"

#include "nwd_log.h"
#include "xattr.h"

#include <linux/limits.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <five_api.h>
#include <scl/memory.h>

#if defined(VERSION_NAME) && defined(VERSION_SUFFIX)
#define VERSION_STRING (VERSION_NAME VERSION_SUFFIX)
#else
#error "Please, define VERSION_NAME and VERSION_SUFFIX"
#endif

static const char kVersionString[] __attribute__((used)) = VERSION_STRING;

enum {
  kIntegrityPathLen = 256,
  kIntegrityResetReasonMaxLen = 256
};

/**
 * @brief Helper function to read string from file in /proc/<pid>/integrity
 * @param [in] file_name name of file in integrity dir
 * @param [out] out_str pointer to output string
 * @return ::PA_TZ_SUCCESS if command is processed successfully,
 *         ::PA_TZ_GENERAL_ERROR if command can not be processed
 */
static PaResult FiveTaskReadIntegrityDirString(const char *file_name,
                                               char *out_str);

/**
 * @brief Dumps integrity reset reasons to logcat
 */
static void LogIntegrityResetReason();

static PaResult FiveTaskReadIntegrityDirString(const char *file_name,
                                               char *out_str) {
  char path[kIntegrityPathLen] = {};

  if (!out_str) {
    return PA_INVALID_ARGUMENTS;
  }

  snprintf(path, sizeof(path), "/proc/self/integrity/%s", file_name);

  FILE *f = fopen(path, "r");
  if (!f) {
    return PA_GENERAL_ERROR;
  }

  size_t bytes_read = fread(out_str, 1, kIntegrityResetReasonMaxLen, f);
  if (bytes_read == 0) {
    fclose(f);
    return PA_GENERAL_ERROR;
  }

  //skip trailing new line
  out_str[bytes_read - 1] = '\0';

  fclose(f);
  return PA_SUCCESS;
}

static void LogIntegrityResetReason() {
  char reset_cause[kIntegrityResetReasonMaxLen] = "unknown";
  char reset_file[kIntegrityResetReasonMaxLen] = "unknown";

  do {
    PaResult result = FiveTaskReadIntegrityDirString("reset_cause",
                                                     reset_cause);
    if (result != PA_SUCCESS) {
      break;
    }

    result = FiveTaskReadIntegrityDirString("reset_file", reset_file);
    if (result != PA_SUCCESS) {
      break;
    }

  } while (0);

  LOG_I("FIVE integrity of current process is none, reason: %s, file: %s\n",
        reset_cause, reset_file);
}

PaResult PaHandlerCreate(PaHandler *handler) {
  if (NULL == handler) {
    LOG_E("Invalid arguments.\n");
    return PA_INVALID_ARGUMENTS;
  }

  TEES_LogSetLevel(TEES_LOG_LEVEL_DEBUG);
  LOG_I("Version: %s", kVersionString);

  handler->pid = getpid();
  handler->flags = getpid();
  handler->data = (uintptr_t)NULL;

  FiveIntegrity intg = INTEGRITY_NONE;
  FiveResult res = FiveTaskGetIntegrity(0, &intg);
  if (res != FIVE_SUCCESS) {
    LOG_E("Internal FIVE error.\n");
    return PA_GENERAL_ERROR;
  }

  LOG_I("FIVE integrity of current process: %x.\n", intg);
  if (intg == INTEGRITY_NONE) {
    LogIntegrityResetReason();
  }

  return PA_SUCCESS;
}

void PaHandlerDestroy(PaHandler *handler) {
  if (NULL == handler) {
    return;
  }

  scl_clean(handler, SCL_SIZEOF(*handler));
}
