#include <gtest/gtest.h>

#include <stdint.h>

extern "C" {
  #include "memory.h"
  #include "config.h"
  #include "pa_tz_api.h"
}

static PaConfig g_pa_config = {0};
static const PhysicalAddress kConfigPhysOffset = 0;

static const PhysicalAddress kAlignedPhys = 0x1000;
static const PhysicalAddress kUnalignedPhys = 0x1150;
static const PhysicalAddress kNumMapPages = 7;

static void * const kValidVirt = (void *)0x1001;

static const size_t kMultiPagesBufferSize = PAGE_SIZE * kNumMapPages + 10;

static int g_platform_sys_map_fail = 0;
static int g_platform_sys_unmap_fail = 0;

extern "C" PaTzResult PlatformSysMap(PhysicalAddress phys, size_t size,
                                     MemoryAccessType type, void **virt) {
  if (g_platform_sys_map_fail) {
    return PA_TZ_MEMORY_PERM_ERROR;
  }

  if (AlignToPageDown(phys) != phys) {
    std::cerr << "phys is not aligned " << std::endl;
    std::cerr << "Got phys = " << phys << std::endl;
    std::cerr << "AlignToPageDown(phys) = "
              << AlignToPageDown(phys) << std::endl;
    return PA_TZ_MEMORY_PERM_ERROR;
  }

  if (AlignToPageUp(size) != size) {
    std::cerr << "size is not algined " << std::endl;
    std::cerr << "Got size = " << size << std::endl;
    std::cerr << "AlignToPageUp(size) = "
              << AlignToPageUp(size) << std::endl;
    return PA_TZ_MEMORY_PERM_ERROR;
  }

  *virt = (void *)phys;
  return PA_TZ_SUCCESS;
}

extern "C" PaTzResult PlatformSysUnmap(void *virt, size_t size) {
  if (g_platform_sys_unmap_fail) {
    return PA_TZ_MEMORY_PERM_ERROR;
  }

  if (size != AlignToPageUp(size)) {
    std::cerr << "size is not algined " << std::endl;
    std::cerr << "Got size = " << size << std::endl;
    std::cerr << "AlignToPageUp(size) = " << AlignToPageUp(size) << std::endl;
    return PA_TZ_MEMORY_PERM_ERROR;
  }

  return PA_TZ_SUCCESS;
}

extern "C" void TEE_MemMove(void* dest, const void* src, uint32_t size) {
  memmove(dest, src, size);
}

extern "C" const PaConfig *GetConfig(void) {
  return &g_pa_config;
}

extern "C" PhysicalAddress KernelGetConfigPhysOffset(void) {
  return kConfigPhysOffset;
}

extern "C" PhysicalAddress KernelGetStartPhysAddr(void) {
  return kConfigPhysOffset;
}


class MemoryMapTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    g_platform_sys_map_fail = 0;
  }

  virtual void TearDown() {
  }
};

