/**
  Implement UnitTestLib

  Copyright (c) Microsoft Corporation.
  Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

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

///
/// Forward declaration of prototype
///
STATIC
VOID
UpdateTestFromSave (
  IN OUT UNIT_TEST              *Test,
  IN     UNIT_TEST_SAVE_HEADER  *SavedState
  );

/**
  This function will determine whether the short name violates any rules that would
  prevent it from being used as a reporting name or as a serialization name.

  Example: If the name cannot be serialized to a filesystem file name.

  @param[in]  ShortTitleString  A pointer to the short title string to be evaluated.

  @retval  TRUE   The string is acceptable.
  @retval  FALSE  The string should not be used.

**/
STATIC
BOOLEAN
IsFrameworkShortNameValid (
  IN CHAR8  *ShortTitleString
  )
{
  // TODO: Finish this function.
  return TRUE;
}

STATIC
CHAR8 *
AllocateAndCopyString (
  IN CHAR8  *StringToCopy
  )
{
  CHAR8  *NewString;
  UINTN  NewStringLength;

  NewString       = NULL;
  NewStringLength = AsciiStrnLenS (StringToCopy, UNIT_TEST_MAX_STRING_LENGTH) + 1;
  NewString       = AllocatePool (NewStringLength * sizeof (CHAR8));
  if (NewString != NULL) {
    AsciiStrCpyS (NewString, NewStringLength, StringToCopy);
  }

  return NewString;
}

STATIC
VOID
SetFrameworkFingerprint (
  OUT UINT8                *Fingerprint,
  IN  UNIT_TEST_FRAMEWORK  *Framework
  )
{
  UINT32  NewFingerprint;

  // For this one we'll just use the title and version as the unique fingerprint.
  NewFingerprint = CalculateCrc32 (Framework->Title, (AsciiStrLen (Framework->Title) * sizeof (CHAR8)));
  NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32 (Framework->VersionString, (AsciiStrLen (Framework->VersionString) * sizeof (CHAR8)));

  CopyMem (Fingerprint, &NewFingerprint, UNIT_TEST_FINGERPRINT_SIZE);
  return;
}

STATIC
VOID
SetSuiteFingerprint (
  OUT UINT8                *Fingerprint,
  IN  UNIT_TEST_FRAMEWORK  *Framework,
  IN  UNIT_TEST_SUITE      *Suite
  )
{
  UINT32  NewFingerprint;

  // For this one, we'll use the fingerprint from the framework, and the title of the suite.
  NewFingerprint = CalculateCrc32 (&Framework->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE);
  NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32 (Suite->Title, (AsciiStrLen (Suite->Title) * sizeof (CHAR8)));
  NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32 (Suite->Name, (AsciiStrLen (Suite->Name) * sizeof (CHAR8)));

  CopyMem (Fingerprint, &NewFingerprint, UNIT_TEST_FINGERPRINT_SIZE);
  return;
}

STATIC
VOID
SetTestFingerprint (
  OUT UINT8            *Fingerprint,
  IN  UNIT_TEST_SUITE  *Suite,
  IN  UNIT_TEST        *Test
  )
{
  UINT32  NewFingerprint;

  // For this one, we'll use the fingerprint from the suite, and the description and classname of the test.
  NewFingerprint = CalculateCrc32 (&Suite->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE);
  NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32 (Test->Description, (AsciiStrLen (Test->Description) * sizeof (CHAR8)));
  NewFingerprint = (NewFingerprint >> 8) ^ CalculateCrc32 (Test->Name, (AsciiStrLen (Test->Name) * sizeof (CHAR8)));

  CopyMem (Fingerprint, &NewFingerprint, UNIT_TEST_FINGERPRINT_SIZE);
  return;
}

STATIC
BOOLEAN
CompareFingerprints (
  IN UINT8  *FingerprintA,
  IN UINT8  *FingerprintB
  )
{
  return (CompareMem (FingerprintA, FingerprintB, UNIT_TEST_FINGERPRINT_SIZE) == 0);
}

