/** @file -- MediaSanitizeUnitTest.c
  Placeholder/framework for developing a Media Sanitize unit test package.

  Copyright (c) Microsoft Corporation.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UnitTestLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Protocol/BlockIo.h>
#include <Protocol/NvmExpressPassthru.h>
#include <Protocol/MediaSanitize.h>

#include "../NvmExpress.h"
#include "../NvmExpressBlockIo.h"
#include "../NvmExpressMediaSanitize.h"
#include "../NvmExpressHci.h"

/**
  Helper function for Nvme pass thru.

  @param[in]     This        Private Data.
  @param[in]     NamespaceId Name Space Id.
  @param[in,out] Packet      Transfer Buffer.
  @param[in]     Event       Event handle.

 **/
EFI_STATUS
EFIAPI
NvmeDeviceUnitTestPassthru (
  IN     EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL        *This,
  IN     UINT32                                    NamespaceId,
  IN OUT EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET  *Packet,
  IN     EFI_EVENT                                 Event OPTIONAL
  )
{
  //
  // Parse command packet for unit testing
  //
  EFI_NVM_EXPRESS_COMMAND     *Command;
  EFI_NVM_EXPRESS_COMPLETION  *Completion;
  NVME_CQ                     *Cqe;
  NVME_ADMIN_FORMAT_NVM       FormatNvmCdw10;
  NVME_ADMIN_SANITIZE         SanitizeCdw1011;

  ASSERT (This);
  ASSERT (Packet);

  Command    = Packet->NvmeCmd;
  Completion = Packet->NvmeCompletion;
  Cqe        = (NVME_CQ *)Completion;

  ZeroMem (&FormatNvmCdw10, sizeof (NVME_ADMIN_FORMAT_NVM));
  ZeroMem (&SanitizeCdw1011, sizeof (NVME_ADMIN_SANITIZE));

  switch (Command->Cdw0.Opcode) {
    case NVME_ADMIN_FORMAT_NVM_CMD:
      UT_LOG_VERBOSE ("%a: Opcode = NVME_ADMIN_FORMAT_NVM_CMD\n", __func__);

      CopyMem (&FormatNvmCdw10, &Command->Cdw10, sizeof (NVME_ADMIN_FORMAT_NVM));

      //
      // FormatNVM Check 1: Validate SES parameter
      //
      if (FormatNvmCdw10.Ses > 0x2) {
        Cqe->Sct = NVME_CQE_SCT_GENERIC_CMD_STATUS;
        Cqe->Sc  = NVME_CQE_SC_INVALID_FIELD_IN_CMD;

        return EFI_INVALID_PARAMETER;
      }

      //
      // FormatNVM Check 2: Validate LbaIndex parameter
      //
      if (FormatNvmCdw10.Lbaf > 0x1) {
        Cqe->Sct = NVME_CQE_SCT_GENERIC_CMD_STATUS;
        Cqe->Sc  = NVME_CQE_SC_INVALID_FIELD_IN_CMD;

        return EFI_INVALID_PARAMETER;
      }

      break;
    case NVME_ADMIN_SANITIZE_CMD:
      UT_LOG_VERBOSE ("%a: Opcode = NVME_ADMIN_SANITIZE_CMD\n", __func__);

      CopyMem (&SanitizeCdw1011, &Command->Cdw10, sizeof (NVME_ADMIN_SANITIZE));

      //
      // Sanitize Check 1: Validate Sanitize Action parameter
      //
      if (SanitizeCdw1011.Sanact > 0x4) {
        Cqe->Sct = NVME_CQE_SCT_GENERIC_CMD_STATUS;
        Cqe->Sc  = NVME_CQE_SC_INVALID_FIELD_IN_CMD;

        return EFI_INVALID_PARAMETER;
      }

      //
      // Sanitize Check 2: Validate overwrite action with non-NULL overwrite pattern
      //
      if (((SanitizeCdw1011.Sanact == SANITIZE_ACTION_OVERWRITE) && (SanitizeCdw1011.Ovrpat != 0xDEADBEEF)) ||
          ((SanitizeCdw1011.Sanact != SANITIZE_ACTION_OVERWRITE) && (SanitizeCdw1011.Ovrpat != 0)))
      {
        Cqe->Sct = NVME_CQE_SCT_GENERIC_CMD_STATUS;
        Cqe->Sc  = NVME_CQE_SC_INVALID_FIELD_IN_CMD;

        return EFI_INVALID_PARAMETER;
      }

      break;
    default:
      UT_LOG_VERBOSE ("%a: Invalid Opcode = 0x%x!!!\n", __func__, Command->Cdw0.Opcode);
      break;
  }

  //
  // Populate CQE (completion queue entry based on opcode and parameters
  //

  return EFI_SUCCESS;
}

