/** @file  BinaryParse.c

 The API to parse the binary.

 Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>

 This program and the accompanying materials are licensed and made available under
 the terms and conditions of the BSD License which accompanies this distribution.
 The full text of the license may be found at
 http://opensource.org/licenses/bsd-license.php

 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#ifndef __GNUC__
#include "windows.h"
#else
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#endif
#include "BinaryParse.h"
#include "BinaryCreate.h"
#include "VariableCommon.h"
#include "FirmwareVolumeBufferLib.h"

extern G_EFI_FD_INFO  gEfiFdInfo;
extern EFI_HANDLE     mParsedGuidedSectionTools;
extern CHAR8          mInputFdName[MAX_FILENAME_LEN];

//
// The Guid to sign the position of Vfr and Uni array in FV
//
EFI_GUID  gVfrArrayAttractGuid                         = EFI_VFR_ATTRACT_GUID;
EFI_GUID  gUniStrArrayAttractGuid                      = EFI_UNI_STR_ATTRACT_GUID;
EFI_GUID  gEfiSystemNvDataFvGuid                       = EFI_SYSTEM_NVDATA_FV_GUID;
EFI_GUID  gEfiCrc32GuidedSectionExtractionProtocolGuid = EFI_CRC32_GUIDED_SECTION_EXTRACTION_PROTOCOL_GUID;

/**
  Converts a three byte length value into a UINT32.

  @param  ThreeByteLength   Pointer to the first of the 3 byte length.
  
  @retval  Length           Size of the section

**/
STATIC
UINT32
Get3ByteLength (
  IN UINT8     *ThreeByteLength
  )
{
  UINT32  Length;

  Length = 0;
  
  if (ThreeByteLength == NULL) {
    return 0;
  }

  Length  = *((UINT32 *) ThreeByteLength);
  Length  = Length & 0x00FFFFFF;

  return Length;
}

/**
  Generate the unique template filename.
**/
CHAR8 *
GenTempFile ( 
 VOID
 )
{
  CHAR8   *TemString;
  TemString = NULL;
#ifndef __GNUC__
  TemString = CloneString (tmpnam (NULL));
#else
  CHAR8 tmp[] = "/tmp/fileXXXXXX";
  UINTN Fdtmp;
  Fdtmp = mkstemp(tmp);
  TemString = CloneString(tmp);
  close(Fdtmp);
#endif
  return TemString;
}

/**
  Check whether exist the same Ifr FFS. If not existed, return TRUE.

  @param[in]   FfsImage   The pointer to the binary.
  @param[in]   FileSize   The size of binary.

  @return The string after convert.  
**/
STATIC
BOOLEAN
NotExistSameFfsIfr (
  IN  VOID     *FfsImage
)
{
  UINT32  Index;

  Index = 0;
  
  while (gEfiFdInfo.FfsArray[Index] != NULL) {
    if (memcmp (gEfiFdInfo.FfsArray[Index], FfsImage, sizeof (EFI_GUID)) == 0) {
      return FALSE;
    }
    Index++;
  }
  return TRUE;
}
  
/**
  This function returns the next larger size that meets the alignment 
  requirement specified.

  @param  ActualSize        The size.
  @param  Alignment         The desired alignment.
  
  @retval The Occupied length

**/
STATIC
UINT32
GetOccupiedSize (
  IN UINT32  ActualSize,
  IN UINT32  Alignment
  )
{
  UINT32  OccupiedSize;

  OccupiedSize = ActualSize;
  while ((OccupiedSize & (Alignment - 1)) != 0) {
    OccupiedSize++;
  }

  return OccupiedSize;
}


