/** @file
  Read FFS Library

  @copyright
  Copyright 2014 - 2021 Intel Corporation. <BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Library/DebugLib.h>
#include <Uefi/UefiBaseType.h>
#include <Library/BaseMemoryLib.h>
#include <Pi/PiFirmwareFile.h>
#include <Library/ReadFfsLib.h>

//
//EFI_FIRMWARE_FILE_SYSTEM3_GUID indicates support for FFS_ATTRIB_LARGE_SIZE
//And thus support for files 16MB or larger.
//
UINT8 *
PreMemReadFFSFile (
  IN EFI_FIRMWARE_VOLUME_HEADER* FwVolHeader,
  IN EFI_GUID FFSGuid,
  IN UINT32   FFSDataSize,
  IN BOOLEAN skipheader
  )
/*++

Routine Description:

  Read FFS file from specified FV in PreMem phase

Arguments:

  FwVolHeader   - FV Base Address

  FFSGuid       - FFS to find & Read

  FFSDataSize   - Data size to read.If this value equal 0, will read the whole FFS size

  skipheader    - TRUE: skip to read the ffs and first section header,read from data directly, for one data section only ffs
                  FALSE:read from header

Returns:

  None

--*/
{
  EFI_FFS_FILE_HEADER         *ffsHdr;
  UINT64                       FvSize;
  UINT32                       FileOccupiedSize;
  UINT32                       FFSDataOffset;
  EFI_FIRMWARE_VOLUME_EXT_HEADER        *FwVolExtHeader;
  UINT32                      FFSSize = 0;

  FvSize = 0;
  FFSDataOffset = 0;
  if (FwVolHeader->ExtHeaderOffset != 0) {
    //
    // Searching for files starts on an 8 byte aligned boundary after the end of the Extended Header if it exists.
    //
    FwVolExtHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER *) ((UINTN)FwVolHeader + FwVolHeader->ExtHeaderOffset );
    ffsHdr = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FwVolExtHeader + FwVolExtHeader->ExtHeaderSize);
  } else {
    ffsHdr =  (EFI_FFS_FILE_HEADER*)(((UINT8 *) FwVolHeader)+ FwVolHeader->HeaderLength);
  }
  ffsHdr = (EFI_FFS_FILE_HEADER *) ALIGN_POINTER (ffsHdr, 8);

  if(FwVolHeader->FvLength == 0xFFFFFFFFFFFFFFFF)  return NULL;

  FvSize =     (UINTN)ffsHdr-(UINTN)FwVolHeader;
  while((FvSize < FwVolHeader->FvLength)&&((UINTN)ffsHdr <((UINTN)FwVolHeader+ (UINTN)FwVolHeader->FvLength))){
     if(CompareGuid (&ffsHdr->Name, &FFSGuid)) break;

     if (IS_FFS_FILE2 (ffsHdr)) {
       FileOccupiedSize = FFS_FILE2_SIZE (ffsHdr) ;
     } else {
       FileOccupiedSize = FFS_FILE_SIZE (ffsHdr) ;
     }

     FvSize+= FileOccupiedSize;
     ffsHdr = (EFI_FFS_FILE_HEADER *)((UINT8 *)ffsHdr + FileOccupiedSize);
     ffsHdr = (EFI_FFS_FILE_HEADER *) ALIGN_POINTER (ffsHdr, 8);
  }

  if (FvSize < FwVolHeader->FvLength) {
     if (IS_FFS_FILE2 (ffsHdr)) {
       FileOccupiedSize = FFS_FILE2_SIZE (ffsHdr) ;
     } else {
       FileOccupiedSize = FFS_FILE_SIZE (ffsHdr) ;
     }
    FFSSize = FileOccupiedSize;

    if(FFSDataSize == 0)
      FFSDataSize= FFSSize;

    if(skipheader){
      if (IS_FFS_FILE2 (ffsHdr)) {
        FFSDataOffset = sizeof(EFI_FFS_FILE_HEADER2) + sizeof(EFI_COMMON_SECTION_HEADER2);
      } else {
        FFSDataOffset = sizeof(EFI_FFS_FILE_HEADER) + sizeof(EFI_COMMON_SECTION_HEADER);
      }
      if(FFSDataSize == 0) {
        if (IS_FFS_FILE2 (ffsHdr)) {
          FFSDataSize = FFSDataSize - sizeof(EFI_FFS_FILE_HEADER2) - sizeof(EFI_COMMON_SECTION_HEADER2);
        } else {
          FFSDataSize = FFSDataSize - sizeof(EFI_FFS_FILE_HEADER) - sizeof(EFI_COMMON_SECTION_HEADER);
        }
      }
    }
  }

   return (UINT8 *)ffsHdr + FFSDataOffset;
}

