#include "base_command.h"

#include <stdio.h>
#include <stdlib.h>
#include <gtest/gtest.h>
#include <unistd.h>

static const char kTestAppName[] = "/vendor/bin/ftest_pa_driver";
static const char kTestAppNamev2[] = "/system/bin/application";
static const char kTestNonExecutable[] = "/data/local/tmp/Makefile";
static const int kMaxAppNameAuthRetryNum = 24;

static const uint32_t kIncorrectProcessNameLength = 1050;

static PaHandler sEarlyInitHandler;

__attribute__ ((constructor))
static void early_init(void) {
  PaHandlerCreate(&sEarlyInitHandler);
}

__attribute__ ((destructor))
static void early_deinit(void) {
  PaHandlerDestroy(&sEarlyInitHandler);
}

class AuthenticateNativeAppTest : public BaseCommandTest {
 public:
  AuthenticateNativeAppTest() {
  }
  virtual ~AuthenticateNativeAppTest() {
  }
};
TEST_F(AuthenticateNativeAppTest, 01_SimpleAuthentication_Passed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = kTestSimpleAuthenticate;
  tci_command.handler = handler;

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ(status, (TEEC_Result)TEEC_SUCCESS) << "Error is occurred during test execution";
  ASSERT_EQ(tci_command.tz_result, PA_TZ_SUCCESS) <<
      "Simple authentication of native application with correct handler SHOULD be passed";
}

TEST_F(AuthenticateNativeAppTest, 02_SimpleAuthentication_AnotherProcess_AuthenticationFailed) {
  TciCommand tci_command = {0};

  // Change Pid of provide handler
  handler.pid = UINT16_MAX;

  tci_command.cmdId = kTestSimpleAuthenticate;
  tci_command.handler = handler;

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ(status, (TEEC_Result)TEEC_SUCCESS) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_AF_TASK_IS_NOT_FOUND, tci_command.tz_result) <<
      "Simple authentication of native application with another process handler "
      "SHOULD be failed with TaskIsNotFound error";
}

TEST_F(AuthenticateNativeAppTest, 03_SimpleAuthenticationWithProvidedAppName_Passed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = KAuthenticateWithProvidedAppName;
  tci_command.handler = handler;
  memcpy(tci_command.app_name, kTestAppName, sizeof(kTestAppName));

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ((TEEC_Result)TEEC_SUCCESS, status) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_SUCCESS, tci_command.tz_result) <<
      "Authentication with provided app name of native application with "
      "correct application name SHOULD be passed";
}

TEST_F(AuthenticateNativeAppTest, 04_SimpleAuthenticationWithProvidedBadAppName_Failed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = KAuthenticateWithProvidedAppName;
  tci_command.handler = handler;
  memcpy(tci_command.app_name, kTestAppNamev2, sizeof(kTestAppNamev2));

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ((TEEC_Result)TEEC_SUCCESS, status) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_AF_APPNAME_IS_INCORRECT, tci_command.tz_result) <<
      "Authentication with provided app name of native application with "
      "incorrect application name SHOULD be failed";
}

TEST_F(AuthenticateNativeAppTest, 05_SimpleAuthenticationWithProvidedHugeAppName_Failed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = KAuthenticateWithProvidedAppName;
  tci_command.handler = handler;

  static char huge_process_name[kIncorrectProcessNameLength] = "";
  for (int i = 0; i < kIncorrectProcessNameLength; i++) {
    huge_process_name[i] = 'x';
  }

  memcpy(tci_command.app_name, huge_process_name, kIncorrectProcessNameLength);

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ((TEEC_Result)TEEC_SUCCESS, status) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_AF_APPNAME_IS_INCORRECT, tci_command.tz_result) <<
      "Authentication with provided app name of native application with "
      "huge application name SHOULD be failed";
}

TEST_F(AuthenticateNativeAppTest, 06_SimpleAuthenticationWithProvidedAppNameList_Passed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = KAuthenticateWithProvidedAppName;
  tci_command.handler = handler;

  // copy 2 incorrect names and 1 correct
  memcpy(tci_command.app_name, kTestAppNamev2, sizeof(kTestAppNamev2));
  memcpy(tci_command.app_name + sizeof(kTestAppNamev2), kTestAppNamev2, sizeof(kTestAppNamev2));
  memcpy(tci_command.app_name + 2 * sizeof(kTestAppNamev2), kTestAppName, sizeof(kTestAppName));

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ((TEEC_Result)TEEC_SUCCESS, status) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_SUCCESS, tci_command.tz_result) <<
      "Authentication with provided app name list of native applications with "
      "correct application name SHOULD be passed";
}

TEST_F(AuthenticateNativeAppTest, 07_SimpleAuthenticationWithProvidedWrongAppNameList_Failed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = KAuthenticateWithProvidedAppName;
  tci_command.handler = handler;

  size_t len = 0;
  for (int i = 0; i < kMaximalNameNumbers; i++) {
    strcpy(tci_command.app_name + len, kTestAppNamev2 + i);
    len += strlen(tci_command.app_name + len) + 1;
  }

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ((TEEC_Result)TEEC_SUCCESS, status) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_AF_APPNAME_IS_INCORRECT, tci_command.tz_result) <<
      "Authentication with provided wrong app name list of native applications "
      "SHOULD be failed";
}

TEST_F(AuthenticateNativeAppTest, 08_SimpleAuthenticationWithProvidedBigAppNameList_Failed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = KAuthenticateWithProvidedAppName;
  tci_command.handler = handler;

  size_t len = 0;
  for (int i = 0; i < 2 * kMaximalNameNumbers; i++) {
    strcpy(tci_command.app_name + len, kTestAppNamev2 + i);
    len += strlen(tci_command.app_name + len) + 1;
  }

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ((TEEC_Result)TEEC_SUCCESS, status) << "Error is occurred during test execution";
  ASSERT_EQ(PA_TZ_AF_APPNAME_IS_INCORRECT, tci_command.tz_result) <<
      "Authentication with provided wrong app name list of native applications "
      "SHOULD be failed";
}

