// SPDX-License-Identifier: MIT OR Apache-2.0

//! [TCG] (Trusted Computing Group) protocol for [TPM] (Trusted Platform
//! Module) 2.0.
//!
//! This protocol is defined in the [TCG EFI Protocol Specification _TPM
//! Family 2.0_][spec]. It is generally implemented only for TPM 2.0
//! devices, but the spec indicates it can also be used for older TPM
//! devices.
//!
//! [spec]: https://trustedcomputinggroup.org/resource/tcg-efi-protocol-specification/
//! [TCG]: https://trustedcomputinggroup.org/
//! [TPM]: https://en.wikipedia.org/wiki/Trusted_Platform_Module

use super::{AlgorithmId, EventType, HashAlgorithm, PcrIndex, v1};
use crate::data_types::{Align, PhysicalAddress, UnalignedSlice};
use crate::proto::unsafe_protocol;
use crate::util::{ptr_write_unaligned_and_add, usize_from_u32};
use crate::{Error, Result, Status, StatusExt};
use core::fmt::{self, Debug, Formatter};
use core::marker::PhantomData;
use core::{ptr, slice};
use ptr_meta::Pointee;
use uefi_raw::protocol::tcg::v2::{Tcg2EventHeader as EventHeader, Tcg2Protocol};

#[cfg(feature = "alloc")]
use {crate::mem::make_boxed, alloc::boxed::Box};

pub use uefi_raw::protocol::tcg::v2::{
    Tcg2EventLogFormat as EventLogFormat, Tcg2HashAlgorithmBitmap,
    Tcg2HashLogExtendEventFlags as HashLogExtendEventFlags, Tcg2Version as Version,
};

/// Information about the protocol and the TPM device.
///
/// Layout compatible with the C type `EFI_TCG2_BOOT_SERVICE_CAPABILITY`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct BootServiceCapability {
    size: u8,

    /// Version of the EFI TCG2 protocol.
    pub structure_version: Version,

    /// Version of the EFI TCG2 protocol.
    pub protocol_version: Version,

    /// Bitmap of supported hash algorithms.
    pub hash_algorithm_bitmap: HashAlgorithm,

    /// Event log formats supported by the firmware.
    pub supported_event_logs: EventLogFormat,

    present_flag: u8,

    /// Maximum size (in bytes) of a command that can be sent to the TPM.
    pub max_command_size: u16,

    /// Maximum size (in bytes) of a response that can be provided by the TPM.
    pub max_response_size: u16,

    /// Manufacturer ID.
    ///
    /// See the [TCG Vendor ID registry].
    ///
    /// [TCG Vendor ID registry]: https://trustedcomputinggroup.org/resource/vendor-id-registry/
    pub manufacturer_id: u32,

    /// Maximum number of supported PCR banks (hashing algorithms).
    pub number_of_pcr_banks: u32,

    /// Bitmap of currently-active PCR banks (hashing algorithms). This
    /// is a subset of the supported algorithms in [`hash_algorithm_bitmap`].
    ///
    /// [`hash_algorithm_bitmap`]: Self::hash_algorithm_bitmap
    pub active_pcr_banks: HashAlgorithm,
}

impl Default for BootServiceCapability {
    fn default() -> Self {
        // OK to unwrap, the size is less than u8.
        let struct_size = u8::try_from(size_of::<Self>()).unwrap();

        Self {
            size: struct_size,
            structure_version: Version::default(),
            protocol_version: Version::default(),
            hash_algorithm_bitmap: HashAlgorithm::default(),
            supported_event_logs: EventLogFormat::default(),
            present_flag: 0,
            max_command_size: 0,
            max_response_size: 0,
            manufacturer_id: 0,
            number_of_pcr_banks: 0,
            active_pcr_banks: HashAlgorithm::default(),
        }
    }
}

impl BootServiceCapability {
    /// Whether the TPM device is present.
    #[must_use]
    pub const fn tpm_present(&self) -> bool {
        self.present_flag != 0
    }
}

/// Event type passed to [`Tcg::hash_log_extend_event`].
///
/// Layout compatible with the C type `EFI_TCG2_EVENT`.
///
/// The TPM v1 spec uses a single generic event type for both creating a
/// new event and reading an event from the log. The v2 spec splits this
/// into two structs: `EFI_TCG2_EVENT` for creating events, and
/// `TCG_PCR_EVENT2` for reading events. To help clarify the usage, our
/// API renames these types to `PcrEventInputs` and `PcrEvent`,
/// respectively.
#[derive(Eq, Pointee)]
#[repr(C, packed)]
pub struct PcrEventInputs {
    size: u32,
    event_header: EventHeader,
    event: [u8],
}

