/** @file
  Supports Fmp Capsule Dependency Expression.
  Copyright (c) Microsoft Corporation.
  Copyright (c) 2020, Intel Corporation. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//
// Define the initial size of the dependency expression evaluation stack
//
#define DEPEX_STACK_SIZE_INCREMENT  0x1000
//
// Type of stack element
//
typedef enum {
  BooleanType,
  VersionType
} ELEMENT_TYPE;
//
// Value of stack element
//
typedef union {
  BOOLEAN    Boolean;
  UINT32     Version;
} ELEMENT_VALUE;
//
// Stack element used to evaluate dependency expressions
//
typedef struct {
  ELEMENT_VALUE    Value;
  ELEMENT_TYPE     Type;
} DEPEX_ELEMENT;
//
// Global stack used to evaluate dependency expressions
//
DEPEX_ELEMENT  *mDepexEvaluationStack        = NULL;
DEPEX_ELEMENT  *mDepexEvaluationStackEnd     = NULL;
DEPEX_ELEMENT  *mDepexEvaluationStackPointer = NULL;
/**
  Grow size of the Depex stack
  @retval EFI_SUCCESS           Stack successfully growed.
  @retval EFI_OUT_OF_RESOURCES  There is not enough system memory to grow the stack.
**/
EFI_STATUS
GrowDepexStack (
  VOID
  )
{
  DEPEX_ELEMENT  *NewStack;
  UINTN          Size;
  Size = DEPEX_STACK_SIZE_INCREMENT;
  if (mDepexEvaluationStack != NULL) {
    Size = Size + (mDepexEvaluationStackEnd - mDepexEvaluationStack);
  }
  NewStack = AllocatePool (Size * sizeof (DEPEX_ELEMENT));
  if (NewStack == NULL) {
    DEBUG ((DEBUG_ERROR, "GrowDepexStack: Cannot allocate memory for dependency evaluation stack!\n"));
    return EFI_OUT_OF_RESOURCES;
  }
  if (mDepexEvaluationStack != NULL) {
    //
    // Copy to Old Stack to the New Stack
    //
    CopyMem (
      NewStack,
      mDepexEvaluationStack,
      (mDepexEvaluationStackEnd - mDepexEvaluationStack) * sizeof (DEPEX_ELEMENT)
      );
    //
    // Free The Old Stack
    //
    FreePool (mDepexEvaluationStack);
  }
  //
  // Make the Stack pointer point to the old data in the new stack
  //
  mDepexEvaluationStackPointer = NewStack + (mDepexEvaluationStackPointer - mDepexEvaluationStack);
  mDepexEvaluationStack        = NewStack;
  mDepexEvaluationStackEnd     = NewStack + Size;
  return EFI_SUCCESS;
}
/**
  Push an element onto the Stack.
  @param[in]  Value                  Value to push.
  @param[in]  Type                   Element Type
  @retval EFI_SUCCESS            The value was pushed onto the stack.
  @retval EFI_OUT_OF_RESOURCES   There is not enough system memory to grow the stack.
  @retval EFI_INVALID_PARAMETER  Wrong stack element type.
**/
EFI_STATUS
Push (
  IN UINT32  Value,
  IN UINTN   Type
  )
{
  EFI_STATUS     Status;
  DEPEX_ELEMENT  Element;
  //
  // Check Type
  //
  if ((Type != BooleanType) && (Type != VersionType)) {
    return EFI_INVALID_PARAMETER;
  }
  //
  // Check for a stack overflow condition
  //
  if (mDepexEvaluationStackPointer == mDepexEvaluationStackEnd) {
    //
    // Grow the stack
    //
    Status = GrowDepexStack ();
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }
  Element.Value.Version = Value;
  Element.Type          = Type;
  //
  // Push the item onto the stack
  //
  *mDepexEvaluationStackPointer = Element;
  mDepexEvaluationStackPointer++;
  return EFI_SUCCESS;
}
/**
  Pop an element from the stack.
  @param[out]  Element                Element to pop.
  @param[in]   Type                   Type of element.
  @retval EFI_SUCCESS            The value was popped onto the stack.
  @retval EFI_ACCESS_DENIED      The pop operation underflowed the stack.
  @retval EFI_INVALID_PARAMETER  Type is mismatched.
**/
EFI_STATUS
Pop (
  OUT DEPEX_ELEMENT  *Element,
  IN  ELEMENT_TYPE   Type
  )
{
  //
  // Check for a stack underflow condition
  //
  if (mDepexEvaluationStackPointer == mDepexEvaluationStack) {
    DEBUG ((DEBUG_ERROR, "EvaluateDependency: Stack underflow!\n"));
    return EFI_ACCESS_DENIED;
  }
  //
  // Pop the item off the stack
  //
  mDepexEvaluationStackPointer--;
  *Element = *mDepexEvaluationStackPointer;
  if ((*Element).Type != Type) {
    DEBUG ((DEBUG_ERROR, "EvaluateDependency: Popped element type is mismatched!\n"));
    return EFI_INVALID_PARAMETER;
  }
  return EFI_SUCCESS;
}
/**
  Evaluate the dependencies. The caller must search all the Fmp instances and
  gather their versions into FmpVersions parameter. If there is PUSH_GUID opcode
  in dependency expression with no FmpVersions provided, the dependency will
  evaluate to FALSE.
  @param[in]   Dependencies       Dependency expressions.
  @param[in]   DependenciesSize   Size of Dependency expressions.
  @param[in]   FmpVersions        Array of Fmp ImageTypeId and version. This
                                  parameter is optional and can be set to NULL.
  @param[in]   FmpVersionsCount   Element count of the array. When FmpVersions
                                  is NULL, FmpVersionsCount must be 0.
  @param[out]  LastAttemptStatus  An optional pointer to a UINT32 that holds the
                                  last attempt status to report back to the caller.
                                  This function will set the value to LAST_ATTEMPT_STATUS_SUCCESS
                                  if an error code is not set.
  @retval TRUE    Dependency expressions evaluate to TRUE.
  @retval FALSE   Dependency expressions evaluate to FALSE.
**/
BOOLEAN
EFIAPI
EvaluateDependency (
  IN  EFI_FIRMWARE_IMAGE_DEP        *Dependencies,
  IN  UINTN                         DependenciesSize,
  IN  FMP_DEPEX_CHECK_VERSION_DATA  *FmpVersions       OPTIONAL,
  IN  UINTN                         FmpVersionsCount,
  OUT UINT32                        *LastAttemptStatus OPTIONAL
  )
{
  EFI_STATUS     Status;
  UINT8          *Iterator;
  UINT8          Index;
  DEPEX_ELEMENT  Element1;
  DEPEX_ELEMENT  Element2;
  GUID           ImageTypeId;
  UINT32         Version;
  UINT32         LocalLastAttemptStatus;
  LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_SUCCESS;
  //
  // Check if parameter is valid.
  //
  if ((Dependencies == NULL) || (DependenciesSize == 0)) {
    return FALSE;
  }
  if ((FmpVersions == NULL) && (FmpVersionsCount > 0)) {
    return FALSE;
  }
  //
  // Clean out memory leaks in Depex Boolean stack. Leaks are only caused by
  // incorrectly formed DEPEX expressions
  //
  mDepexEvaluationStackPointer = mDepexEvaluationStack;
  Iterator = (UINT8 *)Dependencies->Dependencies;
  while (Iterator < (UINT8 *)Dependencies->Dependencies + DependenciesSize) {
    switch (*Iterator) {
      case EFI_FMP_DEP_PUSH_GUID:
        if (Iterator + sizeof (EFI_GUID) >= (UINT8 *)Dependencies->Dependencies + DependenciesSize) {
          DEBUG ((DEBUG_ERROR, "EvaluateDependency: GUID extends beyond end of dependency expression!\n"));
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_GUID_BEYOND_DEPEX;
          goto Error;
        }
        CopyGuid (&ImageTypeId, (EFI_GUID *)(Iterator + 1));
        Iterator = Iterator + sizeof (EFI_GUID);
        for (Index = 0; Index < FmpVersionsCount; Index++) {
          if (CompareGuid (&FmpVersions[Index].ImageTypeId, &ImageTypeId)) {
            Status = Push (FmpVersions[Index].Version, VersionType);
            if (EFI_ERROR (Status)) {
              LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
              goto Error;
            }
            break;
          }
        }
        if (Index == FmpVersionsCount) {
          DEBUG ((DEBUG_ERROR, "EvaluateDependency: %g is not found!\n", &ImageTypeId));
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_FMP_NOT_FOUND;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_PUSH_VERSION:
        if (Iterator + sizeof (UINT32) >= (UINT8 *)Dependencies->Dependencies + DependenciesSize ) {
          DEBUG ((DEBUG_ERROR, "EvaluateDependency: VERSION extends beyond end of dependency expression!\n"));
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_VERSION_BEYOND_DEPEX;
          goto Error;
        }
        Version = *(UINT32 *)(Iterator + 1);
        Status  = Push (Version, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        Iterator = Iterator + sizeof (UINT32);
        break;
      case EFI_FMP_DEP_VERSION_STR:
        Iterator += AsciiStrnLenS ((CHAR8 *)Iterator, DependenciesSize - (Iterator - Dependencies->Dependencies));
        if (Iterator == (UINT8 *)Dependencies->Dependencies + DependenciesSize) {
          DEBUG ((DEBUG_ERROR, "EvaluateDependency: STRING extends beyond end of dependency expression!\n"));
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_VERSION_STR_BEYOND_DEPEX;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_AND:
        Status = Pop (&Element1, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Push (Element1.Value.Boolean & Element2.Value.Boolean, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_OR:
        Status = Pop (&Element1, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Push (Element1.Value.Boolean | Element2.Value.Boolean, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_NOT:
        Status = Pop (&Element1, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Push (!(Element1.Value.Boolean), BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_TRUE:
        Status = Push (TRUE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_FALSE:
        Status = Push (FALSE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_EQ:
        Status = Pop (&Element1, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = (Element1.Value.Version == Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_GT:
        Status = Pop (&Element1, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = (Element1.Value.Version >  Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_GTE:
        Status = Pop (&Element1, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = (Element1.Value.Version >= Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_LT:
        Status = Pop (&Element1, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = (Element1.Value.Version <  Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_LTE:
        Status = Pop (&Element1, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = Pop (&Element2, VersionType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        Status = (Element1.Value.Version <= Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE;
          goto Error;
        }
        break;
      case EFI_FMP_DEP_END:
        Status = Pop (&Element1, BooleanType);
        if (EFI_ERROR (Status)) {
          LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE;
          goto Error;
        }
        return Element1.Value.Boolean;
      default:
        DEBUG ((DEBUG_ERROR, "EvaluateDependency: Unknown Opcode - %02x!\n", *Iterator));
        LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_UNKNOWN_OPCODE;
        goto Error;
    }
    Iterator++;
  }
  DEBUG ((DEBUG_ERROR, "EvaluateDependency: No EFI_FMP_DEP_END Opcode in expression!\n"));
  LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_NO_END_OPCODE;
Error:
  if (LastAttemptStatus != NULL) {
    *LastAttemptStatus = LocalLastAttemptStatus;
  }
  return FALSE;
}
/**
  Validate the dependency expression and output its size.
  @param[in]   Dependencies       Pointer to the EFI_FIRMWARE_IMAGE_DEP.
  @param[in]   MaxDepexSize       Max size of the dependency.
  @param[out]  DepexSize          Size of dependency.
  @param[out]  LastAttemptStatus  An optional pointer to a UINT32 that holds the
                                  last attempt status to report back to the caller.
                                  If a last attempt status error code is not returned,
                                  this function will not modify the LastAttemptStatus value.
  @retval TRUE    The dependency expression is valid.
  @retval FALSE   The dependency expression is invalid.
**/
BOOLEAN
EFIAPI
ValidateDependency (
  IN  EFI_FIRMWARE_IMAGE_DEP  *Dependencies,
  IN  UINTN                   MaxDepexSize,
  OUT UINT32                  *DepexSize,
  OUT UINT32                  *LastAttemptStatus OPTIONAL
  )
{
  UINT8  *Depex;
  if (DepexSize != NULL) {
    *DepexSize = 0;
  }
  if (Dependencies == NULL) {
    return FALSE;
  }
  Depex = Dependencies->Dependencies;
  while (Depex < Dependencies->Dependencies + MaxDepexSize) {
    switch (*Depex) {
      case EFI_FMP_DEP_PUSH_GUID:
        Depex += sizeof (EFI_GUID) + 1;
        break;
      case EFI_FMP_DEP_PUSH_VERSION:
        Depex += sizeof (UINT32) + 1;
        break;
      case EFI_FMP_DEP_VERSION_STR:
        Depex += AsciiStrnLenS ((CHAR8 *)Depex, Dependencies->Dependencies + MaxDepexSize - Depex) + 1;
        break;
      case EFI_FMP_DEP_AND:
      case EFI_FMP_DEP_OR:
      case EFI_FMP_DEP_NOT:
      case EFI_FMP_DEP_TRUE:
      case EFI_FMP_DEP_FALSE:
      case EFI_FMP_DEP_EQ:
      case EFI_FMP_DEP_GT:
      case EFI_FMP_DEP_GTE:
      case EFI_FMP_DEP_LT:
      case EFI_FMP_DEP_LTE:
        Depex += 1;
        break;
      case EFI_FMP_DEP_END:
        Depex += 1;
        if (DepexSize != NULL) {
          *DepexSize = (UINT32)(Depex - Dependencies->Dependencies);
        }
        return TRUE;
      default:
        return FALSE;
    }
  }
  if (LastAttemptStatus != NULL) {
    *LastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_NO_END_OPCODE;
  }
  return FALSE;
}
/**
  Get dependency from firmware image.
  @param[in]  Image               Points to the firmware image.
  @param[in]  ImageSize           Size, in bytes, of the firmware image.
  @param[out] DepexSize           Size, in bytes, of the dependency.
  @param[out] LastAttemptStatus   An optional pointer to a UINT32 that holds the
                                  last attempt status to report back to the caller.
                                  If a last attempt status error code is not returned,
                                  this function will not modify the LastAttemptStatus value.
  @retval  The pointer to dependency.
  @retval  Null
**/
EFI_FIRMWARE_IMAGE_DEP *
EFIAPI
GetImageDependency (
  IN  EFI_FIRMWARE_IMAGE_AUTHENTICATION  *Image,
  IN  UINTN                              ImageSize,
  OUT UINT32                             *DepexSize,
  OUT UINT32                             *LastAttemptStatus  OPTIONAL
  )
{
  EFI_FIRMWARE_IMAGE_DEP  *Depex;
  UINTN                   MaxDepexSize;
  if (Image == NULL) {
    return NULL;
  }
  //
  // Check to make sure that operation can be safely performed.
  //
  if ((((UINTN)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength) < (UINTN)Image) || \
      (((UINTN)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength) >= (UINTN)Image + ImageSize))
  {
    //
    // Pointer overflow. Invalid image.
    //
    if (LastAttemptStatus != NULL) {
      *LastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_GET_DEPEX_FAILURE;
    }
    return NULL;
  }
  Depex        = (EFI_FIRMWARE_IMAGE_DEP *)((UINT8 *)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength);
  MaxDepexSize = ImageSize - (sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength);
  //
  // Validate the dependency and get the size of dependency
  //
  if (ValidateDependency (Depex, MaxDepexSize, DepexSize, LastAttemptStatus)) {
    return Depex;
  }
  return NULL;
}