/** @file
  Unit tests the SplitTable() ImagePropertiesRecordLib Logic

  Copyright (C) Microsoft Corporation.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UnitTestLib.h>
#include <Library/ImagePropertiesRecordLib.h>

#define UNIT_TEST_APP_NAME     "Image Properties Record Lib Unit Test"
#define UNIT_TEST_APP_VERSION  "1.0"

#define NEXT_MEMORY_DESCRIPTOR(MemoryDescriptor, Size) \
  ((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(MemoryDescriptor) + (Size)))

// The starting memory map will contain 6 entries
#define NUMBER_OF_MEMORY_MAP_DESCRIPTORS  6

// Each memory map descriptor will be the sizeof(EFI_MEMORY_DESCRIPTOR) instead of a nonstandard size
// to catch pointer math issues
#define DESCRIPTOR_SIZE  sizeof(EFI_MEMORY_DESCRIPTOR)

// Each memory map descriptor will describe 12 pages
#define BASE_DESCRIPTOR_NUMBER_OF_PAGES  0x0C

// The size, in bytes, of each memory map descriptor range
#define BASE_DESCRIPTOR_ENTRY_SIZE  (EFI_PAGES_TO_SIZE(BASE_DESCRIPTOR_NUMBER_OF_PAGES))

// MACRO to get the starting address of a descriptor's described range based on the index of that descriptor
#define BASE_DESCRIPTOR_START_ADDRESS(DescriptorNumber)  (DescriptorNumber * BASE_DESCRIPTOR_ENTRY_SIZE)

// Virtual start must be zero
#define BASE_DESCRIPTOR_VIRTUAL_START  0x0

// Size of the default memory map
#define BASE_MEMORY_MAP_SIZE  (NUMBER_OF_MEMORY_MAP_DESCRIPTORS * DESCRIPTOR_SIZE)

// Number of images in each test case
#define NUMBER_OF_IMAGES_TO_SPLIT  3

// Maximum number of descriptors required for each image (None->Data->Code->Data->Code->Data->None)
#define MAX_DESCRIPTORS_PER_IMAGE  7

// Number of unused additional descriptors in the starting memory map buffer which is used by the
// SplitTable() logic
#define NUMBER_OF_ADDITIONAL_DESCRIPTORS  (NUMBER_OF_IMAGES_TO_SPLIT * MAX_DESCRIPTORS_PER_IMAGE)

// Size of the memory map with enough space for the starting descriptors and the split descriptors
#define SPLIT_MEMORY_MAP_SIZE  (BASE_MEMORY_MAP_SIZE + (NUMBER_OF_ADDITIONAL_DESCRIPTORS * DESCRIPTOR_SIZE))

typedef enum {
  SectionTypeCode,
  SectionTypeData,
  SectionTypeNotFound
} SECTION_TYPE;

typedef struct {
  EFI_MEMORY_DESCRIPTOR    *MemoryMap;
  LIST_ENTRY               ImageList;
} IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT;

EFI_MEMORY_DESCRIPTOR  BaseMemoryMap[] = {
  {
    EfiConventionalMemory,             // Type
    BASE_DESCRIPTOR_START_ADDRESS (0), // PhysicalStart
    BASE_DESCRIPTOR_VIRTUAL_START,     // VirtualStart
    BASE_DESCRIPTOR_NUMBER_OF_PAGES,   // Number of Pages
    0                                  // Attribute
  },
  {
    EfiConventionalMemory,             // Type
    BASE_DESCRIPTOR_START_ADDRESS (1), // PhysicalStart
    BASE_DESCRIPTOR_VIRTUAL_START,     // VirtualStart
    BASE_DESCRIPTOR_NUMBER_OF_PAGES,   // Number of Pages
    0                                  // Attribute
  },
  {
    EfiConventionalMemory,             // Type
    BASE_DESCRIPTOR_START_ADDRESS (2), // PhysicalStart
    BASE_DESCRIPTOR_VIRTUAL_START,     // VirtualStart
    BASE_DESCRIPTOR_NUMBER_OF_PAGES,   // Number of Pages
    0                                  // Attribute
  },
  {
    EfiConventionalMemory,             // Type
    BASE_DESCRIPTOR_START_ADDRESS (3), // PhysicalStart
    BASE_DESCRIPTOR_VIRTUAL_START,     // VirtualStart
    BASE_DESCRIPTOR_NUMBER_OF_PAGES,   // Number of Pages
    0                                  // Attribute
  },
  {
    EfiConventionalMemory,             // Type
    BASE_DESCRIPTOR_START_ADDRESS (4), // PhysicalStart
    BASE_DESCRIPTOR_VIRTUAL_START,     // VirtualStart
    BASE_DESCRIPTOR_NUMBER_OF_PAGES,   // Number of Pages
    0                                  // Attribute
  },
  {
    EfiConventionalMemory,             // Type
    BASE_DESCRIPTOR_START_ADDRESS (5), // PhysicalStart
    BASE_DESCRIPTOR_VIRTUAL_START,     // VirtualStart
    BASE_DESCRIPTOR_NUMBER_OF_PAGES,   // Number of Pages
    0                                  // Attribute
  }
};

/**
  Returns a bitmap where one bit is set for each section in the image list. For example, if
  there are 3 images and each image 3 sections the returned bitmap will be 111111111.

  @param[in]  ImageRecordList   A list of IMAGE_PROPERTIES_RECORD entries

  @retval A bitmap such that the most significant bit is the number of sections
          in all images and every bit between 0 -> MSB is set

**/
STATIC
UINT64
GetImageSectionBitmap (
  IN LIST_ENTRY  *ImageRecordList
  )
{
  IMAGE_PROPERTIES_RECORD               *ImageRecord;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION  *ImageRecordCodeSection;
  LIST_ENTRY                            *ImageRecordLink;
  LIST_ENTRY                            *ImageRecordCodeSectionLink;
  EFI_PHYSICAL_ADDRESS                  SectionBase;
  UINT64                                ReturnBitmap;
  UINT64                                Shift;

  if (ImageRecordList == NULL) {
    return 0;
  }

  ReturnBitmap = 0;
  Shift        = 0;

  // Walk through each image record
  for (ImageRecordLink = ImageRecordList->ForwardLink;
       ImageRecordLink != ImageRecordList;
       ImageRecordLink = ImageRecordLink->ForwardLink)
  {
    ImageRecord = CR (
                    ImageRecordLink,
                    IMAGE_PROPERTIES_RECORD,
                    Link,
                    IMAGE_PROPERTIES_RECORD_SIGNATURE
                    );

    SectionBase = ImageRecord->ImageBase;

    // Walk through each code entry
    for (ImageRecordCodeSectionLink = ImageRecord->CodeSegmentList.ForwardLink;
         ImageRecordCodeSectionLink != &ImageRecord->CodeSegmentList;
         ImageRecordCodeSectionLink = ImageRecordCodeSectionLink->ForwardLink)
    {
      ImageRecordCodeSection = CR (
                                 ImageRecordCodeSectionLink,
                                 IMAGE_PROPERTIES_RECORD_CODE_SECTION,
                                 Link,
                                 IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE
                                 );

      // Check for data region before the code section base
      if (SectionBase < ImageRecordCodeSection->CodeSegmentBase) {
        ReturnBitmap |= LShiftU64 (1, Shift++);
      }

      // Code section
      ReturnBitmap |= LShiftU64 (1, Shift++);
      SectionBase   = ImageRecordCodeSection->CodeSegmentBase + ImageRecordCodeSection->CodeSegmentSize;
    }

    // Check for data region after the previous code section
    if (SectionBase < (ImageRecord->ImageBase + ImageRecord->ImageSize)) {
      ReturnBitmap |= LShiftU64 (1, Shift++);
    }
  }

  return ReturnBitmap;
}

