use std::{
    collections::HashMap,
    io::{BufReader, Read, Seek},
};

use pkcs8::DecodePublicKey;
use rsa::signature::Verifier as RsaVerifier;
use sev::certs::snp::ecdsa::Signature;
use sev::firmware::guest::{AttestationReport, GuestPolicy, KeyInfo, PlatformInfo, Version};
use sev::firmware::host::TcbVersion;
use sha2::Sha384;
use x509_parser::{
    certificate::X509Certificate, error::PEMError, num_bigint::BigUint, pem::Pem,
    prelude::parse_x509_pem,
};

use crate::ReportSignature;

mod vcek {
    //! This module contains the code for verifying a Versioned Chip Endorsement Key's certificate
    //! chain (VCEK in short).
    //!
    //! Our verification code can make assumptions that a general web PKI can't do:
    //!
    //! - According to <https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf> 2.1, we always have a
    //!   certificate chain of length 3: AMD Root Key (ARK), AMD SEV Signing Key (ASK), VCEK
    //! - AMD gives us the first two certs from the chain, so we always only have to deal with
    //!   one unknown cert
    //! - We know some things about the VCEK (e.g. uses P-384 with SHA-384)

    use der::{asn1::Ia5String, Decode};
    use oid_registry::{asn1_rs::oid, Oid, OID_KEY_TYPE_EC_PUBLIC_KEY, OID_PKCS1_RSASSAPSS};
    use pkcs8::DecodePublicKey;
    use sev::firmware::host::TcbVersion;
    use x509_parser::{
        num_bigint::BigUint,
        prelude::{PEMError, Pem},
    };

    use super::{Error, RootStore};

    const OID_PRODUCT_NAME: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .2);
    const OID_BOOT_LOADER: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .1);
    const OID_TEE: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .2);
    const OID_SNP: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .3);
    const OID_UCODE: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .8);
    const OID_FMC: Oid<'static> = oid!(1.3.6 .1 .4 .1 .3704 .1 .3 .9);

    #[derive(Debug, Clone, Copy)]
    pub enum Product {
        Milan,
        Genoa,
        Turin,
    }

    struct VcekExpected {}
    struct LooksLikeVcek {
        possible_verifying_key: p384::ecdsa::VerifyingKey,
        product_in_cert: Product,
        cert_data: Vec<u8>,
        signature: Vec<u8>,
        tcb_version: TcbVersion,
    }

    struct SignedByKnownAmdRoot {
        product: Product,
        verifying_key: p384::ecdsa::VerifyingKey,
    }

    // Handy alias so we don't have to write out the iterator constraint all the time
    trait Chain: Iterator<Item = Result<Pem, PEMError>> {}
    impl<I: Iterator<Item = Result<Pem, PEMError>>> Chain for I {}

    struct VcekChainVerifier<I: Chain, S> {
        chain: I,
        state: S,
    }

    impl<I: Chain, S> VcekChainVerifier<I, S> {
        fn next_pem(&mut self) -> Result<Pem, Error> {
            self.chain
                .next()
                .map(|result| result.map_err(Error::DecodeError))
                .ok_or(Error::ChainBroken)?
        }
    }

    impl<I: Chain> VcekChainVerifier<I, VcekExpected> {
        fn new(chain: I) -> Self {
            Self {
                chain,
                state: VcekExpected {},
            }
        }
    }

    impl<I: Chain> VcekChainVerifier<I, LooksLikeVcek> {
        fn from(mut current: VcekChainVerifier<I, VcekExpected>) -> Result<Self, Error> {
            let pem = current.next_pem()?;
            if pem.label != "CERTIFICATE" {
                return Err(Error::NotACertificate);
            }
            let cert = pem.parse_x509().map_err(|_| Error::ParseError)?;

            // See also https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf chapter 3 ("VCEK Certificate Format")

            // VCEK should not be a CA certificate
            if cert.is_ca() {
                return Err(Error::WrongBasicConstraints);
            }

            // Should have serial number 0
            if cert.serial != BigUint::from(0usize) {
                return Err(Error::ChainBroken);
            }

            if cert.signature_algorithm.algorithm != OID_PKCS1_RSASSAPSS {
                return Err(Error::ChainBroken);
            }

            let Some(product_name) = cert
                .get_extension_unique(&OID_PRODUCT_NAME)
                .ok()
                .flatten()
                .and_then(|ext| Ia5String::from_der(ext.value).ok())
            else {
                return Err(Error::ChainBroken);
            };

            // `product_name` includes silicon stepping, hence the prefix match (N.B. but only
            // seems to be the case for Milan)
            let product = match product_name.as_str() {
                s if s.starts_with("Genoa") => Product::Genoa,
                s if s.starts_with("Milan") => Product::Milan,
                s if s.starts_with("Turin") => Product::Turin,
                _ => return Err(Error::UnknownProductName(product_name.to_string())),
            };

            let get_u8 = |oid: Oid| {
                cert.get_extension_unique(&oid)
                    .ok()
                    .flatten()
                    .and_then(|ext| u8::from_der(ext.value).ok())
                    .ok_or(Error::ChainBroken)
            };

            let bootloader = get_u8(OID_BOOT_LOADER)?;
            let tee = get_u8(OID_TEE)?;
            let snp = get_u8(OID_SNP)?;
            let microcode = get_u8(OID_UCODE)?;

            let tcb_version = match product {
                Product::Milan | Product::Genoa => TcbVersion {
                    fmc: None,
                    bootloader,
                    tee,
                    snp,
                    microcode,
                },
                Product::Turin => {
                    let fmc = get_u8(OID_FMC)?;
                    TcbVersion {
                        fmc: Some(fmc),
                        bootloader,
                        tee,
                        snp,
                        microcode,
                    }
                }
            };

            if cert
                .subject
                .iter_common_name()
                .next()
                .map(|cn| cn.as_str().map_err(|_| Error::ChainBroken))
                .ok_or(Error::ChainBroken)??
                != "SEV-VCEK"
            {
                return Err(Error::ChainBroken);
            }

            // An EC key is expected
            if cert.public_key().algorithm.algorithm != OID_KEY_TYPE_EC_PUBLIC_KEY {
                return Err(Error::ChainBroken);
            }
            let possible_verifying_key =
                p384::ecdsa::VerifyingKey::from_public_key_der(cert.public_key().raw)
                    .map_err(|_| Error::ChainBroken)?;

            Ok(Self {
                chain: current.chain,
                state: LooksLikeVcek {
                    possible_verifying_key,
                    product_in_cert: product,
                    cert_data: cert.tbs_certificate.as_ref().to_vec(),
                    signature: cert.signature_value.data.to_vec(),
                    tcb_version,
                },
            })
        }
    }

    impl<I: Chain> VcekChainVerifier<I, SignedByKnownAmdRoot> {
        fn from(
            mut current: VcekChainVerifier<I, LooksLikeVcek>,
            root_store: &RootStore,
        ) -> Result<Self, Error> {
            let pem = current.next_pem()?;
            if pem.label != "CERTIFICATE" {
                return Err(Error::NotACertificate);
            }
            let cert = pem.parse_x509().map_err(|_| Error::ParseError)?;

            if root_store
                .find_anchor_by_serial(&cert.serial)
                .is_some_and(|anchor| {
                    anchor.has_signed(&current.state.cert_data, &current.state.signature)
                })
            {
                return Ok(Self {
                    chain: current.chain,
                    state: SignedByKnownAmdRoot {
                        product: current.state.product_in_cert,
                        verifying_key: current.state.possible_verifying_key,
                    },
                });
            }

            Err(Error::ChainBroken)
        }
    }

    #[allow(clippy::missing_errors_doc)]
    pub fn verify_vcek_chain<I: Iterator<Item = Result<Pem, PEMError>>>(
        root_store: &RootStore,
        chain: I,
    ) -> Result<(Product, p384::ecdsa::VerifyingKey, TcbVersion), Error> {
        let vcek_expected = VcekChainVerifier::new(chain);
        let looks_like_vcek = VcekChainVerifier::<I, LooksLikeVcek>::from(vcek_expected)?;
        let tcb_version = looks_like_vcek.state.tcb_version;

        let signed_by_known_amd_root =
            VcekChainVerifier::<I, SignedByKnownAmdRoot>::from(looks_like_vcek, root_store)?;

        Ok((
            signed_by_known_amd_root.state.product,
            signed_by_known_amd_root.state.verifying_key,
            tcb_version,
        ))
    }
}

