/** @file
  Serialize Variables Library implementation

  Copyright (c) 2004 - 2011, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "SerializeVariablesLib.h"

/**
  Serialization format:

  The SerializeVariablesLib interface does not specify a format
  for the serialization of the variable data.  This library uses
  a packed array of a non-uniformly sized data structure elements.

  Each variable is stored (packed) as:
    UINT32   VendorNameSize;  // Name size in bytes
    CHAR16   VendorName[?];   // The variable unicode name including the
                              // null terminating character.
    EFI_GUID VendorGuid;      // The variable GUID
    UINT32   DataSize;        // The size of variable data in bytes
    UINT8    Data[?];         // The variable data

**/

/**
  Unpacks the next variable from the buffer

  @param[in]  Buffer - Buffer pointing to the next variable instance
                On subsequent calls, the pointer should be incremented
                by the returned SizeUsed value.
  @param[in]  MaxSize - Max allowable size for the variable data
                On subsequent calls, this should be decremented
                by the returned SizeUsed value.
  @param[out] Name - Variable name string (address in Buffer)
  @param[out] NameSize - Size of Name in bytes
  @param[out] Guid - GUID of variable (address in Buffer)
  @param[out] Attributes - Attributes of variable
  @param[out] Data - Buffer containing Data for variable (address in Buffer)
  @param[out] DataSize - Size of Data in bytes
  @param[out] SizeUsed - Total size used for this variable instance in Buffer

  @return     EFI_STATUS based on the success or failure of the operation

**/
STATIC
EFI_STATUS
UnpackVariableFromBuffer (
  IN  VOID      *Buffer,
  IN  UINTN     MaxSize,
  OUT CHAR16    **Name,
  OUT UINT32    *NameSize,
  OUT EFI_GUID  **Guid,
  OUT UINT32    *Attributes,
  OUT UINT32    *DataSize,
  OUT VOID      **Data,
  OUT UINTN     *SizeUsed
  )
{
  UINT8  *BytePtr;
  UINTN  Offset;

  BytePtr = (UINT8 *)Buffer;
  Offset  = 0;

  *NameSize = *(UINT32 *)(BytePtr + Offset);
  Offset    = Offset + sizeof (UINT32);

  if (Offset > MaxSize) {
    return EFI_INVALID_PARAMETER;
  }

  *Name  = (CHAR16 *)(BytePtr + Offset);
  Offset = Offset + *(UINT32 *)BytePtr;
  if (Offset > MaxSize) {
    return EFI_INVALID_PARAMETER;
  }

  *Guid  = (EFI_GUID *)(BytePtr + Offset);
  Offset = Offset + sizeof (EFI_GUID);
  if (Offset > MaxSize) {
    return EFI_INVALID_PARAMETER;
  }

  *Attributes = *(UINT32 *)(BytePtr + Offset);
  Offset      = Offset + sizeof (UINT32);
  if (Offset > MaxSize) {
    return EFI_INVALID_PARAMETER;
  }

  *DataSize = *(UINT32 *)(BytePtr + Offset);
  Offset    = Offset + sizeof (UINT32);
  if (Offset > MaxSize) {
    return EFI_INVALID_PARAMETER;
  }

  *Data  = (VOID *)(BytePtr + Offset);
  Offset = Offset + *DataSize;
  if (Offset > MaxSize) {
    return EFI_INVALID_PARAMETER;
  }

  *SizeUsed = Offset;

  return EFI_SUCCESS;
}