TEST_F(MemoryMapTest, MapSinglePageFromAlignedPhys_Success) {
  void *virt = NULL;
  PaTzResult res = PlatformMapRegion(kAlignedPhys, PAGE_SIZE,
                                     kMemoryAccessRead, &virt);
  EXPECT_EQ(virt, (void *)kAlignedPhys);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(MemoryMapTest, MapSinglePageFromUnalignedPhys_Success) {
  void *virt = NULL;
  PaTzResult res = PlatformMapRegion(kUnalignedPhys, PAGE_SIZE,
                                     kMemoryAccessRead, &virt);
  EXPECT_EQ(virt, (void *)kUnalignedPhys);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(MemoryMapTest, MapMultiplePagesFromUnalignedPhys_Success) {
  void *virt = NULL;
  PaTzResult res = PlatformMapRegion(kUnalignedPhys,
                                     kMultiPagesBufferSize,
                                     kMemoryAccessRead, &virt);
  EXPECT_EQ(virt, (void *)kUnalignedPhys);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(MemoryMapTest, MapMultiplePagesFromAlignedPhys_Success) {
  void *virt = NULL;
  PaTzResult res = PlatformMapRegion(kAlignedPhys,
                                     PAGE_SIZE * kNumMapPages + 10,
                                     kMemoryAccessRead, &virt);
  EXPECT_EQ(virt, (void *)kAlignedPhys);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(MemoryMapTest, PlatformSysMapFailed_Failed) {
  g_platform_sys_map_fail = 1;
  void *virt = NULL;
  PaTzResult res = PlatformMapRegion(kAlignedPhys,
                                     PAGE_SIZE * kNumMapPages,
                                     kMemoryAccessRead, &virt);
  EXPECT_EQ(res, PA_TZ_GENERAL_ERROR);
  EXPECT_EQ(virt, (void *)0);
}

class MemoryUnmapTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    g_platform_sys_unmap_fail = 0;
  }

  virtual void TearDown() {
  }
};

TEST_F(MemoryUnmapTest, PlatformUnmapUnalignedRegion_Success) {
  PaTzResult res = PlatformUnmapRegion(kValidVirt, kMultiPagesBufferSize);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(MemoryUnmapTest, PlatformUnmapAlignedRegion_Success) {
  PaTzResult res = PlatformUnmapRegion(kValidVirt, PAGE_SIZE * kNumMapPages);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(MemoryUnmapTest, PlatformSysUnmapFailed_Failed) {
  g_platform_sys_unmap_fail = 1;
  PaTzResult res = PlatformUnmapRegion(kValidVirt, kMultiPagesBufferSize);
  EXPECT_EQ(res, PA_TZ_GENERAL_ERROR);
}

class PhysicalGetBytesTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    g_platform_sys_map_fail = 0;
    g_platform_sys_unmap_fail = 0;
    aligned_phys_mem = AlignToPageUp((PhysicalAddress)&phys_mem_buffer);
  }

  virtual void TearDown() {
  }

  //after alignment, in worst case, size of such buffer will be PAGE_SIZE + 1
  char phys_mem_buffer[2 * PAGE_SIZE];
  PhysicalAddress aligned_phys_mem;
};

TEST_F(PhysicalGetBytesTest, PhysicalGetBytes_Success) {
  char out_buffer[PAGE_SIZE];

  memset(phys_mem_buffer, 0xab, sizeof(phys_mem_buffer));

  PaTzResult res = PhysicalGetBytes(aligned_phys_mem,
                            PAGE_SIZE, out_buffer);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
  EXPECT_EQ(memcmp(out_buffer, (void *)aligned_phys_mem, PAGE_SIZE), 0);
}

TEST_F(PhysicalGetBytesTest, OutIsNull_Failed) {
  PaTzResult res = PhysicalGetBytes(aligned_phys_mem, PAGE_SIZE, NULL);
  EXPECT_EQ(res, PA_TZ_GENERAL_ERROR);
}

TEST_F(PhysicalGetBytesTest, PlatformSysMap_Failed) {
  char out_buffer[PAGE_SIZE];
  g_platform_sys_map_fail = 1;
  PaTzResult res = PhysicalGetBytes(aligned_phys_mem, PAGE_SIZE, out_buffer);
  EXPECT_EQ(res, PA_TZ_GENERAL_ERROR);
}

TEST_F(PhysicalGetBytesTest, PlatformSysMapReturnedNull_Failed) {
  char out_buffer[PAGE_SIZE];
  PaTzResult res = PhysicalGetBytes(0, PAGE_SIZE, out_buffer);
  EXPECT_EQ(res, PA_TZ_SUCCESS);
}

TEST_F(PhysicalGetBytesTest, PlatformSysUnmap_Failed) {
  char out_buffer[PAGE_SIZE];
  g_platform_sys_unmap_fail = 1;
  PaTzResult res = PhysicalGetBytes(aligned_phys_mem, PAGE_SIZE, out_buffer);
  EXPECT_EQ(res, PA_TZ_MEMORY_PERM_ERROR);
}

static const KernelAddress kKernelVirtStart = 0x7FFFFFFFFFFFFFFFULL;
static const KernelAddress kValidKernelVirt = kKernelVirtStart + 1;
static const KernelAddress kInvalidKernelVirt = kKernelVirtStart - 1;

class IsKernelVirtualAddressTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    g_pa_config.memory_conf.va_start = kKernelVirtStart;
  }

  virtual void TearDown() {
  }
};