// Siena and Bergamo use the same root chain as Genoa
static GENOA_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Genoa.pem");
static MILAN_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Milan.pem");
static TURIN_ROOT_CHAIN: &[u8] = include_bytes!("../fixtures/Turin.pem");

#[allow(clippy::enum_variant_names)]
#[derive(Debug)]
pub enum Error {
    // VCEK validation errors
    DecodeError(PEMError),
    ParseError,
    /// Something else than a certificate was seen
    NotACertificate,
    WrongKeyUsage,
    WrongBasicConstraints,
    UnsupportedSignatureAlgorithm,
    ChainBroken,
    UnknownProductName(String),

    // Other report validation errors
    ReportParseError,
    ReportSignatureParseError,
    ReportSignatureMismatch,
    ReportTcbVersionMismatch,
    /// The report didn't satisfy the requested verifications
    RequirementsNotSatisfied(&'static str),
}

/// A trusted certificate. Every certificate in a chain above this one is considered trusted.
#[derive(Debug)]
struct TrustAnchor {
    verifying_key: rsa::pss::VerifyingKey<Sha384>,
}

impl TrustAnchor {
    pub fn has_signed(&self, cert: &[u8], signature: &[u8]) -> bool {
        let Ok(sig) = signature.try_into() else {
            return false;
        };
        self.verifying_key.verify(cert, &sig).is_ok()
    }
}

impl<'a> TryFrom<&X509Certificate<'a>> for TrustAnchor {
    type Error = String;

    fn try_from(value: &X509Certificate<'a>) -> Result<Self, Self::Error> {
        let public_key = rsa::RsaPublicKey::from_public_key_der(value.public_key().raw)
            .map_err(|_| "Could not create RSA public key from certificat public key")?;
        Ok(Self {
            verifying_key: public_key.into(),
        })
    }
}

pub struct RootStore {
    anchors: HashMap<BigUint, TrustAnchor>,
}

impl RootStore {
    /// Creates a new, empty [`RootStore`].
    #[must_use]
    pub fn new() -> Self {
        Self {
            anchors: HashMap::new(),
        }
    }

    #[allow(clippy::missing_errors_doc)]
    pub fn add_chain(&mut self, contents: &[u8]) -> Result<(), String> {
        // N.B. only the leaf of the chain is stored as trust anchor
        let (_, pem) = parse_x509_pem(contents).map_err(|_| "Could not decode entry")?;
        let cert = pem
            .parse_x509()
            .map_err(|_| "Could not parse certificate")?;

        if !cert.is_ca() {
            return Err("Certificates in root store should be CA certificates".to_string());
        }

        // Leaf should have a pathlen of 0
        if cert
            .basic_constraints()
            .unwrap_or(None)
            .and_then(|ext| ext.value.path_len_constraint)
            != Some(0)
        {
            return Err("Leaf in root chain should have pathlen of 0".to_string());
        }

        let anchor = TrustAnchor::try_from(&cert)?;
        if self.anchors.insert(cert.serial.clone(), anchor).is_some() {
            return Err("Multiple certs with same serial".to_string());
        }

        Ok(())
    }

    fn find_anchor_by_serial(&self, serial: &BigUint) -> Option<&TrustAnchor> {
        self.anchors.get(serial)
    }
}

impl Default for RootStore {
    fn default() -> Self {
        let mut store = Self::new();
        store
            .add_chain(MILAN_ROOT_CHAIN)
            .expect("Root chain should be valid");
        store
            .add_chain(GENOA_ROOT_CHAIN)
            .expect("Root chain should be valid");
        store
            .add_chain(TURIN_ROOT_CHAIN)
            .expect("Root chain should be valid");
        store
    }
}

type FnMicrocode = fn(vcek::Product, &AttestationReport) -> Result<(), &'static str>;

/// A builder for [`Requirements`].
///
/// # [`std::default::Default`] implementation
///
/// Note that the builder starts without any requirements. In particular,
/// `RequirementsBuilder::default().build()` is different to `Requirements::default()`.
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct RequirementsBuilder {
    genoa: ProductRequirements,
    milan: ProductRequirements,
    turin: ProductRequirements,
    require_alias_check: bool,
    check_microcode: Option<FnMicrocode>,
}

impl RequirementsBuilder {
    /// Consumes this builder and returns corresponding [`Requirements`].
    #[must_use]
    pub fn build(self) -> Requirements {
        Requirements {
            genoa: self.genoa,
            milan: self.milan,
            turin: self.turin,
            require_alias_check: self.require_alias_check,
            check_microcode: self.check_microcode,
        }
    }

    /// Requires that the report contains that alias detection has completed and that there are no
    /// aliasing addresses. Also sets the required SNP firmware versions for Milan and Genoa as
    /// well as SNP security version number if no higher versions are already set.
    pub fn require_alias_check(mut self) -> Self {
        self.require_alias_check = true;
        if self.genoa.min_committed_version.unwrap_or_default()
            < Requirements::SB_3015_MIN_GENOA_VERSION
        {
            self.genoa.min_committed_version = Some(Requirements::SB_3015_MIN_GENOA_VERSION);
        }
        if self.genoa.min_committed_tcb_snp.unwrap_or_default()
            < Requirements::SB_3015_MIN_GENOA_SNP_SVN
        {
            self.genoa.min_committed_tcb_snp = Some(Requirements::SB_3015_MIN_GENOA_SNP_SVN);
        }

        if self.milan.min_committed_version.unwrap_or_default()
            < Requirements::SB_3015_MIN_MILAN_VERSION
        {
            self.milan.min_committed_version = Some(Requirements::SB_3015_MIN_MILAN_VERSION);
        }
        if self.milan.min_committed_tcb_snp.unwrap_or_default()
            < Requirements::SB_3015_MIN_MILAN_SNP_SVN
        {
            self.milan.min_committed_tcb_snp = Some(Requirements::SB_3015_MIN_MILAN_SNP_SVN);
        }

        self
    }

