/** @file
  SMBIOS String Table Helper

  Copyright (c) 2022, Arm Limited. All rights reserved.<BR>

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

  @par Reference(s):
  - DSP0134 - SMBIOS Specification Version 3.6.0, 2022-06-17
**/

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/SmbiosStringTableLib.h>

/** Add a string to the string table.

  @param[in]   StrTable  Pointer to the string table
  @param[in]   Str       Pointer to the string
  @param[out]  StrRef    Optional pointer to retrieve the string field
                         reference of the string in the string table

  @return EFI_SUCCESS            Success
  @return EFI_INVALID_PARAMETER  Invalid string table pointer
  @return EFI_BUFFER_TOO_SMALL   Insufficient space to add string
**/
EFI_STATUS
EFIAPI
StringTableAddString (
  IN        STRING_TABLE *CONST  StrTable,
  IN  CONST CHAR8                *Str,
  OUT       UINT8                *StrRef      OPTIONAL
  )
{
  UINTN           StrLength;
  STRING_ELEMENT  *StrElement;

  if ((StrTable == NULL) || (Str == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (StrTable->StrCount >= StrTable->MaxStringElements) {
    return EFI_BUFFER_TOO_SMALL;
  }

  StrLength = AsciiStrLen (Str);
  if (StrLength == 0) {
    return EFI_INVALID_PARAMETER;
  }

  // Update the string element
  StrElement            = &StrTable->Elements[StrTable->StrCount];
  StrElement->StringLen = StrLength;
  StrElement->String    = Str;

  // Update String table information
  StrTable->TotalStrLen += StrLength;
  StrTable->StrCount++;

  // Return the index of the string in the string table if requested
  if (StrRef != NULL) {
    // Note: SMBIOS string field references start at 1. So, return the
    // StrCount as the string reference after it is updated.
    *StrRef = StrTable->StrCount;
  }

  return EFI_SUCCESS;
}

/** Returns the total size required to publish the strings to the SMBIOS
    string area.

  @param[in] StrTable              Pointer to the string table

  @return Total size required to publish the strings in the SMBIOS string area.
**/
UINTN
EFIAPI
StringTableGetStringSetSize (
  IN  STRING_TABLE *CONST  StrTable
  )
{
  if (StrTable == NULL) {
    ASSERT (0);
    return 0;
  }

  // See Section 6.1.3 Text strings, SMBIOS Specification Version 3.6.0
  // - If the formatted portion of the structure contains string-reference
  //   fields and all the string fields are set to 0 (no string references),
  //   the formatted section of the structure is followed by two null (00h)
  //   BYTES.
  // - Each string is terminated with a null (00h) BYTE
  // - and the set of strings is terminated with an additional null (00h) BYTE.

  // Therefore, if string count = 0, return 2
  // if string count > 0, the string set size =
  // StrTable->TotalStrLen (total length of the strings in the string table)
  // + StrTable->StrCount (add string count to include '\0' for each string)
  // +1 (an additional '\0' is required at the end of the string set).
  return (StrTable->StrCount == 0) ? 2 :
         (StrTable->TotalStrLen + StrTable->StrCount + 1);
}

/** Iterate through the string table and publish the strings in the SMBIOS
    string area.

  @param[in] StrTable              Pointer to the string table
  @param[in] SmbiosStringAreaStart Start address of the SMBIOS string area.
  @param[in] SmbiosStringAreaSize  Size of the SMBIOS string area.

  @return EFI_SUCCESS            Success
  @return EFI_INVALID_PARAMETER  Invalid string table pointer
  @return EFI_BUFFER_TOO_SMALL   Insufficient space to publish strings
**/
EFI_STATUS
EFIAPI
StringTablePublishStringSet (
  IN        STRING_TABLE  *CONST  StrTable,
  IN        CHAR8         *CONST  SmbiosStringAreaStart,
  IN  CONST UINTN                 SmbiosStringAreaSize
  )
{
  UINT8           Index;
  STRING_ELEMENT  *StrElement;
  CHAR8           *SmbiosString;
  UINTN           BytesRemaining;
  UINTN           BytesCopied;

  if ((StrTable == NULL) || (SmbiosStringAreaStart == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if (SmbiosStringAreaSize < StringTableGetStringSetSize (StrTable)) {
    return EFI_BUFFER_TOO_SMALL;
  }

  SmbiosString   = SmbiosStringAreaStart;
  BytesRemaining = SmbiosStringAreaSize;

  if (StrTable->StrCount == 0) {
    // See Section 6.1.3 Text strings, SMBIOS Specification Version 3.6.0
    // If the formatted portion of the structure contains string-reference
    // fields and all the string fields are set to 0 (no string references),
    // the formatted section of the structure is followed by two null (00h)
    // BYTES.
    *SmbiosString++ = '\0';
  } else {
    for (Index = 0; Index < StrTable->StrCount; Index++) {
      StrElement = &StrTable->Elements[Index];
      AsciiStrCpyS (SmbiosString, BytesRemaining, StrElement->String);

      // See Section 6.1.3 Text strings, SMBIOS Specification Version 3.6.0
      // - Each string is terminated with a null (00h) BYTE
      // Bytes Copied = String length + 1 for the string NULL terminator.
      BytesCopied     = StrElement->StringLen + 1;
      BytesRemaining -= BytesCopied;
      SmbiosString   += BytesCopied;
    }
  }

  // See Section 6.1.3 Text strings, SMBIOS Specification Version 3.6.0
  // - the set of strings is terminated with an additional null (00h) BYTE.
  *SmbiosString = '\0';
  return EFI_SUCCESS;
}

/** Initialise the string table and allocate memory for the string elements.

  @param[in] StrTable           Pointer to the string table
  @param[in] MaxStringElements  Maximum number of strings that the string
                                table can hold.

  @return EFI_SUCCESS            Success
  @return EFI_INVALID_PARAMETER  Invalid string table pointer
  @return EFI_OUT_OF_RESOURCES   Failed to allocate memory for string elements
**/
EFI_STATUS
EFIAPI
StringTableInitialize (
  IN STRING_TABLE *CONST  StrTable,
  IN UINTN                MaxStringElements
  )
{
  STRING_ELEMENT  *Elements;

  if ((StrTable == NULL) || (MaxStringElements > MAX_UINT8)) {
    return EFI_INVALID_PARAMETER;
  }

  ZeroMem (StrTable, sizeof (STRING_TABLE));

  Elements = (STRING_ELEMENT *)AllocateZeroPool (
                                 sizeof (STRING_ELEMENT) * MaxStringElements
                                 );
  if (Elements == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  StrTable->Elements          = Elements;
  StrTable->MaxStringElements = (UINT8)MaxStringElements;
  return EFI_SUCCESS;
}

/** Free memory allocated for the string elements in the string table.

  @param[in] StrTable           Pointer to the string table

  @return EFI_SUCCESS            Success
  @return EFI_INVALID_PARAMETER  Invalid string table pointer or string elements
**/
EFI_STATUS
EFIAPI
StringTableFree (
  IN STRING_TABLE *CONST  StrTable
  )
{
  if ((StrTable == NULL) || (StrTable->Elements == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  FreePool (StrTable->Elements);
  ZeroMem (StrTable, sizeof (STRING_TABLE));
  return EFI_SUCCESS;
}
