text: /** @file
    Provides FMP capsule dependency check services when updating the firmware
    image of a FMP device.
  
    Copyright (c) Microsoft Corporation.<BR>
    Copyright (c) 2020, Intel Corporation. All rights reserved.<BR>
  
    SPDX-License-Identifier: BSD-2-Clause-Patent
  
  **/
  #include <PiDxe.h>
  #include <Library/BaseLib.h>
  #include <Library/BaseMemoryLib.h>
  #include <Library/DebugLib.h>
  #include <Library/FmpDependencyLib.h>
  #include <Library/FmpDependencyCheckLib.h>
  #include <Library/MemoryAllocationLib.h>
  #include <Library/UefiLib.h>
  #include <Library/UefiBootServicesTableLib.h>
  #include <Guid/SystemResourceTable.h>
  #include <LastAttemptStatus.h>
  #include <FmpLastAttemptStatus.h>
  
  /**
    Check dependency for firmware update.
  
    @param[in]  ImageTypeId        Image Type Id.
    @param[in]  Version            New version.
    @param[in]  Dependencies       Fmp dependency.
    @param[in]  DependenciesSize   Size, in bytes, of the Fmp dependency.
    @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    Dependencies are satisfied.
    @retval  FALSE   Dependencies are unsatisfied or dependency check fails.
  
  **/
  BOOLEAN
  EFIAPI
  CheckFmpDependency (
    IN  EFI_GUID                ImageTypeId,
    IN  UINT32                  Version,
    IN  EFI_FIRMWARE_IMAGE_DEP  *Dependencies     OPTIONAL,
    IN  UINT32                  DependenciesSize,
    OUT UINT32                  *LastAttemptStatus OPTIONAL
    )
  {
    EFI_STATUS                        Status;
    EFI_HANDLE                        *HandleBuffer;
    UINTN                             Index;
    EFI_FIRMWARE_MANAGEMENT_PROTOCOL  *Fmp;
    UINTN                             ImageInfoSize;
    UINT32                            LocalLastAttemptStatus;
    UINT32                            *DescriptorVer;
    UINT8                             FmpImageInfoCount;
    UINTN                             *DescriptorSize;
    UINT32                            PackageVersion;
    CHAR16                            *PackageVersionName;
    UINTN                             NumberOfFmpInstance;
    EFI_FIRMWARE_IMAGE_DESCRIPTOR     **FmpImageInfoBuf;
    FMP_DEPEX_CHECK_VERSION_DATA      *FmpVersions;
    UINTN                             FmpVersionsCount;
    BOOLEAN                           IsSatisfied;
  
    LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_SUCCESS;
    FmpImageInfoBuf        = NULL;
    DescriptorVer          = NULL;
    DescriptorSize         = NULL;
    NumberOfFmpInstance    = 0;
    FmpVersions            = NULL;
    FmpVersionsCount       = 0;
    IsSatisfied            = TRUE;
    PackageVersionName     = NULL;
  
    //
    // Get ImageDescriptors of all FMP instances, and archive them for dependency evaluation.
    //
    Status = gBS->LocateHandleBuffer (
                    ByProtocol,
                    &gEfiFirmwareManagementProtocolGuid,
                    NULL,
                    &NumberOfFmpInstance,
                    &HandleBuffer
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "CheckFmpDependency: Get Firmware Management Protocol failed. (%r)", Status));
      IsSatisfied            = FALSE;
      LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_CHECK_LIB_ERROR_FMP_PROTOCOL_NOT_FOUND;
      goto cleanup;
    }
  
    FmpImageInfoBuf = AllocateZeroPool (sizeof (EFI_FIRMWARE_IMAGE_DESCRIPTOR *) * NumberOfFmpInstance);
    if (FmpImageInfoBuf == NULL) {
      IsSatisfied            = FALSE;
      LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_CHECK_LIB_ERROR_MEM_ALLOC_FMP_INFO_BUFFER_FAILED;
      goto cleanup;
    }
  
    DescriptorVer = AllocateZeroPool (sizeof (UINT32) * NumberOfFmpInstance);
    if (DescriptorVer == NULL ) {
      IsSatisfied            = FALSE;
      LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_CHECK_LIB_ERROR_MEM_ALLOC_DESC_VER_BUFFER_FAILED;
      goto cleanup;
    }
  
    DescriptorSize = AllocateZeroPool (sizeof (UINTN) * NumberOfFmpInstance);
    if (DescriptorSize == NULL ) {
      IsSatisfied            = FALSE;
      LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_CHECK_LIB_ERROR_MEM_ALLOC_DESC_SIZE_BUFFER_FAILED;
      goto cleanup;
    }
  
    FmpVersions = AllocateZeroPool (sizeof (FMP_DEPEX_CHECK_VERSION_DATA) * NumberOfFmpInstance);
    if (FmpVersions == NULL) {
      IsSatisfied            = FALSE;
      LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_CHECK_LIB_ERROR_MEM_ALLOC_FMP_VER_BUFFER_FAILED;
      goto cleanup;
    }
  
    for (Index = 0; Index < NumberOfFmpInstance; Index++) {
      Status = gBS->HandleProtocol (
                      HandleBuffer[Index],
                      &gEfiFirmwareManagementProtocolGuid,
                      (VOID **)&Fmp
                      );
      if (EFI_ERROR (Status)) {
        continue;
      }
  
      ImageInfoSize = 0;
      Status        = Fmp->GetImageInfo (
                             Fmp,
                             &ImageInfoSize,
                             NULL,
                             NULL,
                             NULL,
                             NULL,
                             NULL,
                             NULL
                             );
      if (Status != EFI_BUFFER_TOO_SMALL) {
        continue;
      }
  
      FmpImageInfoBuf[Index] = AllocateZeroPool (ImageInfoSize);
      if (FmpImageInfoBuf[Index] == NULL) {
        continue;
      }
  
      Status = Fmp->GetImageInfo (
                      Fmp,
                      &ImageInfoSize,               // ImageInfoSize
                      FmpImageInfoBuf[Index],       // ImageInfo
                      &DescriptorVer[Index],        // DescriptorVersion
                      &FmpImageInfoCount,           // DescriptorCount
                      &DescriptorSize[Index],       // DescriptorSize
                      &PackageVersion,              // PackageVersion
                      &PackageVersionName           // PackageVersionName
                      );
      if (EFI_ERROR (Status)) {
        FreePool (FmpImageInfoBuf[Index]);
        FmpImageInfoBuf[Index] = NULL;
        continue;
      }
  
      if (PackageVersionName != NULL) {
        FreePool (PackageVersionName);
        PackageVersionName = NULL;
      }
  
      CopyGuid (&FmpVersions[FmpVersionsCount].ImageTypeId, &FmpImageInfoBuf[Index]->ImageTypeId);
      FmpVersions[FmpVersionsCount].Version = FmpImageInfoBuf[Index]->Version;
      FmpVersionsCount++;
    }
  
    //
    // Evaluate firmware image's depex, against the version of other Fmp instances.
    //
    if (Dependencies != NULL) {
      IsSatisfied = EvaluateDependency (Dependencies, DependenciesSize, FmpVersions, FmpVersionsCount, &LocalLastAttemptStatus);
    }
  
    if (!IsSatisfied) {
      DEBUG ((DEBUG_ERROR, "CheckFmpDependency: %g\'s dependency is not satisfied!\n", ImageTypeId));
      goto cleanup;
    }
  
  cleanup:
    if (FmpImageInfoBuf != NULL) {
      for (Index = 0; Index < NumberOfFmpInstance; Index++) {
        if (FmpImageInfoBuf[Index] != NULL) {
          FreePool (FmpImageInfoBuf[Index]);
        }
      }
  
      FreePool (FmpImageInfoBuf);
    }
  
    if (DescriptorVer != NULL) {
      FreePool (DescriptorVer);
    }
  
    if (DescriptorSize != NULL) {
      FreePool (DescriptorSize);
    }
  
    if (FmpVersions != NULL) {
      FreePool (FmpVersions);
    }
  
    if (LastAttemptStatus != NULL) {
      *LastAttemptStatus = LocalLastAttemptStatus;
    }
  
    return IsSatisfied;
  }
  