/**
  Parses FFS Sections, and remove the FFS headers. Tis function olny handle one efi in this FFS.

  @param  SectionBuffer     The section base address
  @param  BufferLength      The length of FFS.
  @param  EfiBufferHeader   The structure dual pointer to the efi informations 
  
  @retval  EFI_SUCCESS      The application exited normally.
  @retval  EFI_ABORTED       An error occurred.

**/
EFI_STATUS
ParseSection (
  IN      BOOLEAN              IsFfsOrEfi,
  IN OUT  UINT8                *SectionBuffer,
  IN      UINT32               BufferLength,
  IN OUT  EFI_SECTION_STRUCT   **EfiBufferHeader
  )
{
  UINT32              ParsedLength;
  EFI_SECTION_TYPE    Type;
  UINT8               *Ptr;
  UINT32              SectionLength;
  UINT8               *CompressedBuffer;
  UINT32              CompressedLength;
  UINT8               *UncompressedBuffer;
  UINT32              UncompressedLength;
  UINT8               CompressionType;
  DECOMPRESS_FUNCTION DecompressFunction;
  GETINFO_FUNCTION    GetInfoFunction;
  UINT32              ScratchSize;
  UINT8               *ScratchBuffer;
  EFI_STATUS          Status;
  UINT32              DstSize;
  CHAR8               *ExtractionTool;
  CHAR8               *ToolInputFile;
  CHAR8               *ToolOutputFile;
  CHAR8               *SystemCommandFormatString;
  CHAR8               *SystemCommand;
  UINT8               *ToolOutputBuffer;
  UINT32              ToolOutputLength;
  BOOLEAN             HasDepexSection;

  Ptr                       = NULL;
  SectionLength             = 0;
  CompressedBuffer          = NULL;
  CompressedLength          = 0;
  UncompressedBuffer        = NULL;
  UncompressedLength        = 0;
  CompressionType           = 0;
  ScratchSize               = 0;
  ScratchBuffer             = NULL;
  Status                    = EFI_SUCCESS;
  DstSize                   = 0;
  ExtractionTool            = NULL;
  ToolInputFile             = NULL;
  ToolOutputFile            = NULL;
  SystemCommandFormatString = NULL;
  SystemCommand             = NULL;
  
  //
  // Jump the FFS header
  //
  if (IsFfsOrEfi) {
    SectionBuffer = SectionBuffer + sizeof (EFI_FFS_FILE_HEADER);
    BufferLength  = BufferLength - sizeof (EFI_FFS_FILE_HEADER);
  }
  ParsedLength     = 0;
  HasDepexSection  = FALSE;
  ExtractionTool   = NULL;
  ToolOutputLength = 0;
  ToolOutputBuffer = NULL;
  
  (*EfiBufferHeader)->Length = BufferLength;
  
  while (ParsedLength < BufferLength) {
    Ptr           = SectionBuffer + ParsedLength;

    SectionLength = Get3ByteLength (((EFI_COMMON_SECTION_HEADER *) Ptr)->Size);
    Type          = ((EFI_COMMON_SECTION_HEADER *) Ptr)->Type;

    //
    // This is sort of an odd check, but is necessary because FFS files are
    // padded to a QWORD boundary, meaning there is potentially a whole section
    // header worth of 0xFF bytes.
    //
    if ((SectionLength == 0xffffff) && (Type == 0xff)) {
      ParsedLength += 4;
      continue;
    }
  
    switch (Type) {
    
    case EFI_SECTION_PE32:
    case EFI_SECTION_TE:
      //
      //Got the correct address
      //
     (*EfiBufferHeader)->BufferBase = (UINTN)(Ptr + sizeof (EFI_COMMON_SECTION_HEADER));
      return EFI_SUCCESS;
    
    case EFI_SECTION_RAW:
    case EFI_SECTION_PIC:
       break;
    
    case EFI_SECTION_USER_INTERFACE:
      HasDepexSection = FALSE;
      break;
    
    case EFI_SECTION_FIRMWARE_VOLUME_IMAGE:
    case EFI_SECTION_COMPATIBILITY16:
    case EFI_SECTION_FREEFORM_SUBTYPE_GUID:
      break;
    
    case EFI_SECTION_PEI_DEPEX:
    case EFI_SECTION_DXE_DEPEX:
    case EFI_SECTION_SMM_DEPEX:
      HasDepexSection = TRUE;
      break;
    
    case EFI_SECTION_VERSION:
      break;
    case EFI_SECTION_COMPRESSION:
      UncompressedBuffer  = NULL;
      CompressedLength    = SectionLength - sizeof (EFI_COMPRESSION_SECTION);
      UncompressedLength  = ((EFI_COMPRESSION_SECTION *) Ptr)->UncompressedLength;
      CompressionType     = ((EFI_COMPRESSION_SECTION *) Ptr)->CompressionType;

      if (CompressionType == EFI_NOT_COMPRESSED) {
        if (CompressedLength != UncompressedLength) {
          Error (
            NULL,
            0,
            0,
            "file is not compressed, but the compressed length does not match the uncompressed length",
            NULL
            );
          return EFI_ABORTED;
        }

        UncompressedBuffer = Ptr + sizeof (EFI_COMPRESSION_SECTION);
      } else if (CompressionType == EFI_STANDARD_COMPRESSION) {
        GetInfoFunction     = EfiGetInfo;
        DecompressFunction  = EfiDecompress;
        CompressedBuffer  = Ptr + sizeof (EFI_COMPRESSION_SECTION);

        Status = GetInfoFunction (
                   CompressedBuffer, 
                   CompressedLength, 
                   &DstSize, 
                   &ScratchSize
                   );
        if (EFI_ERROR (Status)) {
          Error (NULL, 0, 0003, "error getting compression info from compression section", NULL);
          return EFI_ABORTED;
        }

        if (DstSize != UncompressedLength) {
          Error (NULL, 0, 0003, "compression error in the compression section", NULL);
          return EFI_ABORTED;
        }

        ScratchBuffer       = malloc (ScratchSize);
        if (ScratchBuffer == NULL) {
          return EFI_ABORTED;
        }
        UncompressedBuffer  = malloc (UncompressedLength);
        if (UncompressedBuffer == NULL) {
          free (ScratchBuffer);
          return EFI_ABORTED;
        }
        memset (UncompressedBuffer, 0, UncompressedLength);

        Status = DecompressFunction (
                   CompressedBuffer,
                   CompressedLength,
                   UncompressedBuffer,
                   UncompressedLength,
                   ScratchBuffer,
                   ScratchSize
                  );
        free (ScratchBuffer);
        if (Status != EFI_SUCCESS) {
          Error (NULL, 0, 0003, "decompress failed", NULL);
          free (UncompressedBuffer);
          return EFI_ABORTED;
        }
      } else {
        Error (NULL, 0, 0003, "unrecognized compression type", "type 0x%X", CompressionType);
        return EFI_ABORTED;
      }

      Status = ParseSection (FALSE, UncompressedBuffer, UncompressedLength, EfiBufferHeader);
      if (Status != EFI_SUCCESS) {
        Error (NULL, 0, 0003, "failed to parse section", NULL);
        free (UncompressedBuffer);
        UncompressedBuffer = NULL;
      } else {
        return EFI_SUCCESS;
      }
      //
      // Store the allocate memory address for UncompressedBuffer
      //
      if (UncompressedBuffer != NULL) {
        (*EfiBufferHeader)->UncompressedBuffer[(*EfiBufferHeader)->UnCompressIndex] = (UINTN) UncompressedBuffer;
        (*EfiBufferHeader)->UnCompressIndex = (*EfiBufferHeader)->UnCompressIndex + 1;
      }
      break;
      
    case EFI_SECTION_GUID_DEFINED:
      //
      // Decompress failed, and then check for CRC32 sections which we can handle internally if needed.
      // Maybe this section is no-compressed.
      //
      if (!CompareGuid (
           &((EFI_GUID_DEFINED_SECTION *) Ptr)->SectionDefinitionGuid,
           &gEfiCrc32GuidedSectionExtractionProtocolGuid
           )) {
        //
        // CRC32 guided section
        //
        Status = ParseSection (
                   FALSE,
                   SectionBuffer + ((EFI_GUID_DEFINED_SECTION *) Ptr)->DataOffset,
                   BufferLength - ((EFI_GUID_DEFINED_SECTION *) Ptr)->DataOffset,
                   EfiBufferHeader
                  );
        if (EFI_ERROR (Status)) {
          Error (NULL, 0, 0003, "parse of CRC32 GUIDED section failed", NULL);
          return EFI_ABORTED;
        } else {
          return EFI_SUCCESS;
        }
      } else {
        ExtractionTool = LookupGuidedSectionToolPath (
                           mParsedGuidedSectionTools,
                           &((EFI_GUID_DEFINED_SECTION *) Ptr)->SectionDefinitionGuid
                           );
  
        if (ExtractionTool != NULL) {
          ToolInputFile  = GenTempFile ();
          ToolOutputFile = GenTempFile ();
          //
          // Construction 'system' command string
          //
          SystemCommandFormatString = "%s -d -o \"%s\" \"%s\"";
          SystemCommand = malloc (
                            strlen (SystemCommandFormatString) \
                            + strlen (ExtractionTool)          \
                            + strlen (ToolInputFile)           \
                            + strlen (ToolOutputFile)          \
                            + 1
                            );
          if (SystemCommand == NULL) {
            free (ExtractionTool);
            free (ToolInputFile);
            free (ToolOutputFile);
            return EFI_ABORTED;
          }
          sprintf (
            SystemCommand,
            "%s -d -o \"%s\" \"%s\"",
            ExtractionTool,
            ToolOutputFile,
            ToolInputFile
            );
          free (ExtractionTool);

          Status = PutFileImage (
                     ToolInputFile,
                     (CHAR8*) Ptr + ((EFI_GUID_DEFINED_SECTION *) Ptr)->DataOffset,
                     SectionLength - ((EFI_GUID_DEFINED_SECTION *) Ptr)->DataOffset
                   );

          if (HasDepexSection) {
            HasDepexSection = FALSE;
          }

          system (SystemCommand);
          remove (ToolInputFile);
          free (ToolInputFile);
          ToolInputFile = NULL;
          free (SystemCommand);
          SystemCommand = NULL;

          if (EFI_ERROR (Status)) {
            Error ("FCE", 0, 0004, "unable to decoded GUIDED section", NULL);
            free (ToolOutputFile);
            return EFI_ABORTED;
          }
  
          Status = GetFileImage (
                     ToolOutputFile,
                     (CHAR8 **)&ToolOutputBuffer,
                     &ToolOutputLength
                    );
          remove (ToolOutputFile);
          free (ToolOutputFile);
          ToolOutputFile = NULL;
          if (EFI_ERROR (Status)) {
            return EFI_ABORTED;
          }
        }
        Status = ParseSection (
                  FALSE,
                  ToolOutputBuffer,
                  ToolOutputLength,
                  EfiBufferHeader
                  );
        if (EFI_ERROR (Status)) {
          Error (NULL, 0, 0003, "parse of decoded GUIDED section failed", NULL);
          return EFI_ABORTED;
        }
      }
      break;
    
    default:
      ;
    }
    ParsedLength += SectionLength;
    //
    // We make then next section begin on a 4-byte boundary
    //
    ParsedLength = GetOccupiedSize (ParsedLength, 4);
  }

  return EFI_ABORTED;
}

