/** @file
  This library is used by other modules to measure data to TPM and Confidential
  Computing (CC) measure registers.

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

**/

#include <PiDxe.h>

#include <Protocol/TcgService.h>
#include <Protocol/Tcg2Protocol.h>

#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/TpmMeasurementLib.h>

#include <Guid/Acpi.h>
#include <IndustryStandard/Acpi.h>
#include <Protocol/CcMeasurement.h>

/**
  Tpm12 measure and log data, and extend the measurement result into a specific PCR.

  @param[in]  PcrIndex         PCR Index.
  @param[in]  EventType        Event type.
  @param[in]  EventLog         Measurement event log.
  @param[in]  LogLen           Event log length in bytes.
  @param[in]  HashData         The start of the data buffer to be hashed, extended.
  @param[in]  HashDataLen      The length, in bytes, of the buffer referenced by HashData

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
STATIC
EFI_STATUS
Tpm12MeasureAndLogData (
  IN UINT32  PcrIndex,
  IN UINT32  EventType,
  IN VOID    *EventLog,
  IN UINT32  LogLen,
  IN VOID    *HashData,
  IN UINT64  HashDataLen
  )
{
  EFI_STATUS            Status;
  EFI_TCG_PROTOCOL      *TcgProtocol;
  TCG_PCR_EVENT         *TcgEvent;
  EFI_PHYSICAL_ADDRESS  EventLogLastEntry;
  UINT32                EventNumber;

  TcgEvent = NULL;

  //
  // Tpm activation state is checked in HashLogExtendEvent
  //
  Status = gBS->LocateProtocol (&gEfiTcgProtocolGuid, NULL, (VOID **)&TcgProtocol);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  TcgEvent = (TCG_PCR_EVENT *)AllocateZeroPool (sizeof (TCG_PCR_EVENT_HDR) + LogLen);
  if (TcgEvent == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  TcgEvent->PCRIndex  = PcrIndex;
  TcgEvent->EventType = EventType;
  TcgEvent->EventSize = LogLen;
  CopyMem (&TcgEvent->Event[0], EventLog, LogLen);
  EventNumber = 1;
  Status      = TcgProtocol->HashLogExtendEvent (
                               TcgProtocol,
                               (EFI_PHYSICAL_ADDRESS)(UINTN)HashData,
                               HashDataLen,
                               TPM_ALG_SHA,
                               TcgEvent,
                               &EventNumber,
                               &EventLogLastEntry
                               );

  FreePool (TcgEvent);

  return Status;
}

/**
  Tpm20 measure and log data, and extend the measurement result into a specific PCR.

  @param[in]  PcrIndex         PCR Index.
  @param[in]  EventType        Event type.
  @param[in]  EventLog         Measurement event log.
  @param[in]  LogLen           Event log length in bytes.
  @param[in]  HashData         The start of the data buffer to be hashed, extended.
  @param[in]  HashDataLen      The length, in bytes, of the buffer referenced by HashData

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
STATIC
EFI_STATUS
Tpm20MeasureAndLogData (
  IN UINT32  PcrIndex,
  IN UINT32  EventType,
  IN VOID    *EventLog,
  IN UINT32  LogLen,
  IN VOID    *HashData,
  IN UINT64  HashDataLen
  )
{
  EFI_STATUS         Status;
  EFI_TCG2_PROTOCOL  *Tcg2Protocol;
  EFI_TCG2_EVENT     *Tcg2Event;

  //
  // TPMPresentFlag is checked in HashLogExtendEvent
  //
  Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **)&Tcg2Protocol);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Tcg2Event = (EFI_TCG2_EVENT *)AllocateZeroPool (LogLen + sizeof (EFI_TCG2_EVENT));
  if (Tcg2Event == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Tcg2Event->Size                 = (UINT32)LogLen + sizeof (EFI_TCG2_EVENT) - sizeof (Tcg2Event->Event);
  Tcg2Event->Header.HeaderSize    = sizeof (EFI_TCG2_EVENT_HEADER);
  Tcg2Event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION;
  Tcg2Event->Header.PCRIndex      = PcrIndex;
  Tcg2Event->Header.EventType     = EventType;
  CopyMem (&Tcg2Event->Event[0], EventLog, LogLen);

  Status = Tcg2Protocol->HashLogExtendEvent (
                           Tcg2Protocol,
                           0,
                           (EFI_PHYSICAL_ADDRESS)(UINTN)HashData,
                           HashDataLen,
                           Tcg2Event
                           );
  FreePool (Tcg2Event);

  return Status;
}

/**
  Cc measure and log data, and extend the measurement result into a
  specific CC MR.

  @param[in]  CcProtocol       Instance of CC measurement protocol
  @param[in]  PcrIndex         PCR Index.
  @param[in]  EventType        Event type.
  @param[in]  EventLog         Measurement event log.
  @param[in]  LogLen           Event log length in bytes.
  @param[in]  HashData         The start of the data buffer to be hashed, extended.
  @param[in]  HashDataLen      The length, in bytes, of the buffer referenced by HashData

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       CC guest not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
  @retval EFI_INVALID_PARAMETER The input parameter is invalid.
**/
STATIC
EFI_STATUS
CcMeasureAndLogData (
  IN EFI_CC_MEASUREMENT_PROTOCOL  *CcProtocol,
  IN UINT32                       PcrIndex,
  IN UINT32                       EventType,
  IN VOID                         *EventLog,
  IN UINT32                       LogLen,
  IN VOID                         *HashData,
  IN UINT64                       HashDataLen
  )
{
  EFI_STATUS       Status;
  EFI_CC_EVENT     *EfiCcEvent;
  EFI_CC_MR_INDEX  MrIndex;

  if (CcProtocol == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Status = CcProtocol->MapPcrToMrIndex (CcProtocol, PcrIndex, &MrIndex);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  EfiCcEvent = (EFI_CC_EVENT *)AllocateZeroPool (LogLen + sizeof (EFI_CC_EVENT));
  if (EfiCcEvent == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  EfiCcEvent->Size                 = (UINT32)LogLen + sizeof (EFI_CC_EVENT) - sizeof (EfiCcEvent->Event);
  EfiCcEvent->Header.HeaderSize    = sizeof (EFI_CC_EVENT_HEADER);
  EfiCcEvent->Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION;
  EfiCcEvent->Header.MrIndex       = MrIndex;
  EfiCcEvent->Header.EventType     = EventType;
  CopyMem (&EfiCcEvent->Event[0], EventLog, LogLen);

  Status = CcProtocol->HashLogExtendEvent (
                         CcProtocol,
                         0,
                         (EFI_PHYSICAL_ADDRESS)(UINTN)HashData,
                         HashDataLen,
                         EfiCcEvent
                         );
  FreePool (EfiCcEvent);

  return Status;
}

/**
  Tpm measure and log data, and extend the measurement result into a specific PCR.

  @param[in]  PcrIndex         PCR Index.
  @param[in]  EventType        Event type.
  @param[in]  EventLog         Measurement event log.
  @param[in]  LogLen           Event log length in bytes.
  @param[in]  HashData         The start of the data buffer to be hashed, extended.
  @param[in]  HashDataLen      The length, in bytes, of the buffer referenced by HashData

  @retval EFI_SUCCESS               Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
EFI_STATUS
EFIAPI
TpmMeasureAndLogData (
  IN UINT32  PcrIndex,
  IN UINT32  EventType,
  IN VOID    *EventLog,
  IN UINT32  LogLen,
  IN VOID    *HashData,
  IN UINT64  HashDataLen
  )
{
  EFI_STATUS                   Status;
  EFI_CC_MEASUREMENT_PROTOCOL  *CcProtocol;

  Status = gBS->LocateProtocol (&gEfiCcMeasurementProtocolGuid, NULL, (VOID **)&CcProtocol);
  if (!EFI_ERROR (Status)) {
    //
    // Try to measure using Cc measurement protocol
    //
    Status = CcMeasureAndLogData (
               CcProtocol,
               PcrIndex,
               EventType,
               EventLog,
               LogLen,
               HashData,
               HashDataLen
               );
  } else {
    //
    // Try to measure using Tpm20 protocol
    //
    Status = Tpm20MeasureAndLogData (
               PcrIndex,
               EventType,
               EventLog,
               LogLen,
               HashData,
               HashDataLen
               );

    if (EFI_ERROR (Status)) {
      //
      // Try to measure using Tpm1.2 protocol
      //
      Status = Tpm12MeasureAndLogData (
                 PcrIndex,
                 EventType,
                 EventLog,
                 LogLen,
                 HashData,
                 HashDataLen
                 );
    }
  }

  return Status;
}