/**
  Helper function to simulate read.

  @param[in]   Private     Private Data.
  @param[in]   NamespaceId Name Space Id.
  @param[in]   Buffer      Transfer Buffer.

 **/
EFI_STATUS
NvmeIdentifyNamespace (
  IN NVME_CONTROLLER_PRIVATE_DATA  *Private,
  IN UINT32                        NamespaceId,
  IN VOID                          *Buffer
  )
{
  EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET  CommandPacket;
  EFI_NVM_EXPRESS_COMMAND                   Command;
  EFI_NVM_EXPRESS_COMPLETION                Completion;

  ZeroMem (&CommandPacket, sizeof (EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET));
  ZeroMem (&Command, sizeof (EFI_NVM_EXPRESS_COMMAND));
  ZeroMem (&Completion, sizeof (EFI_NVM_EXPRESS_COMPLETION));

  CommandPacket.NvmeCmd        = &Command;
  CommandPacket.NvmeCompletion = &Completion;
  Command.Cdw0.Opcode          = NVME_ADMIN_IDENTIFY_CMD;
  Command.Nsid                 = NamespaceId;
  CommandPacket.TransferBuffer = Buffer;
  CommandPacket.TransferLength = sizeof (NVME_ADMIN_NAMESPACE_DATA);
  CommandPacket.CommandTimeout = NVME_GENERIC_TIMEOUT;
  CommandPacket.QueueType      = NVME_ADMIN_QUEUE;

  //
  // Set bit 0 (Cns bit) to 1 to identify a namespace
  //
  CommandPacket.NvmeCmd->Cdw10 = 0;
  CommandPacket.NvmeCmd->Flags = CDW10_VALID;

  return EFI_SUCCESS;
}

/**
  Helper function to simulate read.

  @param[in]   Device  Private Data.
  @param[out]  Buffer  Buffer to read into.
  @param[in]   Lba     Logical Block Addess to read from.
  @param[in]   Blocks  Number of blocks.

 **/
EFI_STATUS
NvmeUnitTestRead (
  IN     NVME_DEVICE_PRIVATE_DATA  *Device,
  OUT VOID                         *Buffer,
  IN     UINT64                    Lba,
  IN     UINTN                     Blocks
  )
{
  UT_ASSERT_NOT_NULL (Device);
  Buffer = NULL;
  Lba    = 0;
  Blocks = 0;

  return EFI_SUCCESS;
}

/**
  Helper function to simulate write.

  @param[in]     Device  Private Data.
  @param[in]     Buffer  Buffer to write.
  @param[in]     Lba     Logical Block Addess to write.
  @param[in]     Blocks  Number of blocks.

 **/
EFI_STATUS
NvmeUnitTestWrite (
  IN NVME_DEVICE_PRIVATE_DATA  *Device,
  IN VOID                      *Buffer,
  IN UINT64                    Lba,
  IN UINTN                     Blocks
  )
{
  UT_ASSERT_NOT_NULL (Device);
  Buffer = NULL;
  Lba    = 0;
  Blocks = 0;

  return EFI_SUCCESS;
}

/**
  Simulated BlockIo read block function.

  @param[in]     This        BlockIo Protocol.
  @param[in]     MediaId     Id of the media.
  @param[in]     Lba         Logical Block Address.
  @param[in]     BufferSize  Size of Buffer.
  @param[out]    Buffer      Actual buffer to use to read.

 **/