/**
  Search the VfrBin Base address.

  According the known GUID gVfrArrayAttractGuid to get the base address from FFS.

  @param Fv                    the Pointer to the FFS
  @param EfiAddr               the Pointer to the EFI in FFS
  @param Length                the length of Fv
  @param Offset                the Pointer to the Addr (Offset)
  @param NumOfMachingOffset    the number of Addr (Offset)
  
  @retval EFI_SUCCESS          Get the address successfully.
**/
EFI_STATUS
SearchVfrBinInFFS (
   IN  VOID      *Fv,
   IN  VOID      *EfiAddr, 
   IN  UINTN     Length,
   OUT UINTN    **Offset,
   OUT UINT8     *NumOfMachingOffset
  )
{
  UINTN        Index;
  EFI_STATUS   Status;
  UINTN        VirOffValue;

  Index       = 0;
  Status      = EFI_SUCCESS;
  VirOffValue = 0;
    
  if ((Fv == NULL) || (Offset == NULL)) {
    return EFI_ABORTED;
  } 
  Status = GetAddressByGuid (
             Fv, 
             &gVfrArrayAttractGuid, 
             Length, 
             Offset, 
             NumOfMachingOffset
             );
  if (Status != EFI_SUCCESS) {
    return EFI_ABORTED;
  }

  while (Index < *NumOfMachingOffset) {
    //
    // Got the virOffset after the GUID
    //
    VirOffValue = *(UINTN *)((UINTN)Fv + *(*Offset + Index));
    //
    //Transfer the offset to the VA address. One modules may own more VfrBin address.
    //
    *(*Offset + Index) = (UINTN) EfiAddr + VirOffValue;
    Index++;
  }
  return EFI_SUCCESS;
}