STATIC
VOID
FreeUnitTestTestEntry (
  IN UNIT_TEST_LIST_ENTRY  *TestEntry
  )
{
  if (TestEntry) {
    if (TestEntry->UT.Description) {
      FreePool (TestEntry->UT.Description);
    }

    if (TestEntry->UT.Name) {
      FreePool (TestEntry->UT.Name);
    }

    FreePool (TestEntry);
  }
}

STATIC
EFI_STATUS
FreeUnitTestSuiteEntry (
  IN UNIT_TEST_SUITE_LIST_ENTRY  *SuiteEntry
  )
{
  UNIT_TEST_LIST_ENTRY  *TestCase;
  UNIT_TEST_LIST_ENTRY  *NextTestCase;
  LIST_ENTRY            *TestCaseList;

  if (SuiteEntry) {
    TestCaseList = &(SuiteEntry->UTS.TestCaseList);
    TestCase     = (UNIT_TEST_LIST_ENTRY *)GetFirstNode (TestCaseList);
    while (&TestCase->Entry != TestCaseList) {
      NextTestCase = (UNIT_TEST_LIST_ENTRY *)GetNextNode (TestCaseList, &TestCase->Entry);
      RemoveEntryList (&TestCase->Entry);
      FreeUnitTestTestEntry (TestCase);
      TestCase = NextTestCase;
    }

    if (SuiteEntry->UTS.Title) {
      FreePool (SuiteEntry->UTS.Title);
    }

    if (SuiteEntry->UTS.Name) {
      FreePool (SuiteEntry->UTS.Name);
    }

    FreePool (SuiteEntry);
  }

  return EFI_SUCCESS;
}

/**
  Cleanup a test framework.

  After tests are run, this will teardown the entire framework and free all
  allocated data within.

  @param[in]  FrameworkHandle  A handle to the current running framework that
                               dispatched the test.  Necessary for recording
                               certain test events with the framework.

  @retval  EFI_SUCCESS            All resources associated with framework were
                                  freed.
  @retval  EFI_INVALID_PARAMETER  FrameworkHandle is NULL.
**/
EFI_STATUS
EFIAPI
FreeUnitTestFramework (
  IN UNIT_TEST_FRAMEWORK_HANDLE  FrameworkHandle
  )
{
  UNIT_TEST_FRAMEWORK         *Framework;
  UNIT_TEST_SUITE_LIST_ENTRY  *Suite;
  UNIT_TEST_SUITE_LIST_ENTRY  *NextSuite;

  Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;
  if (Framework) {
    Suite = (UNIT_TEST_SUITE_LIST_ENTRY *)GetFirstNode (&Framework->TestSuiteList);
    while ((LIST_ENTRY *)Suite != &Framework->TestSuiteList) {
      NextSuite = (UNIT_TEST_SUITE_LIST_ENTRY *)GetNextNode (&Framework->TestSuiteList, (LIST_ENTRY *)Suite);
      RemoveEntryList ((LIST_ENTRY *)Suite);
      FreeUnitTestSuiteEntry (Suite);
      Suite = NextSuite;
    }

    if (Framework->Title) {
      FreePool (Framework->Title);
    }

    if (Framework->ShortTitle) {
      FreePool (Framework->ShortTitle);
    }

    if (Framework->VersionString) {
      FreePool (Framework->VersionString);
    }

    FreePool (Framework);
  }

  return EFI_SUCCESS;
}