    /// Requires that the report's committed SNP firmware version is at least the given version for
    /// Genoa products. Does not lower the required version if a higher version is already
    /// required.
    pub fn min_committed_version_for_genoa(mut self, version: (u8, u8, u8)) -> Self {
        if version > self.genoa.min_committed_version.unwrap_or_default() {
            self.genoa.min_committed_version = Some(version);
        }
        self
    }

    /// Requires that the report's committed SNP firmware version is at least the given version for
    /// Milan products. Does not lower the required version if a higher version is already
    /// required.
    pub fn min_committed_version_for_milan(mut self, version: (u8, u8, u8)) -> Self {
        if version > self.milan.min_committed_version.unwrap_or_default() {
            self.milan.min_committed_version = Some(version);
        }
        self
    }

    /// Requires that the report's committed SNP firmware version is at least the given version for
    /// Turin products. Does not lower the required version if a higher version is already
    /// required.
    pub fn min_committed_version_for_turin(mut self, version: (u8, u8, u8)) -> Self {
        if version > self.turin.min_committed_version.unwrap_or_default() {
            self.turin.min_committed_version = Some(version);
        }
        self
    }

    /// Requires that the report's committed SNP firmware security version number (SVN) is at least
    /// the given version number for Milan products. Does not lower the required version number if
    /// a higher version number is already required.
    pub fn min_committed_snp_svn_for_milan(mut self, snp: u8) -> Self {
        if snp > self.milan.min_committed_tcb_snp.unwrap_or_default() {
            self.milan.min_committed_tcb_snp = Some(snp);
        }
        self
    }

    /// Requires that the report's committed SNP firmware security version number (SVN) is at least
    /// the given version number for Genoa products. Does not lower the required version number if
    /// a higher version number is already required.
    pub fn min_committed_snp_svn_for_genoa(mut self, snp: u8) -> Self {
        if snp > self.genoa.min_committed_tcb_snp.unwrap_or_default() {
            self.genoa.min_committed_tcb_snp = Some(snp);
        }
        self
    }

    /// Requires that the report's committed SNP firmware security version number (SVN) is at least
    /// the given version number for Turin products. Does not lower the required version number if
    /// a higher version number is already required.
    pub fn min_committed_snp_svn_for_turin(mut self, snp: u8) -> Self {
        if snp > self.turin.min_committed_tcb_snp.unwrap_or_default() {
            self.turin.min_committed_tcb_snp = Some(snp);
        }
        self
    }

    pub fn require_sb_7033_mitigations(mut self) -> Self {
        self.check_microcode = Some(Requirements::sb_7033_microcode_check);
        self
    }
}

#[derive(Debug, Clone, Default)]
struct ProductRequirements {
    // Inclusive
    min_committed_version: Option<(u8, u8, u8)>,
    // Inclusive
    min_committed_tcb_snp: Option<u8>,
    // Bitmask: all listed bits must be set in both current_mit_vector and launch_mit_vector
    min_mit_vector: Option<u64>,
}

/// Additional requirements for [`AttestationReport`] verification.
///
/// See also: [`RequirementsBuilder`], [`verify_report`]
#[derive(Debug, Clone)]
pub struct Requirements {
    genoa: ProductRequirements,
    milan: ProductRequirements,
    turin: ProductRequirements,
    require_alias_check: bool,
    check_microcode: Option<FnMicrocode>,
}

impl Default for Requirements {
    fn default() -> Self {
        Self::sb_3023_mitigations()
    }
}

macro_rules! microcode_check {
    ($name:ident, $(($product:pat, $cpu_family:pat) => $min_rev:expr),+ $(,)?) => {
        fn $name(
            product: vcek::Product,
            report: &AttestationReport,
        ) -> Result<(), &'static str> {
            if let Some(cpu_family) = report.cpuid_mod_id.zip(report.cpuid_step) {
                let min_rev = match (product, cpu_family) {
                    $(($product, $cpu_family) => $min_rev,)+
                    _ => return Err("Report doesn't match any known CPU family"),
                };
                if report.committed_tcb.microcode < min_rev {
                    return Err("Committed TCB: Microcode version too small");
                }
            } else {
                return Err("Could not verify microcode version: Missing values for CPU family");
            }
            Ok(())
        }
    };
}