impl PcrEventInputs {
    /// Create a new `PcrEventInputs` using a byte buffer for storage.
    ///
    /// # Errors
    ///
    /// Returns [`Status::BUFFER_TOO_SMALL`] if the `buffer` is not large
    /// enough. The required size will be returned in the error data.
    ///
    /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too
    /// large.
    pub fn new_in_buffer<'buf>(
        buffer: &'buf mut [u8],
        pcr_index: PcrIndex,
        event_type: EventType,
        event_data: &[u8],
    ) -> Result<&'buf mut Self, Option<usize>> {
        let required_size = size_of::<u32>() + size_of::<EventHeader>() + event_data.len();

        if buffer.len() < required_size {
            return Err(Error::new(Status::BUFFER_TOO_SMALL, Some(required_size)));
        }
        let size_field = u32::try_from(required_size)
            .map_err(|_| Error::new(Status::INVALID_PARAMETER, None))?;

        let mut ptr: *mut u8 = buffer.as_mut_ptr().cast();

        unsafe {
            ptr_write_unaligned_and_add(&mut ptr, size_field);
            ptr_write_unaligned_and_add(
                &mut ptr,
                EventHeader {
                    header_size: u32::try_from(size_of::<EventHeader>()).unwrap(),
                    header_version: 1,
                    pcr_index: pcr_index.0,
                    event_type,
                },
            );
            ptr::copy(event_data.as_ptr(), ptr, event_data.len());

            let ptr: *mut Self =
                ptr_meta::from_raw_parts_mut(buffer.as_mut_ptr().cast(), event_data.len());
            Ok(&mut *ptr)
        }
    }

    /// Create a new `PcrEventInputs` in a [`Box`].
    ///
    /// # Errors
    ///
    /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too
    /// large.
    #[cfg(feature = "alloc")]
    pub fn new_in_box(
        pcr_index: PcrIndex,
        event_type: EventType,
        event_data: &[u8],
    ) -> Result<Box<Self>> {
        make_boxed(|buf| Self::new_in_buffer(buf, pcr_index, event_type, event_data))
    }
}

impl Align for PcrEventInputs {
    fn alignment() -> usize {
        1
    }
}

impl Debug for PcrEventInputs {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_struct("PcrEventInputs")
            .field("size", &{ self.size })
            .field("event_header", &self.event_header)
            .field("event", &"<binary data>")
            .finish()
    }
}

// Manual `PartialEq` implementation since it can't be derived for a packed DST.
impl PartialEq for PcrEventInputs {
    fn eq(&self, other: &Self) -> bool {
        self.size == other.size
            && self.event_header == other.event_header
            && self.event == other.event
    }
}

#[repr(C, packed)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct AlgorithmDigestSize {
    algorithm_id: AlgorithmId,
    digest_size: u16,
}

#[derive(Clone, Debug)]
struct AlgorithmDigestSizes<'a>(UnalignedSlice<'a, AlgorithmDigestSize>);

impl AlgorithmDigestSizes<'_> {
    fn get_size(&self, alg: AlgorithmId) -> Option<u16> {
        self.0.iter().find_map(|elem| {
            if { elem.algorithm_id } == alg {
                Some(elem.digest_size)
            } else {
                None
            }
        })
    }
}

fn u32_le_from_bytes_at_offset(bytes: &[u8], offset: usize) -> Option<u32> {
    let bytes = bytes.get(offset..offset + 4)?;
    // OK to unwrap: we know `bytes` is now of length 4.
    let val = u32::from_le_bytes(bytes.try_into().unwrap());
    Some(val)
}

/// Header stored at the beginning of the event log.
///
/// Layout compatible with the C type `TCG_EfiSpecIDEventStruct`.
#[derive(Clone, Debug)]
#[allow(unused)] // We don't current access most of the fields.
struct EventLogHeader<'a> {
    platform_class: u32,
    // major, minor, errata
    spec_version: (u8, u8, u8),
    uintn_size: u8,
    algorithm_digest_sizes: AlgorithmDigestSizes<'a>,
    vendor_info: &'a [u8],
    // Size of the whole header event, in bytes.
    size_in_bytes: usize,
}

impl<'a> EventLogHeader<'a> {
    fn new(event: &'a v1::PcrEvent) -> Option<Self> {
        if event.pcr_index() != PcrIndex(0) {
            return None;
        }
        if { event.event_type() } != EventType::NO_ACTION {
            return None;
        }
        if event.digest() != [0; 20] {
            return None;
        }

        let event = &event.event_data();
        if event.get(..16)? != *b"Spec ID Event03\0" {
            return None;
        }
        let platform_class = u32_le_from_bytes_at_offset(event, 16)?;
        let version_minor = *event.get(20)?;
        let version_major = *event.get(21)?;
        let version_errata = *event.get(22)?;
        let uintn_size = *event.get(23)?;
        let number_of_algorithms = usize_from_u32(u32_le_from_bytes_at_offset(event, 24)?);
        let vendor_info_size_byte_offset =
            28 + (number_of_algorithms * size_of::<AlgorithmDigestSize>());
        let vendor_info_size = usize::from(*event.get(vendor_info_size_byte_offset)?);

        // Safety: we know the slice is big enough because we just
        // safely got the field after the slice (`vendor_info_size`).
        let algorithm_digest_sizes = unsafe {
            let ptr: *const AlgorithmDigestSize = event.as_ptr().add(28).cast();
            AlgorithmDigestSizes(UnalignedSlice::new(ptr, number_of_algorithms))
        };

        let vendor_info_byte_offset = vendor_info_size_byte_offset + 1;
        let vendor_info =
            event.get(vendor_info_byte_offset..vendor_info_byte_offset + vendor_info_size)?;

        // 32 is the size of PcrEventV1 excluding the event data.
        let size_in_bytes = 32 + vendor_info_byte_offset + vendor_info_size;

        Some(Self {
            platform_class,
            spec_version: (version_major, version_minor, version_errata),
            uintn_size,
            algorithm_digest_sizes,
            vendor_info,
            size_in_bytes,
        })
    }
}