EFI_STATUS
EFIAPI
NvmeBlockIoReadBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL  *This,
  IN  UINT32                 MediaId,
  IN  EFI_LBA                Lba,
  IN  UINTN                  BufferSize,
  OUT VOID                   *Buffer
  )
{
  NVME_DEVICE_PRIVATE_DATA  *Device;
  EFI_STATUS                Status;
  EFI_BLOCK_IO_MEDIA        *Media;
  UINTN                     BlockSize;
  UINTN                     NumberOfBlocks;
  UINTN                     IoAlign;

  //
  // Check parameters.
  //
  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Media = This->Media;

  if (MediaId != Media->MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (BufferSize == 0) {
    return EFI_SUCCESS;
  }

  BlockSize = Media->BlockSize;
  if ((BufferSize % BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  NumberOfBlocks = BufferSize / BlockSize;
  if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
    return EFI_INVALID_PARAMETER;
  }

  IoAlign = Media->IoAlign;
  if ((IoAlign > 0) && (((UINTN)Buffer & (IoAlign - 1)) != 0)) {
    return EFI_INVALID_PARAMETER;
  }

  Device = NVME_DEVICE_PRIVATE_DATA_FROM_BLOCK_IO (This);
  Status = NvmeUnitTestRead (Device, Buffer, Lba, NumberOfBlocks);

  return Status;
}

/**
  Simulated BlockIo write block function.

  @param[in]     This        BlockIo Protocol.
  @param[in]     MediaId     Id of the media.
  @param[in]     Lba         Logical Block Address.
  @param[in]     BufferSize  Size of Buffer.
  @param[in]     Buffer      Actual buffer to use to write.

 **/
EFI_STATUS
EFIAPI
NvmeBlockIoWriteBlocks (
  IN  EFI_BLOCK_IO_PROTOCOL  *This,
  IN  UINT32                 MediaId,
  IN  EFI_LBA                Lba,
  IN  UINTN                  BufferSize,
  IN  VOID                   *Buffer
  )
{
  NVME_DEVICE_PRIVATE_DATA  *Device;
  EFI_STATUS                Status;
  EFI_BLOCK_IO_MEDIA        *Media;
  UINTN                     BlockSize;
  UINTN                     NumberOfBlocks;
  UINTN                     IoAlign;

  //
  // Check parameters.
  //
  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Media = This->Media;

  if (MediaId != Media->MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if (BufferSize == 0) {
    return EFI_SUCCESS;
  }

  BlockSize = Media->BlockSize;
  if ((BufferSize % BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  NumberOfBlocks = BufferSize / BlockSize;
  if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
    return EFI_INVALID_PARAMETER;
  }

  IoAlign = Media->IoAlign;
  if ((IoAlign > 0) && (((UINTN)Buffer & (IoAlign - 1)) != 0)) {
    return EFI_INVALID_PARAMETER;
  }

  Device = NVME_DEVICE_PRIVATE_DATA_FROM_BLOCK_IO (This);
  Status = NvmeUnitTestWrite (Device, Buffer, Lba, NumberOfBlocks);

  return Status;
}

/**
  Simulated BlockIo read block ex function.

  @param[in]     This        BlockIo2 Protocol.
  @param[in]     MediaId     Id of the media.
  @param[in]     Lba         Logical Block Address.
  @param[in,out] Token       Block Io2 token.

  @param[in]     BufferSize  Size of Buffer.
  @param[out]    Buffer      Actual buffer to use to read.

 **/
EFI_STATUS
EFIAPI
NvmeBlockIoReadBlocksEx (
  IN     EFI_BLOCK_IO2_PROTOCOL  *This,
  IN     UINT32                  MediaId,
  IN     EFI_LBA                 Lba,
  IN OUT EFI_BLOCK_IO2_TOKEN     *Token,
  IN     UINTN                   BufferSize,
  OUT VOID                       *Buffer
  )
{
  NVME_DEVICE_PRIVATE_DATA  *Device;
  EFI_BLOCK_IO_MEDIA        *Media;
  UINTN                     BlockSize;
  UINTN                     NumberOfBlocks;
  UINTN                     IoAlign;
  EFI_STATUS                Status;

  //
  // Check parameters.
  //
  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Media = This->Media;

  if (MediaId != Media->MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  BlockSize = Media->BlockSize;
  if ((BufferSize % BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  NumberOfBlocks = BufferSize / BlockSize;
  if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
    return EFI_INVALID_PARAMETER;
  }

  IoAlign = Media->IoAlign;
  if ((IoAlign > 0) && (((UINTN)Buffer & (IoAlign - 1)) != 0)) {
    return EFI_INVALID_PARAMETER;
  }

  Device = NVME_DEVICE_PRIVATE_DATA_FROM_BLOCK_IO2 (This);
  Status = NvmeUnitTestRead (Device, Buffer, Lba, NumberOfBlocks);

  return Status;
}

/**
  Simulated BlockIo write block ex function.

  @param[in]     This        BlockIo2 Protocol.
  @param[in]     MediaId     Id of the media.
  @param[in]     Lba         Logical Block Address.
  @param[in,out] Token       Block Io2 token.
  @param[in]     BufferSize  Size of Buffer.

  @param[in]     Buffer      Actual buffer to use to write.

 **/
EFI_STATUS
EFIAPI
NvmeBlockIoWriteBlocksEx (
  IN     EFI_BLOCK_IO2_PROTOCOL  *This,
  IN     UINT32                  MediaId,
  IN     EFI_LBA                 Lba,
  IN OUT EFI_BLOCK_IO2_TOKEN     *Token,
  IN     UINTN                   BufferSize,
  IN     VOID                    *Buffer
  )
{
  NVME_DEVICE_PRIVATE_DATA  *Device;
  EFI_BLOCK_IO_MEDIA        *Media;
  UINTN                     BlockSize;
  UINTN                     NumberOfBlocks;
  UINTN                     IoAlign;
  EFI_STATUS                Status;

  //
  // Check parameters.
  //
  if (This == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Media = This->Media;

  if (MediaId != Media->MediaId) {
    return EFI_MEDIA_CHANGED;
  }

  if (Buffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  BlockSize = Media->BlockSize;
  if ((BufferSize % BlockSize) != 0) {
    return EFI_BAD_BUFFER_SIZE;
  }

  NumberOfBlocks = BufferSize / BlockSize;
  if ((Lba + NumberOfBlocks - 1) > Media->LastBlock) {
    return EFI_INVALID_PARAMETER;
  }

  IoAlign = Media->IoAlign;
  if ((IoAlign > 0) && (((UINTN)Buffer & (IoAlign - 1)) != 0)) {
    return EFI_INVALID_PARAMETER;
  }

  Device = NVME_DEVICE_PRIVATE_DATA_FROM_BLOCK_IO2 (This);
  Status = NvmeUnitTestWrite (Device, Buffer, Lba, NumberOfBlocks);

  return Status;
}

/**
  MediaSanitizePurgeUnitTest to initialize a Private Namespace instance.

  @param[in]  ppDevice  Nvme Private Data structure to destory and free.
 **/
UNIT_TEST_STATUS
EFIAPI
NvmeDestroyDeviceInstance (
  NVME_DEVICE_PRIVATE_DATA  **ppDevice
  )
{
  //
  // Free in following order to to avoid dangling pointers:
  //
  // 1 - NVME_ADMIN_CONTROLLER_DATA
  // 2 - NVME_CONTROLLER_PRIVATE_DATA
  // 3 - NVME_DEVICE_PRIVATE_DATA
  //
  FreePool ((*ppDevice)->Controller->ControllerData);
  (*ppDevice)->Controller->ControllerData = NULL;

  FreePool ((*ppDevice)->Controller);
  (*ppDevice)->Controller = NULL;

  FreePool ((*ppDevice));
  *ppDevice = NULL;

  return UNIT_TEST_PASSED;
}

/**
  MediaSanitizePurgeUnitTest to initialize a Private Namespace instance.

  @param[in]  ppDevice  Nvme Private Data structure to initialize.
 **/
UNIT_TEST_STATUS
EFIAPI
NvmeCreateDeviceInstance (
  NVME_DEVICE_PRIVATE_DATA  **ppDevice
  )
{
  NVME_ADMIN_NAMESPACE_DATA     *NamespaceData;
  NVME_CONTROLLER_PRIVATE_DATA  *Private;
  NVME_DEVICE_PRIVATE_DATA      *Device;

  Private = AllocateZeroPool (sizeof (NVME_CONTROLLER_PRIVATE_DATA));

  Private->Signature     = NVME_CONTROLLER_PRIVATE_DATA_SIGNATURE;
  Private->Cid[0]        = 0;
  Private->Cid[1]        = 0;
  Private->Cid[2]        = 0;
  Private->Pt[0]         = 0;
  Private->Pt[1]         = 0;
  Private->Pt[2]         = 0;
  Private->SqTdbl[0].Sqt = 0;
  Private->SqTdbl[1].Sqt = 0;
  Private->SqTdbl[2].Sqt = 0;
  Private->CqHdbl[0].Cqh = 0;
  Private->CqHdbl[1].Cqh = 0;
  Private->CqHdbl[2].Cqh = 0;
  Private->AsyncSqHead   = 0;

  Private->ControllerData = (NVME_ADMIN_CONTROLLER_DATA *)AllocateZeroPool (sizeof (NVME_ADMIN_CONTROLLER_DATA));

  UT_LOG_VERBOSE ("%a: Allocated and Initialized NVME_CONTROLLER_PRIVATE_DATA\n", __func__);
  UT_LOG_VERBOSE ("%a: Allocated and Initialized NVME_ADMIN_CONTROLLER_DATA\n", __func__);

  Private->ControllerData->Nn          = 1; // One namespace
  Private->ControllerData->Sanicap.Bes = 1; // Block Erase Supported
  Private->ControllerData->Sanicap.Ces = 1; // Crypto Erase Supported
  Private->ControllerData->Sanicap.Ows = 1; // Overwrite Supported

  NamespaceData = AllocateZeroPool (sizeof (NVME_ADMIN_NAMESPACE_DATA));
  UT_LOG_VERBOSE ("%a: Allocated and Initialized NVME_ADMIN_NAMESPACE_DATA\n", __func__);

  Device = (NVME_DEVICE_PRIVATE_DATA *)(AllocateZeroPool (sizeof (NVME_DEVICE_PRIVATE_DATA)));

  //
  // Initialize SSD namespace instance data
  //
  Device->Signature     = NVME_DEVICE_PRIVATE_DATA_SIGNATURE;
  Device->NamespaceId   = 0;
  Device->NamespaceUuid = 1;

  Device->Controller = Private;

  //
  // Build BlockIo media structure
  //
  Device->Media.MediaId          = 0;
  Device->Media.RemovableMedia   = FALSE;
  Device->Media.MediaPresent     = TRUE;
  Device->Media.LogicalPartition = FALSE;
  Device->Media.ReadOnly         = FALSE;
  Device->Media.WriteCaching     = FALSE;
  Device->Media.BlockSize        = (UINT32)(1 << 9); // 512 byte sector size

  Device->Media.LastBlock                     = 0x4000; // NamespaceData=>Nsze
  Device->Media.LogicalBlocksPerPhysicalBlock = 1;
  Device->Media.LowestAlignedLba              = 1;

  Device->BlockIo.Revision    = EFI_BLOCK_IO_PROTOCOL_REVISION2;
  Device->BlockIo.Media       = &Device->Media;
  Device->BlockIo.ReadBlocks  = NvmeBlockIoReadBlocks;
  Device->BlockIo.WriteBlocks = NvmeBlockIoWriteBlocks;

  Device->BlockIo2.Media         = &Device->Media;
  Device->BlockIo2.ReadBlocksEx  = NvmeBlockIoReadBlocksEx;
  Device->BlockIo2.WriteBlocksEx = NvmeBlockIoWriteBlocksEx;

  Device->MediaSanitize.Revision    = MEDIA_SANITIZE_PROTOCOL_REVISION;
  Device->MediaSanitize.Media       = &Device->Media;
  Device->MediaSanitize.MediaClear  = NvmExpressMediaClear;
  Device->MediaSanitize.MediaPurge  = NvmExpressMediaPurge;
  Device->MediaSanitize.MediaFormat = NvmExpressMediaFormat;

  Device->Controller->Passthru.Mode             = 0;
  Device->Controller->Passthru.PassThru         = NvmeDeviceUnitTestPassthru;
  Device->Controller->Passthru.BuildDevicePath  = NULL;
  Device->Controller->Passthru.GetNamespace     = NULL;
  Device->Controller->Passthru.GetNextNamespace = NULL;

  CopyMem (&Device->NamespaceData, NamespaceData, sizeof (NVME_ADMIN_NAMESPACE_DATA));
  *ppDevice = Device;

  UT_LOG_VERBOSE ("%a: Allocated and Initialized NVME_DEVICE_PRIVATE_DATA\n", __func__);

  return UNIT_TEST_PASSED;
}

/**
  MediaSanitizePurgeUnitTest to Test calls to NvmExpressMediaPurge.

  @param[in]  Context  Unit test case context
 **/
UNIT_TEST_STATUS
EFIAPI
MediaSanitizePurgeUnitTest (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  UINT32                    PurgeAction;
  UINT32                    OverwritePattern;
  UNIT_TEST_STATUS          UnitTestStatus;
  NVME_DEVICE_PRIVATE_DATA  *NvmeDevice;
  EFI_STATUS                Status;

  UnitTestStatus = UNIT_TEST_PASSED;
  NvmeDevice     = NULL;
  Status         = EFI_SUCCESS;

  UnitTestStatus = NvmeCreateDeviceInstance (&NvmeDevice);

  UT_ASSERT_STATUS_EQUAL (UnitTestStatus, UNIT_TEST_PASSED);
  UT_ASSERT_NOT_NULL (NvmeDevice);

  UT_LOG_VERBOSE ("%a: Create Device Instance Status = 0x%x\n", __func__, UnitTestStatus);
  UT_LOG_VERBOSE ("%a: Device = 0x%x\n", __func__, NvmeDevice);
  UT_LOG_VERBOSE ("%a: Device->BlockIo = 0x%x\n", __func__, NvmeDevice->BlockIo);
  UT_LOG_VERBOSE ("%a: Device->Signature = 0x%x\n", __func__, NvmeDevice->Signature);

  //
  // Case 1: Block Erase
  //
  PurgeAction      = SANITIZE_ACTION_BLOCK_ERASE;
  OverwritePattern = 0;

  Status = NvmExpressMediaPurge (
             &NvmeDevice->MediaSanitize,
             NvmeDevice->Media.MediaId,
             PurgeAction,
             OverwritePattern
             );

  UT_ASSERT_NOT_EFI_ERROR (Status);

  UnitTestStatus = NvmeDestroyDeviceInstance (&NvmeDevice);

  return UNIT_TEST_PASSED;
}

/**
  NvmeSanitizeUnitTest to Test calls to NvmExpressSanitize.

  @param[in]  Context  Unit test case context
 **/
UNIT_TEST_STATUS
EFIAPI
NvmeSanitizeUnitTest (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  UINT32                    NamespaceId;
  UINT32                    SanitizeAction;
  UINT32                    NoDeallocateAfterSanitize;
  UINT32                    OverwritePattern;
  UNIT_TEST_STATUS          UnitTestStatus;
  NVME_DEVICE_PRIVATE_DATA  *NvmeDevice;
  EFI_STATUS                Status;

  NamespaceId               = 0;
  UnitTestStatus            = UNIT_TEST_PASSED;
  NvmeDevice                = NULL;
  Status                    = EFI_SUCCESS;
  SanitizeAction            = SANITIZE_ACTION_BLOCK_ERASE;
  NoDeallocateAfterSanitize = 0;
  OverwritePattern          = 0;

  UnitTestStatus = NvmeCreateDeviceInstance (&NvmeDevice);

  UT_ASSERT_STATUS_EQUAL (UnitTestStatus, UNIT_TEST_PASSED);
  UT_ASSERT_NOT_NULL (NvmeDevice);

  UT_LOG_VERBOSE ("%a: Create Device Instance Status = 0x%x\n", __func__, UnitTestStatus);
  UT_LOG_VERBOSE ("%a: Device = 0x%x\n", __func__, NvmeDevice);
  UT_LOG_VERBOSE ("%a: Device->BlockIo = 0x%x\n", __func__, NvmeDevice->BlockIo);
  UT_LOG_VERBOSE ("%a: Device->Signature = 0x%x\n", __func__, NvmeDevice->Signature);

  //
  // Case 1: Block Erase
  //
  SanitizeAction            = SANITIZE_ACTION_BLOCK_ERASE;
  NoDeallocateAfterSanitize = 0;
  OverwritePattern          = 0;

  Status = NvmExpressSanitize (
             &NvmeDevice->BlockIo,
             NamespaceId,
             SanitizeAction,
             NoDeallocateAfterSanitize,
             OverwritePattern
             );

  UT_ASSERT_NOT_EFI_ERROR (Status);

  //
  // Case 2: Crypto Erase
  //
  SanitizeAction            = SANITIZE_ACTION_CRYPTO_ERASE;
  NoDeallocateAfterSanitize = 0;
  OverwritePattern          = 0;

  Status = NvmExpressSanitize (
             &NvmeDevice->BlockIo,
             NamespaceId,
             SanitizeAction,
             NoDeallocateAfterSanitize,
             OverwritePattern
             );

  UT_ASSERT_NOT_EFI_ERROR (Status);

  //
  // Case 3: Overwrite
  //
  SanitizeAction            = SANITIZE_ACTION_OVERWRITE;
  NoDeallocateAfterSanitize = 0;
  OverwritePattern          = 0xDEADBEEF;

  Status = NvmExpressSanitize (
             &NvmeDevice->BlockIo,
             NamespaceId,
             SanitizeAction,
             NoDeallocateAfterSanitize,
             OverwritePattern
             );

  UT_ASSERT_NOT_EFI_ERROR (Status);

  //
  // Case 4: Block Erase (invalid overwrite pattern)
  //
  SanitizeAction            = SANITIZE_ACTION_BLOCK_ERASE;
  NoDeallocateAfterSanitize = 0;
  OverwritePattern          = 0xDEADBEEF;

  Status = NvmExpressSanitize (
             &NvmeDevice->BlockIo,
             NamespaceId,
             SanitizeAction,
             NoDeallocateAfterSanitize,
             OverwritePattern
             );

  UT_ASSERT_STATUS_EQUAL (Status, EFI_INVALID_PARAMETER);

  //
  // Case 5: Overwrite (invalid overwrite pattern)
  //
  SanitizeAction            = SANITIZE_ACTION_OVERWRITE;
  NoDeallocateAfterSanitize = 0;
  OverwritePattern          = 0;

  Status = NvmExpressSanitize (
             &NvmeDevice->BlockIo,
             NamespaceId,
             SanitizeAction,
             NoDeallocateAfterSanitize,
             OverwritePattern
             );

  UT_ASSERT_STATUS_EQUAL (Status, EFI_INVALID_PARAMETER);

  UnitTestStatus = NvmeDestroyDeviceInstance (&NvmeDevice);

  return UNIT_TEST_PASSED;
}

/**
  NvmeFormatNvmUnitTest to Test calls to NvmExpressFormatNvm.

  @param[in]  Context  Unit test case context
 **/
UNIT_TEST_STATUS
EFIAPI
NvmeFormatNvmUnitTest (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  UINT32                    NamespaceId;
  UINT32                    Ses;
  UINT32                    Flbas;
  NVME_DEVICE_PRIVATE_DATA  *NvmeDevice;
  UNIT_TEST_STATUS          UnitTestStatus;
  EFI_STATUS                Status;

  NamespaceId    = 0;
  NvmeDevice     = NULL;
  UnitTestStatus = UNIT_TEST_PASSED;
  Status         = EFI_SUCCESS;

  UnitTestStatus = NvmeCreateDeviceInstance (&NvmeDevice);

  UT_ASSERT_STATUS_EQUAL (UnitTestStatus, UNIT_TEST_PASSED);
  UT_ASSERT_NOT_NULL (NvmeDevice);

  UT_LOG_VERBOSE ("%a: Create Device Instance Status = 0x%x\n", __func__, UnitTestStatus);
  UT_LOG_VERBOSE ("%a: Device = 0x%x\n", __func__, NvmeDevice);
  UT_LOG_VERBOSE ("%a: Device->BlockIo = 0x%x\n", __func__, NvmeDevice->BlockIo);
  UT_LOG_VERBOSE ("%a: Device->Signature = 0x%x\n", __func__, NvmeDevice->Signature);

  //
  // Case 1: User Data Erase (Flbas = 0)
  //
  Ses    = SES_USER_DATA_ERASE;
  Flbas  = 0;
  Status = NvmExpressFormatNvm (
             &NvmeDevice->BlockIo,
             NamespaceId,
             Ses,
             Flbas
             );

  UT_ASSERT_NOT_EFI_ERROR (Status);

  //
  // Case 2: Crypto Erase (Flbas = 0)
  //
  Ses    = SES_CRYPTO_ERASE;
  Flbas  = 0;
  Status = NvmExpressFormatNvm (
             &NvmeDevice->BlockIo,
             NamespaceId,
             Ses,
             Flbas
             );

  UT_ASSERT_NOT_EFI_ERROR (Status);

  //
  // Case 3: User Data Erase (Invalid Flbas = 3)
  //
  Ses    = SES_USER_DATA_ERASE;
  Flbas  = 3;
  Status = NvmExpressFormatNvm (
             &NvmeDevice->BlockIo,
             NamespaceId,
             Ses,
             Flbas
             );

  UT_ASSERT_STATUS_EQUAL (Status, EFI_INVALID_PARAMETER);

  //
  // Case 4: Invalid SES (Flba = 0)
  //
  Ses    = 0xFF;
  Flbas  = 0;
  Status = NvmExpressFormatNvm (
             &NvmeDevice->BlockIo,
             NamespaceId,
             Ses,
             Flbas
             );

  UT_ASSERT_STATUS_EQUAL (Status, EFI_INVALID_PARAMETER);

  UnitTestStatus = NvmeDestroyDeviceInstance (&NvmeDevice);

  return UNIT_TEST_PASSED;
}

/**
  Baseline Unit Test.

  @param[in]  Context  Unit test case context
 **/
UNIT_TEST_STATUS
EFIAPI
UnitTestBaseline (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  UINT32  A;
  UINT32  B;
  UINT32  C;

  A = 1;
  B = 1;
  C = A + B;

  UT_ASSERT_EQUAL (C, 2);
  UT_ASSERT_NOT_EQUAL (0, 1);

  return UNIT_TEST_PASSED;
}

/**
  Test Case that locks a variable using the Variable Policy Protocol with a
  policy other than LOCK_NOW then attempts to lock the same variable using the
  Variable Lock Protocol.  The call to Variable Policy is expected to succeed
  and the call to Variable Lock is expected to fail.

  @retval EFI_SUCCES  Success
  @retval Other       Error
  **/
EFI_STATUS
EFIAPI
MediaSanitizeUnitTestEntry (
  VOID
  )
{
  EFI_STATUS                  Status;
  UNIT_TEST_FRAMEWORK_HANDLE  Framework;
  UNIT_TEST_SUITE_HANDLE      NvmeFormatNvmTestSuite;
  UNIT_TEST_SUITE_HANDLE      NvmeSanitizeTestSuite;
  UNIT_TEST_SUITE_HANDLE      MediaSanitizeProtocolTestSuite;

  Framework = NULL;

  #define UNIT_TEST_NAME     "Media Sanitize Protocol Unit Test"
  #define UNIT_TEST_VERSION  "1.0"

  DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_NAME, UNIT_TEST_VERSION));

  //
  // Start setting up the test framework for running the tests.
  //
  Status = InitUnitTestFramework (
             &Framework,
             UNIT_TEST_NAME,
             gEfiCallerBaseName,
             UNIT_TEST_VERSION
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status));
    goto EXIT;
  }

  //
  // Populate the NVM Express Format NVM Unit Test Suite.
  //
  Status = CreateUnitTestSuite (
             &NvmeFormatNvmTestSuite,
             Framework,
             "NVM Express Format NVM Test Suite",
             "Nvm.Express.Format.Nvm",
             NULL,
             NULL
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for NvmeFormatNvmTestSuite. Status = %r\n", Status));
    Status = EFI_OUT_OF_RESOURCES;
    goto EXIT;
  }

  //
  // Add baseline sanity test case
  //
  AddTestCase (
    NvmeFormatNvmTestSuite,          // Test Suite Handle
    "Baseline Format NVM Unit Test", // Test Description
    "FormatNVM",                     // Test Class
    UnitTestBaseline,                // UNIT_TEST_FUNCTION()
    NULL,                            // (Optional) UNIT_TEST_PREREQUISITE()
    NULL,                            // (Optional) UNIT_TEST_CLEANUP()
    NULL                             // (Optional) UNIT_TEST_CONTEXT
    );

  //
  // Add test case for NvmExpressFormatNvm()
  //
  AddTestCase (
    NvmeFormatNvmTestSuite,               // Test Suite Handle
    "Admin Format NVM Command Unit Test", // Test Description
    "FormatNVM",                          // Test Class
    NvmeFormatNvmUnitTest,                // UNIT_TEST_FUNCTION()
    NULL,                                 // (Optional) UNIT_TEST_PREREQUISITE()
    NULL,                                 // (Optional) UNIT_TEST_CLEANUP()
    NULL                                  // (Optional) UNIT_TEST_CONTEXT
    );

  //
  // Populate the NVM Express Sanitize Unit Test Suite.
  //
  Status = CreateUnitTestSuite (
             &NvmeSanitizeTestSuite,
             Framework,
             "NVM Express Sanitize Test Suite",
             "Nvm.Express.Sanitize",
             NULL,
             NULL
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for NvmeSanitizTestSuite. Status = %r\n", Status));
    Status = EFI_OUT_OF_RESOURCES;
    goto EXIT;
  }

  //
  // Add baseline sanity test
  //
  AddTestCase (
    NvmeSanitizeTestSuite,         // Test Suite Handle
    "Baseline Sanitize Unit Test", // Test Description
    "Sanitize",                    // Test Class
    UnitTestBaseline,              // UNIT_TEST_FUNCTION()
    NULL,                          // (Optional) UNIT_TEST_PREREQUISITE()
    NULL,                          // (Optional) UNIT_TEST_CLEANUP()
    NULL                           // (Optional) UNIT_TEST_CONTEXT
    );

  //
  // Add test case for NvmExressSanitize()
  //
  AddTestCase (
    NvmeSanitizeTestSuite,              // Test Suite Handle
    "Admin Sanitize Command Unit Test", // Test Description
    "Sanitize",                         // Test Class
    NvmeSanitizeUnitTest,               // UNIT_TEST_FUNCTION()
    NULL,                               // (Optional) UNIT_TEST_PREREQUISITE()
    NULL,                               // (Optional) UNIT_TEST_CLEANUP()
    NULL                                // (Optional) UNIT_TEST_CONTEXT
    );

  //
  // Populate the Media Sanitize Protocol Unit Test Suite.
  //
  Status = CreateUnitTestSuite (
             &MediaSanitizeProtocolTestSuite,
             Framework,
             "Media Sanitize Protocol Test Suite",
             "Media.Sanitize.Protocol",
             NULL,
             NULL
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for MediaSanitizeProtocolTestSuite. Status = %r\n", Status));
    Status = EFI_OUT_OF_RESOURCES;
    goto EXIT;
  }

  //
  // Add test case for Media Purge
  //
  AddTestCase (
    MediaSanitizeProtocolTestSuite,     // Test Suite Handle
    "Baseline MediaSanitize Unit Test", // Test Description
    "MediaSanitize",                    // Test Class
    UnitTestBaseline,                   // UNIT_TEST_FUNCTION()
    NULL,                               // (Optional) UNIT_TEST_PREREQUISITE()
    NULL,                               // (Optional) UNIT_TEST_CLEANUP()
    NULL                                // (Optional) UNIT_TEST_CONTEXT
    );

  //
  // Add test case for Media Purge
  //
  AddTestCase (
    MediaSanitizeProtocolTestSuite,      // Test Suite Handle
    "Protocol Media Sanitize Unit Test", // Test Description
    "MediaPurge",                        // Test Class
    MediaSanitizePurgeUnitTest,          // UNIT_TEST_FUNCTION()
    NULL,                                // (Optional) UNIT_TEST_PREREQUISITE()
    NULL,                                // (Optional) UNIT_TEST_CLEANUP()
    NULL                                 // (Optional) UNIT_TEST_CONTEXT
    );

  //
  // 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 MediaSanitizeUnitTestMain  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
MediaSanitizeUnitTestMain (
  IN INT32  Argc,
  IN CHAR8  *Argv[]
  )
{
  return MediaSanitizeUnitTestEntry ();
}
