/** @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 ((
      EFI_D_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", __FUNCTION__,
            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", __FUNCTION__,
            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;
}