EFI_STATUS
ReadFFSFile (
  IN EFI_FIRMWARE_VOLUME_HEADER* FwVolHeader,
  IN EFI_GUID FFSGuid,
  IN UINT32   FFSDataSize,
  IN OUT VOID *FFSData,
  OUT UINT32  *FFSSize,
  IN BOOLEAN skipheader
  )
/*++

Routine Description:

  Read FFS file from FV

Arguments:

  FwVolHeader - FV Base Address

  FFSGuid       - FFS to find & Read

  FFSDataSize   - Data size to read.If this value equal 0, will read the whole FFS size

  FFSData       - Pointer to buffer for read.

  FFSSize  - FFS file size FYI for caller.

  skipheader - TRUE: skip to read the ffs and first section header,read from data directly, for one data section only ffs
               FALSE:read from header

Returns:

  None

--*/
{
  EFI_FFS_FILE_HEADER         *ffsHdr;
  UINT64                       FvSize;
  UINT32                       FileOccupiedSize;
  UINT32                       FFSDataOffset;
  EFI_STATUS                   Status;
  EFI_FIRMWARE_VOLUME_EXT_HEADER        *FwVolExtHeader;

  Status = EFI_NOT_FOUND;
  *FFSSize = 0;
  FvSize = 0;
  FFSDataOffset = 0;
  if (FwVolHeader->ExtHeaderOffset != 0) {
    //
    // Searching for files starts on an 8 byte aligned boundary after the end of the Extended Header if it exists.
    //
    FwVolExtHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER *) ((UINTN)FwVolHeader + FwVolHeader->ExtHeaderOffset );
    ffsHdr = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FwVolExtHeader + FwVolExtHeader->ExtHeaderSize);
  } else {
    ffsHdr =  (EFI_FFS_FILE_HEADER*)(((UINT8 *) FwVolHeader)+ FwVolHeader->HeaderLength);
  }
  ffsHdr = (EFI_FFS_FILE_HEADER *) ALIGN_POINTER (ffsHdr, 8);

  if(FwVolHeader->FvLength == 0xFFFFFFFFFFFFFFFF) {
    return EFI_VOLUME_CORRUPTED;
  }

  FvSize =     (UINTN)ffsHdr-(UINTN)FwVolHeader;
  while((FvSize < FwVolHeader->FvLength)&&((UINTN)ffsHdr <((UINTN)FwVolHeader+ (UINTN)FwVolHeader->FvLength))){
     if(CompareGuid (&ffsHdr->Name, &FFSGuid)){
       Status = EFI_SUCCESS;
       break;
     }
     if (IS_FFS_FILE2 (ffsHdr)) {
       FileOccupiedSize = FFS_FILE2_SIZE (ffsHdr) ;
     } else {
       FileOccupiedSize = FFS_FILE_SIZE (ffsHdr) ;
     }

     FvSize+= FileOccupiedSize;
     ffsHdr = (EFI_FFS_FILE_HEADER *)((UINT8 *)ffsHdr + FileOccupiedSize);
     ffsHdr = (EFI_FFS_FILE_HEADER *) ALIGN_POINTER (ffsHdr, 8);
  }

  if (FvSize < FwVolHeader->FvLength) {
     if (IS_FFS_FILE2 (ffsHdr)) {
       FileOccupiedSize = FFS_FILE2_SIZE (ffsHdr) ;
     } else {
       FileOccupiedSize = FFS_FILE_SIZE (ffsHdr) ;
     }
    *FFSSize = FileOccupiedSize;

  if(FFSDataSize == 0) {
    FFSDataSize= *FFSSize;
  }

  if(skipheader){
    if (IS_FFS_FILE2 (ffsHdr)) {
      FFSDataOffset = sizeof(EFI_FFS_FILE_HEADER2) + sizeof(EFI_COMMON_SECTION_HEADER2);
    } else {
      FFSDataOffset = sizeof(EFI_FFS_FILE_HEADER) + sizeof(EFI_COMMON_SECTION_HEADER);
    }
    if(FFSDataSize == 0) {
      if (IS_FFS_FILE2 (ffsHdr)) {
        FFSDataSize = FFSDataSize - sizeof(EFI_FFS_FILE_HEADER2) - sizeof(EFI_COMMON_SECTION_HEADER2);
      } else {
        FFSDataSize = FFSDataSize - sizeof(EFI_FFS_FILE_HEADER) - sizeof(EFI_COMMON_SECTION_HEADER);
      }
    }
  }

  CopyMem(FFSData,(UINT8*)ffsHdr + FFSDataOffset,FFSDataSize);
  }
  return Status;
}

