/** @file

  Provides services to convert a BMP graphics image to a GOP BLT buffer and
  from a GOP BLT buffer to a BMP graphics image.

  Caution: This module requires additional review when modified.
  This module processes external input - BMP image.
  This external input must be validated carefully to avoid security issue such
  as buffer overflow, integer overflow.

  TranslateBmpToGopBlt() receives untrusted input and performs basic validation.

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

  All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/SafeIntLib.h>
#include <IndustryStandard/Bmp.h>

#include <Library/BmpSupportLib.h>

//
// BMP Image header for an uncompressed 24-bit per pixel BMP image.
//
const BMP_IMAGE_HEADER  mBmpImageHeaderTemplate = {
  'B',    // CharB
  'M',    // CharM
  0,      // Size will be updated at runtime
  {0, 0}, // Reserved
  sizeof (BMP_IMAGE_HEADER), // ImageOffset
  sizeof (BMP_IMAGE_HEADER) - OFFSET_OF (BMP_IMAGE_HEADER, HeaderSize), // HeaderSize
  0,      // PixelWidth will be updated at runtime
  0,      // PixelHeight will be updated at runtime
  1,      // Planes
  24,     // BitPerPixel
  0,      // CompressionType
  0,      // ImageSize will be updated at runtime
  0,      // XPixelsPerMeter
  0,      // YPixelsPerMeter
  0,      // NumberOfColors
  0       // ImportantColors
};

/**
  Translate a *.BMP graphics image to a GOP blt buffer. If a NULL Blt buffer
  is passed in a GopBlt buffer will be allocated by this routine using
  EFI_BOOT_SERVICES.AllocatePool(). If a GopBlt buffer is passed in it will be
  used if it is big enough.

  @param[in]       BmpImage      Pointer to BMP file.
  @param[in]       BmpImageSize  Number of bytes in BmpImage.
  @param[in, out]  GopBlt        Buffer containing GOP version of BmpImage.
  @param[in, out]  GopBltSize    Size of GopBlt in bytes.
  @param[out]      PixelHeight   Height of GopBlt/BmpImage in pixels.
  @param[out]      PixelWidth    Width of GopBlt/BmpImage in pixels.

  @retval  RETURN_SUCCESS            GopBlt and GopBltSize are returned.
  @retval  RETURN_INVALID_PARAMETER  BmpImage is NULL.
  @retval  RETURN_INVALID_PARAMETER  GopBlt is NULL.
  @retval  RETURN_INVALID_PARAMETER  GopBltSize is NULL.
  @retval  RETURN_INVALID_PARAMETER  PixelHeight is NULL.
  @retval  RETURN_INVALID_PARAMETER  PixelWidth is NULL.
  @retval  RETURN_UNSUPPORTED        BmpImage is not a valid *.BMP image.
  @retval  RETURN_BUFFER_TOO_SMALL   The passed in GopBlt buffer is not big
                                     enough.  The required size is returned in
                                     GopBltSize.
  @retval  RETURN_OUT_OF_RESOURCES   The GopBlt buffer could not be allocated.

**/
RETURN_STATUS
EFIAPI
TranslateBmpToGopBlt (
  IN     VOID                           *BmpImage,
  IN     UINTN                          BmpImageSize,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL  **GopBlt,
  IN OUT UINTN                          *GopBltSize,
  OUT    UINTN                          *PixelHeight,
  OUT    UINTN                          *PixelWidth
  )
{
  UINT8                          *Image;
  UINT8                          *ImageHeader;
  BMP_IMAGE_HEADER               *BmpHeader;
  BMP_COLOR_MAP                  *BmpColorMap;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltBuffer;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *Blt;
  UINT32                         BltBufferSize;
  UINTN                          Index;
  UINTN                          Height;
  UINTN                          Width;
  UINTN                          ImageIndex;
  UINT32                         DataSizePerLine;
  BOOLEAN                        IsAllocated;
  UINT32                         ColorMapNum;
  RETURN_STATUS                  Status;
  UINT32                         DataSize;
  UINT32                         Temp;

  if (BmpImage == NULL || GopBlt == NULL || GopBltSize == NULL) {
    return RETURN_INVALID_PARAMETER;
  }
  if (PixelHeight == NULL || PixelWidth == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  if (BmpImageSize < sizeof (BMP_IMAGE_HEADER)) {
    DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: BmpImageSize too small\n"));
    return RETURN_UNSUPPORTED;
  }

  BmpHeader = (BMP_IMAGE_HEADER *)BmpImage;

  if (BmpHeader->CharB != 'B' || BmpHeader->CharM != 'M') {
    DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: BmpHeader->Char fields incorrect\n"));
    return RETURN_UNSUPPORTED;
  }

  //
  // Doesn't support compress.
  //
  if (BmpHeader->CompressionType != 0) {
    DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: Compression Type unsupported.\n"));
    return RETURN_UNSUPPORTED;
  }

  if ((BmpHeader->PixelHeight == 0) || (BmpHeader->PixelWidth == 0)) {
    DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: BmpHeader->PixelHeight or BmpHeader->PixelWidth is 0.\n"));
    return RETURN_UNSUPPORTED;
  }

  //
  // Only support BITMAPINFOHEADER format.
  // BITMAPFILEHEADER + BITMAPINFOHEADER = BMP_IMAGE_HEADER
  //
  if (BmpHeader->HeaderSize != sizeof (BMP_IMAGE_HEADER) - OFFSET_OF (BMP_IMAGE_HEADER, HeaderSize)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: BmpHeader->Headership is not as expected.  Headersize is 0x%x\n",
      BmpHeader->HeaderSize
      ));
    return RETURN_UNSUPPORTED;
  }

  //
  // The data size in each line must be 4 byte alignment.
  //
  Status = SafeUint32Mult (
             BmpHeader->PixelWidth,
             BmpHeader->BitPerPixel,
             &DataSizePerLine
             );
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: invalid BmpImage... PixelWidth:0x%x BitPerPixel:0x%x\n",
      BmpHeader->PixelWidth,
      BmpHeader->BitPerPixel
      ));
    return RETURN_UNSUPPORTED;
  }

  Status = SafeUint32Add (DataSizePerLine, 31, &DataSizePerLine);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: invalid BmpImage... DataSizePerLine:0x%x\n",
      DataSizePerLine
      ));

    return RETURN_UNSUPPORTED;
  }

  DataSizePerLine = (DataSizePerLine >> 3) &(~0x3);
  Status = SafeUint32Mult (
             DataSizePerLine,
             BmpHeader->PixelHeight,
             &BltBufferSize
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: invalid BmpImage... DataSizePerLine:0x%x PixelHeight:0x%x\n",
      DataSizePerLine, BmpHeader->PixelHeight
      ));

    return RETURN_UNSUPPORTED;
  }

  Status = SafeUint32Mult (
             BmpHeader->PixelHeight,
             DataSizePerLine,
             &DataSize
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: invalid BmpImage... PixelHeight:0x%x DataSizePerLine:0x%x\n",
      BmpHeader->PixelHeight, DataSizePerLine
      ));

    return RETURN_UNSUPPORTED;
  }

  if ((BmpHeader->Size != BmpImageSize) ||
      (BmpHeader->Size < BmpHeader->ImageOffset) ||
      (BmpHeader->Size - BmpHeader->ImageOffset != DataSize)) {

    DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... \n"));
    DEBUG ((DEBUG_ERROR, "   BmpHeader->Size: 0x%x\n", BmpHeader->Size));
    DEBUG ((DEBUG_ERROR, "   BmpHeader->ImageOffset: 0x%x\n", BmpHeader->ImageOffset));
    DEBUG ((DEBUG_ERROR, "   BmpImageSize: 0x%lx\n", (UINTN)BmpImageSize));
    DEBUG ((DEBUG_ERROR, "   DataSize: 0x%lx\n", (UINTN)DataSize));

    return RETURN_UNSUPPORTED;
  }

  //
  // Calculate Color Map offset in the image.
  //
  Image = BmpImage;
  BmpColorMap = (BMP_COLOR_MAP *)(Image + sizeof (BMP_IMAGE_HEADER));
  if (BmpHeader->ImageOffset < sizeof (BMP_IMAGE_HEADER)) {
    return RETURN_UNSUPPORTED;
  }

  if (BmpHeader->ImageOffset > sizeof (BMP_IMAGE_HEADER)) {
    switch (BmpHeader->BitPerPixel) {
    case 1:
      ColorMapNum = 2;
      break;
    case 4:
      ColorMapNum = 16;
      break;
    case 8:
      ColorMapNum = 256;
      break;
    default:
      ColorMapNum = 0;
      break;
    }
    //
    // BMP file may has padding data between the bmp header section and the
    // bmp data section.
    //
    if (BmpHeader->ImageOffset - sizeof (BMP_IMAGE_HEADER) < sizeof (BMP_COLOR_MAP) * ColorMapNum) {
      return RETURN_UNSUPPORTED;
    }
  }

  //
  // Calculate graphics image data address in the image
  //
  Image = ((UINT8 *)BmpImage) + BmpHeader->ImageOffset;
  ImageHeader = Image;

  //
  // Calculate the BltBuffer needed size.
  //
  Status = SafeUint32Mult (
             BmpHeader->PixelWidth,
             BmpHeader->PixelHeight,
             &BltBufferSize
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: invalid BltBuffer needed size... PixelWidth:0x%x PixelHeight:0x%x\n",
      BmpHeader->PixelWidth, BmpHeader->PixelHeight
      ));

    return RETURN_UNSUPPORTED;
  }

  Temp = BltBufferSize;
  Status = SafeUint32Mult (
             BltBufferSize,
             sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL),
             &BltBufferSize
             );

  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateBmpToGopBlt: invalid BltBuffer needed size... PixelWidth x PixelHeight:0x%x struct size:0x%x\n",
      Temp, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
      ));

    return RETURN_UNSUPPORTED;
  }

  IsAllocated = FALSE;
  if (*GopBlt == NULL) {
    //
    // GopBlt is not allocated by caller.
    //
    DEBUG ((DEBUG_INFO, "Bmp Support: Allocating 0x%X bytes of memory\n", BltBufferSize));
    *GopBltSize = (UINTN)BltBufferSize;
    *GopBlt = AllocatePool (*GopBltSize);
    IsAllocated = TRUE;
    if (*GopBlt == NULL) {
      return RETURN_OUT_OF_RESOURCES;
    }
  } else {
    //
    // GopBlt has been allocated by caller.
    //
    if (*GopBltSize < (UINTN)BltBufferSize) {
      *GopBltSize = (UINTN)BltBufferSize;
      return RETURN_BUFFER_TOO_SMALL;
    }
  }

  *PixelWidth  = BmpHeader->PixelWidth;
  *PixelHeight = BmpHeader->PixelHeight;
  DEBUG ((DEBUG_INFO, "BmpHeader->ImageOffset 0x%X\n", BmpHeader->ImageOffset));
  DEBUG ((DEBUG_INFO, "BmpHeader->PixelWidth 0x%X\n", BmpHeader->PixelWidth));
  DEBUG ((DEBUG_INFO, "BmpHeader->PixelHeight 0x%X\n", BmpHeader->PixelHeight));
  DEBUG ((DEBUG_INFO, "BmpHeader->BitPerPixel 0x%X\n", BmpHeader->BitPerPixel));
  DEBUG ((DEBUG_INFO, "BmpHeader->ImageSize 0x%X\n", BmpHeader->ImageSize));
  DEBUG ((DEBUG_INFO, "BmpHeader->HeaderSize 0x%X\n", BmpHeader->HeaderSize));
  DEBUG ((DEBUG_INFO, "BmpHeader->Size 0x%X\n", BmpHeader->Size));

  //
  // Translate image from BMP to Blt buffer format
  //
  BltBuffer = *GopBlt;
  for (Height = 0; Height < BmpHeader->PixelHeight; Height++) {
    Blt = &BltBuffer[ (BmpHeader->PixelHeight - Height - 1) * BmpHeader->PixelWidth];
    for (Width = 0; Width < BmpHeader->PixelWidth; Width++, Image++, Blt++) {
      switch (BmpHeader->BitPerPixel) {
      case 1:
        //
        // Translate 1-bit (2 colors) BMP to 24-bit color
        //
        for (Index = 0; Index < 8 && Width < BmpHeader->PixelWidth; Index++) {
          Blt->Red   = BmpColorMap[ ((*Image) >> (7 - Index)) & 0x1].Red;
          Blt->Green = BmpColorMap[ ((*Image) >> (7 - Index)) & 0x1].Green;
          Blt->Blue  = BmpColorMap[ ((*Image) >> (7 - Index)) & 0x1].Blue;
          Blt++;
          Width++;
        }

        Blt--;
        Width--;
        break;

      case 4:
        //
        // Translate 4-bit (16 colors) BMP Palette to 24-bit color
        //
        Index = (*Image) >> 4;
        Blt->Red   = BmpColorMap[Index].Red;
        Blt->Green = BmpColorMap[Index].Green;
        Blt->Blue  = BmpColorMap[Index].Blue;
        if (Width < (BmpHeader->PixelWidth - 1)) {
          Blt++;
          Width++;
          Index = (*Image) & 0x0f;
          Blt->Red   = BmpColorMap[Index].Red;
          Blt->Green = BmpColorMap[Index].Green;
          Blt->Blue  = BmpColorMap[Index].Blue;
        }
        break;

      case 8:
        //
        // Translate 8-bit (256 colors) BMP Palette to 24-bit color
        //
        Blt->Red   = BmpColorMap[*Image].Red;
        Blt->Green = BmpColorMap[*Image].Green;
        Blt->Blue  = BmpColorMap[*Image].Blue;
        break;

      case 24:
        //
        // It is 24-bit BMP.
        //
        Blt->Blue  = *Image++;
        Blt->Green = *Image++;
        Blt->Red   = *Image;
        break;

      case 32:
        //
        //Conver 32 bit to 24bit bmp - just ignore the final byte of each pixel
        Blt->Blue  = *Image++;
        Blt->Green = *Image++;
        Blt->Red   = *Image++;
        break;

      default:
        //
        // Other bit format BMP is not supported.
        //
        if (IsAllocated) {
          FreePool (*GopBlt);
          *GopBlt = NULL;
        }
        DEBUG ((DEBUG_ERROR, "Bmp Bit format not supported.  0x%X\n", BmpHeader->BitPerPixel));
        return RETURN_UNSUPPORTED;
        break;
      };

    }

    ImageIndex = (UINTN)Image - (UINTN)ImageHeader;
    if ((ImageIndex % 4) != 0) {
      //
      // Bmp Image starts each row on a 32-bit boundary!
      //
      Image = Image + (4 - (ImageIndex % 4));
    }
  }

  return RETURN_SUCCESS;
}