/// TPM event log as returned by [`Tcg::get_event_log_v2`].
///
/// This type of event log can contain multiple hash types (e.g. SHA-1, SHA-256,
/// SHA-512, etc).
#[derive(Debug)]
pub struct EventLog<'a> {
    // Tie the lifetime to the protocol, and by extension, boot services.
    _lifetime: PhantomData<&'a Tcg>,

    location: *const u8,
    last_entry: *const u8,

    is_truncated: bool,
}

impl EventLog<'_> {
    /// Iterator of events in the log.
    #[must_use]
    pub fn iter(&self) -> EventLogIter<'_> {
        if let Some(header) = self.header() {
            // Advance past the header
            let location = unsafe { self.location.add(header.size_in_bytes) };

            EventLogIter {
                log: self,
                location,
                header: self.header(),
            }
        } else {
            EventLogIter {
                log: self,
                location: ptr::null(),
                header: None,
            }
        }
    }

    /// Header at the beginning of the event log.
    fn header(&self) -> Option<EventLogHeader<'_>> {
        // The spec is unclear if the header is present when there are
        // no entries, so lets assume that `self.location` will be null
        // if there's no header, and otherwise valid.
        if self.location.is_null() {
            None
        } else {
            // Safety: we trust that the protocol has given us a valid range
            // of memory to read from.
            let event = unsafe { v1::PcrEvent::from_ptr(self.location) };
            EventLogHeader::new(event)
        }
    }

    /// Whether the event log is truncated due to not enough space in the log to
    /// contain some events.
    #[must_use]
    pub const fn is_truncated(&self) -> bool {
        self.is_truncated
    }
}

/// Digests in a PCR event.
#[derive(Clone)]
pub struct PcrEventDigests<'a> {
    data: &'a [u8],
    algorithm_digest_sizes: AlgorithmDigestSizes<'a>,
}

impl Debug for PcrEventDigests<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.debug_list().entries(self.clone()).finish()
    }
}

impl<'a> IntoIterator for PcrEventDigests<'a> {
    type Item = (AlgorithmId, &'a [u8]);
    type IntoIter = PcrEventDigestIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        PcrEventDigestIter {
            digests: self,
            offset: 0,
        }
    }
}

/// Iterator over a list of digests.
#[derive(Debug)]
pub struct PcrEventDigestIter<'a> {
    digests: PcrEventDigests<'a>,
    offset: usize,
}

impl<'a> Iterator for PcrEventDigestIter<'a> {
    type Item = (AlgorithmId, &'a [u8]);

    fn next(&mut self) -> Option<Self::Item> {
        let data = &self.digests.data[self.offset..];
        let alg = data.get(..2)?;
        let alg = AlgorithmId(u16::from_le_bytes([alg[0], alg[1]]));
        let digest_size = usize::from(self.digests.algorithm_digest_sizes.get_size(alg)?);
        let digest = data.get(2..2 + digest_size)?;
        self.offset += 2 + digest_size;
        Some((alg, digest))
    }
}

/// PCR event from an [`EventLog`].
///
/// This roughly corresponds to the C type `TCG_PCR_EVENT2`, but is not layout
/// compatible.
///
/// The TPM v1 spec uses a single generic event type for both creating a
/// new event and reading an event from the log. The v2 spec splits this
/// into two structs: `EFI_TCG2_EVENT` for creating events, and
/// `TCG_PCR_EVENT2` for reading events. To help clarify the usage, our
/// API renames these types to `PcrEventInputs` and `PcrEvent`,
/// respectively.
#[derive(Debug)]
pub struct PcrEvent<'a> {
    pcr_index: PcrIndex,
    event_type: EventType,
    digests: &'a [u8],
    event_data: &'a [u8],

    // Precalculate the pointer to the next event.
    next: *const u8,

    // This data from the v2 log header is needed to parse the digest data.
    algorithm_digest_sizes: AlgorithmDigestSizes<'a>,
}