TEST_F(AuthenticateNativeAppTest, 09_SimpleAuthenticationWithHandlerFromConstructor_Passed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = kTestSimpleAuthenticate;
  tci_command.handler = sEarlyInitHandler;

  TEEC_Result status = SendCommand(tci_command);

  ASSERT_EQ(status, (TEEC_Result)TEEC_SUCCESS) << "Error is occurred during test execution";
  ASSERT_EQ(tci_command.tz_result, PA_TZ_SUCCESS) <<
      "Simple authentication of native application with correct handler "
      "from constructor SHOULD be passed";

}

TEST_F(AuthenticateNativeAppTest, 10_AuthenticationAfterFork_Passed) {

  UnloadApp();

  pid_t child_pid = fork();
  ASSERT_NE(child_pid, -1)
    << "Error is occurred during fork execution: "
    << strerror(errno);

  if (child_pid == 0) {

    LoadApp();

    TciCommand tci_command = {0};

    tci_command.cmdId = kTestSimpleAuthenticate;
    tci_command.handler = handler;
    tci_command.handler.pid = getpid();

    TEEC_Result status = SendCommand(tci_command);

    //here we can't use ASSERT_EQ because on failure it is needed to exit from child
    EXPECT_EQ(status, (TEEC_Result)TEEC_SUCCESS)
      << "Error is occurred during test execution "
      << "(child;pid=" << getpid() << ")";
    EXPECT_EQ(tci_command.tz_result, PA_TZ_SUCCESS)
      << "Simple authentication of native application "
      << "(child;pid=" << getpid() << ")"
      << " with correct handler after fork SHOULD be passed";

    UnloadApp();

    exit(tci_command.tz_result);
  } else {
    int ret;
    waitpid(child_pid, &ret, 0);
    LoadApp();

    ASSERT_EQ(WEXITSTATUS(ret), 0) << "Child should return zero exit code";
  }
}

TEST_F(AuthenticateNativeAppTest, 11_AuthenticationAfterExecFail_Failed) {
  UnloadApp();

  // Since it is not possible to recover integrity to previous state in the end
  // of the test, it is necessary to run this test in isolated process
  pid_t child_pid = fork();
  if (child_pid == 0) {
    LoadApp();

    TciCommand tci_command = {0};

    tci_command.cmdId = KAuthenticateWithProvidedAppName;
    tci_command.handler = handler;
    memcpy(tci_command.app_name, kTestAppName, sizeof(kTestAppName));

    // Currently after execve to invalid executable file (+x flag must be set)
    // FIVE will set integrity to none
    int ret = execve(kTestNonExecutable, nullptr, nullptr);
    EXPECT_EQ(-1, ret) << "Execve SHOULD fail\n";

    if (ret == -1) {
      TEEC_Result status = SendCommand(tci_command);

      EXPECT_EQ(status, (TEEC_Result)TEEC_SUCCESS) << "Error is occurred "
                                                  "during test execution";
      EXPECT_EQ(tci_command.tz_result, PA_TZ_AF_INTEGRITY_IS_NONE) <<
          "Simple authentication of native application after execve failure "
          "SHOULD fail";
    }

    UnloadApp();

    exit(tci_command.tz_result);
  } else if (child_pid > 0) {
      int ret;
      waitpid(child_pid, &ret, 0);
      EXPECT_NE(WEXITSTATUS(ret), 0) << "Child should not return zero exit code";
  } else {
      EXPECT_NE(child_pid, -1)
        << "Error is occurred during fork execution: "
        << strerror(errno);
  }

  LoadApp();
}

TEST_F(AuthenticateNativeAppTest, 12_AppNameAuthentication_Passed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = kAuthenticateByAppName;
  memset(&tci_command.handler, 0, sizeof(tci_command.handler));
  strcpy(tci_command.app_name, kTestAppName);

  for (int i = 0; i < kMaxAppNameAuthRetryNum; ++i) {
    TEEC_Result status = SendCommand(tci_command);
    ASSERT_EQ(status, (TEEC_Result)TEEC_SUCCESS) << "Error is occurred during test execution";
    if (tci_command.tz_result != PA_TZ_AF_TASK_HAS_DUPLICATE) {
      break;
    }
    usleep(20);
  }
  ASSERT_EQ(tci_command.tz_result, PA_TZ_SUCCESS) <<
      "Application name authentication of native application with correct app name SHOULD be passed";
}

TEST_F(AuthenticateNativeAppTest, 13_NonExistingAppNameAuthentication_Failed) {
  TciCommand tci_command = {0};

  tci_command.cmdId = kAuthenticateByAppName;
  memset(&tci_command.handler, 0, sizeof(tci_command.handler));
  strcpy(tci_command.app_name, kTestNonExecutable);

  for (int i = 0; i < kMaxAppNameAuthRetryNum; ++i) {
    TEEC_Result status = SendCommand(tci_command);
    ASSERT_EQ(status, (TEEC_Result)TEEC_SUCCESS) << "Error is occurred during test execution";
    if (tci_command.tz_result != PA_TZ_AF_TASK_HAS_DUPLICATE) {
      break;
    }
    usleep(20);
  }
  ASSERT_EQ(tci_command.tz_result, PA_TZ_AF_TASK_IS_NOT_FOUND) <<
      "Application name authentication of native application with non-existing app name SHOULD be failed";
}