/**
  Search the UniBin Base address.

  According the known GUID gUniStrArrayAttractGuid to get the base address from FFS.

  @param Fv                    the Pointer to the FFS
  @param EfiAddr               the Pointer to the EFI in FFS
  @param Length                the length of Fv
  @param Offset                the Pointer to the Addr (Offset)
   
  @retval Base address         Get the address successfully.
**/
EFI_STATUS
SearchUniBinInFFS (
   IN VOID      *Fv,
   IN  VOID     *EfiAddr, 
   IN  UINTN    Length,
   OUT UINTN    **Offset
  )
{
  UINT8        NumOfMachingOffset;
  EFI_STATUS   Status;
  UINTN        VirOffValue;

  NumOfMachingOffset = 0;
  Status             = EFI_SUCCESS;
  VirOffValue        = 0;
  
  if ((Fv == NULL) || (Offset == NULL)) {
    return EFI_ABORTED;
  } 
  Status = GetAddressByGuid (
             Fv, 
             &gUniStrArrayAttractGuid, 
             Length, 
             Offset, 
             &NumOfMachingOffset
             );
  if (Status != EFI_SUCCESS) {
    return EFI_ABORTED;
  }
  //
  //Transfer the offset to the VA address. There is only one UniArray in one modules.
  //
  if (NumOfMachingOffset == 1) {
    VirOffValue  = *(UINTN *)((UINTN)Fv + **Offset);
    **Offset     = (UINTN) EfiAddr + VirOffValue;
  } else {
    printf ("Error. Find more than 1 UniBin in FFS.\n");
    return EFI_ABORTED;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
SearchNvStoreDatabaseInFd(
  IN VOID     *Fv,
  IN UINTN    length
  )
{
  EFI_STATUS   Status;
  Status = EFI_SUCCESS;
  UINTN        Offset;
  PCD_NV_STORE_DEFAULT_BUFFER_HEADER *NvStoreHeader;
  Offset = 0;
  if (Fv == NULL) {
    printf ("The FV is NULL.");
    return EFI_ABORTED;
  }
  while (Offset < (length - sizeof(PCD_NV_STORE_DEFAULT_BUFFER_HEADER))){
    NvStoreHeader = (PCD_NV_STORE_DEFAULT_BUFFER_HEADER *)((UINT8*)Fv + Offset);
    if (NvStoreHeader->Signature == PCD_NV_STORE_DEFAULT_BUFFER_SIGNATURE) {
      gEfiFdInfo.ExistNvStoreDatabase = TRUE;
      gEfiFdInfo.NvStoreDatabase = (UINT8 *) NvStoreHeader;
      break;
    }
    Offset++;
  }
  if (Offset == (length - sizeof(PCD_NV_STORE_DEFAULT_BUFFER_HEADER))) {
    //printf ("Not found the PcdNvStoreDefaultValueBuffer\n");
    return Status;
  }
  return Status;
}
/**
  Search the EFI Variable Base address.

  According the known GUID gEfiSystemNvDataFvGuid to get the base address from FFS.

  @param Fv                    the Pointer to the FFS
  @param Length                the length of Fv
  @param Offset                the Pointer to the Addr (Offset)
  @param NumOfMachingOffset    the number of IFR array in one FFS
   
  @retval EFI_SUCCESS          Get the address successfully.
  @retval EFI_ABORTED          An error occured.
**/
EFI_STATUS
SearchEfiVarInFFS (
   IN VOID      *Fv,
   IN  UINTN     Length,
   OUT UINTN    **Offset,
   OUT UINT8    *NumOfMachingOffset
  )
{
  EFI_STATUS   Status;
  UINT8        Index;

  Status = EFI_SUCCESS;
  Index  = 0;
  
  if ((Fv == NULL) || (Offset == NULL)) {
    printf ("The FV or offset is NULL.");
    return EFI_ABORTED;
  } 
  Status = GetVariableAddressByGuid (
             Fv, 
             &gEfiSystemNvDataFvGuid, 
             Length, 
             Offset, 
             NumOfMachingOffset
             );
  if (Status != EFI_SUCCESS) {
    return EFI_ABORTED;
  }
  //
  //Transfer the offset to the VA address.
  //
  while (Index < *NumOfMachingOffset) {
    *(*Offset + Index) = (UINTN) Fv + *(*Offset + Index);
    Index++;
  }
  return EFI_SUCCESS;
}

STATIC
BOOLEAN
GetNextOffset (
  IN UINT8 *Data,
  IN EFI_GUID *Guid,
  IN UINTN Len,
  IN OUT UINTN *Offset
  )
{
  UINTN NextOffset;
  if (*Offset >= Len || Len - *Offset <= sizeof (EFI_GUID)) {
    return FALSE;
  }

  for (NextOffset = *Offset; NextOffset < Len - sizeof (EFI_GUID); NextOffset++) {
    if (CompareGuid(Guid, (EFI_GUID*)(Data + NextOffset)) == 0) {
      *Offset = NextOffset + sizeof(EFI_GUID);
      return TRUE;
    }
  }
  return FALSE;
}

/**
  Get the address by Guid.

  Parse the FD image, and find the GUID address.There may be some Guids matching the 
  searched Guid.

  @param Fv                     the Pointer to the image.
  @param Guid                   The Guid need to find.
  @param Offset                 The dual Pointer to the offset.
  @param NumOfMatchGuid         The number of matching Guid offset.
  
  @retval EFI_SUCCESS           The Search was complete successfully
  @return EFI_ABORTED           An error occurred
**/
EFI_STATUS
GetVariableAddressByGuid (
  IN  VOID        *Fv,
  IN  EFI_GUID    *Guid,
  IN  UINTN       Len,
  OUT UINTN       **Offset,
  OUT UINT8       *NumOfMatchGuid
  )
{
  UINTN       NextOffset;
  UINT8       Flag;
  
  if( (Fv == NULL) || (Fv == NULL) || (Guid == NULL) ){
    return EFI_ABORTED;
  }

  Flag            = 0;
  NextOffset      = 0;
  
  if (NumOfMatchGuid != NULL) {
    *NumOfMatchGuid = 0;
  }
  while (GetNextOffset(Fv, Guid, Len, &NextOffset)) {
    Flag = 1;
    if (NumOfMatchGuid != NULL && Offset != NULL) {
      if (*NumOfMatchGuid == 0) {
        *Offset = malloc (sizeof (UINTN) * MAX_MATCH_GUID_NUM);
        if (*Offset == NULL) {
          return EFI_ABORTED;
        }
        memset (*Offset, 0, sizeof (UINTN) * MAX_MATCH_GUID_NUM);
      }
      *(*Offset + *NumOfMatchGuid) = NextOffset;
      (*NumOfMatchGuid)++;
    } else {
      return EFI_SUCCESS;
    }
  }
  
  if( Flag == 0 ) {
    return EFI_ABORTED;
  }
  return EFI_SUCCESS;
}

/**
  Get the address by Guid.

  Parse the FFS image, and find the GUID address.There may be some Guids matching the 
  searched Guid.

  @param Fv                     the Pointer to the image.
  @param Guid                   The Guid need to find.
  @param Offset                 The dual Pointer to the offset.
  @param NumOfMatchGuid         The number of matching Guid offset.
  
  @retval EFI_SUCCESS           The Search was complete successfully
  @return EFI_ABORTED           An error occurred
**/
EFI_STATUS
GetAddressByGuid (
  IN  VOID        *Fv,
  IN  EFI_GUID    *Guid,
  IN  UINTN       Len,
  OUT UINTN       **Offset,
  OUT UINT8       *NumOfMatchGuid
  )
{  
  VOID        *LocalFv;
  UINT8       Flag;

  EFI_RAW_SECTION* Section;
  UINT8       *RawData;
  VOID*       SectionStart;
  UINTN       NextOffset;
  UINTN       Key;
  UINTN       TotalSectionsSize;
  UINTN       SecLen;
  UINTN       SecHdr;
  EFI_STATUS  Status;

  if( (Fv == NULL) || (Fv == NULL) || (Guid == NULL) || Len == 0 ){
    return EFI_ABORTED;
  }

  LocalFv         = Fv;
  Flag            = 0;
  Section         = NULL;
  Key             = 0;

  if (NumOfMatchGuid != NULL) {
    *NumOfMatchGuid = 0;
  }

  SectionStart = (VOID*)((UINTN)LocalFv + FvBufGetFfsHeaderSize(LocalFv));
  TotalSectionsSize = Len - FvBufGetFfsHeaderSize(LocalFv);
  while (TRUE) {
    Status = FvBufFindNextSection (
               SectionStart,
               TotalSectionsSize,
               &Key,
               (VOID **)&Section
               );
    if (Section == NULL || EFI_ERROR (Status)) {
      break;
    }

    if (EFI_SECTION_RAW == Section->Type) {
      if ((*(UINT32 *)Section->Size & 0xffffff) == 0xffffff) {
        SecLen = ((EFI_RAW_SECTION2 *)Section)->ExtendedSize;
        SecHdr = sizeof(EFI_RAW_SECTION2);
      } else {
        SecLen = *(UINT32 *)Section->Size & 0xffffff;
        SecHdr = sizeof(EFI_RAW_SECTION);
      }
      if (SecLen <= SecHdr || SecLen - SecHdr < sizeof(EFI_GUID)) {
        continue;
      }
      RawData = (UINT8 *)Section + SecHdr;
      NextOffset = 0;
      while (GetNextOffset(RawData, Guid, SecLen - SecHdr, &NextOffset)) {
        Flag = 1;
        if ((NumOfMatchGuid != NULL) && (Offset != NULL)) {
          if (*NumOfMatchGuid == 0) {
            *Offset = malloc (sizeof (UINTN) * MAX_MATCH_GUID_NUM);
            if (*Offset == NULL) {
              return EFI_ABORTED;
            }
            memset (*Offset, 0, sizeof (UINTN) * MAX_MATCH_GUID_NUM);
          }
          *(*Offset + *NumOfMatchGuid) = NextOffset + (RawData - (UINT8 *)Fv);
          (*NumOfMatchGuid)++;
        } else {
          return EFI_SUCCESS;
        }
      }
    }
  }

  if( Flag == 0 ) {
    return EFI_ABORTED;
  }
  return EFI_SUCCESS;
}

/**
  Parse the Ffs header to get the size.

  @param  InputFile             The pointer to the input file
  @param  FvSize                The pointer to the file size

  @return EFI_SUCCESS           Get the file size successfully
**/
EFI_STATUS
ReadFfsHeader (
  IN   FILE       *InputFile,
  OUT  UINT32     *FvSize
  )
{
  EFI_FFS_FILE_HEADER         FfsHeader;
  EFI_FV_FILETYPE             Type;

  //
  // Check input parameters
  //
  if ((InputFile == NULL) || (FvSize == NULL)) {
    return EFI_ABORTED;
  }
  //
  // Read the header
  //
  fread (
    &FfsHeader, 
    sizeof (EFI_FFS_FILE_HEADER), 
    1, 
    InputFile
    );
  Type    = FfsHeader.Type;
  
  if (Type == EFI_FV_FILETYPE_DRIVER) {
    *FvSize = *(UINT32 *)FfsHeader.Size & 0xffffff;
  } else if (Type == EFI_FV_FILETYPE_APPLICATION) {
    *FvSize = *(UINT32 *)FfsHeader.Size & 0xffffff;
  } else if (Type == EFI_FV_FILETYPE_FREEFORM) {
    *FvSize = *(UINT32 *)FfsHeader.Size & 0xffffff;
  } else {
    return EFI_ABORTED;
  }
  return EFI_SUCCESS;
}

/*
  Read the length of the whole FD 
  
  This function determines the size of the FV.  
  
  @param   InputFile        The file that contains the FV image.
  @param   FvSize           The size of the FV.

  @retval  EFI_SUCCESS      The application exited normally.
  @retval  EFI_ABORTED      An error occurred.

**/
STATIC
EFI_STATUS
ReadFdHeader (
  IN FILE       *InputFile,
  OUT UINT32    *FvSize
  )
{
  //
  // Check input parameters
  //
  if ((InputFile == NULL) || (FvSize == NULL)) {
    return EFI_ABORTED;
  }
  *FvSize = 0;
  //
  // Get the total size of FD file (Fixed the length)
  //
  fseek(InputFile,0,SEEK_END);       
  *FvSize = ftell(InputFile); 
  fseek(InputFile,0,SEEK_SET); 

  if (*FvSize == 0) {
    return EFI_ABORTED;
  }
  return EFI_SUCCESS;
}

/**
  Read the file to memory.

  @param   InputFile        The file that contains the FV image.
  @param   Size             The size of the file.
  
  @retval The pointer to the begining position of memory.
**/
VOID *
ReadFileToMemory (
  IN CHAR8      *FileName,
  OUT UINT32    *Size
  ) 
{
  FILE                          *InFile;
  VOID                          *Address;
  UINT32                        BytesRead;
  EFI_STATUS                    Status;

  InFile          = NULL;
  Address         = NULL;
  BytesRead       = 0;
  Status          = EFI_SUCCESS;
  
  InFile = fopen (FileName,"rb"); 
  if (InFile == NULL) {
    return NULL;
  }
  //
  // Determine the size of FV
  //
  Status = ReadFdHeader (InFile, Size);
  if (Status != EFI_SUCCESS) {
    fclose (InFile);
    return NULL;
  }
  //
  // Allocate a buffer for the FV image
  //
  Address = malloc (*Size);
  if (Address == NULL) {
    fclose (InFile);
    return NULL;
  }
  memset (Address, 0, *Size);
  //
  // Seek to the start of the image, then read the entire FV to the buffer
  //
  fseek (InFile, 0, SEEK_SET);
  BytesRead = fread (Address, 1, *Size, InFile);
  fclose (InFile);
  if ((UINTN) BytesRead != *Size) {
    free (Address);
    return NULL;
  }
  return Address;
}

/**
  Search the EFI variables address in Fd.

  Open and read the *.fd to the memory, initialize the global structure.
  Update the EFI variables addr and the begining position of memory.
  
  @retval EFI_SUCCESS          Get the address successfully.
**/
EFI_STATUS
GetEfiVariablesAddr (
  VOID
  ) 
{
  VOID                          *FdImage;
  UINT32                        FdSize;
  EFI_STATUS                    Status;
  UINTN                         *EfiVarAddr;
  UINT8                         NumOfMachingVar;
  UINT32                        Index;
  BOOLEAN                       GotFlag;
  EFI_FIRMWARE_VOLUME_HEADER    *Variable;
  BOOLEAN                       AuthencitatedMonotonicOrNot;
  BOOLEAN                       AuthencitatedBasedTimeOrNot;
  BOOLEAN                       NormalOrNot;
  
  FdImage         = NULL;
  FdSize          = 0;
  Status          = EFI_SUCCESS;
  EfiVarAddr      = NULL;
  NumOfMachingVar = 0;
  Index           = 0;
  GotFlag         = TRUE;
  Variable        = NULL;

  FdImage = ReadFileToMemory (mInputFdName, &FdSize);
  if (FdImage == NULL) {
    return EFI_ABORTED;
  }
  Status = SearchNvStoreDatabaseInFd(FdImage, FdSize);
  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }
  Status = SearchEfiVarInFFS (
             FdImage, 
             FdSize, 
             &EfiVarAddr, 
             &NumOfMachingVar
             );
  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }
  //
  // Check the signature "_FVH"
  //
  Index       = 0;
  GotFlag     = FALSE;
  
  while (Index < NumOfMachingVar) { 
    Variable = (EFI_FIRMWARE_VOLUME_HEADER *)(*(EfiVarAddr + Index) - 0x20);
    if (Variable->Signature == 0x4856465F) {
      AuthencitatedMonotonicOrNot  = CheckMonotonicBasedVarStore ((UINT8 *)Variable + Variable->HeaderLength);
      AuthencitatedBasedTimeOrNot  = CheckTimeBasedVarStoreOrNot ((UINT8 *)Variable + Variable->HeaderLength);
      NormalOrNot                  = CheckNormalVarStoreOrNot ((UINT8 *)Variable + Variable->HeaderLength);
      if (AuthencitatedMonotonicOrNot || AuthencitatedBasedTimeOrNot || NormalOrNot) {
        GotFlag = TRUE;
        gEfiFdInfo.EfiVariableAddr = (UINTN)Variable;
        break;
      }
    }
    Index++;
  }
  free (EfiVarAddr);
  if (!GotFlag) {
    return EFI_ABORTED;
  }
  gEfiFdInfo.Fd              = FdImage;
  gEfiFdInfo.FdSize          = FdSize;
  
  return EFI_SUCCESS;
}