BOOLEAN
NormalHobToCompressHob(IN OUT VOID* hobAddr,IN OUT UINTN* size)
{
  UINTN i,j,k;
  COMPRESS_HOBO_DATA CompressHob;
  COMPRESS_ITEM Hobitem[MAX_COMPRESS_ITEM];
  UINT16 TempBuffer[MAX_FFS_BUFFER_SIZE/4];
  UINTN offset=0;
  UINTN RemainSize = 0;

  CompressHob.Signature = 0x5A45524F;
  CompressHob.Count =0;
  j=0;
  i=0;
  RemainSize = (*size % 2) + 2;
  if(MAX_COMPRESS_ITEM*sizeof(COMPRESS_ITEM)+MAX_FFS_BUFFER_SIZE/4+sizeof(COMPRESS_HOBO_DATA) > MAX_FFS_BUFFER_SIZE) {
    return FALSE;
  }

  if((*size) > MAX_FFS_BUFFER_SIZE) {
    return FALSE;
  }

 ZeroMem(TempBuffer,MAX_FFS_BUFFER_SIZE/2);

  while(i < (*size - RemainSize)){

  if(j>=MAX_COMPRESS_ITEM) {
    return FALSE;
  }

   //search for duplicate array
   if(*(UINT16*)((UINTN)hobAddr+i)==*(UINT16*)((UINTN)hobAddr+i+2) ){

    for(k=2;(i+k)<(*size- RemainSize);k+=2 ){
         if(*(UINT16*)((UINTN)hobAddr+i)!=*(UINT16*)((UINTN)hobAddr+i+k)){
           break;
         }
    }

    if(i+k>= *size - (*size % 2)) {
      k -=2;
    }

    Hobitem[j].Value = *(UINT16*)((UINTN)hobAddr+i);
    Hobitem[j].Length =(UINT16) k;
    Hobitem[j].Type = COMPRESS_DUPLICATE;
    Hobitem[j].Offset = 0;
    j++;
    CompressHob.Count =(UINT32)j;
    i+=k;

   }
   else{//single array
    for(k=2;i+k+2 <= (*size- RemainSize);k+=2){
       if(offset >= MAX_FFS_BUFFER_SIZE/4) {
         return FALSE;
       }
       TempBuffer[offset]= *(UINT16*)((UINTN)hobAddr+i+k-2);
       offset +=1;
       if(*(UINT16*)((UINTN)hobAddr+i+k)==*(UINT16*)((UINTN)hobAddr+i+k+2)){
         k += 2;
         break;
       }

    }


    Hobitem[j].Length = (UINT16) k - 2;
    Hobitem[j].Type = COMPRESS_SINGLE;
    Hobitem[j].Offset = (UINT16)(offset - (Hobitem[j].Length/2) );
    Hobitem[j].Value =0;
    j++;
    CompressHob.Count  =(UINT32)j;
    i+=k - 2;

   }

  }

  if(j>=MAX_COMPRESS_ITEM) {
    //not worth to compress
    return FALSE;
  }

  //process last one
  CopyMem((UINT8*)(&TempBuffer[offset]), (UINT8*)((UINTN)hobAddr + *size - RemainSize), RemainSize);
  Hobitem[j].Length = (UINT16)RemainSize;
  Hobitem[j].Type = COMPRESS_SINGLE;
  Hobitem[j].Offset = (UINT16)offset;
  Hobitem[j].Value =0;
  j++;
  CompressHob.Count  =(UINT32)j;

  CopyMem(hobAddr,(VOID*)&CompressHob,sizeof(COMPRESS_HOBO_DATA));
  offset = sizeof(COMPRESS_HOBO_DATA);
  for(i=0; i < CompressHob.Count;i++){
    CopyMem((UINT8*)((UINTN)hobAddr+offset),(UINT8*)&Hobitem[i],sizeof(COMPRESS_ITEM));
    offset += sizeof(COMPRESS_ITEM);
    if(Hobitem[i].Type == COMPRESS_SINGLE){
      CopyMem((UINT8*)((UINTN)hobAddr+offset),&TempBuffer[Hobitem[i].Offset],Hobitem[i].Length);
      offset += Hobitem[i].Length;
    }
  }

  *size = offset;

  return TRUE;
}