/**
  Searches the input image list for a section which exactly matches the memory range Buffer -> Buffer + Length.

  @param[in] Buffer           Start Address to check
  @param[in] Length           Length to check
  @param[out] Type             The type of the section which corresponds with the memory
                              range Buffer -> Buffer + Length (Code or Data) or SectionTypeNotFound
                              if no image section matches the memory range
  @param[in] ImageRecordList  A list of IMAGE_PROPERTIES_RECORD entries to check against
                              the memory range Buffer -> Buffer + Length

  @retval A bitmap with a single bit set (1 << Shift) where Shift corresponds with the number of sections inspected
          in the image list before arriving at the section matching the memory range Buffer -> Buffer + Length
**/
STATIC
UINT64
MatchDescriptorToImageSection (
  IN  EFI_PHYSICAL_ADDRESS  Buffer,
  IN  UINT64                Length,
  OUT SECTION_TYPE          *Type,
  IN  LIST_ENTRY            *ImageRecordList
  )
{
  IMAGE_PROPERTIES_RECORD               *ImageRecord;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION  *ImageRecordCodeSection;
  LIST_ENTRY                            *ImageRecordLink;
  LIST_ENTRY                            *ImageRecordCodeSectionLink;
  EFI_PHYSICAL_ADDRESS                  SectionBase;
  UINT8                                 Shift;

  Shift = 0;

  if (ImageRecordList == NULL) {
    return 1;
  }

  // Walk through each image record
  for (ImageRecordLink = ImageRecordList->ForwardLink;
       ImageRecordLink != ImageRecordList;
       ImageRecordLink = ImageRecordLink->ForwardLink)
  {
    ImageRecord = CR (
                    ImageRecordLink,
                    IMAGE_PROPERTIES_RECORD,
                    Link,
                    IMAGE_PROPERTIES_RECORD_SIGNATURE
                    );

    SectionBase = ImageRecord->ImageBase;

    // Walk through each code entry
    for (ImageRecordCodeSectionLink = ImageRecord->CodeSegmentList.ForwardLink;
         ImageRecordCodeSectionLink != &ImageRecord->CodeSegmentList;
         ImageRecordCodeSectionLink = ImageRecordCodeSectionLink->ForwardLink)
    {
      ImageRecordCodeSection = CR (
                                 ImageRecordCodeSectionLink,
                                 IMAGE_PROPERTIES_RECORD_CODE_SECTION,
                                 Link,
                                 IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE
                                 );

      if (SectionBase < ImageRecordCodeSection->CodeSegmentBase) {
        // Check the data region before the code section base
        if ((Buffer == SectionBase) &&
            (Length == ImageRecordCodeSection->CodeSegmentBase - SectionBase))
        {
          *Type = SectionTypeData;
          return LShiftU64 (1, Shift);
        }

        Shift++;
      }

      // Check the code region
      if ((Buffer == ImageRecordCodeSection->CodeSegmentBase) &&
          (Length == ImageRecordCodeSection->CodeSegmentSize))
      {
        *Type = SectionTypeCode;
        return LShiftU64 (1, Shift);
      }

      Shift++;
      SectionBase = ImageRecordCodeSection->CodeSegmentBase + ImageRecordCodeSection->CodeSegmentSize;
    }

    // Check the data region after the code section
    if (SectionBase < (ImageRecord->ImageBase + ImageRecord->ImageSize)) {
      if ((Buffer == SectionBase) &&
          (Length == (ImageRecord->ImageBase + ImageRecord->ImageSize) - SectionBase))
      {
        *Type = SectionTypeData;
        return LShiftU64 (1, Shift);
      }

      Shift++;
    }
  }

  // No image sections match
  *Type = SectionTypeNotFound;
  return 0;
}

