// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
// Copyright © 2023, Microsoft Corporation
//
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
use std::cmp;
use std::collections::HashMap;
use std::ffi::CString;
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
use std::iter;
use std::sync::{Arc, Mutex};
use std::{ffi, io};

use hypervisor::HypervisorType;
#[cfg(feature = "sev_snp")]
use igvm::IgvmInitializationHeader;
use igvm::snp_defs::SevVmsa;
use igvm::{IgvmDirectiveHeader, IgvmFile, IgvmPlatformHeader};
#[cfg(feature = "sev_snp")]
use igvm_defs::{IGVM_VHS_MEMORY_MAP_ENTRY, MemoryMapEntryType};
use igvm_defs::{
    IGVM_VHS_PARAMETER, IGVM_VHS_PARAMETER_INSERT, IgvmPageDataType, IgvmPlatformType,
};
use log::debug;
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
use log::error;
#[cfg(feature = "sev_snp")]
use log::info;
#[cfg(feature = "mshv")]
use mshv_bindings::*;
use thiserror::Error;
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
use vm_memory::Bytes;
#[cfg(feature = "sev_snp")]
use vm_memory::{GuestAddress, GuestAddressSpace, GuestMemory};
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
use vm_migration::Snapshottable;
#[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
use vmm_sys_util::errno;
use zerocopy::IntoBytes;
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
use zerocopy::{FromBytes, FromZeros};

#[cfg(feature = "sev_snp")]
use crate::GuestMemoryMmap;
use crate::cpu;
use crate::cpu::CpuManager;
use crate::igvm::loader::Loader;
use crate::igvm::{BootPageAcceptance, HV_PAGE_SIZE, IgvmLoadedInfo, StartupMemoryType, loader};
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
#[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
use crate::sev::{MeasuredBootInfo, SEV_HASH_BLOCK_ADDRESS, SEV_HASH_BLOCK_SIZE};

#[cfg(feature = "sev_snp")]
const ISOLATED_PAGE_SHIFT: u32 = 12;
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
const SNP_CPUID_LIMIT: u32 = 64;
// see section 7.1
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
#[cfg(all(feature = "kvm", feature = "sev_snp"))]
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq, IntoBytes, FromBytes)]
pub struct SnpCpuidFunc {
    pub eax_in: u32,
    pub ecx_in: u32,
    pub xcr0_in: u64,
    pub xss_in: u64,
    pub eax: u32,
    pub ebx: u32,
    pub ecx: u32,
    pub edx: u32,
    pub reserved: u64,
}