impl Requirements {
    const SB_3023_MIN_GENOA_MIT_VEC: u64 = (1 << 0) | (1 << 1);
    const SB_3023_MIN_TURIN_MIT_VEC: u64 = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5);

    const SB_3020_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x31);
    const SB_3020_MIN_GENOA_SNP_SVN: u8 = 0x1B;
    const SB_3020_MIN_GENOA_MIT_VEC: u64 = 1 << 1;
    const SB_3020_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x23);
    const SB_3020_MIN_MILAN_SNP_SVN: u8 = 0x1B;
    const SB_3020_MIN_MILAN_MIT_VEC: u64 = 1 << 1;
    const SB_3020_MIN_TURIN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x41);
    const SB_3020_MIN_TURIN_SNP_SVN: u8 = 0x04;
    const SB_3020_MIN_TURIN_MIT_VEC: u64 = 1 << 0;

    const SB_3019_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x28);
    const SB_3019_MIN_GENOA_SNP_SVN: u8 = 0x17;
    const SB_3019_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x18);
    const SB_3019_MIN_MILAN_SNP_SVN: u8 = 0x18;
    const SB_3019_MIN_TURIN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x3b);

    const SB_3015_MIN_GENOA_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x26);
    const SB_3015_MIN_GENOA_SNP_SVN: u8 = 0x16;
    const SB_3015_MIN_MILAN_VERSION: (u8, u8, u8) = (0x1, 0x37, 0x16);
    const SB_3015_MIN_MILAN_SNP_SVN: u8 = 0x17;

    /// Checks for mitigations against [AMD-SB-3023](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3023.html)
    #[must_use]
    pub fn sb_3023_mitigations() -> Self {
        let sb_3020 = Self::sb_3020_mitigations();
        Self {
            genoa: ProductRequirements {
                min_mit_vector: Some(Self::SB_3023_MIN_GENOA_MIT_VEC),
                ..sb_3020.genoa
            },
            turin: ProductRequirements {
                min_mit_vector: Some(Self::SB_3023_MIN_TURIN_MIT_VEC),
                ..sb_3020.turin
            },
            check_microcode: Some(Self::sb_3023_microcode_check),
            ..sb_3020
        }
    }

    /// Checks for mitigations against [AMD-SB-3020](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3020.html)
    #[must_use]
    pub fn sb_3020_mitigations() -> Self {
        Self {
            genoa: ProductRequirements {
                min_committed_version: Some(Self::SB_3020_MIN_GENOA_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3020_MIN_GENOA_SNP_SVN),
                min_mit_vector: Some(Self::SB_3020_MIN_GENOA_MIT_VEC),
            },
            milan: ProductRequirements {
                min_committed_version: Some(Self::SB_3020_MIN_MILAN_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3020_MIN_MILAN_SNP_SVN),
                min_mit_vector: Some(Self::SB_3020_MIN_MILAN_MIT_VEC),
            },
            turin: ProductRequirements {
                min_committed_version: Some(Self::SB_3020_MIN_TURIN_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3020_MIN_TURIN_SNP_SVN),
                min_mit_vector: Some(Self::SB_3020_MIN_TURIN_MIT_VEC),
            },
            require_alias_check: true,
            check_microcode: Some(Self::sb_3020_microcode_check),
        }
    }

    /// Checks for mitigations against [AMD-SB-3019](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html)
    #[must_use]
    pub fn sb_3019_mitigations() -> Self {
        Self {
            genoa: ProductRequirements {
                min_committed_version: Some(Self::SB_3019_MIN_GENOA_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3019_MIN_GENOA_SNP_SVN),
                ..Default::default()
            },
            milan: ProductRequirements {
                min_committed_version: Some(Self::SB_3019_MIN_MILAN_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3019_MIN_MILAN_SNP_SVN),
                ..Default::default()
            },
            turin: ProductRequirements {
                min_committed_version: Some(Self::SB_3019_MIN_TURIN_VERSION),
                ..Default::default()
            },
            require_alias_check: true,
            check_microcode: None,
        }
    }

    /// Checks for mitigations against [AMD-SB-3015](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3015.html)
    #[must_use]
    pub fn sb_3015_mitigations() -> Self {
        Self {
            genoa: ProductRequirements {
                min_committed_version: Some(Self::SB_3015_MIN_GENOA_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3015_MIN_GENOA_SNP_SVN),
                ..Default::default()
            },
            milan: ProductRequirements {
                min_committed_version: Some(Self::SB_3015_MIN_MILAN_VERSION),
                min_committed_tcb_snp: Some(Self::SB_3015_MIN_MILAN_SNP_SVN),
                ..Default::default()
            },
            // Turin not affected by SB-3015
            turin: ProductRequirements::default(),
            require_alias_check: true,
            check_microcode: None,
        }
    }

    /// Checks for mitigations against [AMD-SB-3011](https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3011.html)
    #[must_use]
    pub fn sb_3011_mitigations() -> Self {
        Self {
            genoa: ProductRequirements {
                min_committed_version: Some((0x1, 0x37, 0x24)),
                min_committed_tcb_snp: Some(0x16),
                ..Default::default()
            },
            milan: ProductRequirements {
                min_committed_version: Some((0x1, 0x37, 0x14)),
                min_committed_tcb_snp: Some(0x17),
                ..Default::default()
            },
            // Turin not affected by SB-3011
            turin: ProductRequirements::default(),
            require_alias_check: false,
            check_microcode: None,
        }
    }

    microcode_check!(sb_3023_microcode_check,
        // Milan
        (vcek::Product::Milan, (1, 1)) => 0xDE,
        // Milan-X
        (vcek::Product::Milan, (1, 2)) => 0x47,
        // Genoa
        (vcek::Product::Genoa, (0x11, 1)) => 0x56,
        // Genoa-X
        (vcek::Product::Genoa, (0x11, 2)) => 0x51,
        // Bergamo/Siena
        (vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
        // Turin Classic
        (vcek::Product::Turin, (2, 1)) => 0x51,
        // Turin Dense
        (vcek::Product::Turin, (0x11, 0)) => 0x4E,
    );

    microcode_check!(sb_3020_microcode_check,
        // Milan
        (vcek::Product::Milan, (1, 1)) => 0xDE,
        // Milan-X
        (vcek::Product::Milan, (1, 2)) => 0x45,
        // Genoa
        (vcek::Product::Genoa, (0x11, 1)) => 0x56,
        // Genoa-X
        (vcek::Product::Genoa, (0x11, 2)) => 0x51,
        // Bergamo/Siena
        (vcek::Product::Genoa, (0xA0, 2)) => 0x1B,
        // Turin Classic
        (vcek::Product::Turin, (2, 1)) => 0x50,
        // Turin Dense
        (vcek::Product::Turin, (0x11, 0)) => 0x4D,
    );

    microcode_check!(sb_7033_microcode_check,
        // Milan
        (vcek::Product::Milan, (1, 1)) => 0xDB,
        // Milan-X
        (vcek::Product::Milan, (1, 2)) => 0x44,
        // Genoa
        (vcek::Product::Genoa, (0x11, 1)) => 0x54,
        // Genoa-X
        (vcek::Product::Genoa, (0x11, 2)) => 0x4F,
        // Bergamo/Siena
        (vcek::Product::Genoa, (0xA0, 2)) => 0x19,
        // Turin Classic
        (vcek::Product::Turin, (2, 1)) => 0x47,
    );

    fn verify(
        &self,
        product: vcek::Product,
        report: &AttestationReport,
    ) -> Result<(), &'static str> {
        if report.policy.debug_allowed() {
            return Err("Debug is enabled");
        }

        if report.vmpl > 3 {
            return Err("VMPL is not <= 3");
        }

        if report.key_info.signing_key() != 0 {
            return Err("Signing key is not VCEK");
        }

        if self.require_alias_check && !report.plat_info.alias_check_complete() {
            return Err("Alias check complete is false");
        }

        let requirements = match product {
            vcek::Product::Milan => &self.milan,
            vcek::Product::Genoa => &self.genoa,
            vcek::Product::Turin => &self.turin,
        };

        if let Some(min_wanted) = requirements.min_committed_version {
            if (
                report.committed.major,
                report.committed.minor,
                report.committed.build,
            ) < min_wanted
            {
                return Err("Firmware version too small");
            }
        }

        if let Some(min_tcb_snp) = requirements.min_committed_tcb_snp {
            if report.committed_tcb.snp < min_tcb_snp {
                return Err("Committed TCB: SNP patch level too small");
            }
        }

        if let Some(min_mit_vec) = requirements.min_mit_vector {
            match report.current_mit_vector {
                None => return Err("Current mitigation vector: not present in report"),
                Some(v) if v & min_mit_vec != min_mit_vec => {
                    return Err("Current mitigation vector: required mitigation bits not set")
                }
                Some(_) => {}
            }
            match report.launch_mit_vector {
                None => return Err("Launch mitigation vector: not present in report"),
                Some(v) if v & min_mit_vec != min_mit_vec => {
                    return Err("Launch mitigation vector: required mitigation bits not set")
                }
                Some(_) => {}
            }
        }

        if let Some(check) = self.check_microcode {
            check(product, report)?;
        }

        Ok(())
    }
}