/**
  Iterates through the variables in the buffer, and calls a callback
  function for each variable found.

  @param[in]  CallbackFunction - Function called for each variable instance
  @param[in]  Context - Passed to each call of CallbackFunction
  @param[in]  Buffer - Buffer containing serialized variables
  @param[in]  MaxSize - Size of Buffer in bytes

  @return     EFI_STATUS based on the success or failure of the operation

**/
STATIC
EFI_STATUS
IterateVariablesInBuffer (
  IN VARIABLE_SERIALIZATION_ITERATION_CALLBACK  CallbackFunction,
  IN VOID                                       *CallbackContext,
  IN VOID                                       *Buffer,
  IN UINTN                                      MaxSize
  )
{
  RETURN_STATUS  Status;
  UINTN          TotalSizeUsed;
  UINTN          SizeUsed;

  CHAR16    *Name;
  UINT32    NameSize;
  CHAR16    *AlignedName;
  UINT32    AlignedNameMaxSize;
  EFI_GUID  *Guid;
  UINT32    Attributes;
  UINT32    DataSize;
  VOID      *Data;

  SizeUsed           = 0;
  AlignedName        = NULL;
  AlignedNameMaxSize = 0;
  Name               = NULL;
  Guid               = NULL;
  Attributes         = 0;
  DataSize           = 0;
  Data               = NULL;

  for (
       Status = EFI_SUCCESS, TotalSizeUsed = 0;
       !EFI_ERROR (Status) && (TotalSizeUsed < MaxSize);
       )
  {
    Status = UnpackVariableFromBuffer (
               (VOID *)((UINT8 *)Buffer + TotalSizeUsed),
               (MaxSize - TotalSizeUsed),
               &Name,
               &NameSize,
               &Guid,
               &Attributes,
               &DataSize,
               &Data,
               &SizeUsed
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // We copy the name to a separately allocated buffer,
    // to be sure it is 16-bit aligned.
    //
    if (NameSize > AlignedNameMaxSize) {
      if (AlignedName != NULL) {
        FreePool (AlignedName);
      }

      AlignedName = AllocatePool (NameSize);
    }

    if (AlignedName == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    CopyMem (AlignedName, Name, NameSize);

    TotalSizeUsed = TotalSizeUsed + SizeUsed;

    //
    // Run the callback function
    //
    Status = (*CallbackFunction)(
  CallbackContext,
  AlignedName,
  Guid,
  Attributes,
  DataSize,
  Data
  );
  }

  if (AlignedName != NULL) {
    FreePool (AlignedName);
  }

  //
  // Make sure the entire buffer was used, or else return an error
  //
  if (TotalSizeUsed != MaxSize) {
    DEBUG ((
      DEBUG_ERROR,
      "Deserialize variables error: TotalSizeUsed(%Lu) != MaxSize(%Lu)\n",
      (UINT64)TotalSizeUsed,
      (UINT64)MaxSize
      ));
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}

STATIC
RETURN_STATUS
EFIAPI
IterateVariablesCallbackNop (
  IN  VOID      *Context,
  IN  CHAR16    *VariableName,
  IN  EFI_GUID  *VendorGuid,
  IN  UINT32    Attributes,
  IN  UINTN     DataSize,
  IN  VOID      *Data
  )
{
  return RETURN_SUCCESS;
}

STATIC
RETURN_STATUS
EFIAPI
IterateVariablesCallbackSetInInstance (
  IN  VOID      *Context,
  IN  CHAR16    *VariableName,
  IN  EFI_GUID  *VendorGuid,
  IN  UINT32    Attributes,
  IN  UINTN     DataSize,
  IN  VOID      *Data
  )
{
  EFI_HANDLE  Instance;

  Instance = (EFI_HANDLE)Context;

  return SerializeVariablesAddVariable (
           Instance,
           VariableName,
           VendorGuid,
           Attributes,
           DataSize,
           Data
           );
}

STATIC
RETURN_STATUS
EFIAPI
IterateVariablesCallbackSetSystemVariable (
  IN  VOID      *Context,
  IN  CHAR16    *VariableName,
  IN  EFI_GUID  *VendorGuid,
  IN  UINT32    Attributes,
  IN  UINTN     DataSize,
  IN  VOID      *Data
  )
{
  EFI_STATUS           Status;
  STATIC CONST UINT32  AuthMask =
    EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS |
    EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;

  Status = gRT->SetVariable (
                  VariableName,
                  VendorGuid,
                  Attributes,
                  DataSize,
                  Data
                  );

  if ((Status == EFI_SECURITY_VIOLATION) && ((Attributes & AuthMask) != 0)) {
    DEBUG ((
      DEBUG_WARN,
      "%a: setting authenticated variable \"%s\" "
      "failed with EFI_SECURITY_VIOLATION, ignoring\n",
      __func__,
      VariableName
      ));
    Status = EFI_SUCCESS;
  } else if (Status == EFI_WRITE_PROTECTED) {
    DEBUG ((
      DEBUG_WARN,
      "%a: setting ReadOnly variable \"%s\" "
      "failed with EFI_WRITE_PROTECTED, ignoring\n",
      __func__,
      VariableName
      ));
    Status = EFI_SUCCESS;
  }

  return Status;
}

STATIC
RETURN_STATUS
EnsureExtraBufferSpace (
  IN  SV_INSTANCE  *Instance,
  IN  UINTN        Size
  )
{
  VOID   *NewBuffer;
  UINTN  NewSize;

  NewSize = Instance->DataSize + Size;
  if (NewSize <= Instance->BufferSize) {
    return RETURN_SUCCESS;
  }

  //
  // Double the required size to lessen the need to re-allocate in the future
  //
  NewSize = 2 * NewSize;

  NewBuffer = AllocatePool (NewSize);
  if (NewBuffer == NULL) {
    return RETURN_OUT_OF_RESOURCES;
  }

  if (Instance->BufferPtr != NULL) {
    CopyMem (NewBuffer, Instance->BufferPtr, Instance->DataSize);
    FreePool (Instance->BufferPtr);
  }

  Instance->BufferPtr  = NewBuffer;
  Instance->BufferSize = NewSize;

  return RETURN_SUCCESS;
}

STATIC
VOID
AppendToBuffer (
  IN  SV_INSTANCE  *Instance,
  IN  VOID         *Data,
  IN  UINTN        Size
  )
{
  UINTN  NewSize;

  ASSERT (Instance != NULL);
  ASSERT (Data != NULL);

  NewSize = Instance->DataSize + Size;
  ASSERT ((Instance->DataSize + Size) <= Instance->BufferSize);

  CopyMem (
    (VOID *)(((UINT8 *)(Instance->BufferPtr)) + Instance->DataSize),
    Data,
    Size
    );

  Instance->DataSize = NewSize;
}

/**
  Creates a new variable serialization instance

  @param[out]  Handle - Handle for a variable serialization instance

  @retval      RETURN_SUCCESS - The variable serialization instance was
                 successfully created.
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 create the variable serialization instance.

**/
RETURN_STATUS
EFIAPI
SerializeVariablesNewInstance (
  OUT EFI_HANDLE  *Handle
  )
{
  SV_INSTANCE  *New;

  New = AllocateZeroPool (sizeof (*New));
  if (New == NULL) {
    return RETURN_OUT_OF_RESOURCES;
  }

  New->Signature = SV_SIGNATURE;

  *Handle = (EFI_HANDLE)New;
  return RETURN_SUCCESS;
}

/**
  Free memory associated with a variable serialization instance

  @param[in]  Handle - Handle for a variable serialization instance

  @retval      RETURN_SUCCESS - The variable serialization instance was
                 successfully freed.
  @retval      RETURN_INVALID_PARAMETER - Handle was not a valid
                 variable serialization instance.

**/
RETURN_STATUS
EFIAPI
SerializeVariablesFreeInstance (
  IN EFI_HANDLE  Handle
  )
{
  SV_INSTANCE  *Instance;

  Instance = SV_FROM_HANDLE (Handle);

  if (Instance->Signature != SV_SIGNATURE) {
    return RETURN_INVALID_PARAMETER;
  }

  Instance->Signature = 0;

  if (Instance->BufferPtr != NULL) {
    FreePool (Instance->BufferPtr);
  }

  FreePool (Instance);

  return RETURN_SUCCESS;
}

/**
  Creates a new variable serialization instance using the given
  binary representation of the variables to fill the new instance

  @param[out] Handle - Handle for a variable serialization instance
  @param[in]  Buffer - A buffer with the serialized representation
                of the variables.  Must be the same format as produced
                by SerializeVariablesToBuffer.
  @param[in]  Size - This is the size of the binary representation
                of the variables.

  @retval      RETURN_SUCCESS - The binary representation was successfully
                 imported into a new variable serialization instance
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 create the new variable serialization instance

**/
RETURN_STATUS
EFIAPI
SerializeVariablesNewInstanceFromBuffer (
  OUT EFI_HANDLE  *Handle,
  IN  VOID        *Buffer,
  IN  UINTN       Size
  )
{
  RETURN_STATUS  Status;

  Status = SerializeVariablesNewInstance (Handle);
  if (RETURN_ERROR (Status)) {
    return Status;
  }

  Status = IterateVariablesInBuffer (
             IterateVariablesCallbackNop,
             NULL,
             Buffer,
             Size
             );
  if (RETURN_ERROR (Status)) {
    SerializeVariablesFreeInstance (*Handle);
    return Status;
  }

  Status = IterateVariablesInBuffer (
             IterateVariablesCallbackSetInInstance,
             (VOID *)*Handle,
             Buffer,
             Size
             );
  if (RETURN_ERROR (Status)) {
    SerializeVariablesFreeInstance (*Handle);
    return Status;
  }

  return Status;
}

/**
  Iterates all variables found with RuntimeServices GetNextVariableName

  @param[in]   CallbackFunction - Function called for each variable instance
  @param[in]   Context - Passed to each call of CallbackFunction

  @retval      RETURN_SUCCESS - All variables were iterated without the
                 CallbackFunction returning an error
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 iterate through the variables
  @return      Any of RETURN_ERROR indicates an error reading the variable
                 or an error was returned from CallbackFunction

**/
RETURN_STATUS
EFIAPI
SerializeVariablesIterateSystemVariables (
  IN VARIABLE_SERIALIZATION_ITERATION_CALLBACK  CallbackFunction,
  IN VOID                                       *Context
  )
{
  RETURN_STATUS  Status;
  UINTN          VariableNameBufferSize;
  UINTN          VariableNameSize;
  CHAR16         *VariableName;
  EFI_GUID       VendorGuid;
  UINTN          VariableDataBufferSize;
  UINTN          VariableDataSize;
  VOID           *VariableData;
  UINT32         VariableAttributes;
  VOID           *NewBuffer;

  //
  // Initialize the variable name and data buffer variables.
  //
  VariableNameBufferSize = sizeof (CHAR16);
  VariableName           = AllocateZeroPool (VariableNameBufferSize);

  VariableDataBufferSize = 0;
  VariableData           = NULL;

  for ( ; ;) {
    //
    // Get the next variable name and guid
    //
    VariableNameSize = VariableNameBufferSize;
    Status           = gRT->GetNextVariableName (
                              &VariableNameSize,
                              VariableName,
                              &VendorGuid
                              );
    if (Status == EFI_BUFFER_TOO_SMALL) {
      //
      // The currently allocated VariableName buffer is too small,
      // so we allocate a larger buffer, and copy the old buffer
      // to it.
      //
      NewBuffer = AllocatePool (VariableNameSize);
      if (NewBuffer == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        break;
      }

      CopyMem (NewBuffer, VariableName, VariableNameBufferSize);
      if (VariableName != NULL) {
        FreePool (VariableName);
      }

      VariableName           = NewBuffer;
      VariableNameBufferSize = VariableNameSize;

      //
      // Try to get the next variable name again with the larger buffer.
      //
      Status = gRT->GetNextVariableName (
                      &VariableNameSize,
                      VariableName,
                      &VendorGuid
                      );
    }

    if (EFI_ERROR (Status)) {
      if (Status == EFI_NOT_FOUND) {
        Status = EFI_SUCCESS;
      }

      break;
    }

    //
    // Get the variable data and attributes
    //
    VariableDataSize = VariableDataBufferSize;
    Status           = gRT->GetVariable (
                              VariableName,
                              &VendorGuid,
                              &VariableAttributes,
                              &VariableDataSize,
                              VariableData
                              );
    if (Status == EFI_BUFFER_TOO_SMALL) {
      //
      // The currently allocated VariableData buffer is too small,
      // so we allocate a larger buffer.
      //
      if (VariableDataBufferSize != 0) {
        FreePool (VariableData);
        VariableData           = NULL;
        VariableDataBufferSize = 0;
      }

      VariableData = AllocatePool (VariableDataSize);
      if (VariableData == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        break;
      }

      VariableDataBufferSize = VariableDataSize;

      //
      // Try to read the variable again with the larger buffer.
      //
      Status = gRT->GetVariable (
                      VariableName,
                      &VendorGuid,
                      &VariableAttributes,
                      &VariableDataSize,
                      VariableData
                      );
    }

    if (EFI_ERROR (Status)) {
      break;
    }

    //
    // Run the callback function
    //
    Status = (*CallbackFunction)(
  Context,
  VariableName,
  &VendorGuid,
  VariableAttributes,
  VariableDataSize,
  VariableData
  );
    if (EFI_ERROR (Status)) {
      break;
    }
  }

  if (VariableName != NULL) {
    FreePool (VariableName);
  }

  if (VariableData != NULL) {
    FreePool (VariableData);
  }

  return Status;
}

/**
  Iterates all variables found in the variable serialization instance

  @param[in]   Handle - Handle for a variable serialization instance
  @param[in]   CallbackFunction - Function called for each variable instance
  @param[in]   Context - Passed to each call of CallbackFunction

  @retval      RETURN_SUCCESS - All variables were iterated without the
                 CallbackFunction returning an error
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 iterate through the variables
  @return      Any of RETURN_ERROR indicates an error reading the variable
                 or an error was returned from CallbackFunction

**/
RETURN_STATUS
EFIAPI
SerializeVariablesIterateInstanceVariables (
  IN EFI_HANDLE                                 Handle,
  IN VARIABLE_SERIALIZATION_ITERATION_CALLBACK  CallbackFunction,
  IN VOID                                       *Context
  )
{
  SV_INSTANCE  *Instance;

  Instance = SV_FROM_HANDLE (Handle);

  if ((Instance->BufferPtr != NULL) && (Instance->DataSize != 0)) {
    return IterateVariablesInBuffer (
             CallbackFunction,
             Context,
             Instance->BufferPtr,
             Instance->DataSize
             );
  } else {
    return RETURN_SUCCESS;
  }
}

/**
  Sets all variables found in the variable serialization instance

  @param[in]   Handle - Handle for a variable serialization instance

  @retval      RETURN_SUCCESS - All variables were set successfully
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 set all the variables
  @return      Any of RETURN_ERROR indicates an error reading the variables
                 or in attempting to set a variable

**/
RETURN_STATUS
EFIAPI
SerializeVariablesSetSerializedVariables (
  IN EFI_HANDLE  Handle
  )
{
  return SerializeVariablesIterateInstanceVariables (
           Handle,
           IterateVariablesCallbackSetSystemVariable,
           NULL
           );
}

/**
  Adds a variable to the variable serialization instance

  @param[in] Handle - Handle for a variable serialization instance
  @param[in] VariableName - Refer to RuntimeServices GetVariable
  @param[in] VendorGuid - Refer to RuntimeServices GetVariable
  @param[in] Attributes - Refer to RuntimeServices GetVariable
  @param[in] DataSize - Refer to RuntimeServices GetVariable
  @param[in] Data - Refer to RuntimeServices GetVariable

  @retval      RETURN_SUCCESS - All variables were set successfully
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 add the variable
  @retval      RETURN_INVALID_PARAMETER - Handle was not a valid
                 variable serialization instance or
                 VariableName, VariableGuid or Data are NULL.

**/
RETURN_STATUS
EFIAPI
SerializeVariablesAddVariable (
  IN EFI_HANDLE  Handle,
  IN CHAR16      *VariableName,
  IN EFI_GUID    *VendorGuid,
  IN UINT32      Attributes,
  IN UINTN       DataSize,
  IN VOID        *Data
  )
{
  RETURN_STATUS  Status;
  SV_INSTANCE    *Instance;
  UINT32         SerializedNameSize;
  UINT32         SerializedDataSize;
  UINTN          SerializedSize;

  Instance = SV_FROM_HANDLE (Handle);

  if ((Instance->Signature != SV_SIGNATURE) ||
      (VariableName == NULL) || (VendorGuid == NULL) || (Data == NULL))
  {
  }

  SerializedNameSize = (UINT32)StrSize (VariableName);

  SerializedSize =
    sizeof (SerializedNameSize) +
    SerializedNameSize +
    sizeof (*VendorGuid) +
    sizeof (Attributes) +
    sizeof (SerializedDataSize) +
    DataSize;

  Status = EnsureExtraBufferSpace (
             Instance,
             SerializedSize
             );
  if (RETURN_ERROR (Status)) {
    return Status;
  }

  //
  // Add name size (UINT32)
  //
  AppendToBuffer (Instance, (VOID *)&SerializedNameSize, sizeof (SerializedNameSize));

  //
  // Add variable unicode name string
  //
  AppendToBuffer (Instance, (VOID *)VariableName, SerializedNameSize);

  //
  // Add variable GUID
  //
  AppendToBuffer (Instance, (VOID *)VendorGuid, sizeof (*VendorGuid));

  //
  // Add variable attributes
  //
  AppendToBuffer (Instance, (VOID *)&Attributes, sizeof (Attributes));

  //
  // Add variable data size (UINT32)
  //
  SerializedDataSize = (UINT32)DataSize;
  AppendToBuffer (Instance, (VOID *)&SerializedDataSize, sizeof (SerializedDataSize));

  //
  // Add variable data
  //
  AppendToBuffer (Instance, Data, DataSize);

  return RETURN_SUCCESS;
}

/**
  Serializes the variables known to this instance into the
  provided buffer.

  @param[in]     Handle - Handle for a variable serialization instance
  @param[out]    Buffer - A buffer to store the binary representation
                   of the variables.
  @param[in,out] Size - On input this is the size of the buffer.
                   On output this is the size of the binary representation
                   of the variables.

  @retval      RETURN_SUCCESS - The binary representation was successfully
                 completed and returned in the buffer.
  @retval      RETURN_OUT_OF_RESOURCES - There we not enough resources to
                 save the variables to the buffer.
  @retval      RETURN_INVALID_PARAMETER - Handle was not a valid
                 variable serialization instance or
                 Size or Buffer were NULL.
  @retval      RETURN_BUFFER_TOO_SMALL - The Buffer size as indicated by
                 the Size parameter was too small for the serialized
                 variable data.  Size is returned with the required size.

**/
RETURN_STATUS
EFIAPI
SerializeVariablesToBuffer (
  IN     EFI_HANDLE  Handle,
  OUT    VOID        *Buffer,
  IN OUT UINTN       *Size
  )
{
  SV_INSTANCE  *Instance;

  Instance = SV_FROM_HANDLE (Handle);

  if (Size == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  if (*Size < Instance->DataSize) {
    *Size = Instance->DataSize;
    return RETURN_BUFFER_TOO_SMALL;
  }

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

  *Size = Instance->DataSize;
  CopyMem (Buffer, Instance->BufferPtr, Instance->DataSize);

  return RETURN_SUCCESS;
}