#[cfg(all(feature = "kvm", feature = "sev_snp"))]
#[repr(C)]
#[derive(Debug, Clone, FromBytes, IntoBytes)]
pub struct SnpCpuidInfo {
    pub count: u32,
    pub _reserved1: u32,
    pub _reserved2: u64,
    pub entries: [SnpCpuidFunc; SNP_CPUID_LIMIT as usize],
}
#[derive(Debug, Error)]
pub enum Error {
    #[error("command line is not a valid C string")]
    InvalidCommandLine(#[source] ffi::NulError),
    #[error("failed to read igvm file")]
    Igvm(#[source] io::Error),
    #[error("invalid igvm file")]
    InvalidIgvmFile(#[source] igvm::Error),
    #[error("multiple SNP ID blocks in IGVM file")]
    DuplicateSnpIdBlock,
    #[error("invalid guest memory map")]
    InvalidGuestMemmap(#[source] arch::Error),
    #[error("loader error")]
    Loader(#[source] loader::Error),
    #[error("parameter too large for parameter area")]
    ParameterTooLarge,
    #[error("Error importing isolated pages")]
    ImportIsolatedPages(#[source] hypervisor::HypervisorVmError),
    #[error("Error completing importing isolated pages")]
    CompleteIsolatedImport(#[source] hypervisor::HypervisorVmError),
    #[error("Error decoding host data")]
    FailedToDecodeHostData(#[source] hex::FromHexError),
    #[error("Error allocating address space")]
    MemoryManager(#[source] MemoryManagerError),
    #[error("IGVM file not provided")]
    MissingIgvm,
    #[error("Error applying VMSA to vCPU registers: {0}")]
    SetVmsa(#[source] cpu::Error),
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    #[error("Error building SEV-SNP measured boot hash block")]
    MeasuredBoot(#[source] errno::Error),
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    #[error(
        "igvmfile inserts unmeasured parameter area [0x{region_start:x}, 0x{region_end:x}) over SEV-SNP kernel hashes region [0x{hash_start:x}, 0x{hash_end:x})"
    )]
    MeasuredBootHashOverlap {
        region_start: u64,
        region_end: u64,
        hash_start: u64,
        hash_end: u64,
    },
}

// KVM SNP page types — linux/arch/x86/include/uapi/asm/sev-guest.h
#[cfg(feature = "kvm")]
const KVM_SNP_PAGE_TYPE_NORMAL: u32 = 1;
#[cfg(feature = "kvm")]
const KVM_SNP_PAGE_TYPE_VMSA: u32 = 2;
#[cfg(feature = "kvm")]
const KVM_SNP_PAGE_TYPE_ZERO: u32 = 3;
#[cfg(feature = "kvm")]
const KVM_SNP_PAGE_TYPE_UNMEASURED: u32 = 4;
#[cfg(feature = "kvm")]
const KVM_SNP_PAGE_TYPE_SECRETS: u32 = 5;
#[cfg(feature = "kvm")]
const KVM_SNP_PAGE_TYPE_CPUID: u32 = 6;

// Consolidated page type/size configuration per hypervisor.
struct PageTypeConfig {
    isolated_page_size_4kb: u32,
    normal: u32,
    #[cfg_attr(not(feature = "kvm"), expect(dead_code))]
    zero: u32,
    unmeasured: u32,
    cpuid: u32,
    secrets: u32,
    vmsa: u32,
}

#[derive(Copy, Clone)]
struct GpaPages {
    #[cfg_attr(not(feature = "sev_snp"), expect(dead_code))]
    pub page_type: u32,
    #[cfg_attr(not(feature = "sev_snp"), expect(dead_code))]
    pub gpa: u64,
    #[allow(dead_code)] // usage pattern too complex, we stick to allow()
    pub page_size: u32,
}

#[derive(Debug)]
enum ParameterAreaState {
    /// Parameter area has been declared via a ParameterArea header.
    Allocated { data: Vec<u8>, max_size: u64 },
    /// Parameter area inserted and invalid to use.
    Inserted,
}

#[cfg(feature = "sev_snp")]
fn igvm_memmap_from_ram_range(ram_range: (u64, u64)) -> IGVM_VHS_MEMORY_MAP_ENTRY {
    assert!(ram_range.0.is_multiple_of(HV_PAGE_SIZE));
    assert!((ram_range.1 - ram_range.0).is_multiple_of(HV_PAGE_SIZE));

    IGVM_VHS_MEMORY_MAP_ENTRY {
        starting_gpa_page_number: ram_range.0 / HV_PAGE_SIZE,
        number_of_pages: (ram_range.1 - ram_range.0) / HV_PAGE_SIZE,
        entry_type: MemoryMapEntryType::MEMORY,
        flags: 0,
        reserved: 0,
    }
}

#[cfg(feature = "sev_snp")]
fn generate_memory_map(
    guest_mem: &GuestMemoryMmap,
) -> Result<Vec<IGVM_VHS_MEMORY_MAP_ENTRY>, Error> {
    let mut memory_map = Vec::new();

    // Get usable physical memory ranges
    let ram_ranges = arch::generate_ram_ranges(guest_mem).map_err(Error::InvalidGuestMemmap)?;

    for ram_range in ram_ranges {
        memory_map.push(igvm_memmap_from_ram_range(ram_range));
    }

    Ok(memory_map)
}

// Import a parameter to the given parameter area.
fn import_parameter(
    parameter_areas: &mut HashMap<u32, ParameterAreaState>,
    info: &IGVM_VHS_PARAMETER,
    parameter: &[u8],
) -> Result<(), Error> {
    let (parameter_area, max_size) = match parameter_areas
        .get_mut(&info.parameter_area_index)
        .expect("parameter area should be present")
    {
        ParameterAreaState::Allocated { data, max_size } => (data, max_size),
        ParameterAreaState::Inserted => panic!("igvmfile is not valid"),
    };
    let offset = info.byte_offset as usize;
    let end_of_parameter = offset + parameter.len();

    if end_of_parameter > *max_size as usize {
        // TODO: tracing for which parameter was too big?
        return Err(Error::ParameterTooLarge);
    }

    if parameter_area.len() < end_of_parameter {
        parameter_area.resize(end_of_parameter, 0);
    }

    parameter_area[offset..end_of_parameter].copy_from_slice(parameter);
    Ok(())
}

///
/// Extract guest policy from the IGVM initialization headers.
#[cfg(feature = "sev_snp")]
pub fn extract_guest_policy(igvm_file: &IgvmFile) -> Option<igvm_defs::SnpPolicy> {
    for header in igvm_file.initializations() {
        if let IgvmInitializationHeader::GuestPolicy { policy, .. } = header
            && *policy != 0
        {
            return Some(igvm_defs::SnpPolicy::from_bits(*policy));
        }
    }
    None
}

///
/// Extract sev_features from the boot CPU (vp_index 0) VMSA.
///
#[cfg(feature = "sev_snp")]
pub fn extract_sev_features(igvm_file: &IgvmFile) -> u64 {
    for header in igvm_file.directives() {
        if let IgvmDirectiveHeader::SnpVpContext { vp_index, vmsa, .. } = header
            && *vp_index == 0
        {
            return vmsa.sev_features.into();
        }
    }
    0
}

///
/// Load the given IGVM file to guest memory.
/// Right now it only supports SNP based isolation.
/// We can boot legacy VM with an igvm file without
/// any isolation.
///
/// NOTE: KVM and MSHV have different page type values and CPUID/VMSA handling.
/// Hypervisor-specific code paths are gated by runtime type checks. A future
/// refactor could split these into separate KVM/MSHV loader implementations.
#[expect(clippy::needless_pass_by_value)]
pub fn load_igvm(
    igvm_file: IgvmFile,
    memory_manager: Arc<Mutex<MemoryManager>>,
    cpu_manager: Arc<Mutex<CpuManager>>,
    cmdline: &str,
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))] measured_boot: Option<
        MeasuredBootInfo,
    >,
    #[cfg(feature = "sev_snp")] host_data: &Option<String>,
) -> Result<Box<IgvmLoadedInfo>, Error> {
    let hypervisor_type = cpu_manager.lock().unwrap().hypervisor_type();
    let page_types = match hypervisor_type {
        #[cfg(feature = "mshv")]
        HypervisorType::Mshv => PageTypeConfig {
            isolated_page_size_4kb: mshv_bindings::hv_isolated_page_size_HV_ISOLATED_PAGE_SIZE_4KB,
            normal: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_NORMAL,
            zero: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_ZERO,
            unmeasured: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_UNMEASURED,
            cpuid: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_CPUID,
            secrets: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_SECRETS,
            vmsa: mshv_bindings::hv_isolated_page_type_HV_ISOLATED_PAGE_TYPE_VMSA,
        },
        #[cfg(feature = "kvm")]
        HypervisorType::Kvm => PageTypeConfig {
            isolated_page_size_4kb: HV_PAGE_SIZE as u32,
            normal: KVM_SNP_PAGE_TYPE_NORMAL,
            zero: KVM_SNP_PAGE_TYPE_ZERO,
            unmeasured: KVM_SNP_PAGE_TYPE_UNMEASURED,
            cpuid: KVM_SNP_PAGE_TYPE_CPUID,
            secrets: KVM_SNP_PAGE_TYPE_SECRETS,
            vmsa: KVM_SNP_PAGE_TYPE_VMSA,
        },
    };

    let mut loaded_info: Box<IgvmLoadedInfo> = Box::default();
    let command_line = CString::new(cmdline).map_err(Error::InvalidCommandLine)?;
    let memory = memory_manager.lock().as_ref().unwrap().guest_memory();
    let mut gpas: Vec<GpaPages> = Vec::new();
    let proc_count = cpu_manager.lock().unwrap().vcpus().len() as u32;

    #[cfg(feature = "sev_snp")]
    let mut host_data_contents = [0; 32];
    #[cfg(feature = "sev_snp")]
    if let Some(host_data_str) = host_data {
        hex::decode_to_slice(host_data_str, &mut host_data_contents as &mut [u8])
            .map_err(Error::FailedToDecodeHostData)?;
    }

    #[cfg(feature = "sev_snp")]
    let sev_snp_enabled = cpu_manager.lock().unwrap().sev_snp_enabled();
    let mask = match &igvm_file.platforms()[0] {
        IgvmPlatformHeader::SupportedPlatform(info) => {
            debug_assert!(info.platform_type == IgvmPlatformType::SEV_SNP);
            info.compatibility_mask
        }
    };

    let mut loader = Loader::new(memory);

    let mut parameter_areas: HashMap<u32, ParameterAreaState> = HashMap::new();
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    let measured_boot_hash_block = if hypervisor_type == HypervisorType::Kvm {
        measured_boot
            .as_ref()
            .map(|measured_boot| {
                measured_boot
                    .build_hash_block()
                    .map_err(Error::MeasuredBoot)
            })
            .transpose()?
    } else {
        None
    };
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    let measured_boot_hash_page_base = SEV_HASH_BLOCK_ADDRESS / HV_PAGE_SIZE;
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    let measured_boot_hash_offset = (SEV_HASH_BLOCK_ADDRESS % HV_PAGE_SIZE) as usize;
    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    let mut measured_boot_hash_block_inserted = measured_boot_hash_block.is_none();

    for header in igvm_file.directives() {
        debug_assert!(header.compatibility_mask().unwrap_or(mask) & mask == mask);

        match header {
            IgvmDirectiveHeader::PageData {
                gpa,
                compatibility_mask: _,
                flags,
                data_type,
                data,
            } => {
                // With the "mshv" feature enabled, `data` is modified via pointer, so `data` needs
                // to be mutable.
                #[cfg(feature = "mshv")]
                let mut data = data.clone();

                debug_assert!((data.len() as u64).is_multiple_of(HV_PAGE_SIZE));

                // TODO: only 4k or empty page data supported right now
                assert!(data.len() as u64 == HV_PAGE_SIZE || data.is_empty());

                let acceptance = match *data_type {
                    IgvmPageDataType::NORMAL => {
                        if flags.unmeasured() {
                            gpas.push(GpaPages {
                                gpa: *gpa,
                                page_type: page_types.unmeasured,
                                page_size: page_types.isolated_page_size_4kb,
                            });
                            BootPageAcceptance::ExclusiveUnmeasured
                        } else {
                            let page_type = match hypervisor_type {
                                #[cfg(feature = "kvm")]
                                HypervisorType::Kvm if data.is_empty() => page_types.zero,
                                _ => page_types.normal,
                            };
                            gpas.push(GpaPages {
                                gpa: *gpa,
                                page_type,
                                page_size: page_types.isolated_page_size_4kb,
                            });
                            BootPageAcceptance::Exclusive
                        }
                    }
                    IgvmPageDataType::SECRETS => {
                        gpas.push(GpaPages {
                            gpa: *gpa,
                            page_type: page_types.secrets,
                            page_size: page_types.isolated_page_size_4kb,
                        });
                        BootPageAcceptance::SecretsPage
                    }
                    IgvmPageDataType::CPUID_DATA => {
                        #[cfg(feature = "mshv")]
                        if hypervisor_type == HypervisorType::Mshv {
                            // SAFETY: CPUID is readonly
                            unsafe {
                                let cpuid_page_p = data.as_mut_ptr().cast();
                                let cpuid_page: &mut hv_psp_cpuid_page = &mut *cpuid_page_p;
                                for i in 0..cpuid_page.count {
                                    let leaf = cpuid_page.cpuid_leaf_info[i as usize];
                                    let mut in_leaf = cpu_manager
                                        .lock()
                                        .unwrap()
                                        .get_cpuid_leaf(
                                            0,
                                            leaf.eax_in,
                                            leaf.ecx_in,
                                            leaf.xfem_in,
                                            leaf.xss_in,
                                        )
                                        .unwrap();
                                    if leaf.eax_in == 1 {
                                        in_leaf[2] &= 0x7FFFFFFF;
                                    }
                                    cpuid_page.cpuid_leaf_info[i as usize].eax_out = in_leaf[0];
                                    cpuid_page.cpuid_leaf_info[i as usize].ebx_out = in_leaf[1];
                                    cpuid_page.cpuid_leaf_info[i as usize].ecx_out = in_leaf[2];
                                    cpuid_page.cpuid_leaf_info[i as usize].edx_out = in_leaf[3];
                                }
                            }
                        }
                        gpas.push(GpaPages {
                            gpa: *gpa,
                            page_type: page_types.cpuid,
                            page_size: page_types.isolated_page_size_4kb,
                        });
                        BootPageAcceptance::CpuidPage
                    }
                    // TODO: other data types SNP / TDX only, unsupported
                    _ => todo!("unsupported IgvmPageDataType"),
                };

                #[allow(unused_mut)]
                let mut imported_page = false;
                #[cfg(all(feature = "kvm", feature = "sev_snp"))]
                if hypervisor_type == HypervisorType::Kvm
                    && *data_type == IgvmPageDataType::CPUID_DATA
                {
                    let mut new_cp = SnpCpuidInfo::new_zeroed();

                    let entries = cpu_manager.lock().unwrap().common_cpuid();
                    let cp_count = cmp::min(SNP_CPUID_LIMIT as usize, entries.len());
                    // TODO: Filter cpuid rather than truncate
                    for (i, entry) in entries.iter().enumerate().take(cp_count) {
                        new_cp.entries[i].eax_in = entry.function;
                        new_cp.entries[i].ecx_in = entry.index;
                        new_cp.entries[i].eax = entry.eax;
                        new_cp.entries[i].ebx = entry.ebx;
                        new_cp.entries[i].ecx = entry.ecx;
                        new_cp.entries[i].edx = entry.edx;
                        /*
                         * Guest kernels will calculate EBX themselves using the 0xD
                         * subfunctions corresponding to the individual XSAVE areas, so only
                         * encode the base XSAVE size in the initial leaves, corresponding
                         * to the initial XCR0=1 state. (https://tinyurl.com/qemu-cpuid)
                         */
                        if new_cp.entries[i].eax_in == 0xd
                            && (new_cp.entries[i].ecx_in == 0x0 || new_cp.entries[i].ecx_in == 0x1)
                        {
                            new_cp.entries[i].ebx = 0x240;
                            new_cp.entries[i].xcr0_in = 1;
                            new_cp.entries[i].xss_in = 0;
                        }

                        // KVM SNP launch may reject a CPUID page with bits it intends
                        // to sanitize internally. Pre-clearing the known unsafe bits keeps
                        // the CPUID page stable across launch updates.
                        match (new_cp.entries[i].eax_in, new_cp.entries[i].ecx_in) {
                            (0x1, 0x0) => {
                                new_cp.entries[i].ecx &= !(1 << 24);
                            }
                            (0x7, 0x0) => {
                                new_cp.entries[i].ebx &= !0x2;
                                new_cp.entries[i].edx = 0;
                            }
                            (0x80000008, 0x0) => {
                                new_cp.entries[i].ebx &= !0x0200_0000;
                            }
                            (0x80000021, 0x0) => {
                                new_cp.entries[i].ecx = 0;
                            }
                            _ => {}
                        }
                    }
                    new_cp.count = cp_count as u32;
                    loader
                        .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, new_cp.as_mut_bytes())
                        .map_err(Error::Loader)?;
                    imported_page = true;
                }
                #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
                if let Some(hash_block) = measured_boot_hash_block.as_ref().filter(|_| {
                    !imported_page && gpa / HV_PAGE_SIZE == measured_boot_hash_page_base
                }) {
                    let mut page = if data.is_empty() {
                        vec![0; HV_PAGE_SIZE as usize]
                    } else {
                        let mut page = data.clone();
                        page.resize(HV_PAGE_SIZE as usize, 0);
                        page
                    };
                    // If a data page from the bootloader contains this range,
                    // we need to ensure that the measured boot table is injected
                    // prior to importing the pages
                    page[measured_boot_hash_offset
                        ..measured_boot_hash_offset + SEV_HASH_BLOCK_SIZE]
                        .copy_from_slice(&hash_block[..SEV_HASH_BLOCK_SIZE]);
                    loader
                        .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, &page)
                        .map_err(Error::Loader)?;
                    measured_boot_hash_block_inserted = true;
                    imported_page = true;
                    if let Some(last) = gpas.last_mut() {
                        debug_assert_eq!(last.gpa, *gpa);
                        last.page_type = page_types.normal;
                    }
                }
                if !imported_page {
                    loader
                        .import_pages(gpa / HV_PAGE_SIZE, 1, acceptance, data.as_ref())
                        .map_err(Error::Loader)?;
                }
            }
            IgvmDirectiveHeader::ParameterArea {
                number_of_bytes,
                parameter_area_index,
                initial_data,
            } => {
                debug_assert!(number_of_bytes % HV_PAGE_SIZE == 0);
                debug_assert!(
                    initial_data.is_empty() || initial_data.len() as u64 == *number_of_bytes
                );

                // Allocate a new parameter area. It must not be already used.
                if parameter_areas
                    .insert(
                        *parameter_area_index,
                        ParameterAreaState::Allocated {
                            data: initial_data.clone(),
                            max_size: *number_of_bytes,
                        },
                    )
                    .is_some()
                {
                    panic!("IgvmFile is not valid, invalid invariant");
                }
            }
            IgvmDirectiveHeader::VpCount(info) => {
                import_parameter(&mut parameter_areas, info, proc_count.as_bytes())?;
            }
            IgvmDirectiveHeader::MmioRanges(_info) => {
                todo!("unsupported IgvmPageDataType");
            }
            IgvmDirectiveHeader::MemoryMap(_info) =>
            {
                #[cfg(feature = "sev_snp")]
                if sev_snp_enabled {
                    let guest_mem = memory_manager.lock().unwrap().boot_guest_memory();
                    let memory_map = generate_memory_map(&guest_mem)?;
                    import_parameter(&mut parameter_areas, _info, memory_map.as_bytes())?;
                } else {
                    todo!("Not implemented");
                }
            }
            IgvmDirectiveHeader::CommandLine(info) => {
                import_parameter(&mut parameter_areas, info, command_line.as_bytes_with_nul())?;
            }
            IgvmDirectiveHeader::RequiredMemory {
                gpa,
                compatibility_mask: _,
                number_of_bytes,
                vtl2_protectable: _,
            } => {
                let memory_type = StartupMemoryType::Ram;
                loaded_info.gpas.push(*gpa);
                loader
                    .verify_startup_memory_available(
                        gpa / HV_PAGE_SIZE,
                        *number_of_bytes as u64 / HV_PAGE_SIZE,
                        memory_type,
                    )
                    .map_err(Error::Loader)?;
            }
            IgvmDirectiveHeader::SnpVpContext {
                gpa,
                compatibility_mask: _,
                vp_index,
                vmsa,
            } => {
                assert_eq!(gpa % HV_PAGE_SIZE, 0);
                let mut data: [u8; HV_PAGE_SIZE as usize] = [0; HV_PAGE_SIZE as usize];
                let len = size_of::<SevVmsa>();
                loaded_info.vmsa_gpa = *gpa;
                loaded_info.vmsa = **vmsa;
                // Only supported for index zero
                if *vp_index == 0 {
                    data[..len].copy_from_slice(vmsa.as_bytes());
                    loader
                        .import_pages(gpa / HV_PAGE_SIZE, 1, BootPageAcceptance::VpContext, &data)
                        .map_err(Error::Loader)?;
                }

                // Set vCPU initial register state from VMSA before SNP_LAUNCH_FINISH
                #[cfg(all(feature = "kvm", feature = "sev_snp"))]
                if hypervisor_type == HypervisorType::Kvm {
                    let vcpus = cpu_manager.lock().unwrap().vcpus();
                    for vcpu in vcpus {
                        let vcpu_locked = vcpu.lock().unwrap();
                        let vcpu_id: u16 = vcpu_locked.id().parse().unwrap();
                        if vcpu_id == *vp_index {
                            vcpu_locked
                                .setup_sev_snp_regs(loaded_info.vmsa)
                                .map_err(Error::SetVmsa)?;
                            vcpu_locked
                                .set_sev_control_register(0)
                                .map_err(Error::SetVmsa)?;
                        }
                    }
                }

                gpas.push(GpaPages {
                    gpa: *gpa,
                    page_type: page_types.vmsa,
                    page_size: page_types.isolated_page_size_4kb,
                });
            }
            IgvmDirectiveHeader::SnpIdBlock {
                compatibility_mask,
                author_key_enabled,
                reserved,
                ld,
                family_id,
                image_id,
                version,
                guest_svn,
                id_key_algorithm,
                author_key_algorithm,
                id_key_signature,
                id_public_key,
                author_key_signature,
                author_public_key,
            } => {
                if loaded_info.has_snp_id_block {
                    return Err(Error::DuplicateSnpIdBlock);
                }
                loaded_info.snp_id_block.compatibility_mask = *compatibility_mask;
                loaded_info.snp_id_block.author_key_enabled = *author_key_enabled;
                loaded_info.snp_id_block.reserved = *reserved;
                loaded_info.snp_id_block.ld = *ld;
                loaded_info.snp_id_block.family_id = *family_id;
                loaded_info.snp_id_block.image_id = *image_id;
                loaded_info.snp_id_block.version = *version;
                loaded_info.snp_id_block.guest_svn = *guest_svn;
                loaded_info.snp_id_block.id_key_algorithm = *id_key_algorithm;
                loaded_info.snp_id_block.author_key_algorithm = *author_key_algorithm;
                loaded_info.snp_id_block.id_key_signature = **id_key_signature;
                loaded_info.snp_id_block.id_public_key = **id_public_key;
                loaded_info.snp_id_block.author_key_signature = **author_key_signature;
                loaded_info.snp_id_block.author_public_key = **author_public_key;
                loaded_info.has_snp_id_block = true;
            }
            IgvmDirectiveHeader::X64VbsVpContext {
                vtl: _,
                registers: _,
                compatibility_mask: _,
            } => {
                todo!("VbsVpContext not supported");
            }
            IgvmDirectiveHeader::VbsMeasurement { .. } => {
                todo!("VbsMeasurement not supported")
            }
            IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT {
                gpa,
                compatibility_mask: _,
                parameter_area_index,
            }) => {
                debug_assert!(gpa % HV_PAGE_SIZE == 0);

                let area = parameter_areas
                    .get_mut(parameter_area_index)
                    .expect("igvmfile should be valid");
                #[cfg(feature = "kvm")]
                let page_count = match area {
                    ParameterAreaState::Allocated { max_size, .. } => *max_size / HV_PAGE_SIZE,
                    ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"),
                };
                match area {
                    ParameterAreaState::Allocated { data, max_size } => {
                        #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
                        if measured_boot_hash_block.is_some() {
                            let region_end = *gpa + *max_size;
                            let hash_end = SEV_HASH_BLOCK_ADDRESS + SEV_HASH_BLOCK_SIZE as u64;
                            if *gpa <= SEV_HASH_BLOCK_ADDRESS && hash_end <= region_end {
                                // In the case of parameter being inserted where the kernel hashes table lies,
                                // we should reject the igvmfile since it would interfere with the launch digest
                                return Err(Error::MeasuredBootHashOverlap {
                                    region_start: *gpa,
                                    region_end,
                                    hash_start: SEV_HASH_BLOCK_ADDRESS,
                                    hash_end,
                                });
                            }
                        }

                        loader
                            .import_pages(
                                gpa / HV_PAGE_SIZE,
                                *max_size / HV_PAGE_SIZE,
                                BootPageAcceptance::ExclusiveUnmeasured,
                                data,
                            )
                            .map_err(Error::Loader)?;
                    }
                    ParameterAreaState::Inserted => panic!("igvmfile is invalid, multiple insert"),
                }
                *area = ParameterAreaState::Inserted;
                #[cfg(feature = "kvm")]
                if hypervisor_type == HypervisorType::Kvm {
                    for page_index in 0..page_count {
                        gpas.push(GpaPages {
                            gpa: *gpa + page_index * HV_PAGE_SIZE,
                            page_type: page_types.unmeasured,
                            page_size: page_types.isolated_page_size_4kb,
                        });
                    }
                }
                #[cfg(feature = "mshv")]
                if hypervisor_type == HypervisorType::Mshv {
                    gpas.push(GpaPages {
                        gpa: *gpa,
                        page_type: page_types.unmeasured,
                        page_size: page_types.isolated_page_size_4kb,
                    });
                }
            }
            IgvmDirectiveHeader::ErrorRange { .. } => {
                todo!("Error Range not supported")
            }
            _ => {
                todo!("Header not supported!!")
            }
        }
    }

    #[cfg(all(feature = "kvm", feature = "sev_snp", feature = "fw_cfg"))]
    if let Some(hash_block) = measured_boot_hash_block.as_ref()
        && !measured_boot_hash_block_inserted
    {
        // Fallback to adding kernel hashes after importing if the page data wasn't found previously
        let mut page = vec![0u8; HV_PAGE_SIZE as usize];
        page[measured_boot_hash_offset..measured_boot_hash_offset + SEV_HASH_BLOCK_SIZE]
            .copy_from_slice(&hash_block[..SEV_HASH_BLOCK_SIZE]);
        loader
            .import_pages(
                measured_boot_hash_page_base,
                1,
                BootPageAcceptance::Exclusive,
                &page,
            )
            .map_err(Error::Loader)?;
        gpas.push(GpaPages {
            gpa: measured_boot_hash_page_base * HV_PAGE_SIZE,
            page_type: page_types.normal,
            page_size: page_types.isolated_page_size_4kb,
        });
    }

    #[cfg(feature = "sev_snp")]
    if sev_snp_enabled {
        memory_manager
            .lock()
            .unwrap()
            .allocate_address_space()
            .map_err(Error::MemoryManager)?;
        use std::time::Instant;

        let mut now = Instant::now();

        #[cfg(feature = "mshv")]
        {
            // KVM preserves original IGVM ordering because the SNP launch digest
            // is order-sensitive. MSHV sorts by GPA for fewer hypercalls.
            if hypervisor_type == HypervisorType::Mshv {
                gpas.sort_by_key(|a| a.gpa);
            }
        }

        let gpas_grouped = gpas
            .iter()
            .fold(Vec::<Vec<GpaPages>>::new(), |mut acc, gpa| {
                #[cfg(feature = "kvm")]
                let same_page_size = hypervisor_type != HypervisorType::Kvm
                    || acc
                        .last()
                        .is_some_and(|last_vec| last_vec[0].page_size == gpa.page_size);
                #[cfg(not(feature = "kvm"))]
                let same_page_size = true;
                if let Some(last_vec) = acc.last_mut()
                    && last_vec[0].page_type == gpa.page_type
                    && same_page_size
                {
                    last_vec.push(*gpa);
                    return acc;
                }
                acc.push(vec![*gpa]);
                acc
            });

        // Import pages as groups of PFNs to reduce hypercalls.
        for group in gpas_grouped.iter() {
            info!(
                "Importing {} page{}",
                group.len(),
                if group.len() > 1 { "s" } else { "" }
            );
            // Convert the gpa into PFN as MSHV hypercall takes an array
            // of PFN for importing the isolated pages
            let pfns: Vec<u64> = group
                .iter()
                .map(|gpa| gpa.gpa >> ISOLATED_PAGE_SHIFT)
                .collect();
            let guest_memory = memory_manager.lock().unwrap().guest_memory().memory();
            let uaddrs: Vec<_> = group
                .iter()
                .map(|gpa| {
                    let guest_region_mmap = guest_memory.to_region_addr(GuestAddress(gpa.gpa));
                    let uaddr_base = guest_region_mmap.unwrap().0.as_ptr() as u64;
                    let uaddr_offset: u64 = guest_region_mmap.unwrap().1.0;
                    uaddr_base + uaddr_offset
                })
                .collect();
            #[cfg(feature = "kvm")]
            let page_type = group[0].page_type;
            #[cfg(feature = "kvm")]
            let mut new_cp = SnpCpuidInfo::new_zeroed();
            #[cfg(feature = "kvm")]
            if hypervisor_type == HypervisorType::Kvm {
                let _ = guest_memory.read(new_cp.as_mut_bytes(), GuestAddress(group[0].gpa));
            }
            let import_result = memory_manager
                .lock()
                .unwrap()
                .vm
                .import_isolated_pages(
                    group[0].page_type,
                    page_types.isolated_page_size_4kb,
                    &pfns,
                    &uaddrs,
                )
                .map_err(Error::ImportIsolatedPages);
            #[cfg(feature = "kvm")]
            if hypervisor_type == HypervisorType::Kvm
                && import_result.is_err()
                && page_type == page_types.cpuid
            {
                // When we import the CPUID page, the firmware will change any cpuid fns that
                // could lead to an insecure guest, we must then make sure to import the updated cpuid
                // https://elixir.bootlin.com/linux/v6.11/source/arch/x86/kvm/svm/sev.c#L2322
                let mut updated_cp = SnpCpuidInfo::new_zeroed();
                let _ = guest_memory.read(updated_cp.as_mut_bytes(), GuestAddress(group[0].gpa));
                for (set, got) in iter::zip(new_cp.entries.iter(), updated_cp.entries.iter()) {
                    if set != got {
                        error!("Set cpuid fn: {set:#x?}, but firmware expects: {got:#x?}");
                    }
                }
                memory_manager
                    .lock()
                    .unwrap()
                    .vm
                    .import_isolated_pages(
                        group[0].page_type,
                        page_types.isolated_page_size_4kb,
                        &pfns,
                        &uaddrs,
                    )
                    .map_err(Error::ImportIsolatedPages)?;
                continue;
            }
            import_result?;
        }

        info!(
            "Time it took to for hashing pages {:.2?} and page_count {:?}",
            now.elapsed(),
            gpas.len()
        );

        #[cfg(feature = "mshv")]
        let id_block_enabled = if hypervisor_type == HypervisorType::Mshv {
            1
        } else {
            u8::from(loaded_info.has_snp_id_block)
        };
        #[cfg(not(feature = "mshv"))]
        let id_block_enabled = u8::from(loaded_info.has_snp_id_block);

        #[cfg(feature = "mshv")]
        let auth_key_enabled = if hypervisor_type == HypervisorType::Mshv {
            0
        } else {
            loaded_info.snp_id_block.author_key_enabled
        };
        #[cfg(not(feature = "mshv"))]
        let auth_key_enabled = loaded_info.snp_id_block.author_key_enabled;

        now = Instant::now();
        // Call Complete Isolated Import since we are done importing isolated pages
        memory_manager
            .lock()
            .unwrap()
            .vm
            .complete_isolated_import(
                loaded_info.snp_id_block,
                host_data_contents,
                id_block_enabled,
                auth_key_enabled,
            )
            .map_err(Error::CompleteIsolatedImport)?;

        info!(
            "Time it took to for launch complete command  {:.2?}",
            now.elapsed()
        );
    }

    debug!("Dumping the contents of VMSA page: {:x?}", loaded_info.vmsa);
    Ok(loaded_info)
}