impl<'a> PcrEvent<'a> {
    unsafe fn from_ptr(ptr: *const u8, header: EventLogHeader<'a>) -> Option<Self> {
        let ptr_u32: *const u32 = ptr.cast();
        let pcr_index = PcrIndex(unsafe { ptr_u32.read_unaligned() });
        let event_type = EventType(unsafe { ptr_u32.add(1).read_unaligned() });
        let digests_count = unsafe { ptr_u32.add(2).read_unaligned() };
        let digests_ptr: *const u8 = unsafe { ptr.add(12) };

        // Get the byte size of the digests so that the digests iterator
        // can be safe code.
        let mut digests_byte_size = 0;
        let mut elem_ptr = digests_ptr;
        for _ in 0..digests_count {
            let algorithm_id = AlgorithmId(unsafe { elem_ptr.cast::<u16>().read_unaligned() });
            let alg_and_digest_size = size_of::<AlgorithmId>()
                + usize::from(header.algorithm_digest_sizes.get_size(algorithm_id)?);
            digests_byte_size += alg_and_digest_size;
            elem_ptr = unsafe { elem_ptr.add(alg_and_digest_size) };
        }

        let digests = unsafe { slice::from_raw_parts(digests_ptr, digests_byte_size) };
        let event_size_ptr = unsafe { digests_ptr.add(digests_byte_size) };
        let event_size = usize_from_u32(unsafe { event_size_ptr.cast::<u32>().read_unaligned() });
        let event_data_ptr = unsafe { event_size_ptr.add(4) };
        let event_data = unsafe { slice::from_raw_parts(event_data_ptr, event_size) };

        Some(Self {
            pcr_index,
            event_type,
            digests,
            event_data,
            next: unsafe { event_data_ptr.add(event_size) },
            algorithm_digest_sizes: header.algorithm_digest_sizes,
        })
    }

    /// PCR index for the event.
    #[must_use]
    pub const fn pcr_index(&self) -> PcrIndex {
        self.pcr_index
    }

    /// Type of event, indicating what type of data is stored in [`event_data`].
    ///
    /// [`event_data`]: Self::event_data
    #[must_use]
    pub const fn event_type(&self) -> EventType {
        self.event_type
    }

    /// Raw event data. The meaning of this data can be determined from
    /// the [`event_type`].
    ///
    /// Note that this data is independent of what is hashed in [`digests`].
    ///
    /// [`digests`]: Self::digests
    /// [`event_type`]: Self::event_type
    #[must_use]
    pub const fn event_data(&self) -> &[u8] {
        self.event_data
    }

    /// Digests of the data hashed for this event.
    #[must_use]
    pub fn digests(&self) -> PcrEventDigests<'_> {
        PcrEventDigests {
            data: self.digests,
            algorithm_digest_sizes: self.algorithm_digest_sizes.clone(),
        }
    }
}

/// Iterator for events in [`EventLog`].
#[derive(Debug)]
pub struct EventLogIter<'a> {
    log: &'a EventLog<'a>,
    header: Option<EventLogHeader<'a>>,
    location: *const u8,
}

impl<'a> Iterator for EventLogIter<'a> {
    type Item = PcrEvent<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        // The spec says that `last_entry` will be null if there are no
        // events. Presumably `location` will be null as well, but check
        // both just to be safe.
        if self.location.is_null() || self.log.last_entry.is_null() {
            return None;
        }

        // Safety: we trust that the protocol has given us a valid range
        // of memory to read from.
        let event = unsafe { PcrEvent::from_ptr(self.location, self.header.clone()?)? };

        // If this is the last entry, set the location to null so that
        // future calls to `next()` return `None`.
        if self.location == self.log.last_entry {
            self.location = ptr::null();
        } else {
            self.location = event.next;
        }

        Some(event)
    }
}

/// Trusted Computing Group [`Protocol`] (TCG) for TPM 2.0 devices.
///
/// The corresponding C type is `EFI_TCG2_PROTOCOL`.
///
/// [`Protocol`]: uefi::proto::Protocol
#[derive(Debug)]
#[repr(transparent)]
#[unsafe_protocol(Tcg2Protocol::GUID)]
pub struct Tcg(Tcg2Protocol);

impl Tcg {
    /// Get information about the protocol and TPM device.
    pub fn get_capability(&mut self) -> Result<BootServiceCapability> {
        let mut capability = BootServiceCapability::default();
        unsafe {
            (self.0.get_capability)(&mut self.0, ptr::from_mut(&mut capability).cast())
                .to_result_with_val(|| capability)
        }
    }

    /// Get the V1 event log. This provides events in the same format as a V1
    /// TPM, so all events use SHA-1 hashes.
    pub fn get_event_log_v1(&mut self) -> Result<v1::EventLog<'_>> {
        let mut location = 0;
        let mut last_entry = 0;
        let mut truncated = 0;

        let status = unsafe {
            (self.0.get_event_log)(
                &mut self.0,
                EventLogFormat::TCG_1_2,
                &mut location,
                &mut last_entry,
                &mut truncated,
            )
        };