/**
  Walks through the input memory map and checks that every memory descriptor with an attribute matches
  an image in ImageRecordList.

  @param[in]        MemoryMapSize                 The size, in bytes, of the memory map
  @param[in]        MemoryMap                     A pointer to the buffer containing the memory map
  @param[in]        ImageRecordList               A list of IMAGE_PROPERTIES_RECORD entries

  @retval TRUE if all memory descriptors with attributes match an image section and have the correct attributes

**/
STATIC
BOOLEAN
IsMemoryMapValid (
  IN UINTN                  MemoryMapSize,
  IN EFI_MEMORY_DESCRIPTOR  *MemoryMap,
  IN LIST_ENTRY             *ImageRecordList
  )
{
  UINT64        ImageSectionsBitmap;
  UINT64        ReturnSectionBitmask;
  UINT64        NumberOfDescriptors;
  UINT8         Index;
  SECTION_TYPE  Type;

  Index               = 0;
  NumberOfDescriptors = MemoryMapSize / DESCRIPTOR_SIZE;

  UT_ASSERT_EQUAL (MemoryMapSize % DESCRIPTOR_SIZE, 0);
  UT_ASSERT_NOT_NULL (MemoryMap);
  UT_ASSERT_NOT_NULL (ImageRecordList);

  // The returned bitmap will have one bit is set for each section in the image list.
  // If there are 3 images and 3 sections each image, the resulting bitmap will
  // be 0000000000000000000000000000000000000000000000000000000111111111. Flipping that bitmap
  // results in 1111111111111111111111111111111111111111111111111111111000000000. The return value
  // of each iteration through MatchDescriptorToImageSection() is one set bit corrosponding to the number
  // of sections before finding the section which matched the descriptor memory range which we
  // OR with ImageSectionsBitmap. If, at the end of the loop, every bit in ImageSectionsBitmap is set,
  // we must have matched every image in ImageRecordList with a descriptor in the memory map which has
  // nonzero attributes.
  ImageSectionsBitmap = ~GetImageSectionBitmap (ImageRecordList);

  // For each descriptor in the memory map
  for ( ; Index < NumberOfDescriptors; Index++) {
    if (MemoryMap[Index].Attribute != 0) {
      ReturnSectionBitmask = MatchDescriptorToImageSection (
                               MemoryMap[Index].PhysicalStart,
                               EFI_PAGES_TO_SIZE (MemoryMap[Index].NumberOfPages),
                               &Type,
                               ImageRecordList
                               );

      // Make sure the attributes of the descriptor match the returned section type.
      // DATA sections should have execution protection and CODE sections should have
      // write protection.
      if ((Type == SectionTypeNotFound) ||
          ((Type == SectionTypeData) && (MemoryMap[Index].Attribute == EFI_MEMORY_RP)) ||
          ((Type == SectionTypeCode) && (MemoryMap[Index].Attribute == EFI_MEMORY_XP)))
      {
        return FALSE;
      }

      // If the bit associated with image found has already been set, then there must be a duplicate
      // in the memory map meaning it is invalid.
      UT_ASSERT_EQUAL (ImageSectionsBitmap & ReturnSectionBitmask, 0);

      ImageSectionsBitmap |= ReturnSectionBitmask;
    }
  }

  // If every bit in ImageSectionsBitmap is set, the return value will be TRUE
  return !(~ImageSectionsBitmap);
}