/**
  Pick up the FFS which includes IFR section.

  Parse all FFS extracted by BfmLib, and save all which includes IFR 
  Binary to gEfiFdInfo structure.
  
  @retval EFI_SUCCESS          Get the address successfully.
  @retval EFI_BUFFER_TOO_SMALL Memory can't be allocated.
  @retval EFI_ABORTED          Read FFS Failed.
**/
EFI_STATUS 
FindFileInFolder (
  IN   CHAR8    *FolderName,
  OUT  BOOLEAN  *ExistStorageInBfv,
  OUT  BOOLEAN  *SizeOptimized
)
{
  CHAR8           *FileName;
  CHAR8           *CurFolderName;
  EFI_STATUS      Status;
  UINTN           MaxFileNameLen;
  UINTN           Index;
  CHAR8           FileNameArry[MAX_FILENAME_LEN];
  FILE            *FfsFile;
  UINTN           FileSize;
  VOID            *FfsImage;
  UINTN           BytesRead;

  FileName       = NULL;
  CurFolderName  = NULL;
  Status         = EFI_SUCCESS;
  MaxFileNameLen = 0;
  Index          = 0;
  FileSize       = 0;
  BytesRead      = 0;
  FfsImage       = NULL;
  FfsFile        = NULL;
  
  MaxFileNameLen = strlen (FolderName) + MAX_FILENAME_LEN;
  CurFolderName  = (CHAR8 *)calloc(
                     strlen (FolderName) + strlen (OS_SEP_STR) + strlen ("*.*")+ 1, 
                     sizeof(CHAR8)
                     );
  if (CurFolderName == NULL) {
    return EFI_BUFFER_TOO_SMALL;
  }
  strcpy (CurFolderName, FolderName);
  strcat (CurFolderName, OS_SEP_STR);
  strcat (CurFolderName, "*.*");
  FileName = (CHAR8 *)calloc(
               MaxFileNameLen, 
               sizeof(CHAR8)
               );
  if (FileName == NULL) {
    free (CurFolderName);
    return EFI_BUFFER_TOO_SMALL;
  }

#ifndef __GNUC__
  HANDLE          FindHandle;
  WIN32_FIND_DATA FindFileData; 

  if((FindHandle = FindFirstFile(CurFolderName, &FindFileData)) != INVALID_HANDLE_VALUE){ 
    do {
      memset (FileName, 0, MaxFileNameLen);
      if ((strcmp (FindFileData.cFileName, ".") == 0)
      || (strcmp (FindFileData.cFileName, "..") == 0)
      ) {
        continue;
      }
      if (strlen(FolderName) + strlen ("\\") + strlen (FindFileData.cFileName) > MAX_FILENAME_LEN - 1) {
        Status = EFI_ABORTED;
        goto Done;
      }
      snprintf (FileNameArry, MAX_FILENAME_LEN, "%s%c%s", FolderName, OS_SEP, FindFileData.cFileName);
      FfsFile = fopen (FileNameArry, "rb");
      if (FfsFile == NULL) {
        Status = EFI_ABORTED;
        goto Done;
      }
      Status = ReadFfsHeader (FfsFile, (UINT32 *)&FileSize);
      if (EFI_ERROR (Status)) {
        fclose (FfsFile);
        Status  = EFI_SUCCESS;
        continue;
      }
      //
      // Allocate a buffer for the FFS file
      //
      FfsImage = malloc (FileSize);
      if (FfsImage == NULL) {
        fclose (FfsFile);
        Status = EFI_BUFFER_TOO_SMALL;
        goto Done;
      }
      //
      // Seek to the start of the image, then read the entire FV to the buffer
      //
      fseek (FfsFile, 0, SEEK_SET);
      BytesRead = fread (FfsImage, 1, FileSize, FfsFile);
      fclose (FfsFile);
      
      if ((UINTN) BytesRead != FileSize) {
        free (FfsImage); 
        Status = EFI_ABORTED;
        goto Done;
      }
      //
      // Check whether exists the storage ffs in BFV for multi-platform mode
      //
      if (CompareGuid(&gEfiFfsBfvForMultiPlatformGuid,(EFI_GUID *) FfsImage) == 0) {
        *ExistStorageInBfv            = TRUE;  
	    *SizeOptimized                = FALSE;
        gEfiFdInfo.StorageFfsInBfv    = FfsImage;
        continue;
      }
	  //
      // Check whether exists the optimized storage ffs in BFV for multi-platform mode
      //
      if (CompareGuid(&gEfiFfsBfvForMultiPlatformGuid2,(EFI_GUID *) FfsImage) == 0) {
        *ExistStorageInBfv            = TRUE;
		*SizeOptimized                = TRUE;
        gEfiFdInfo.StorageFfsInBfv    = FfsImage;
        continue;
      }
      //
      // Check whether current FFS includes IFR
      //
      Status = GetAddressByGuid (
                 FfsImage, 
                 &gVfrArrayAttractGuid, 
                 FileSize, 
                 NULL, 
                 NULL
                );
      if (EFI_ERROR (Status)) {
        free (FfsImage);
        Status  = EFI_SUCCESS;
      } else {
       //
       // Check whether existed same IFR binary. If existed, not insert the new one.
       //
       if (NotExistSameFfsIfr (FfsImage)) {
         gEfiFdInfo.FfsArray[Index] = FfsImage;
         gEfiFdInfo.Length[Index++] = FileSize;
       } 
      }

    } while (FindNextFile (FindHandle, &FindFileData)); 
    FindClose(FindHandle); 
  } else {
    Status = EFI_ABORTED;
    goto Done;
  }

Done:
  free (CurFolderName);
  free (FileName);

#else
  struct dirent *pDirent;
  DIR *pDir;
  if((pDir = opendir(FolderName)) != NULL){
    while ((pDirent = readdir(pDir)) != NULL){
      memset (FileName, 0, MaxFileNameLen);
      if ((strcmp (pDirent->d_name, ".") == 0)
      || (strcmp (pDirent->d_name, "..") == 0)
      ) {
        continue;
      }
      sprintf (FileNameArry, "%s%c%s", FolderName, OS_SEP, pDirent->d_name);
      FfsFile = fopen (FileNameArry, "rb");
      Status = ReadFfsHeader (FfsFile, (UINT32 *)&FileSize);
      if (EFI_ERROR (Status)) {
        fclose (FfsFile);
        Status  = EFI_SUCCESS;
        continue;
      }
      //
      // Allocate a buffer for the FFS file
      //
      FfsImage = malloc (FileSize);
      if (FfsImage == NULL) {
        fclose (FfsFile);
        Status = EFI_BUFFER_TOO_SMALL;
        goto Done;
      }
      //
      // Seek to the start of the image, then read the entire FV to the buffer
      //
      fseek (FfsFile, 0, SEEK_SET);
      BytesRead = fread (FfsImage, 1, FileSize, FfsFile);
      fclose (FfsFile);

      if ((UINTN) BytesRead != FileSize) {
        free (FfsImage); 
        Status = EFI_ABORTED;
        goto Done;
      }
      //
      // Check whether exists the storage ffs in BFV for multi-platform mode
      //
      if (CompareGuid(&gEfiFfsBfvForMultiPlatformGuid,(EFI_GUID *) FfsImage) == 0) {
        *ExistStorageInBfv            = TRUE;
        *SizeOptimized                = FALSE;
        gEfiFdInfo.StorageFfsInBfv    = FfsImage;
        continue;
      }
      //
      // Check whether exists the optimized storage ffs in BFV for multi-platform mode
      //
      if (CompareGuid(&gEfiFfsBfvForMultiPlatformGuid2,(EFI_GUID *) FfsImage) == 0) {
        *ExistStorageInBfv            = TRUE;
        *SizeOptimized                = TRUE;
        gEfiFdInfo.StorageFfsInBfv    = FfsImage;
        continue;
      }
      //
      // Check whether current FFS includes IFR
      //
      Status = GetAddressByGuid (
                 FfsImage, 
                 &gVfrArrayAttractGuid,
                 FileSize, 
                 NULL, 
                 NULL
                );
      if (EFI_ERROR (Status)) {
        free (FfsImage);
        Status  = EFI_SUCCESS;
      } else {
       //
       // Check whether existed same IFR binary. If existed, not insert the new one.
       //
       if (NotExistSameFfsIfr (FfsImage)) {
         gEfiFdInfo.FfsArray[Index] = FfsImage;
         gEfiFdInfo.Length[Index++] = FileSize;
       } 
      }

    }
    closedir(pDir);
  } else {
    Status = EFI_ABORTED;
    goto Done;
  }

Done:
  free (CurFolderName);
  free (FileName);
#endif
  return Status;
}