        if status.is_success() {
            let is_truncated = truncated != 0;

            let log = unsafe {
                v1::EventLog::new(location as *const u8, last_entry as *const u8, is_truncated)
            };

            Ok(log)
        } else {
            Err(status.into())
        }
    }

    /// Get the V2 event log. This format allows for a flexible list of hash types.
    pub fn get_event_log_v2(&mut self) -> Result<EventLog<'_>> {
        let mut location = 0;
        let mut last_entry = 0;
        let mut truncated = 0;

        let status = unsafe {
            (self.0.get_event_log)(
                &mut self.0,
                EventLogFormat::TCG_2,
                &mut location,
                &mut last_entry,
                &mut truncated,
            )
        };

        if status.is_success() {
            let is_truncated = truncated != 0;

            let log = EventLog {
                _lifetime: PhantomData,
                location: location as *const u8,
                last_entry: last_entry as *const u8,
                is_truncated,
            };

            Ok(log)
        } else {
            Err(status.into())
        }
    }

    /// Extend a PCR and add an entry to the event log.
    pub fn hash_log_extend_event(
        &mut self,
        flags: HashLogExtendEventFlags,
        data_to_hash: &[u8],
        event: &PcrEventInputs,
    ) -> Result {
        let event: *const PcrEventInputs = event;
        let (event, _event_size) = ptr_meta::to_raw_parts(event);
        unsafe {
            (self.0.hash_log_extend_event)(
                &mut self.0,
                flags,
                data_to_hash.as_ptr() as PhysicalAddress,
                // OK to unwrap, usize fits in u64.
                u64::try_from(data_to_hash.len()).unwrap(),
                event.cast(),
            )
            .to_result()
        }
    }

    /// Send a command directly to the TPM.
    ///
    /// Constructing the input block and parsing the output block are outside
    /// the scope of this crate. See the [TPM 2.0 Specification][spec], in
    /// particular Part 2 (Structures) and Part 3 (Commands).
    ///
    /// Note that TPM structures are big endian.
    ///
    /// [spec]: https://trustedcomputinggroup.org/resource/tpm-library-specification/
    pub fn submit_command(
        &mut self,
        input_parameter_block: &[u8],
        output_parameter_block: &mut [u8],
    ) -> Result {
        let input_parameter_block_len = u32::try_from(input_parameter_block.len())
            .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?;
        let output_parameter_block_len = u32::try_from(output_parameter_block.len())
            .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?;

        unsafe {
            (self.0.submit_command)(
                &mut self.0,
                input_parameter_block_len,
                input_parameter_block.as_ptr(),
                output_parameter_block_len,
                output_parameter_block.as_mut_ptr(),
            )
            .to_result()
        }
    }

    /// Get a bitmap of the active PCR banks. Each bank corresponds to a hash
    /// algorithm.
    pub fn get_active_pcr_banks(&mut self) -> Result<HashAlgorithm> {
        let mut active_pcr_banks = Tcg2HashAlgorithmBitmap::empty();

        let status = unsafe { (self.0.get_active_pcr_banks)(&mut self.0, &mut active_pcr_banks) };

        status.to_result_with_val(|| HashAlgorithm::from_bits_retain(active_pcr_banks.bits()))
    }

    /// Set the active PCR banks. Each bank corresponds to a hash
    /// algorithm. This change will not take effect until the system is
    /// rebooted twice.
    pub fn set_active_pcr_banks(&mut self, active_pcr_banks: HashAlgorithm) -> Result {
        let active_pcr_banks = Tcg2HashAlgorithmBitmap::from_bits_retain(active_pcr_banks.bits());
        unsafe { (self.0.set_active_pcr_banks)(&mut self.0, active_pcr_banks) }.to_result()
    }

    /// Get the stored result of calling [`Tcg::set_active_pcr_banks`] in a
    /// previous boot.
    ///
    /// If there was no attempt to set the active PCR banks in a previous boot,
    /// this returns `None`. Otherwise, it returns a numeric response code:
    /// * `0x00000000`: Success
    /// * `0x00000001..=0x00000FFF`: TPM error code
    /// * `0xfffffff0`: The operation was canceled by the user or timed out
    /// * `0xfffffff1`: Firmware error
    pub fn get_result_of_set_active_pcr_banks(&mut self) -> Result<Option<u32>> {
        let mut operation_present = 0;
        let mut response = 0;

        let status = unsafe {
            (self.0.get_result_of_set_active_pcr_banks)(
                &mut self.0,
                &mut operation_present,
                &mut response,
            )
        };

        status.to_result_with_val(|| {
            if operation_present == 0 {
                None
            } else {
                Some(response)
            }
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::vec::Vec;
    use core::slice;

    #[test]
    fn test_new_event() {
        let mut buf = [0; 22];
        let event_data = [0x12, 0x13, 0x14, 0x15];
        let event =
            PcrEventInputs::new_in_buffer(&mut buf, PcrIndex(4), EventType::IPL, &event_data)
                .unwrap();

        assert_eq!({ event.size }, 22);
        assert_eq!(
            event.event_header,
            EventHeader {
                header_size: 14,
                header_version: 1,
                pcr_index: 4,
                event_type: EventType::IPL,
            }
        );

        // Cast to a byte slice to check the data is exactly as expected.
        let event_ptr: *const PcrEventInputs = event;
        let event_ptr: *const u8 = event_ptr.cast();
        let event_bytes = unsafe { slice::from_raw_parts(event_ptr, size_of_val(event)) };

        #[rustfmt::skip]
        assert_eq!(event_bytes, [
            // Size
            0x16, 0x00, 0x00, 0x00,

            // Header
            // Header size
            0x0e, 0x00, 0x00, 0x00,
            // Header version
            0x01, 0x00,
            // PCR index
            0x04, 0x00, 0x00, 0x00,
            // Event type
            0x0d, 0x00, 0x00, 0x00,
            // Event data
            0x12, 0x13, 0x14, 0x15,
        ]);

        // Check that `new_in_box` gives the same value.
        assert_eq!(
            event,
            &*PcrEventInputs::new_in_box(PcrIndex(4), EventType::IPL, &event_data).unwrap()
        );
    }

    #[test]
    fn test_event_log_v2() {
        // This data comes from dumping the TPM event log in a VM
        // (truncated to just two entries after the header).
        #[rustfmt::skip]
        let bytes = [
            // Header event
            // PCR index
            0x00, 0x00, 0x00, 0x00,
            // Event type
            0x03, 0x00, 0x00, 0x00,
            // SHA1 digest
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            // Event data size
            0x2d, 0x00, 0x00, 0x00,
            // Spec ID event data
            // Signature
            0x53, 0x70, 0x65, 0x63,
            0x20, 0x49, 0x44, 0x20,
            0x45, 0x76, 0x65, 0x6e,
            0x74, 0x30, 0x33, 0x00,
            // Platform class
            0x00, 0x00, 0x00, 0x00,
            // Spec version (minor, major, errata) (yes the order is weird)
            0x00, 0x02, 0x00,
            // Uintn size
            0x02,
            // Number of algorithms
            0x04, 0x00, 0x00, 0x00,
            // Digest sizes
            // SHA1, size
            0x04, 0x00,
            0x14, 0x00,
            // SHA256, size
            0x0b, 0x00,
            0x20, 0x00,
            // SHA384, size
            0x0c, 0x00,
            0x30, 0x00,
            // SHA512, size
            0x0d, 0x00,
            0x40, 0x00,
            // Vendor info size
            0x00,

            // Event 1
            // PCR index
            0x00, 0x00, 0x00, 0x00,
            // Event type
            0x08, 0x00, 0x00, 0x00,
            // Digest count
            0x04, 0x00, 0x00, 0x00,
            // Digests
            // SHA1
            0x04, 0x00,
            0x14, 0x89, 0xf9, 0x23, 0xc4, 0xdc, 0xa7, 0x29, 0x17, 0x8b,
            0x3e, 0x32, 0x33, 0x45, 0x85, 0x50, 0xd8, 0xdd, 0xdf, 0x29,
            // SHA256
            0x0b, 0x00,
            0x96, 0xa2, 0x96, 0xd2, 0x24, 0xf2, 0x85, 0xc6,
            0x7b, 0xee, 0x93, 0xc3, 0x0f, 0x8a, 0x30, 0x91,
            0x57, 0xf0, 0xda, 0xa3, 0x5d, 0xc5, 0xb8, 0x7e,
            0x41, 0x0b, 0x78, 0x63, 0x0a, 0x09, 0xcf, 0xc7,
            // SHA384
            0x0c, 0x00,
            0x1d, 0xd6, 0xf7, 0xb4, 0x57, 0xad, 0x88, 0x0d,
            0x84, 0x0d, 0x41, 0xc9, 0x61, 0x28, 0x3b, 0xab,
            0x68, 0x8e, 0x94, 0xe4, 0xb5, 0x93, 0x59, 0xea,
            0x45, 0x68, 0x65, 0x81, 0xe9, 0x0f, 0xec, 0xce,
            0xa3, 0xc6, 0x24, 0xb1, 0x22, 0x61, 0x13, 0xf8,
            0x24, 0xf3, 0x15, 0xeb, 0x60, 0xae, 0x0a, 0x7c,
            // SHA512
            0x0d, 0x00,
            0x5e, 0xa7, 0x1d, 0xc6, 0xd0, 0xb4, 0xf5, 0x7b,
            0xf3, 0x9a, 0xad, 0xd0, 0x7c, 0x20, 0x8c, 0x35,
            0xf0, 0x6c, 0xd2, 0xba, 0xc5, 0xfd, 0xe2, 0x10,
            0x39, 0x7f, 0x70, 0xde, 0x11, 0xd4, 0x39, 0xc6,
            0x2e, 0xc1, 0xcd, 0xf3, 0x18, 0x37, 0x58, 0x86,
            0x5f, 0xd3, 0x87, 0xfc, 0xea, 0x0b, 0xad, 0xa2,
            0xf6, 0xc3, 0x7a, 0x4a, 0x17, 0x85, 0x1d, 0xd1,
            0xd7, 0x8f, 0xef, 0xe6, 0xf2, 0x04, 0xee, 0x54,
            // Event size
            0x02, 0x00, 0x00, 0x00,
            // Event data
            0x00, 0x00,

            // Event 2
            // PCR index
            0x00, 0x00, 0x00, 0x00,
            // Event type
            0x08, 0x00, 0x00, 0x80,
            // Digest count
            0x04, 0x00, 0x00, 0x00,
            // SHA1
            0x04, 0x00,
            0xc7, 0x06, 0xe7, 0xdd, 0x36, 0x39, 0x29, 0x84, 0xeb, 0x06,
            0xaa, 0xa0, 0x8f, 0xf3, 0x36, 0x84, 0x40, 0x77, 0xb3, 0xed,
            // SHA256
            0x0b, 0x00,
            0x3a, 0x30, 0x8e, 0x95, 0x87, 0x84, 0xbf, 0xd0,
            0xf6, 0xe3, 0xf1, 0xbd, 0x4d, 0x42, 0x14, 0xd3,
            0x0a, 0x4c, 0x55, 0x00, 0xa4, 0x5b, 0x06, 0xda,
            0x96, 0xfc, 0x90, 0x33, 0x8f, 0x69, 0xb3, 0x61,
            // SHA384
            0x0c, 0x00,
            0xc0, 0xd0, 0x75, 0x96, 0xc5, 0x9a, 0x90, 0x7b,
            0x79, 0x71, 0x6f, 0xc9, 0xf3, 0x6a, 0xad, 0x8f,
            0x0f, 0x26, 0xf2, 0x02, 0x67, 0x5b, 0x42, 0x5a,
            0x52, 0x3f, 0x72, 0xec, 0xb6, 0xf2, 0x53, 0x99,
            0x57, 0xf0, 0xd9, 0x2c, 0x0a, 0x7d, 0xce, 0xaa,
            0xf9, 0x9e, 0x60, 0x0e, 0x54, 0x18, 0xf1, 0xdc,
            // SHA512
            0x0d, 0x00,
            0x9a, 0xe9, 0x25, 0xdc, 0x9c, 0xd2, 0x9d, 0xf0,
            0xe5, 0x80, 0x54, 0x35, 0xa5, 0x99, 0x06, 0x1f,
            0xcf, 0x32, 0x98, 0xcc, 0x2a, 0x15, 0xe4, 0x87,
            0x99, 0xa2, 0x0c, 0x9c, 0xe5, 0x6c, 0x8f, 0xe5,
            0x84, 0x09, 0x75, 0xaf, 0xf0, 0xe1, 0xb6, 0x98,
            0x20, 0x07, 0x5e, 0xe4, 0x29, 0x79, 0x8b, 0x5d,
            0xbb, 0xe5, 0xd1, 0xa2, 0x74, 0x36, 0xab, 0x49,
            0xf1, 0x9b, 0x7a, 0x04, 0x11, 0xd2, 0x96, 0x2c,
            // Event size
            0x10, 0x00, 0x00, 0x00,
            // Event data
            0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00,
        ];

        let log = EventLog {
            _lifetime: PhantomData,
            location: bytes.as_ptr(),
            last_entry: unsafe { bytes.as_ptr().add(267) },
            is_truncated: false,
        };

        let header = log.header().unwrap();
        assert_eq!(header.platform_class, 0);
        assert_eq!(header.spec_version, (2, 0, 0));
        assert_eq!(header.uintn_size, 2);
        assert_eq!(
            header.algorithm_digest_sizes.0.to_vec(),
            [
                AlgorithmDigestSize {
                    algorithm_id: AlgorithmId::SHA1,
                    digest_size: 20,
                },
                AlgorithmDigestSize {
                    algorithm_id: AlgorithmId::SHA256,
                    digest_size: 32,
                },
                AlgorithmDigestSize {
                    algorithm_id: AlgorithmId::SHA384,
                    digest_size: 48,
                },
                AlgorithmDigestSize {
                    algorithm_id: AlgorithmId::SHA512,
                    digest_size: 64,
                },
            ]
        );
        assert_eq!(header.vendor_info, []);

        let mut iter = log.iter();

        // Entry 1
        let entry = iter.next().unwrap();
        assert_eq!(entry.pcr_index, PcrIndex(0));
        assert_eq!(entry.event_type, EventType::CRTM_VERSION);
        #[rustfmt::skip]
        assert_eq!(
            entry.digests().into_iter().collect::<Vec<_>>(),
            [
                (AlgorithmId::SHA1, [
                    0x14, 0x89, 0xf9, 0x23, 0xc4, 0xdc, 0xa7, 0x29, 0x17, 0x8b,
                    0x3e, 0x32, 0x33, 0x45, 0x85, 0x50, 0xd8, 0xdd, 0xdf, 0x29,
                ].as_slice()),
                (AlgorithmId::SHA256, [
                    0x96, 0xa2, 0x96, 0xd2, 0x24, 0xf2, 0x85, 0xc6,
                    0x7b, 0xee, 0x93, 0xc3, 0x0f, 0x8a, 0x30, 0x91,
                    0x57, 0xf0, 0xda, 0xa3, 0x5d, 0xc5, 0xb8, 0x7e,
                    0x41, 0x0b, 0x78, 0x63, 0x0a, 0x09, 0xcf, 0xc7,
                ].as_slice()),
                (AlgorithmId::SHA384, [
                    0x1d, 0xd6, 0xf7, 0xb4, 0x57, 0xad, 0x88, 0x0d,
                    0x84, 0x0d, 0x41, 0xc9, 0x61, 0x28, 0x3b, 0xab,
                    0x68, 0x8e, 0x94, 0xe4, 0xb5, 0x93, 0x59, 0xea,
                    0x45, 0x68, 0x65, 0x81, 0xe9, 0x0f, 0xec, 0xce,
                    0xa3, 0xc6, 0x24, 0xb1, 0x22, 0x61, 0x13, 0xf8,
                    0x24, 0xf3, 0x15, 0xeb, 0x60, 0xae, 0x0a, 0x7c,
                ].as_slice()),
                (AlgorithmId::SHA512, [
                    0x5e, 0xa7, 0x1d, 0xc6, 0xd0, 0xb4, 0xf5, 0x7b,
                    0xf3, 0x9a, 0xad, 0xd0, 0x7c, 0x20, 0x8c, 0x35,
                    0xf0, 0x6c, 0xd2, 0xba, 0xc5, 0xfd, 0xe2, 0x10,
                    0x39, 0x7f, 0x70, 0xde, 0x11, 0xd4, 0x39, 0xc6,
                    0x2e, 0xc1, 0xcd, 0xf3, 0x18, 0x37, 0x58, 0x86,
                    0x5f, 0xd3, 0x87, 0xfc, 0xea, 0x0b, 0xad, 0xa2,
                    0xf6, 0xc3, 0x7a, 0x4a, 0x17, 0x85, 0x1d, 0xd1,
                    0xd7, 0x8f, 0xef, 0xe6, 0xf2, 0x04, 0xee, 0x54,
                ].as_slice()),
            ]
        );
        assert_eq!(entry.event_data, [0, 0]);

        // Entry 2
        let entry = iter.next().unwrap();
        assert_eq!(entry.pcr_index, PcrIndex(0));
        assert_eq!(entry.event_type, EventType::EFI_PLATFORM_FIRMWARE_BLOB);
        #[rustfmt::skip]
        assert_eq!(
            entry.digests().into_iter().collect::<Vec<_>>(),
            [
                (AlgorithmId::SHA1, [
                    0xc7, 0x06, 0xe7, 0xdd, 0x36, 0x39, 0x29, 0x84, 0xeb, 0x06,
                    0xaa, 0xa0, 0x8f, 0xf3, 0x36, 0x84, 0x40, 0x77, 0xb3, 0xed,
                ].as_slice()),
                (AlgorithmId::SHA256, [
                    0x3a, 0x30, 0x8e, 0x95, 0x87, 0x84, 0xbf, 0xd0,
                    0xf6, 0xe3, 0xf1, 0xbd, 0x4d, 0x42, 0x14, 0xd3,
                    0x0a, 0x4c, 0x55, 0x00, 0xa4, 0x5b, 0x06, 0xda,
                    0x96, 0xfc, 0x90, 0x33, 0x8f, 0x69, 0xb3, 0x61,
                ].as_slice()),
                (AlgorithmId::SHA384, [
                    0xc0, 0xd0, 0x75, 0x96, 0xc5, 0x9a, 0x90, 0x7b,
                    0x79, 0x71, 0x6f, 0xc9, 0xf3, 0x6a, 0xad, 0x8f,
                    0x0f, 0x26, 0xf2, 0x02, 0x67, 0x5b, 0x42, 0x5a,
                    0x52, 0x3f, 0x72, 0xec, 0xb6, 0xf2, 0x53, 0x99,
                    0x57, 0xf0, 0xd9, 0x2c, 0x0a, 0x7d, 0xce, 0xaa,
                    0xf9, 0x9e, 0x60, 0x0e, 0x54, 0x18, 0xf1, 0xdc,
                ].as_slice()),
                (AlgorithmId::SHA512, [
                    0x9a, 0xe9, 0x25, 0xdc, 0x9c, 0xd2, 0x9d, 0xf0,
                    0xe5, 0x80, 0x54, 0x35, 0xa5, 0x99, 0x06, 0x1f,
                    0xcf, 0x32, 0x98, 0xcc, 0x2a, 0x15, 0xe4, 0x87,
                    0x99, 0xa2, 0x0c, 0x9c, 0xe5, 0x6c, 0x8f, 0xe5,
                    0x84, 0x09, 0x75, 0xaf, 0xf0, 0xe1, 0xb6, 0x98,
                    0x20, 0x07, 0x5e, 0xe4, 0x29, 0x79, 0x8b, 0x5d,
                    0xbb, 0xe5, 0xd1, 0xa2, 0x74, 0x36, 0xab, 0x49,
                    0xf1, 0x9b, 0x7a, 0x04, 0x11, 0xd2, 0x96, 0x2c,
                ].as_slice()),
            ]
        );
        #[rustfmt::skip]
        assert_eq!(entry.event_data, [
            0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00,
        ]);

        assert!(iter.next().is_none());
    }
}