/**
  Separate the image sections in the memory map and run a check to ensure the output is valid.

  @param[in]  Context   Context containing the memory map and image record pointers

  @retval TRUE if the memory map is split correctly
**/
STATIC
BOOLEAN
SeparateAndCheck (
  IN IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *Context
  )
{
  UINTN  MemoryMapSize;

  MemoryMapSize = BASE_MEMORY_MAP_SIZE;

  // Separate the memory map so each image section has its own descriptor
  SplitTable (
    &MemoryMapSize,
    Context->MemoryMap,
    DESCRIPTOR_SIZE,
    &Context->ImageList,
    NUMBER_OF_ADDITIONAL_DESCRIPTORS
    );

  // Ensure the updated memory map is valid
  return IsMemoryMapValid (MemoryMapSize, Context->MemoryMap, &Context->ImageList);
}

/**
  Test the case where the image range contains multiple code sections and does not perfectly align with
  the existing memory descriptor.

  @param[in]  Context   Context containing the memory map and image record pointers

  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
                                        case was successful.
  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
**/
UNIT_TEST_STATUS
EFIAPI
MaxOutAdditionalDescriptors (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  IMAGE_PROPERTIES_RECORD                    *Image1;
  IMAGE_PROPERTIES_RECORD                    *Image2;
  IMAGE_PROPERTIES_RECORD                    *Image3;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage1;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage2;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage3;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *AddCodeSectionInImage1;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *AddCodeSectionInImage2;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *AddCodeSectionInImage3;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *TestContext;

  TestContext = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)Context;

  Image1 = CR (TestContext->ImageList.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image2 = CR (Image1->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image3 = CR (Image2->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);

  CodeSectionInImage1 = CR (Image1->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage2 = CR (Image2->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage3 = CR (Image3->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);

  ///////////////
  // Descriptor 1
  ///////////////
  // |         |      |      |      |      |      |             |
  // | 4K PAGE | DATA | CODE | DATA | CODE | DATA | 4K PAGE * 5 |
  // |         |      |      |      |      |      |             |

  Image1->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (1) + EFI_PAGE_SIZE;
  Image1->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE - EFI_PAGE_SIZE - EFI_PAGE_SIZE;
  Image1->CodeSegmentCount             = 2;
  CodeSectionInImage1->CodeSegmentBase = Image1->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage1->CodeSegmentSize = EFI_PAGE_SIZE;
  TestContext->MemoryMap[1].Type       = EfiBootServicesCode;

  AddCodeSectionInImage1                  = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_CODE_SECTION));
  AddCodeSectionInImage1->Signature       = IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE;
  AddCodeSectionInImage1->CodeSegmentBase = CodeSectionInImage1->CodeSegmentBase + CodeSectionInImage1->CodeSegmentSize + EFI_PAGE_SIZE;
  AddCodeSectionInImage1->CodeSegmentSize = EFI_PAGE_SIZE;

  InsertTailList (&Image1->CodeSegmentList, &AddCodeSectionInImage1->Link);

  ///////////////
  // Descriptor 2
  ///////////////
  // |         |      |      |      |      |      |             |
  // | 4K PAGE | DATA | CODE | DATA | CODE | DATA | 4K PAGE * 5 |
  // |         |      |      |      |      |      |             |

  Image2->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (2) + EFI_PAGE_SIZE;
  Image2->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE - EFI_PAGE_SIZE - EFI_PAGE_SIZE;
  Image2->CodeSegmentCount             = 2;
  CodeSectionInImage2->CodeSegmentBase = Image2->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage2->CodeSegmentSize = EFI_PAGE_SIZE;
  TestContext->MemoryMap[2].Type       = EfiLoaderCode;

  AddCodeSectionInImage2                  = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_CODE_SECTION));
  AddCodeSectionInImage2->Signature       = IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE;
  AddCodeSectionInImage2->CodeSegmentBase = CodeSectionInImage2->CodeSegmentBase + CodeSectionInImage2->CodeSegmentSize + EFI_PAGE_SIZE;
  AddCodeSectionInImage2->CodeSegmentSize = EFI_PAGE_SIZE;

  InsertTailList (&Image2->CodeSegmentList, &AddCodeSectionInImage2->Link);

  ///////////////
  // Descriptor 3
  ///////////////
  // |         |      |      |      |      |      |             |
  // | 4K PAGE | DATA | CODE | DATA | CODE | DATA | 4K PAGE * 5 |
  // |         |      |      |      |      |      |             |

  Image3->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (3) + EFI_PAGE_SIZE;
  Image3->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE - EFI_PAGE_SIZE - EFI_PAGE_SIZE;
  Image3->CodeSegmentCount             = 2;
  CodeSectionInImage3->CodeSegmentBase = Image3->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage3->CodeSegmentSize = EFI_PAGE_SIZE;
  TestContext->MemoryMap[3].Type       = EfiRuntimeServicesCode;

  AddCodeSectionInImage3                  = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_CODE_SECTION));
  AddCodeSectionInImage3->Signature       = IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE;
  AddCodeSectionInImage3->CodeSegmentBase = CodeSectionInImage3->CodeSegmentBase + CodeSectionInImage3->CodeSegmentSize + EFI_PAGE_SIZE;
  AddCodeSectionInImage3->CodeSegmentSize = EFI_PAGE_SIZE;

  InsertTailList (&Image3->CodeSegmentList, &AddCodeSectionInImage3->Link);

  UT_ASSERT_TRUE (SeparateAndCheck (TestContext));

  return UNIT_TEST_PASSED;
}