fn parse_report(bytes: &[u8]) -> Option<AttestationReport> {
    if bytes.len() < 1184 {
        return None;
    }

    let read_u32 = |off: usize| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap());
    let read_u64 = |off: usize| u64::from_le_bytes(bytes[off..off + 8].try_into().unwrap());

    let version = read_u32(0x00);

    let turin_like = if version >= 3 {
        // Turin-like if CPU_FAM_ID is not the one for Genoa and Milan
        bytes[0x188] != 0x19
    } else {
        let chip_id = &bytes[0x1A0..0x1E0];
        if chip_id == [0u8; 64] {
            return None;
        }

        // Turin-like if the last 8 bytes of CHIP_ID are zero
        chip_id[8..] == [0u8; 56]
    };

    let parse_tcb = |off: usize| -> TcbVersion {
        let b = &bytes[off..off + 8];
        if turin_like {
            TcbVersion {
                fmc: Some(b[0]),
                bootloader: b[1],
                tee: b[2],
                snp: b[3],
                microcode: b[7],
            }
        } else {
            TcbVersion {
                fmc: None,
                bootloader: b[0],
                tee: b[1],
                snp: b[6],
                microcode: b[7],
            }
        }
    };

    let (cpuid_fam_id, cpuid_mod_id, cpuid_step) = if version >= 3 {
        (Some(bytes[0x188]), Some(bytes[0x189]), Some(bytes[0x18A]))
    } else {
        (None, None, None)
    };

    let parse_version = |off: usize| Version {
        build: bytes[off],
        minor: bytes[off + 1],
        major: bytes[off + 2],
    };

    let r: [u8; 72] = bytes[0x2A0..0x2E8].try_into().ok()?;
    let s: [u8; 72] = bytes[0x2E8..0x330].try_into().ok()?;

    Some(AttestationReport {
        version,
        guest_svn: read_u32(0x04),
        policy: GuestPolicy(read_u64(0x08)),
        family_id: bytes[0x10..0x20].try_into().ok()?,
        image_id: bytes[0x20..0x30].try_into().ok()?,
        vmpl: read_u32(0x30),
        sig_algo: read_u32(0x34),
        current_tcb: parse_tcb(0x38),
        plat_info: PlatformInfo(read_u64(0x40)),
        key_info: KeyInfo(read_u32(0x48)),
        report_data: bytes[0x50..0x90].try_into().ok()?,
        measurement: bytes[0x90..0xC0].try_into().ok()?,
        host_data: bytes[0xC0..0xE0].try_into().ok()?,
        id_key_digest: bytes[0xE0..0x110].try_into().ok()?,
        author_key_digest: bytes[0x110..0x140].try_into().ok()?,
        report_id: bytes[0x140..0x160].try_into().ok()?,
        report_id_ma: bytes[0x160..0x180].try_into().ok()?,
        reported_tcb: parse_tcb(0x180),
        cpuid_fam_id,
        cpuid_mod_id,
        cpuid_step,
        chip_id: bytes[0x1A0..0x1E0].try_into().ok()?,
        committed_tcb: parse_tcb(0x1E0),
        current: parse_version(0x1E8),
        committed: parse_version(0x1EC),
        launch_tcb: parse_tcb(0x1F0),
        launch_mit_vector: (version >= 5).then(|| read_u64(0x1F8)),
        current_mit_vector: (version >= 5).then(|| read_u64(0x200)),
        signature: Signature::new(r, s),
    })
}

#[allow(clippy::missing_errors_doc)]
pub fn verify_report<R: Read + Seek>(
    report_contents: &[u8],
    vcek_chain: R,
    root_store: &RootStore,
    verifications: &Requirements,
) -> Result<AttestationReport, Error> {
    if report_contents.len() < 1184 {
        return Err(Error::ReportParseError);
    }

    let report = parse_report(report_contents).ok_or(Error::ReportParseError)?;
    let sig = ReportSignature::try_from(&report).map_err(|_| Error::ReportParseError)?;
    let ecdsa_sig =
        p384::ecdsa::Signature::try_from(&sig).map_err(|_| Error::ReportSignatureParseError)?;
    let (product, key, tcb_version) = vcek::verify_vcek_chain(
        root_store,
        Pem::iter_from_reader(BufReader::new(vcek_chain)),
    )?;

    if report.reported_tcb != tcb_version {
        return Err(Error::ReportTcbVersionMismatch);
    }

    if key.verify(&report_contents[..0x2a0], &ecdsa_sig).is_ok() {
        verifications
            .verify(product, &report)
            .map(|()| report)
            .map_err(Error::RequirementsNotSatisfied)
    } else {
        Err(Error::ReportSignatureMismatch)
    }
}

#[cfg(test)]
mod tests {
    use std::{fs::File, io::Cursor, path::PathBuf};

    use ecdsa::signature::Signer;
    use p384::{ecdsa::SigningKey, NistP384};
    use rand_core::OsRng;
    use sev::{
        certs::snp::ecdsa::Signature, firmware::guest::AttestationReport, parser::ByteParser,
    };
    use x509_parser::{num_bigint::BigUint, prelude::Pem};

    use crate::verification::{Error, Requirements, RootStore};

    use super::{vcek, vcek::verify_vcek_chain, verify_report, RequirementsBuilder};