BOOLEAN
CompressHobToNormalHob(IN OUT VOID* hobAddr,OUT UINTN* size)
{
  UINTN i;
  COMPRESS_HOBO_DATA CompressHob  ;
  COMPRESS_ITEM Hobitem[MAX_COMPRESS_ITEM];
  UINTN offset=0;
  UINT16 TempBuffer[MAX_FFS_BUFFER_SIZE/4];

  CopyMem((VOID*)&CompressHob,hobAddr,sizeof(COMPRESS_HOBO_DATA));

  if(CompressHob.Signature != 0x5A45524F) {
    return FALSE;
  }

  if(CompressHob.Count>MAX_COMPRESS_ITEM) {
    //not worth to compress
    return FALSE;
  }

  if(*size > MAX_FFS_BUFFER_SIZE) {
    return FALSE;
  }

  if(MAX_COMPRESS_ITEM*sizeof(COMPRESS_ITEM)+MAX_FFS_BUFFER_SIZE/4+sizeof(COMPRESS_HOBO_DATA) > MAX_FFS_BUFFER_SIZE) {
    return FALSE;
  }

  offset = sizeof(COMPRESS_HOBO_DATA);
  for(i=0; i < CompressHob.Count;i++){
    CopyMem((VOID*)&Hobitem[i],(UINT8*)((UINTN)hobAddr+offset),sizeof(COMPRESS_ITEM));
    offset += sizeof(COMPRESS_ITEM);
    if(Hobitem[i].Type == COMPRESS_SINGLE){
      CopyMem(&TempBuffer[Hobitem[i].Offset],(UINT8*)((UINTN)hobAddr+offset),Hobitem[i].Length);
      offset += Hobitem[i].Length;
    }
  }

  offset =0;
  for(i=0; i < CompressHob.Count;i++){
    if(Hobitem[i].Type ==COMPRESS_DUPLICATE){
     SetMem16((VOID*)((UINTN)hobAddr+offset),Hobitem[i].Length,Hobitem[i].Value);
     offset += Hobitem[i].Length;
    }
    else if(Hobitem[i].Type ==COMPRESS_SINGLE){
     CopyMem((UINT8*)((UINTN)hobAddr+offset),&TempBuffer[Hobitem[i].Offset],Hobitem[i].Length);
     offset += Hobitem[i].Length;

    }

  }

  *size = offset;

  return TRUE;
}

EFI_STATUS
ValidateCommonFvHeader (
  EFI_FIRMWARE_VOLUME_HEADER            *FwVolHeader
  )
/*++

Routine Description:
  Check the integrity of firmware volume header

Arguments:
  FwVolHeader           - A pointer to a firmware volume header

Returns:
  EFI_SUCCESS           - The firmware volume is consistent
  EFI_NOT_FOUND         - The firmware volume has corrupted. So it is not an FV

--*/
{
  UINT16  *Ptr;
  UINT16  HeaderLength;
  UINT16  Checksum;

  //
  // Verify the header revision, header signature, length
  // Length of FvBlock cannot be 2**64-1
  // HeaderLength cannot be an odd number
  //
  if ((FwVolHeader->Revision != EFI_FVH_REVISION) ||
      (FwVolHeader->Signature != EFI_FVH_SIGNATURE) ||
      (FwVolHeader->FvLength == ((UINTN) -1)) ||
      ((FwVolHeader->HeaderLength & 0x01) != 0)
      ) {
    return EFI_NOT_FOUND;
  }
  //
  // Verify the header checksum
  //
  HeaderLength  = (UINT16) (FwVolHeader->HeaderLength / 2);
  Ptr           = (UINT16 *) FwVolHeader;
  Checksum      = 0;
  while (HeaderLength > 0) {
    Checksum = Checksum + (*Ptr);
    Ptr++;
    HeaderLength--;
  }

  if (Checksum != 0) {
    return EFI_NOT_FOUND;
  }

  return EFI_SUCCESS;
}