/**
  Test the case where multiple image ranges lie within an existing memory descriptor.

  @param[in]  Context   Context containing the memory map and image record pointers

  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
                                        case was successful.
  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
**/
UNIT_TEST_STATUS
EFIAPI
MultipleImagesInOneDescriptor (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  IMAGE_PROPERTIES_RECORD                    *Image1;
  IMAGE_PROPERTIES_RECORD                    *Image2;
  IMAGE_PROPERTIES_RECORD                    *Image3;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage1;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage2;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage3;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *TestContext;

  TestContext = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)Context;

  Image1 = CR (TestContext->ImageList.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image2 = CR (Image1->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image3 = CR (Image2->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);

  CodeSectionInImage1 = CR (Image1->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage2 = CR (Image2->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage3 = CR (Image3->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);

  ///////////////
  // Descriptor 1
  ///////////////
  // |         |      |      |      |         |      |      |      |      |      |      |         |
  // | 4K PAGE | DATA | CODE | DATA | 4K PAGE | DATA | CODE | DATA | DATA | CODE | DATA | 4K PAGE |
  // |         |      |      |      |         |      |      |      |      |      |      |         |

  Image1->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (1) + EFI_PAGE_SIZE;
  Image1->ImageSize                    = EFI_PAGES_TO_SIZE (3);
  Image1->CodeSegmentCount             = 1;
  CodeSectionInImage1->CodeSegmentBase = Image1->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage1->CodeSegmentSize = EFI_PAGE_SIZE;
  TestContext->MemoryMap[1].Type       = EfiBootServicesCode;

  Image2->ImageBase                    = Image1->ImageBase + Image1->ImageSize + EFI_PAGE_SIZE;
  Image2->ImageSize                    = EFI_PAGES_TO_SIZE (3);
  Image2->CodeSegmentCount             = 1;
  CodeSectionInImage2->CodeSegmentBase = Image2->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage2->CodeSegmentSize = EFI_PAGE_SIZE;

  Image3->ImageBase                    = Image2->ImageBase + Image2->ImageSize;
  Image3->ImageSize                    = EFI_PAGES_TO_SIZE (3);
  Image3->CodeSegmentCount             = 1;
  CodeSectionInImage3->CodeSegmentBase = Image3->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage3->CodeSegmentSize = EFI_PAGE_SIZE;

  UT_ASSERT_TRUE (SeparateAndCheck (TestContext));

  return UNIT_TEST_PASSED;
}

/**
  Test the case where all image ranges do not fit perfectly within an existing memory descriptor.

  @param[in]  Context   Context containing the memory map and image record pointers

  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
                                        case was successful.
  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
**/
UNIT_TEST_STATUS
EFIAPI
ImagesDontFitDescriptors (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  IMAGE_PROPERTIES_RECORD                    *Image1;
  IMAGE_PROPERTIES_RECORD                    *Image2;
  IMAGE_PROPERTIES_RECORD                    *Image3;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage1;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage2;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage3;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *TestContext;

  TestContext = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)Context;

  Image1 = CR (TestContext->ImageList.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image2 = CR (Image1->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image3 = CR (Image2->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);

  CodeSectionInImage1 = CR (Image1->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage2 = CR (Image2->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage3 = CR (Image3->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);

  ///////////////
  // Descriptor 1
  ///////////////
  // |         |      |          |          |
  // | 4K PAGE | DATA | CODE * 2 | DATA * 8 |
  // |         |      |          |          |

  Image1->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (1) + EFI_PAGE_SIZE;
  Image1->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE - EFI_PAGE_SIZE;
  Image1->CodeSegmentCount             = 1;
  CodeSectionInImage1->CodeSegmentBase = Image1->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage1->CodeSegmentSize = EFI_PAGES_TO_SIZE (2);
  TestContext->MemoryMap[1].Type       = EfiBootServicesCode;

  ///////////////
  // Descriptor 3
  ///////////////
  // |      |          |          |         |
  // | DATA | CODE * 3 | DATA * 7 | 4K PAGE |
  // |      |          |          |         |

  Image2->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (3);
  Image2->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE - EFI_PAGE_SIZE;
  Image2->CodeSegmentCount             = 1;
  CodeSectionInImage2->CodeSegmentBase = Image2->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage2->CodeSegmentSize = EFI_PAGES_TO_SIZE (3);
  TestContext->MemoryMap[3].Type       = EfiLoaderCode;

  ///////////////
  // Descriptor 4
  ///////////////
  // |         |      |          |          |         |
  // | 4K PAGE | DATA | CODE * 2 | DATA * 7 | 4K PAGE |
  // |         |      |          |          |         |

  Image3->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (4) + EFI_PAGE_SIZE;
  Image3->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE - EFI_PAGE_SIZE - EFI_PAGE_SIZE;
  Image3->CodeSegmentCount             = 1;
  CodeSectionInImage3->CodeSegmentBase = Image3->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage3->CodeSegmentSize = EFI_PAGES_TO_SIZE (2);
  TestContext->MemoryMap[4].Type       = EfiRuntimeServicesCode;

  UT_ASSERT_TRUE (SeparateAndCheck (TestContext));

  return UNIT_TEST_PASSED;
}

/**
  Test the case where all image ranges fit perfectly within an existing memory descriptor.

  @param[in]  Context   Context containing the memory map and image record pointers

  @retval  UNIT_TEST_PASSED             The Unit test has completed and the test
                                        case was successful.
  @retval  UNIT_TEST_ERROR_TEST_FAILED  A test case assertion has failed.
**/
UNIT_TEST_STATUS
EFIAPI
ImagesFitDescriptors (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  IMAGE_PROPERTIES_RECORD                    *Image1;
  IMAGE_PROPERTIES_RECORD                    *Image2;
  IMAGE_PROPERTIES_RECORD                    *Image3;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage1;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage2;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *CodeSectionInImage3;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *TestContext;

  TestContext = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)Context;

  Image1 = CR (TestContext->ImageList.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image2 = CR (Image1->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);
  Image3 = CR (Image2->Link.ForwardLink, IMAGE_PROPERTIES_RECORD, Link, IMAGE_PROPERTIES_RECORD_SIGNATURE);

  CodeSectionInImage1 = CR (Image1->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage2 = CR (Image2->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);
  CodeSectionInImage3 = CR (Image3->CodeSegmentList.ForwardLink, IMAGE_PROPERTIES_RECORD_CODE_SECTION, Link, IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE);

  ///////////////
  // Descriptor 1
  ///////////////
  // |      |          |          |
  // | DATA | CODE * 3 | DATA * 8 |
  // |      |          |          |

  Image1->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (1);
  Image1->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE;
  Image1->CodeSegmentCount             = 1;
  CodeSectionInImage1->CodeSegmentBase = Image1->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage1->CodeSegmentSize = EFI_PAGES_TO_SIZE (3);
  TestContext->MemoryMap[1].Type       = EfiBootServicesCode;

  ///////////////
  // Descriptor 2
  ///////////////
  // |      |          |          |
  // | DATA | CODE * 4 | DATA * 7 |
  // |      |          |          |

  Image2->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (2);
  Image2->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE;
  Image2->CodeSegmentCount             = 1;
  CodeSectionInImage2->CodeSegmentBase = Image2->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage2->CodeSegmentSize = EFI_PAGES_TO_SIZE (4);
  TestContext->MemoryMap[2].Type       = EfiLoaderCode;

  ///////////////
  // Descriptor 3
  ///////////////
  // |      |          |          |
  // | DATA | CODE * 3 | DATA * 8 |
  // |      |          |          |

  Image3->ImageBase                    = BASE_DESCRIPTOR_START_ADDRESS (3);
  Image3->ImageSize                    = BASE_DESCRIPTOR_ENTRY_SIZE;
  Image3->CodeSegmentCount             = 1;
  CodeSectionInImage3->CodeSegmentBase = Image3->ImageBase + EFI_PAGE_SIZE;
  CodeSectionInImage3->CodeSegmentSize = EFI_PAGES_TO_SIZE (3);
  TestContext->MemoryMap[3].Type       = EfiRuntimeServicesCode;

  UT_ASSERT_TRUE (SeparateAndCheck (TestContext));

  return UNIT_TEST_PASSED;
}

/**
  Free all allocated memory.

  @param[in]  Context   Context containing the memory map and image record pointers
**/
VOID
EFIAPI
TestCleanup (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  IMAGE_PROPERTIES_RECORD                    *ImageRecord;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION       *ImageRecordCodeSection;
  LIST_ENTRY                                 *ImageRecordLink;
  LIST_ENTRY                                 *CodeSegmentListHead;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *TestContext;

  TestContext     = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)Context;
  ImageRecordLink = &TestContext->ImageList;

  while (!IsListEmpty (ImageRecordLink)) {
    ImageRecord = CR (
                    ImageRecordLink->ForwardLink,
                    IMAGE_PROPERTIES_RECORD,
                    Link,
                    IMAGE_PROPERTIES_RECORD_SIGNATURE
                    );

    CodeSegmentListHead = &ImageRecord->CodeSegmentList;
    while (!IsListEmpty (CodeSegmentListHead)) {
      ImageRecordCodeSection = CR (
                                 CodeSegmentListHead->ForwardLink,
                                 IMAGE_PROPERTIES_RECORD_CODE_SECTION,
                                 Link,
                                 IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE
                                 );
      RemoveEntryList (&ImageRecordCodeSection->Link);
      FreePool (ImageRecordCodeSection);
    }

    RemoveEntryList (&ImageRecord->Link);
    FreePool (ImageRecord);
  }

  if (TestContext->MemoryMap != NULL) {
    FreePool (TestContext->MemoryMap);
  }
}

/**
  Create a generic image list with the proper signatures which will be customized for each test
  and allocate the default memory map.

  @param[out]  TestContext   Context which will be passed to the test cases
**/
STATIC
VOID
CreateBaseContextEntry (
  OUT IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *TestContext
  )
{
  IMAGE_PROPERTIES_RECORD               *Image1;
  IMAGE_PROPERTIES_RECORD               *Image2;
  IMAGE_PROPERTIES_RECORD               *Image3;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION  *CodeSectionInImage1;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION  *CodeSectionInImage2;
  IMAGE_PROPERTIES_RECORD_CODE_SECTION  *CodeSectionInImage3;

  InitializeListHead (&TestContext->ImageList);

  Image1              = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD));
  CodeSectionInImage1 = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_CODE_SECTION));

  Image1->Signature              = IMAGE_PROPERTIES_RECORD_SIGNATURE;
  CodeSectionInImage1->Signature = IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE;
  InitializeListHead (&Image1->CodeSegmentList);

  InsertTailList (&TestContext->ImageList, &Image1->Link);
  InsertTailList (&Image1->CodeSegmentList, &CodeSectionInImage1->Link);

  Image2              = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD));
  CodeSectionInImage2 = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_CODE_SECTION));

  Image2->Signature              = IMAGE_PROPERTIES_RECORD_SIGNATURE;
  CodeSectionInImage2->Signature = IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE;
  InitializeListHead (&Image2->CodeSegmentList);

  InsertTailList (&TestContext->ImageList, &Image2->Link);
  InsertTailList (&Image2->CodeSegmentList, &CodeSectionInImage2->Link);

  Image3              = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD));
  CodeSectionInImage3 = AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_CODE_SECTION));

  Image3->Signature              = IMAGE_PROPERTIES_RECORD_SIGNATURE;
  CodeSectionInImage3->Signature = IMAGE_PROPERTIES_RECORD_CODE_SECTION_SIGNATURE;
  InitializeListHead (&Image3->CodeSegmentList);

  InsertTailList (&TestContext->ImageList, &Image3->Link);
  InsertTailList (&Image3->CodeSegmentList, &CodeSectionInImage3->Link);

  TestContext->MemoryMap = AllocateZeroPool (SPLIT_MEMORY_MAP_SIZE);
  CopyMem (TestContext->MemoryMap, &BaseMemoryMap, BASE_MEMORY_MAP_SIZE);

  return;
}

