#include <gtest/gtest.h>

#include <stdint.h>

extern "C" {
  #include "memory.h"
  #include "mock.h"
  #include "kernel_access.h"
}

extern "C" uint32_t IsKernelVirtualAddress(KernelAddress address) {
  return 1;
}

class KernelAccessTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    InitMocks();
    KernelAccessInit(&context);
  }

  virtual void TearDown() {
    KernelAccessDeinit(&context);
    DeinitMocks();
  }

protected:
  KernelAccessInfo context;
};

TEST_F(KernelAccessTest, KernelAccessGetBytes) {
  const int size = 64;
  uint8_t buffer[size];

  // Start memory
  int offset = 0;
  memset(buffer, 0, sizeof(buffer));

  PaTzResult result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress(), size, buffer);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_TRUE(memcmp(buffer, GetMockBuffer(offset), size) == 0);

  // Half memory
  offset = (GetTestSize() / 2) - 1;
  memset(buffer, 0, sizeof(buffer));

  result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress() + offset, size, buffer);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_TRUE(memcmp(buffer, GetMockBuffer(offset), size) == 0);
}

TEST_F(KernelAccessTest, KernelAccessGetUint8) {
  uint8_t value;

  // Start memory
  int offset = 0;
  value = 0;

  PaTzResult result = KernelAccessGetUint8(&context, (KernelAddress)GetTestPhysicalAddress() + offset, &value);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_TRUE(value == *((uint8_t *)GetMockBuffer(offset)));

  // Half memory
  offset = (GetTestSize() / 2) - 1;
  value = 0;

  result = KernelAccessGetUint8(&context, (KernelAddress)GetTestPhysicalAddress() + offset, &value);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_TRUE(value == *((uint8_t *)GetMockBuffer(offset)));
}


TEST_F(KernelAccessTest, KernelAccessGetUint32) {
  uint32_t value;

  // Start memory
  int offset = 0;
  value = 0;

  PaTzResult result = KernelAccessGetUint32(&context, (KernelAddress)GetTestPhysicalAddress() + offset, &value);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);
  EXPECT_EQ(value, *((uint32_t *)GetMockBuffer(offset)));

  // Half memory
  offset = (GetTestSize() / 2) - 1;
  value = 0;

  result = KernelAccessGetUint32(&context, (KernelAddress)GetTestPhysicalAddress() + offset, &value);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);
  EXPECT_EQ(value, *((uint32_t *)GetMockBuffer(offset)));
}

TEST_F(KernelAccessTest, KernelAccessGetUint64) {
  uint64_t value;

  // Start memory
  int offset = 0;
  value = 0;

  PaTzResult result = KernelAccessGetUint64(&context, (KernelAddress)GetTestPhysicalAddress() + offset, &value);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_EQ(value, *((uint64_t *)GetMockBuffer(offset)));

  // Half memory
  offset = (GetTestSize() / 2) - 1;
  value = 0;

  result = KernelAccessGetUint64(&context, (KernelAddress)GetTestPhysicalAddress() + offset, &value);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_EQ(value, *((uint64_t *)GetMockBuffer(offset)));
}

TEST_F(KernelAccessTest, KernelAccessInitInvalidArguments) {
  PaTzResult result = KernelAccessInit(NULL);

  EXPECT_TRUE(PA_TZ_GENERAL_ERROR == result);
}

TEST_F(KernelAccessTest, KernelAccessDeinitInvalidArguments) {
  PaTzResult result = KernelAccessDeinit(NULL);

  EXPECT_TRUE(PA_TZ_GENERAL_ERROR == result);
}

TEST_F(KernelAccessTest, KernelAccessGetBytesWithoutContext) {
  const int size = 64;
  uint8_t buffer[size];

  // Start memory
  int offset = 0;
  memset(buffer, 0, sizeof(buffer));

  PaTzResult result = KernelAccessGetBytes(NULL, (KernelAddress)GetTestPhysicalAddress(), size, buffer);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_TRUE(memcmp(buffer, GetMockBuffer(offset), size) == 0);

  // Half memory
  offset = (GetTestSize() / 2) - 1;
  memset(buffer, 0, sizeof(buffer));

  result = KernelAccessGetBytes(NULL, (KernelAddress)GetTestPhysicalAddress() + offset, size, buffer);
  EXPECT_TRUE(GetMockCallVirtToPhysCounter() > 0);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  EXPECT_TRUE(memcmp(buffer, GetMockBuffer(offset), size) == 0);
}

TEST_F(KernelAccessTest, KernelAccessGetBytesIncorrectVirtualAddress) {
  const int size = 64;
  uint8_t buffer[size];

  // Start memory
  int offset = 0;
  memset(buffer, 0, sizeof(buffer));

  PaTzResult result = KernelAccessGetBytes(NULL, (KernelAddress)1000, size, buffer);
  EXPECT_TRUE(PA_TZ_GENERAL_ERROR == result);
}

TEST_F(KernelAccessTest, KernelAccessGetBytesMultipleTimes) {
  const int size = 64;
  uint8_t buffer[size];

  // Start memory
  int offset = 0;
  memset(buffer, 0, sizeof(buffer));

  // Mock defines inner bufer for 4 pages
  for (size_t i = 0; i < 4; ++i) {
    PaTzResult result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress() + i * PAGE_SIZE, size, buffer);
    EXPECT_TRUE(PA_TZ_SUCCESS == result);
  }
}

TEST_F(KernelAccessTest, KernelAccessGetBytesCheckRemap) {
  const int size = 64;
  uint8_t buffer[size];

  // Start memory
  int offset = 0;
  memset(buffer, 0, sizeof(buffer));

  PaTzResult result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress(), size, buffer);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  size_t page_mapped_1 = GetMockCallMapCounter();

  result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress(), size, buffer);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  size_t page_mapped_2 = GetMockCallMapCounter();

  // Pages should not be remap
  EXPECT_TRUE(page_mapped_1 == page_mapped_2);
}

TEST_F(KernelAccessTest, KernelAccessGetBytesInvalidMappings) {
  const int size = 64;
  uint8_t buffer[size];

  // Start memory
  int offset = 0;
  memset(buffer, 0, sizeof(buffer));

  PaTzResult result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress(), size, buffer);
  EXPECT_TRUE(PA_TZ_SUCCESS == result);

  // Damage maps table to emulate incorrect behavior of map sub system
  // The test can be fragile.
  context.maps[0].virt = 0;

  result = KernelAccessGetBytes(&context, (KernelAddress)GetTestPhysicalAddress(), size, buffer);
  EXPECT_TRUE(PA_TZ_GENERAL_ERROR == result);
}