/**
  Method to Initialize the Unit Test framework.  This function registers the
  test name and also initializes the internal state of the test framework to
  receive any new suites and tests.

  @param[out]  FrameworkHandle  Unit test framework to be created.
  @param[in]   Title            Null-terminated ASCII string that is the user
                                friendly name of the framework. String is
                                copied.
  @param[in]   ShortTitle       Null-terminated ASCII short string that is the
                                short name of the framework with no spaces.
                                String is copied.
  @param[in]   VersionString    Null-terminated ASCII version string for the
                                framework. String is copied.

  @retval  EFI_SUCCESS            The unit test framework was initialized.
  @retval  EFI_INVALID_PARAMETER  FrameworkHandle is NULL.
  @retval  EFI_INVALID_PARAMETER  Title is NULL.
  @retval  EFI_INVALID_PARAMETER  ShortTitle is NULL.
  @retval  EFI_INVALID_PARAMETER  VersionString is NULL.
  @retval  EFI_INVALID_PARAMETER  ShortTitle is invalid.
  @retval  EFI_OUT_OF_RESOURCES   There are not enough resources available to
                                  initialize the unit test framework.
**/
EFI_STATUS
EFIAPI
InitUnitTestFramework (
  OUT UNIT_TEST_FRAMEWORK_HANDLE  *FrameworkHandle,
  IN  CHAR8                       *Title,
  IN  CHAR8                       *ShortTitle,
  IN  CHAR8                       *VersionString
  )
{
  EFI_STATUS                  Status;
  UNIT_TEST_FRAMEWORK_HANDLE  NewFrameworkHandle;
  UNIT_TEST_FRAMEWORK         *NewFramework;
  UINTN                       SaveStateSize;

  Status       = EFI_SUCCESS;
  NewFramework = NULL;

  //
  // First, check all pointers and make sure nothing's broked.
  //
  if ((FrameworkHandle == NULL) || (Title == NULL) ||
      (ShortTitle == NULL) || (VersionString == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Next, determine whether all of the strings are good to use.
  //
  if (!IsFrameworkShortNameValid (ShortTitle)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Next, set aside some space to start messing with the framework.
  //
  NewFramework = AllocateZeroPool (sizeof (UNIT_TEST_FRAMEWORK));
  if (NewFramework == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Next, set up all the test data.
  //
  NewFrameworkHandle          = (UNIT_TEST_FRAMEWORK_HANDLE)NewFramework;
  NewFramework->Title         = AllocateAndCopyString (Title);
  NewFramework->ShortTitle    = AllocateAndCopyString (ShortTitle);
  NewFramework->VersionString = AllocateAndCopyString (VersionString);
  NewFramework->Log           = NULL;
  NewFramework->CurrentTest   = NULL;
  NewFramework->SavedState    = NULL;
  if ((NewFramework->Title == NULL) ||
      (NewFramework->ShortTitle == NULL) ||
      (NewFramework->VersionString == NULL))
  {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  InitializeListHead (&(NewFramework->TestSuiteList));

  //
  // Create the framework fingerprint.
  //
  SetFrameworkFingerprint (&NewFramework->Fingerprint[0], NewFramework);

  //
  // If there is a persisted context, load it now.
  //
  if (DoesCacheExist (NewFrameworkHandle)) {
    Status = LoadUnitTestCache (NewFrameworkHandle, (VOID **)(&NewFramework->SavedState), &SaveStateSize);
    if (EFI_ERROR (Status)) {
      //
      // Don't actually report it as an error, but emit a warning.
      //
      DEBUG ((DEBUG_ERROR, "%a - Cache was detected, but failed to load.\n", __func__));
      Status = EFI_SUCCESS;
    }
  }

Exit:
  //
  // If we're good, then let's copy the framework.
  //
  if (!EFI_ERROR (Status)) {
    *FrameworkHandle = NewFrameworkHandle;
  } else {
    //
    // Otherwise, we need to undo this horrible thing that we've done.
    //
    FreeUnitTestFramework (NewFrameworkHandle);
  }

  return Status;
}

/**
  Registers a Unit Test Suite in the Unit Test Framework.
  At least one test suite must be registered, because all test cases must be
  within a unit test suite.

  @param[out]  SuiteHandle      Unit test suite to create
  @param[in]   FrameworkHandle  Unit test framework to add unit test suite to
  @param[in]   Title            Null-terminated ASCII string that is the user
                                friendly name of the test suite.  String is
                                copied.
  @param[in]   Name             Null-terminated ASCII string that is the short
                                name of the test suite with no spaces.  String
                                is copied.
  @param[in]   Setup            Setup function, runs before suite.  This is an
                                optional parameter that may be NULL.
  @param[in]   Teardown         Teardown function, runs after suite.  This is an
                                optional parameter that may be NULL.

  @retval  EFI_SUCCESS            The unit test suite was created.
  @retval  EFI_INVALID_PARAMETER  SuiteHandle is NULL.
  @retval  EFI_INVALID_PARAMETER  FrameworkHandle is NULL.
  @retval  EFI_INVALID_PARAMETER  Title is NULL.
  @retval  EFI_INVALID_PARAMETER  Name is NULL.
  @retval  EFI_OUT_OF_RESOURCES   There are not enough resources available to
                                  initialize the unit test suite.
**/
EFI_STATUS
EFIAPI
CreateUnitTestSuite (
  OUT UNIT_TEST_SUITE_HANDLE      *SuiteHandle,
  IN  UNIT_TEST_FRAMEWORK_HANDLE  FrameworkHandle,
  IN  CHAR8                       *Title,
  IN  CHAR8                       *Name,
  IN  UNIT_TEST_SUITE_SETUP       Setup     OPTIONAL,
  IN  UNIT_TEST_SUITE_TEARDOWN    Teardown  OPTIONAL
  )
{
  EFI_STATUS                  Status;
  UNIT_TEST_SUITE_LIST_ENTRY  *NewSuiteEntry;
  UNIT_TEST_FRAMEWORK         *Framework;

  Status    = EFI_SUCCESS;
  Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;

  //
  // First, let's check to make sure that our parameters look good.
  //
  if ((SuiteHandle == NULL) || (Framework == NULL) || (Title == NULL) || (Name == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Create the new entry.
  //
  NewSuiteEntry = AllocateZeroPool (sizeof (UNIT_TEST_SUITE_LIST_ENTRY));
  if (NewSuiteEntry == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Copy the fields we think we need.
  //
  NewSuiteEntry->UTS.NumTests        = 0;
  NewSuiteEntry->UTS.Title           = AllocateAndCopyString (Title);
  NewSuiteEntry->UTS.Name            = AllocateAndCopyString (Name);
  NewSuiteEntry->UTS.Setup           = Setup;
  NewSuiteEntry->UTS.Teardown        = Teardown;
  NewSuiteEntry->UTS.ParentFramework = FrameworkHandle;
  InitializeListHead (&(NewSuiteEntry->Entry));             // List entry for sibling suites.
  InitializeListHead (&(NewSuiteEntry->UTS.TestCaseList));  // List entry for child tests.
  if (NewSuiteEntry->UTS.Title == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  if (NewSuiteEntry->UTS.Name == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  //
  // Create the suite fingerprint.
  //
  SetSuiteFingerprint (&NewSuiteEntry->UTS.Fingerprint[0], Framework, &NewSuiteEntry->UTS);

Exit:
  //
  // If everything is going well, add the new suite to the tail list for the framework.
  //
  if (!EFI_ERROR (Status)) {
    InsertTailList (&(Framework->TestSuiteList), (LIST_ENTRY *)NewSuiteEntry);
    *SuiteHandle = (UNIT_TEST_SUITE_HANDLE)(&NewSuiteEntry->UTS);
  } else {
    //
    // Otherwise, make with the destruction.
    //
    FreeUnitTestSuiteEntry (NewSuiteEntry);
  }

  return Status;
}

/**
  Adds test case to Suite

  @param[in]  SuiteHandle   Unit test suite to add test to.
  @param[in]  Description   Null-terminated ASCII string that is the user
                            friendly description of a test.  String is copied.
  @param[in]  Name          Null-terminated ASCII string that is the short name
                            of the test with no spaces.  String is copied.
  @param[in]  Function      Unit test function.
  @param[in]  Prerequisite  Prerequisite function, runs before test.  This is
                            an optional parameter that may be NULL.
  @param[in]  CleanUp       Clean up function, runs after test.  This is an
                            optional parameter that may be NULL.
  @param[in]  Context       Pointer to context.    This is an optional parameter
                            that may be NULL.

  @retval  EFI_SUCCESS            The unit test case was added to Suite.
  @retval  EFI_INVALID_PARAMETER  SuiteHandle is NULL.
  @retval  EFI_INVALID_PARAMETER  Description is NULL.
  @retval  EFI_INVALID_PARAMETER  Name is NULL.
  @retval  EFI_INVALID_PARAMETER  Function is NULL.
  @retval  EFI_OUT_OF_RESOURCES   There are not enough resources available to
                                  add the unit test case to Suite.
**/
EFI_STATUS
EFIAPI
AddTestCase (
  IN UNIT_TEST_SUITE_HANDLE  SuiteHandle,
  IN CHAR8                   *Description,
  IN CHAR8                   *Name,
  IN UNIT_TEST_FUNCTION      Function,
  IN UNIT_TEST_PREREQUISITE  Prerequisite  OPTIONAL,
  IN UNIT_TEST_CLEANUP       CleanUp       OPTIONAL,
  IN UNIT_TEST_CONTEXT       Context       OPTIONAL
  )
{
  EFI_STATUS            Status;
  UNIT_TEST_LIST_ENTRY  *NewTestEntry;
  UNIT_TEST_FRAMEWORK   *ParentFramework;
  UNIT_TEST_SUITE       *Suite;

  Status = EFI_SUCCESS;
  Suite  = (UNIT_TEST_SUITE *)SuiteHandle;

  //
  // First, let's check to make sure that our parameters look good.
  //
  if ((Suite == NULL) || (Description == NULL) || (Name == NULL) || (Function == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  ParentFramework = (UNIT_TEST_FRAMEWORK *)Suite->ParentFramework;
  //
  // Create the new entry.
  NewTestEntry = AllocateZeroPool (sizeof (UNIT_TEST_LIST_ENTRY));
  if (NewTestEntry == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Copy the fields we think we need.
  NewTestEntry->UT.Description       = AllocateAndCopyString (Description);
  NewTestEntry->UT.Name              = AllocateAndCopyString (Name);
  NewTestEntry->UT.FailureType       = FAILURETYPE_NOFAILURE;
  NewTestEntry->UT.FailureMessage[0] = '\0';
  NewTestEntry->UT.Log               = NULL;
  NewTestEntry->UT.Prerequisite      = Prerequisite;
  NewTestEntry->UT.CleanUp           = CleanUp;
  NewTestEntry->UT.RunTest           = Function;
  NewTestEntry->UT.Context           = Context;
  NewTestEntry->UT.Result            = UNIT_TEST_PENDING;
  NewTestEntry->UT.ParentSuite       = SuiteHandle;
  InitializeListHead (&(NewTestEntry->Entry));  // List entry for sibling tests.
  if (NewTestEntry->UT.Description == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  if (NewTestEntry->UT.Name == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Exit;
  }

  //
  // Create the test fingerprint.
  //
  SetTestFingerprint (&NewTestEntry->UT.Fingerprint[0], Suite, &NewTestEntry->UT);

  // TODO: Make sure that duplicate fingerprints cannot be created.

  //
  // If there is saved test data, update this record.
  //
  if (ParentFramework->SavedState != NULL) {
    UpdateTestFromSave (&NewTestEntry->UT, ParentFramework->SavedState);
  }

Exit:
  //
  // If everything is going well, add the new suite to the tail list for the framework.
  //
  if (!EFI_ERROR (Status)) {
    InsertTailList (&(Suite->TestCaseList), (LIST_ENTRY *)NewTestEntry);
    Suite->NumTests++;
  } else {
    //
    // Otherwise, make with the destruction.
    //
    FreeUnitTestTestEntry (NewTestEntry);
  }

  return Status;
}

STATIC
VOID
UpdateTestFromSave (
  IN OUT UNIT_TEST              *Test,
  IN     UNIT_TEST_SAVE_HEADER  *SavedState
  )
{
  UNIT_TEST_SAVE_TEST     *CurrentTest;
  UNIT_TEST_SAVE_TEST     *MatchingTest;
  UINT8                   *FloatingPointer;
  UNIT_TEST_SAVE_CONTEXT  *SavedContext;
  UINTN                   Index;

  //
  // First, evaluate the inputs.
  //
  if ((Test == NULL) || (SavedState == NULL)) {
    return;
  }

  if (SavedState->TestCount == 0) {
    return;
  }

  //
  // Next, determine whether a matching test can be found.
  // Start at the beginning.
  //
  MatchingTest    = NULL;
  FloatingPointer = (UINT8 *)SavedState + sizeof (*SavedState);
  for (Index = 0; Index < SavedState->TestCount; Index++) {
    CurrentTest = (UNIT_TEST_SAVE_TEST *)FloatingPointer;
    if (CompareFingerprints (&Test->Fingerprint[0], &CurrentTest->Fingerprint[0])) {
      MatchingTest = CurrentTest;
      //
      // If there's a saved context, it's important that we iterate through the entire list.
      //
      if (!SavedState->HasSavedContext) {
        break;
      }
    }

    //
    // If we didn't find it, we have to increment to the next test.
    //
    FloatingPointer = (UINT8 *)CurrentTest + CurrentTest->Size;
  }

  //
  // If a matching test was found, copy the status.
  //
  if (MatchingTest) {
    //
    // Override the test status with the saved status.
    //
    Test->Result = MatchingTest->Result;

    Test->FailureType = MatchingTest->FailureType;
    AsciiStrnCpyS (
      &Test->FailureMessage[0],
      UNIT_TEST_MAX_STRING_LENGTH,
      &MatchingTest->FailureMessage[0],
      UNIT_TEST_MAX_STRING_LENGTH
      );

    //
    // If there is a log string associated, grab that.
    // We can tell that there's a log string because the "size" will be larger than
    // the structure size.
    // IMPORTANT NOTE: There are security implications here.
    //                 This data is user-supplied and we're about to play kinda
    //                 fast and loose with data buffers.
    //
    if (MatchingTest->Size > sizeof (UNIT_TEST_SAVE_TEST)) {
      UnitTestLogInit (Test, (UINT8 *)MatchingTest->Log, MatchingTest->Size - sizeof (UNIT_TEST_SAVE_TEST));
    }
  }

  //
  // If the saved context exists and matches this test, grab it, too.
  //
  if (SavedState->HasSavedContext) {
    //
    // If there was a saved context, the "matching test" loop will have placed the FloatingPointer
    // at the beginning of the context structure.
    //
    SavedContext = (UNIT_TEST_SAVE_CONTEXT *)FloatingPointer;
    if (((SavedContext->Size - sizeof (UNIT_TEST_SAVE_CONTEXT)) > 0) &&
        CompareFingerprints (&Test->Fingerprint[0], &SavedContext->Fingerprint[0]))
    {
      //
      // Override the test context with the saved context.
      //
      Test->Context = (VOID *)SavedContext->Data;
    }
  }
}

STATIC
UNIT_TEST_SAVE_HEADER *
SerializeState (
  IN UNIT_TEST_FRAMEWORK_HANDLE  FrameworkHandle,
  IN UNIT_TEST_CONTEXT           ContextToSave       OPTIONAL,
  IN UINTN                       ContextToSaveSize
  )
{
  UNIT_TEST_FRAMEWORK     *Framework;
  UNIT_TEST_SAVE_HEADER   *Header;
  LIST_ENTRY              *SuiteListHead;
  LIST_ENTRY              *Suite;
  LIST_ENTRY              *TestListHead;
  LIST_ENTRY              *Test;
  UINT32                  TestCount;
  UINT32                  TotalSize;
  UINTN                   LogSize;
  UNIT_TEST_SAVE_TEST     *TestSaveData;
  UNIT_TEST_SAVE_CONTEXT  *TestSaveContext;
  UNIT_TEST               *UnitTest;
  UINT8                   *FloatingPointer;

  Framework = (UNIT_TEST_FRAMEWORK *)FrameworkHandle;
  Header    = NULL;

  //
  // First, let's not make assumptions about the parameters.
  //
  if ((Framework == NULL) ||
      ((ContextToSave != NULL) && (ContextToSaveSize == 0)) ||
      (ContextToSaveSize > MAX_UINT32))
  {
    return NULL;
  }

  //
  // Next, we've gotta figure out the resources that will be required to serialize the
  // the framework state so that we can persist it.
  // To start with, we're gonna need a header.
  //
  TotalSize = sizeof (UNIT_TEST_SAVE_HEADER);
  //
  // Now we need to figure out how many tests there are.
  //
  TestCount = 0;
  //
  // Iterate all suites.
  //
  SuiteListHead = &Framework->TestSuiteList;
  for (Suite = GetFirstNode (SuiteListHead); Suite != SuiteListHead; Suite = GetNextNode (SuiteListHead, Suite)) {
    //
    // Iterate all tests within the suite.
    //
    TestListHead = &((UNIT_TEST_SUITE_LIST_ENTRY *)Suite)->UTS.TestCaseList;
    for (Test = GetFirstNode (TestListHead); Test != TestListHead; Test = GetNextNode (TestListHead, Test)) {
      UnitTest = &((UNIT_TEST_LIST_ENTRY *)Test)->UT;
      //
      // Account for the size of a test structure.
      //
      TotalSize += sizeof (UNIT_TEST_SAVE_TEST);
      //
      // If there's a log, make sure to account for the log size.
      //
      if (UnitTest->Log != NULL) {
        //
        // The +1 is for the NULL character. Can't forget the NULL character.
        //
        LogSize = (AsciiStrLen (UnitTest->Log) + 1) * sizeof (CHAR8);
        ASSERT (LogSize < MAX_UINT32);
        TotalSize += (UINT32)LogSize;
      }

      //
      // Increment the test count.
      //
      TestCount++;
    }
  }

  //
  // If there are no tests, we're done here.
  //
  if (TestCount == 0) {
    return NULL;
  }

  //
  // Add room for the context, if there is one.
  //
  if (ContextToSave != NULL) {
    TotalSize += sizeof (UNIT_TEST_SAVE_CONTEXT) + (UINT32)ContextToSaveSize;
  }

  //
  // Now that we know the size, we need to allocate space for the serialized output.
  //
  Header = AllocateZeroPool (TotalSize);
  if (Header == NULL) {
    return NULL;
  }

  //
  // Alright, let's start setting up some data.
  //
  Header->Version       = UNIT_TEST_PERSISTENCE_LIB_VERSION;
  Header->SaveStateSize = TotalSize;
  CopyMem (&Header->Fingerprint[0], &Framework->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE);
  CopyMem (&Header->StartTime, &Framework->StartTime, sizeof (EFI_TIME));
  Header->TestCount       = TestCount;
  Header->HasSavedContext = FALSE;

  //
  // Start adding all of the test cases.
  // Set the floating pointer to the start of the current test save buffer.
  //
  FloatingPointer = (UINT8 *)Header + sizeof (UNIT_TEST_SAVE_HEADER);
  //
  // Iterate all suites.
  //
  SuiteListHead = &Framework->TestSuiteList;
  for (Suite = GetFirstNode (SuiteListHead); Suite != SuiteListHead; Suite = GetNextNode (SuiteListHead, Suite)) {
    //
    // Iterate all tests within the suite.
    //
    TestListHead = &((UNIT_TEST_SUITE_LIST_ENTRY *)Suite)->UTS.TestCaseList;
    for (Test = GetFirstNode (TestListHead); Test != TestListHead; Test = GetNextNode (TestListHead, Test)) {
      TestSaveData = (UNIT_TEST_SAVE_TEST *)FloatingPointer;
      UnitTest     = &((UNIT_TEST_LIST_ENTRY *)Test)->UT;

      //
      // Save the fingerprint.
      //
      CopyMem (&TestSaveData->Fingerprint[0], &UnitTest->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE);

      //
      // Save the result.
      //
      TestSaveData->Result      = UnitTest->Result;
      TestSaveData->FailureType = UnitTest->FailureType;
      AsciiStrnCpyS (&TestSaveData->FailureMessage[0], UNIT_TEST_MAX_STRING_LENGTH, &UnitTest->FailureMessage[0], UNIT_TEST_MAX_STRING_LENGTH);

      //
      // If there is a log, save the log.
      //
      FloatingPointer += sizeof (UNIT_TEST_SAVE_TEST);
      if (UnitTest->Log != NULL) {
        //
        // The +1 is for the NULL character. Can't forget the NULL character.
        //
        LogSize = (AsciiStrLen (UnitTest->Log) + 1) * sizeof (CHAR8);
        CopyMem (FloatingPointer, UnitTest->Log, LogSize);
        FloatingPointer += LogSize;
      }

      //
      // Update the size once the structure is complete.
      // NOTE: Should this be a straight cast without validation?
      //
      TestSaveData->Size = (UINT32)(FloatingPointer - (UINT8 *)TestSaveData);
    }
  }

  //
  // If there is a context to save, let's do that now.
  //
  if ((ContextToSave != NULL) && (Framework->CurrentTest != NULL)) {
    TestSaveContext       = (UNIT_TEST_SAVE_CONTEXT *)FloatingPointer;
    TestSaveContext->Size = (UINT32)ContextToSaveSize + sizeof (UNIT_TEST_SAVE_CONTEXT);
    CopyMem (&TestSaveContext->Fingerprint[0], &Framework->CurrentTest->Fingerprint[0], UNIT_TEST_FINGERPRINT_SIZE);
    CopyMem (((UINT8 *)TestSaveContext + sizeof (UNIT_TEST_SAVE_CONTEXT)), ContextToSave, ContextToSaveSize);
    Header->HasSavedContext = TRUE;
  }

  return Header;
}

/**
  Leverages a framework-specific mechanism (see UnitTestPersistenceLib if you're
  a framework author) to save the state of the executing framework along with
  any allocated data so that the test may be resumed upon reentry. A test case
  should pass any needed context (which, to prevent an infinite loop, should be
  at least the current execution count) which will be saved by the framework and
  passed to the test case upon resume.

  This should be called while the current test framework is valid and active. It is
  generally called from within a test case prior to quitting or rebooting.

  @param[in]  ContextToSave      A buffer of test case-specific data to be saved
                                 along with framework state.  Will be passed as
                                 "Context" to the test case upon resume.  This
                                 is an optional parameter that may be NULL.
  @param[in]  ContextToSaveSize  Size of the ContextToSave buffer.

  @retval  EFI_SUCCESS            The framework state and context were saved.
  @retval  EFI_NOT_FOUND          An active framework handle was not found.
  @retval  EFI_INVALID_PARAMETER  ContextToSave is not NULL and
                                  ContextToSaveSize is 0.
  @retval  EFI_INVALID_PARAMETER  ContextToSave is >= 4GB.
  @retval  EFI_OUT_OF_RESOURCES   There are not enough resources available to
                                  save the framework and context state.
  @retval  EFI_DEVICE_ERROR       The framework and context state could not be
                                  saved to a persistent storage device due to a
                                  device error.
**/
EFI_STATUS
EFIAPI
SaveFrameworkState (
  IN UNIT_TEST_CONTEXT  ContextToSave     OPTIONAL,
  IN UINTN              ContextToSaveSize
  )
{
  EFI_STATUS                  Status;
  UNIT_TEST_FRAMEWORK_HANDLE  FrameworkHandle;
  UNIT_TEST_SAVE_HEADER       *Header;

  Header          = NULL;
  FrameworkHandle = GetActiveFrameworkHandle ();
  if (FrameworkHandle == NULL) {
    DEBUG ((DEBUG_ERROR, "%a - Could not save state! FrameworkHandle not initialized\n", __func__));
    return EFI_DEVICE_ERROR;
  }

  //
  // Return a unique error code if the framework is not set.
  //
  if (FrameworkHandle == NULL) {
    return EFI_NOT_FOUND;
  }

  //
  // First, let's not make assumptions about the parameters.
  //
  if (((ContextToSave != NULL) && (ContextToSaveSize == 0)) ||
      (ContextToSaveSize > MAX_UINT32))
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Now, let's package up all the data for saving.
  //
  Header = SerializeState (FrameworkHandle, ContextToSave, ContextToSaveSize);
  if (Header == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // All that should be left to do is save it using the associated persistence lib.
  //
  Status = SaveUnitTestCache (FrameworkHandle, Header, Header->SaveStateSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a - Could not save state! %r\n", __func__, Status));
    Status = EFI_DEVICE_ERROR;
  }

  //
  // Free data that was used.
  //
  FreePool (Header);

  return Status;
}
