/** @file
  Serialize Variables Library implementation

  Copyright (c) 2004 - 2011, Intel Corporation. All rights reserved.<BR>
  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#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(%d) != MaxSize(%d)\n",
      TotalSizeUsed,
      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;
  }
  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;
}