/**
  Initialze the unit test framework, suite, and unit tests.

  @retval  EFI_SUCCESS           All test cases were dispatched.
  @retval  EFI_OUT_OF_RESOURCES  There are not enough resources available to
                                 initialize the unit tests.
**/
STATIC
EFI_STATUS
EFIAPI
UnitTestingEntry (
  VOID
  )
{
  EFI_STATUS                                 Status;
  UNIT_TEST_FRAMEWORK_HANDLE                 Framework;
  UNIT_TEST_SUITE_HANDLE                     ImagePropertiesRecordTests;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *Context1;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *Context2;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *Context3;
  IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT  *Context4;

  DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_APP_NAME, UNIT_TEST_APP_VERSION));

  Framework = NULL;

  Context1 = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT));
  Context2 = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT));
  Context3 = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT));
  Context4 = (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT *)AllocateZeroPool (sizeof (IMAGE_PROPERTIES_RECORD_HOST_TEST_CONTEXT));

  CreateBaseContextEntry (Context1);
  CreateBaseContextEntry (Context2);
  CreateBaseContextEntry (Context3);
  CreateBaseContextEntry (Context4);

  //
  // Start setting up the test framework for running the tests.
  //
  Status = InitUnitTestFramework (&Framework, UNIT_TEST_APP_NAME, gEfiCallerBaseName, UNIT_TEST_APP_VERSION);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status));
    goto EXIT;
  }

  //
  // Populate the Unit Test Suite.
  //
  Status = CreateUnitTestSuite (&ImagePropertiesRecordTests, Framework, "Image Properties Record Tests", "ImagePropertiesRecordLib.SplitTable", NULL, NULL);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for the Image Properties Record Tests\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto EXIT;
  }

  //
  // --------------Suite-----------Description--------------Name----------Function--------Pre---Post-------------------Context-----------
  //
  AddTestCase (ImagePropertiesRecordTests, "All images fit perfectly into existing descriptors", "ImagesFitDescriptors", ImagesFitDescriptors, NULL, TestCleanup, Context1);
  AddTestCase (ImagePropertiesRecordTests, "All images don't fit perfectly into existing descriptors", "ImagesDontFitDescriptors", ImagesDontFitDescriptors, NULL, TestCleanup, Context2);
  AddTestCase (ImagePropertiesRecordTests, "All Images are contined In single descriptor", "MultipleImagesInOneDescriptor", MultipleImagesInOneDescriptor, NULL, TestCleanup, Context3);
  AddTestCase (ImagePropertiesRecordTests, "Multiple code sections each image", "MaxOutAdditionalDescriptors", MaxOutAdditionalDescriptors, NULL, TestCleanup, Context4);

  //
  // Execute the tests.
  //
  Status = RunAllTestSuites (Framework);

EXIT:
  if (Framework) {
    FreeUnitTestFramework (Framework);
  }

  return Status;
}

///
/// Avoid ECC error for function name that starts with lower case letter
///
#define ImagePropertiesRecordLibUnitTestMain  main

/**
  Standard POSIX C entry point for host based unit test execution.

  @param[in] Argc  Number of arguments
  @param[in] Argv  Array of pointers to arguments

  @retval 0      Success
  @retval other  Error
**/
INT32
ImagePropertiesRecordLibUnitTestMain (
  IN INT32  Argc,
  IN CHAR8  *Argv[]
  )
{
  return UnitTestingEntry ();
}