TEST_F(IsKernelVirtualAddressTest, ValidKernelVirt_Success) {
  EXPECT_TRUE(IsKernelVirtualAddress(kValidKernelVirt)) <<
      "Addresses between kKernelVirtStart and kKernelVirtEnd SHOULD be valid";
}

TEST_F(IsKernelVirtualAddressTest, InvalidKernelVirt_Failed) {
  EXPECT_FALSE(IsKernelVirtualAddress(kInvalidKernelVirt)) <<
      "Addresses lower than kKernelVirtStart SHOULD be invalid";
}

static const uint64_t kSysRamRangesNum = 2;
static const PhysicalAddress kRange1StartPhys = 0x7FFFFFFFFFFFFFFFULL;
static const PhysicalAddress kRange1EndPhys   = 0x9000000000000000ULL;
static const PhysicalAddress kRange2StartPhys = 0xAFFFFFFFFFFFFFFFULL;
static const PhysicalAddress kRange2EndPhys   = 0xD000000000000000ULL;

static const PhysicalAddress kValidPhys = kRange2StartPhys + 32;
static const PhysicalAddress kInvalidPhys = kRange1EndPhys + 32;

class IsPhysicalAddressTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    g_pa_config.memory_conf.sys_ram_ranges_num = kSysRamRangesNum;
    g_pa_config.memory_conf.sys_ram_ranges[0].start = kRange1StartPhys;
    g_pa_config.memory_conf.sys_ram_ranges[0].end   = kRange1EndPhys;
    g_pa_config.memory_conf.sys_ram_ranges[1].start = kRange2StartPhys;
    g_pa_config.memory_conf.sys_ram_ranges[1].end   = kRange2EndPhys;
  }

  virtual void TearDown() {
  }
};

TEST_F(IsPhysicalAddressTest, ValidPhys_Success) {
  EXPECT_TRUE(IsPhysicalAddress(kValidPhys)) <<
      "Addresses " << std::hex << kValidPhys
      << " from system ram ranges SHOULD be valid";
}

TEST_F(IsPhysicalAddressTest, InvalidPhys_Failed) {
  EXPECT_FALSE(IsPhysicalAddress(kInvalidPhys)) <<
      "Addresses " << std::hex << kInvalidPhys
      << " not from system ram ranges SHOULD be invalid";
}

static const uint64_t kVaBits = 38;
static const uint64_t kKimageVoffset = 0x10;
static const uint64_t kPageOffset = (0xffffffffffffffffULL) << (kVaBits - 1);

static const uint64_t kKimageVirt = 0x24;
static const uint64_t kKimagePhys = kKimageVirt - kKimageVoffset;

static const uint64_t kLinearMapVirt = (1ULL << (kVaBits - 1)) + 0x10;
static const uint64_t kLinearMapPhys = 0x10 + kConfigPhysOffset;

class KernelVirtToPhysTest : public ::testing::Test {
protected:
  virtual void SetUp() {
    g_pa_config.memory_conf.va_bits = kVaBits;
    g_pa_config.memory_conf.kimage_voffset = kKimageVoffset;
    g_pa_config.memory_conf.page_offset = kPageOffset;
  }

  virtual void TearDown() {
  }
};

TEST_F(KernelVirtToPhysTest, NullTest) {
  EXPECT_EQ(KernelVirtToPhys(0), 0) <<
      "NULL SHOULD be translated to NULL";
}

TEST_F(KernelVirtToPhysTest, KernelImageVirtTest) {
  EXPECT_EQ(KernelVirtToPhys(kKimageVirt), kKimagePhys) <<
      "Kernel image virtual address SHOULD be shifted by kimage_voffset";
}

TEST_F(KernelVirtToPhysTest, LinearMapVirtTest) {
  EXPECT_EQ(KernelVirtToPhys(kLinearMapVirt), kLinearMapPhys) <<
      "Kernel linear map virtual address SHOULD be shifted by page_offset";
}