/**
  Translate a GOP blt buffer to an uncompressed 24-bit per pixel BMP graphics
  image. If a NULL BmpImage is passed in a BmpImage buffer will be allocated by
  this routine using EFI_BOOT_SERVICES.AllocatePool(). If a BmpImage buffer is
  passed in it will be used if it is big enough.

  @param [in]      GopBlt        Pointer to GOP blt buffer.
  @param [in]      PixelHeight   Height of GopBlt/BmpImage in pixels.
  @param [in]      PixelWidth    Width of GopBlt/BmpImage in pixels.
  @param [in, out] BmpImage      Buffer containing BMP version of GopBlt.
  @param [in, out] BmpImageSize  Size of BmpImage in bytes.

  @retval RETURN_SUCCESS            BmpImage and BmpImageSize are returned.
  @retval RETURN_INVALID_PARAMETER  GopBlt is NULL.
  @retval RETURN_INVALID_PARAMETER  BmpImage is NULL.
  @retval RETURN_INVALID_PARAMETER  BmpImageSize is NULL.
  @retval RETURN_UNSUPPORTED        GopBlt cannot be converted to a *.BMP image.
  @retval RETURN_BUFFER_TOO_SMALL   The passed in BmpImage buffer is not big
                                    enough.  The required size is returned in
                                    BmpImageSize.
  @retval RETURN_OUT_OF_RESOURCES   The BmpImage buffer could not be allocated.

**/
RETURN_STATUS
EFIAPI
TranslateGopBltToBmp (
  IN     EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *GopBlt,
  IN     UINT32                         PixelHeight,
  IN     UINT32                         PixelWidth,
  IN OUT VOID                           **BmpImage,
  IN OUT UINT32                         *BmpImageSize
  )
{
  RETURN_STATUS                  Status;
  UINT32                         PaddingSize;
  UINT32                         BmpSize;
  BMP_IMAGE_HEADER               *BmpImageHeader;
  UINT8                          *Image;
  UINTN                          Col;
  UINTN                          Row;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltPixel;

  if (GopBlt == NULL || BmpImage == NULL || BmpImageSize == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  if ((PixelHeight == 0) || (PixelWidth == 0)) {
    return RETURN_UNSUPPORTED;
  }

  //
  // Allocate memory for BMP file.
  //
  PaddingSize = PixelWidth & 0x3;

  //
  // First check PixelWidth * 3 + PaddingSize doesn't overflow
  //
  Status = SafeUint32Mult (PixelWidth, 3, &BmpSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n",
      PixelHeight,
      PixelWidth
      ));
    return RETURN_UNSUPPORTED;
  }
  Status = SafeUint32Add (BmpSize, PaddingSize, &BmpSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n",
      PixelHeight,
      PixelWidth
      ));
    return RETURN_UNSUPPORTED;
  }

  //
  // Second check (mLogoWidth * 3 + PaddingSize) * mLogoHeight + sizeof (BMP_IMAGE_HEADER) doesn't overflow
  //
  Status = SafeUint32Mult (BmpSize, PixelHeight, &BmpSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n",
      PixelHeight,
      PixelWidth
      ));
    return RETURN_UNSUPPORTED;
  }
  Status = SafeUint32Add (BmpSize, sizeof (BMP_IMAGE_HEADER), &BmpSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n",
      PixelHeight,
      PixelWidth
      ));
    return RETURN_UNSUPPORTED;
  }

  //
  // The image should be stored in EfiBootServicesData, allowing the system to
  // reclaim the memory
  //
  if (*BmpImage == NULL) {
    *BmpImage = AllocateZeroPool (BmpSize);
    if (*BmpImage == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    *BmpImageSize = BmpSize;
  } else if (*BmpImageSize < BmpSize) {
    *BmpImageSize = BmpSize;
    return RETURN_BUFFER_TOO_SMALL;
  }

  BmpImageHeader = (BMP_IMAGE_HEADER *)*BmpImage;
  CopyMem (BmpImageHeader, &mBmpImageHeaderTemplate, sizeof (BMP_IMAGE_HEADER));
  BmpImageHeader->Size        = *BmpImageSize;
  BmpImageHeader->ImageSize   = *BmpImageSize - sizeof (BMP_IMAGE_HEADER);
  BmpImageHeader->PixelWidth  = PixelWidth;
  BmpImageHeader->PixelHeight = PixelHeight;

  //
  // Convert BLT buffer to BMP file.
  //
  Image = (UINT8 *)BmpImageHeader + sizeof (BMP_IMAGE_HEADER);
  for (Row = 0; Row < PixelHeight; Row++) {
    BltPixel = &GopBlt[(PixelHeight - Row - 1) * PixelWidth];

    for (Col = 0; Col < PixelWidth; Col++) {
      *Image++ = BltPixel->Blue;
      *Image++ = BltPixel->Green;
      *Image++ = BltPixel->Red;
      BltPixel++;
    }

    //
    // Padding for 4 byte alignment.
    //
    Image += PaddingSize;
  }

  return RETURN_SUCCESS;
}