    // Re-implementation of std's `assert_matches!` which is unfortunately still unstable
    macro_rules! assert_m {
        ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
            match $left {
                $( $pattern )|+ $( if $guard )? => {}
                ref left_val => {
                    panic!(
                        "Expected match where there is none\nleft: {left_val:?}\nright: {}",
                        stringify!($($pattern)|+ $(if $guard)?),
                    );
                }
            }
        };
    }

    #[test]
    fn verify_report_returns_ok_for_valid_report_and_chain() {
        let result = verify_report(
            &hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap(),
            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
            &RootStore::default(),
            // N.B. doesn't use `Requirements::default` because the fixtures are reports without
            // the required mitigations
            &RequirementsBuilder::default().build(),
        );

        assert!(result.is_ok());
    }

    #[test]
    fn verify_report_returns_ok_for_valid_report_and_chain_with_sb_3015_mitigations() {
        let result = verify_report(
            &genoa_report_alias_check_fixture_bytes(),
            genoa_vcek_chain_alias_check_fixture(),
            &RootStore::default(),
            &Requirements::sb_3015_mitigations(),
        );
        assert!(result.is_ok());
    }

    #[test]
    fn default_requirements_require_sb_3023_mitigations() {
        let result = verify_report(
            &hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap(),
            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
            &RootStore::default(),
            &Requirements::default(),
        );

        assert_m!(
            result,
            Err(Error::RequirementsNotSatisfied(
                "Alias check complete is false"
            ))
        );
    }

    #[test]
    fn verify_vcek_chain_returns_ok_for_valid_chain() {
        let result = verify_vcek_chain(
            &RootStore::default(),
            Pem::iter_from_buffer(include_bytes!("../fixtures/tests/valid_vcek_chain.data")),
        );

        assert!(result.is_ok());
    }

    #[test]
    fn verify_vcek_chain_returns_err_for_selfsigned_chain() {
        let result = verify_vcek_chain(
            &RootStore::default(),
            Pem::iter_from_buffer(include_bytes!(
                "../fixtures/tests/selfsigned_vcek_chain.data"
            )),
        );

        assert!(result.is_err());
    }

    #[test]
    fn verify_vcek_chain_returns_err_for_invalid_chain() {
        let result = verify_vcek_chain(
            &RootStore::default(),
            Pem::iter_from_buffer(include_bytes!("../fixtures/tests/vcek_wrong_order.data")),
        );

        assert!(result.is_err());
    }

    #[test]
    fn default_root_store_finds_milan_serial() {
        let root_store = RootStore::default();

        assert!(root_store
            .find_anchor_by_serial(&BigUint::from(0x10001u32))
            .is_some());
    }

    #[test]
    fn root_store_returns_none_for_unknown_serial() {
        let root_store = RootStore::new();

        assert!(root_store
            .find_anchor_by_serial(&BigUint::from(0x1234u32))
            .is_none());
    }

    #[test]
    fn invalid_report_signature_returns_err() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        // 0 values result in a signature error
        report.signature = Signature::new([0u8; 72], [0u8; 72]);

        let result = verify_report(
            &report.to_bytes().unwrap(),
            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap(),
            &RootStore::default(),
            &Requirements::default(),
        );

        assert_m!(result, Err(Error::ReportSignatureParseError));
    }

    #[test]
    fn wrong_report_signature_returns_err() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        let signing_key = SigningKey::random(&mut OsRng);
        let signature = signing_key.sign(b"wurzelpfropf");
        report.signature = create_signature_from_signature(&signature);

        let result = verify_report(
            &report.to_bytes().unwrap(),
            vcek_chain_fixture(),
            &RootStore::default(),
            &Requirements::default(),
        );

        assert_m!(result, Err(Error::ReportSignatureMismatch));
    }

    #[test]
    fn wrong_tcb_version_returns_err() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        let signing_key = SigningKey::random(&mut OsRng);
        let signature = signing_key.sign(b"wurzelpfropf");
        report.signature = create_signature_from_signature(&signature);

        let result = verify_report(
            &report.to_bytes().unwrap(),
            File::open(PathBuf::from(
                "./fixtures/tests/valid_vcek_wrong_tcb_version.data",
            ))
            .unwrap(),
            &RootStore::default(),
            &Requirements::default(),
        );

        assert_m!(result, Err(Error::ReportTcbVersionMismatch));
    }

    #[test]
    fn debug_enabled_returns_err() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        report.policy.set_debug_allowed(true);

        assert_m!(
            RequirementsBuilder::default()
                .build()
                .verify(vcek::Product::Milan, &report),
            Err("Debug is enabled")
        );
    }

    #[test]
    fn non_guest_vmpl_returns_err() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        report.vmpl = 4;

        assert_m!(
            RequirementsBuilder::default()
                .build()
                .verify(vcek::Product::Milan, &report),
            Err("VMPL is not <= 3")
        );
    }

    #[test]
    fn signing_key_not_vcek_returns_err() {
        let mut report_bytes = report_fixture_bytes();
        // KEY_INFO is at offset 0x48 in the attestation report structure.
        // SIGNING_KEY is bits 4:2; set to 1 (VLEK).
        report_bytes[0x48] |= 1 << 2;
        let report = AttestationReport::from_bytes(&report_bytes).unwrap();

        assert_m!(
            RequirementsBuilder::default()
                .build()
                .verify(vcek::Product::Milan, &report),
            Err("Signing key is not VCEK")
        );
    }

    #[test]
    fn requirements_check_for_minimal_committed_snp_version_milan() {
        fn verify(version: (u8, u8, u8)) -> Result<AttestationReport, Error> {
            verify_report(
                &report_fixture_bytes(),
                vcek_chain_fixture(),
                &RootStore::default(),
                &RequirementsBuilder::default()
                    .min_committed_version_for_milan(version)
                    .build(),
            )
        }

        fn version_too_small(version: (u8, u8, u8)) {
            assert_m!(
                verify(version),
                Err(Error::RequirementsNotSatisfied(
                    "Firmware version too small"
                ))
            );
        }

        version_too_small((1, 52, 5));
        version_too_small((1, 53, 4));
        version_too_small((2, 0, 0));

        assert_m!(verify((1, 52, 4)), Ok(_));
        assert_m!(verify((1, 50, 0)), Ok(_));
    }

    #[test]
    fn requirements_check_for_minimal_committed_snp_version_genoa() {
        fn verify(version: (u8, u8, u8)) -> Result<AttestationReport, Error> {
            verify_report(
                &genoa_report_fixture_bytes(),
                genoa_vcek_chain_fixture(),
                &RootStore::default(),
                &RequirementsBuilder::default()
                    .min_committed_version_for_genoa(version)
                    .build(),
            )
        }

        fn version_too_small(version: (u8, u8, u8)) {
            assert_m!(
                verify(version),
                Err(Error::RequirementsNotSatisfied(
                    "Firmware version too small"
                ))
            );
        }

        version_too_small((1, 55, 22));
        version_too_small((1, 56, 21));
        version_too_small((2, 0, 0));

        assert_m!(verify((1, 55, 21)), Ok(_));
        assert_m!(verify((1, 54, 0)), Ok(_));
    }

    #[test]
    fn requirements_check_for_minimal_committed_snp_svn_milan() {
        fn verify(svn: u8) -> Result<AttestationReport, Error> {
            verify_report(
                &report_fixture_bytes(),
                vcek_chain_fixture(),
                &RootStore::default(),
                &RequirementsBuilder::default()
                    .min_committed_snp_svn_for_milan(svn)
                    .build(),
            )
        }

        assert_m!(verify(8), Ok(_));
        assert_m!(
            verify(9),
            Err(Error::RequirementsNotSatisfied(
                "Committed TCB: SNP patch level too small"
            ))
        );
    }

    #[test]
    fn requirements_check_for_minimal_committed_snp_svn_genoa() {
        fn verify(svn: u8) -> Result<AttestationReport, Error> {
            verify_report(
                &genoa_report_fixture_bytes(),
                genoa_vcek_chain_fixture(),
                &RootStore::default(),
                &RequirementsBuilder::default()
                    .min_committed_snp_svn_for_genoa(svn)
                    .build(),
            )
        }

        assert_m!(verify(14), Ok(_));
        assert_m!(
            verify(15),
            Err(Error::RequirementsNotSatisfied(
                "Committed TCB: SNP patch level too small"
            ))
        );
    }

    #[test]
    fn requirements_check_for_alias_check_milan() {
        let result = verify_report(
            &report_fixture_bytes(),
            vcek_chain_fixture(),
            &RootStore::default(),
            &RequirementsBuilder::default().require_alias_check().build(),
        );
        assert_m!(
            result,
            Err(Error::RequirementsNotSatisfied(
                "Alias check complete is false"
            ))
        );
    }

    #[test]
    fn requirements_check_for_alias_check_genoa() {
        let result = verify_report(
            &genoa_report_fixture_bytes(),
            genoa_vcek_chain_fixture(),
            &RootStore::default(),
            &RequirementsBuilder::default().require_alias_check().build(),
        );
        assert_m!(
            result,
            Err(Error::RequirementsNotSatisfied(
                "Alias check complete is false"
            ))
        );
    }

    #[test]
    fn requirements_check_for_alias_check_genoa_ok() {
        let result = verify_report(
            &genoa_report_alias_check_fixture_bytes(),
            genoa_vcek_chain_alias_check_fixture(),
            &RootStore::default(),
            &RequirementsBuilder::default().require_alias_check().build(),
        );
        assert!(result.is_ok());
    }

    #[test]
    fn requirements_check_microcode_missing_cpu_fields() {
        let result = verify_report(
            &report_fixture_bytes(),
            vcek_chain_fixture(),
            &RootStore::default(),
            &RequirementsBuilder::default()
                .require_sb_7033_mitigations()
                .build(),
        );
        assert_m!(
            result,
            Err(Error::RequirementsNotSatisfied(
                "Could not verify microcode version: Missing values for CPU family"
            )),
        );
    }

    #[test]
    fn verify_report_passes_for_authentic_genoa_v5_report() {
        let result = verify_report(
            &hex::decode(include_str!("../fixtures/tests/report_v5.data").trim()).unwrap(),
            File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain_v5.data")).unwrap(),
            &RootStore::default(),
            &Requirements::default(),
        );
        assert!(result.is_ok());
    }

    #[test]
    fn verify_vcek_chain_returns_ok_for_milan_with_microcode_above_128() {
        let (product, _key, tcb_version) = verify_vcek_chain(
            &RootStore::default(),
            Pem::iter_from_buffer(include_bytes!(
                "../fixtures/tests/valid_vcek_chain_milan.data"
            )),
        )
        .unwrap();

        assert_m!(product, vcek::Product::Milan);
        assert_eq!(tcb_version.microcode, 0xDB);
    }

    #[test]
    fn verify_report_returns_ok_for_milan_with_microcode_above_128() {
        let result = verify_report(
            &hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap(),
            Cursor::new(include_str!(
                "../fixtures/tests/valid_vcek_chain_milan.data"
            )),
            &RootStore::default(),
            &RequirementsBuilder::default().build(),
        );

        assert!(result.is_ok());
    }

    #[test]
    fn verify_vcek_chain_returns_ok_for_turin() {
        let (product, _key, tcb_version) = verify_vcek_chain(
            &RootStore::default(),
            Pem::iter_from_buffer(include_bytes!(
                "../fixtures/tests/valid_vcek_chain_turin.data"
            )),
        )
        .unwrap();

        assert_m!(product, vcek::Product::Turin);
        assert_eq!(tcb_version.microcode, 0x47);
    }

    #[test]
    fn verify_report_returns_ok_for_turin() {
        let result = verify_report(
            &hex::decode(include_str!("../fixtures/tests/report_turin.data").trim()).unwrap(),
            Cursor::new(include_str!(
                "../fixtures/tests/valid_vcek_chain_turin.data"
            )),
            &RootStore::default(),
            &RequirementsBuilder::default().build(),
        );

        assert!(result.is_ok());
    }

    #[test]
    fn requirements_builder_doesnt_lower_version() {
        let requirements = RequirementsBuilder::default()
            .min_committed_version_for_genoa((1, 52, 1))
            .min_committed_version_for_genoa((1, 50, 0))
            .min_committed_version_for_milan((1, 52, 1))
            .min_committed_version_for_milan((1, 50, 0))
            .min_committed_version_for_turin((1, 52, 1))
            .min_committed_version_for_turin((1, 50, 0))
            .build();

        assert_eq!(requirements.genoa.min_committed_version, Some((1, 52, 1)));
        assert_eq!(requirements.milan.min_committed_version, Some((1, 52, 1)));
        assert_eq!(requirements.turin.min_committed_version, Some((1, 52, 1)));
    }

    #[test]
    fn requirements_builder_sets_higher_version() {
        let requirements = RequirementsBuilder::default()
            .min_committed_version_for_genoa((1, 52, 1))
            .min_committed_version_for_genoa((1, 52, 2))
            .min_committed_version_for_milan((1, 52, 1))
            .min_committed_version_for_milan((1, 52, 2))
            .min_committed_version_for_turin((1, 52, 1))
            .min_committed_version_for_turin((1, 52, 2))
            .build();

        assert_eq!(requirements.genoa.min_committed_version, Some((1, 52, 2)));
        assert_eq!(requirements.milan.min_committed_version, Some((1, 52, 2)));
        assert_eq!(requirements.turin.min_committed_version, Some((1, 52, 2)));
    }

    fn create_signature_from_signature(signature: &ecdsa::Signature<NistP384>) -> Signature {
        // p384 uses big endian, but SNP firmware uses little endian for the signature,
        // hence reverse to match endianess. Additionally, the scalars in p384 ECDSA signatures are
        // 48 bytes, but the report's fields are 72 bytes, so the remaining bytes are filled with
        // zeros.
        // See table 115: "Format for an ECDSA P-384 with SHA-384 Signature" in
        // "SEV Secure Nested Paging Firmware ABI Specification" (publication #56860)
        // https://www.amd.com/system/files/TechDocs/56860.pdf
        let mut r: [u8; 72] = [0u8; 72];
        r[24..].copy_from_slice(&signature.r().to_bytes());
        r.reverse();
        let mut s: [u8; 72] = [0u8; 72];
        s[24..].copy_from_slice(&signature.s().to_bytes());
        s.reverse();

        Signature::new(r, s)
    }

    fn genoa_report_fixture_bytes() -> Vec<u8> {
        hex::decode(include_bytes!("../fixtures/tests/report_genoa.data")).unwrap()
    }

    fn genoa_report_alias_check_fixture_bytes() -> Vec<u8> {
        hex::decode(include_bytes!(
            "../fixtures/tests/report_genoa_alias_check.data"
        ))
        .unwrap()
    }

    fn report_fixture_bytes() -> Vec<u8> {
        hex::decode(include_bytes!("../fixtures/tests/report.data")).unwrap()
    }

    fn genoa_vcek_chain_fixture() -> impl std::io::Read + std::io::Seek {
        File::open(PathBuf::from(
            "./fixtures/tests/valid_vcek_chain_genoa.data",
        ))
        .unwrap()
    }

    fn genoa_vcek_chain_alias_check_fixture() -> impl std::io::Read + std::io::Seek {
        File::open(PathBuf::from(
            "./fixtures/tests/valid_vcek_chain_genoa_alias_check.data",
        ))
        .unwrap()
    }

    fn vcek_chain_fixture() -> impl std::io::Read + std::io::Seek {
        File::open(PathBuf::from("./fixtures/tests/valid_vcek_chain.data")).unwrap()
    }

    #[test]
    fn parser_matches_sev_parser_milan() {
        let bytes =
            hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
        let expected = AttestationReport::from_bytes(&bytes).unwrap();
        let actual = super::parse_report(&bytes).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn parser_matches_sev_parser_genoa() {
        let bytes = genoa_report_fixture_bytes();
        let expected = AttestationReport::from_bytes(&bytes).unwrap();
        let actual = super::parse_report(&bytes).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn parser_matches_sev_parser_v5() {
        let bytes = hex::decode(include_str!("../fixtures/tests/report_v5.data").trim()).unwrap();
        let expected = AttestationReport::from_bytes(&bytes).unwrap();
        let actual = super::parse_report(&bytes).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn parser_matches_sev_parser_turin() {
        let bytes =
            hex::decode(include_str!("../fixtures/tests/report_turin.data").trim()).unwrap();
        let expected = AttestationReport::from_bytes(&bytes).unwrap();
        let actual = super::parse_report(&bytes).unwrap();
        assert_eq!(actual, expected);
    }

    #[test]
    fn parse_report_ignores_reserved_bytes_for_unknown_version() {
        // Simulate a hypothetical future version that uses the currently reserved
        // area between current_mit_vector and the signature (0x208..0x2A0).
        let mut bytes =
            hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
        bytes[0..4].copy_from_slice(&42u32.to_le_bytes());
        bytes[0x208..0x2A0].fill(0xAB);

        let report = super::parse_report(&bytes).unwrap();
        assert_eq!(report.version, 42);
        // Fields that precede the reserved area must be unaffected.
        assert!(report.current_mit_vector.is_some());
    }

    #[test]
    fn parse_report_truncated() {
        let bytes =
            hex::decode(include_str!("../fixtures/tests/report_milan.data").trim()).unwrap();
        assert!(super::parse_report(&bytes[..1183]).is_none());
    }

    #[test]
    fn microcode_check_macro_accepts_above_minimum() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        report.cpuid_mod_id = Some(1);
        report.cpuid_step = Some(1);
        // All three checks for Milan (1,1) require at most 0xDE
        report.committed_tcb.microcode = 0xDE;
        assert!(Requirements::sb_3023_microcode_check(vcek::Product::Milan, &report).is_ok());
        assert!(Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report).is_ok());
        assert!(Requirements::sb_7033_microcode_check(vcek::Product::Milan, &report).is_ok());
    }

    #[test]
    fn microcode_check_macro_rejects_below_minimum() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        report.cpuid_mod_id = Some(1);
        report.cpuid_step = Some(1);
        // SB-3020 Milan (1,1) minimum is 0xDE
        report.committed_tcb.microcode = 0xDD;
        assert_eq!(
            Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
            Err("Committed TCB: Microcode version too small"),
        );
    }

    #[test]
    fn microcode_check_macro_rejects_unknown_cpu_family() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        report.cpuid_mod_id = Some(0xFF);
        report.cpuid_step = Some(0xFF);
        assert_eq!(
            Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
            Err("Report doesn't match any known CPU family"),
        );
    }

    #[test]
    fn microcode_check_macro_rejects_missing_cpu_fields() {
        let mut report = AttestationReport::from_bytes(&report_fixture_bytes()).unwrap();
        report.cpuid_mod_id = None;
        report.cpuid_step = None;
        assert_eq!(
            Requirements::sb_3020_microcode_check(vcek::Product::Milan, &report),
            Err("Could not verify microcode version: Missing values for CPU family"),
        );
    }

    fn sb_3020_passing_genoa_report() -> AttestationReport {
        use sev::firmware::{
            guest::{PlatformInfo, Version},
            host::TcbVersion,
        };
        AttestationReport {
            plat_info: PlatformInfo(1 << 5), // alias_check_complete
            committed: Version::new(1, 0x37, 0x31),
            committed_tcb: TcbVersion {
                snp: 0x1b,
                microcode: 0x56,
                ..Default::default()
            },
            cpuid_mod_id: Some(0x11),
            cpuid_step: Some(1),
            current_mit_vector: Some(Requirements::SB_3020_MIN_GENOA_MIT_VEC),
            launch_mit_vector: Some(Requirements::SB_3020_MIN_GENOA_MIT_VEC),
            ..Default::default()
        }
    }

    #[test]
    fn requirements_check_mit_vector_missing() {
        let mut report = sb_3020_passing_genoa_report();
        report.current_mit_vector = None;
        assert_eq!(
            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
            Err("Current mitigation vector: not present in report"),
        );
    }

    #[test]
    fn requirements_check_mit_vector_insufficient() {
        let mut report = sb_3020_passing_genoa_report();
        report.current_mit_vector = Some(0);
        assert_eq!(
            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
            Err("Current mitigation vector: required mitigation bits not set"),
        );
    }

    #[test]
    fn requirements_check_mit_vector_valid() {
        let report = sb_3020_passing_genoa_report();
        assert!(Requirements::sb_3020_mitigations()
            .verify(vcek::Product::Genoa, &report)
            .is_ok());
    }

    #[test]
    fn requirements_check_launch_mit_vector_missing() {
        let mut report = sb_3020_passing_genoa_report();
        report.launch_mit_vector = None;
        assert_eq!(
            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
            Err("Launch mitigation vector: not present in report"),
        );
    }

    #[test]
    fn requirements_check_launch_mit_vector_insufficient() {
        let mut report = sb_3020_passing_genoa_report();
        report.launch_mit_vector = Some(0);
        assert_eq!(
            Requirements::sb_3020_mitigations().verify(vcek::Product::Genoa, &report),
            Err("Launch mitigation vector: required mitigation bits not set"),
        );
    }

    #[test]
    fn requirements_check_sb_3023_mit_vector_requires_extra_genoa_bits() {
        let mut report = sb_3020_passing_genoa_report();
        report.committed_tcb.microcode = 0x58;
        assert_eq!(
            Requirements::sb_3023_mitigations().verify(vcek::Product::Genoa, &report),
            Err("Current mitigation vector: required mitigation bits not set"),
        );
        report.current_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
        report.launch_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
        assert!(Requirements::sb_3023_mitigations()
            .verify(vcek::Product::Genoa, &report)
            .is_ok());
    }

    #[test]
    fn requirements_check_sb_3023_launch_mit_vector_checked_independently_of_current() {
        let mut report = sb_3020_passing_genoa_report();
        report.committed_tcb.microcode = 0x58;
        report.current_mit_vector = Some(Requirements::SB_3023_MIN_GENOA_MIT_VEC);
        assert_eq!(
            Requirements::sb_3023_mitigations().verify(vcek::Product::Genoa, &report),
            Err("Launch mitigation vector: required mitigation bits not set"),
        );
    }
}
