igvm/
lib.rs

1// SPDX-License-Identifier: MIT
2//
3// Copyright (c) Microsoft Corporation.
4
5//! Provides a Rust implementation of an Independent Guest Virtual Machine
6//! (IGVM) file format parser, with the specification defined by the
7//! [`igvm_defs`] crate.
8//!
9//! This can be used to build or read IGVM files from their binary format. Note
10//! that this parser may not implement all the specified structure types or
11//! semantics defined in the IGVM file format.
12
13#![deny(unsafe_code)]
14// Enables the `doc_cfg` feature when the `docsrs` configuration attribute
15// is defined.
16#![cfg_attr(docsrs, feature(doc_cfg))]
17
18use hv_defs::HvArm64RegisterName;
19use hv_defs::HvX64RegisterName;
20use hv_defs::Vtl;
21use igvm_defs::*;
22use page_table::PageTableRelocationBuilder;
23use range_map_vec::RangeMap;
24use registers::AArch64Register;
25use registers::X86Register;
26use snp_defs::SevVmsa;
27use std::collections::BTreeMap;
28use std::collections::BTreeSet;
29use std::collections::HashMap;
30use std::fmt;
31use std::mem::size_of;
32use std::mem::size_of_val;
33use thiserror::Error;
34use zerocopy::FromBytes;
35use zerocopy::FromZeros;
36use zerocopy::Immutable;
37use zerocopy::IntoBytes;
38use zerocopy::KnownLayout;
39
40#[cfg(feature = "igvm-c")]
41pub mod c_api;
42
43pub mod hv_defs;
44pub mod page_table;
45pub mod registers;
46pub mod snp_defs;
47
48// Define type alias for no padding u64.
49#[allow(non_camel_case_types)]
50type u64_le = zerocopy::U64<zerocopy::LittleEndian>;
51
52/// The guest isolation type of the platform.
53#[derive(Debug, PartialEq, Eq)]
54pub enum IsolationType {
55    /// This guest is not isolated, and is the native type [`igvm_defs::IgvmPlatformType::NATIVE`].
56    NotIsolated,
57    /// This guest is isolated with VBS.
58    Vbs,
59    /// This guest is isolated with SNP (physical or emulated).
60    Snp,
61    /// This guest is isolated with TDX (physical or emulated).
62    Tdx,
63    /// This guest is isolated with SEV (physical or emulated).
64    Sev,
65    /// This guest is isolated with SEV-ES (physical or emulated).
66    SevEs,
67}
68
69impl From<IsolationType> for igvm_defs::IgvmPlatformType {
70    fn from(typ: IsolationType) -> Self {
71        match typ {
72            IsolationType::NotIsolated => IgvmPlatformType::NATIVE,
73            IsolationType::Vbs => IgvmPlatformType::VSM_ISOLATION,
74            IsolationType::Snp => IgvmPlatformType::SEV_SNP,
75            IsolationType::Tdx => IgvmPlatformType::TDX,
76            IsolationType::Sev => IgvmPlatformType::SEV,
77            IsolationType::SevEs => IgvmPlatformType::SEV_ES,
78        }
79    }
80}
81
82/// Align x up to the next multiple of 8.
83fn align_8(x: usize) -> usize {
84    (x + 7) & !7
85}
86
87/// Helper function to parse the given type from a byte slice, updating the
88/// passed in slice with the remaining bytes left.
89///
90/// On failure, returns [`BinaryHeaderError::InvalidVariableHeaderSize`].
91fn read_header<T: FromBytes + Immutable + KnownLayout>(
92    bytes: &mut &[u8],
93) -> Result<T, BinaryHeaderError> {
94    T::read_from_prefix(bytes)
95        .map_err(|_| BinaryHeaderError::InvalidVariableHeaderSize) // todo: zerocopy: map_err
96        .map(|(header, remaining)| {
97            *bytes = remaining;
98            header
99        })
100}
101
102/// Helper function to append a given binary header to a variable header
103/// section.
104fn append_header<T: IntoBytes + Immutable + KnownLayout>(
105    header: &T,
106    header_type: IgvmVariableHeaderType,
107    variable_headers: &mut Vec<u8>,
108) {
109    let header = header.as_bytes();
110
111    // Append the fixed header first. The fixed header must correctly describe
112    // the length of the structure, but the structure may not be aligned to 8
113    // bytes. Variable headers must be 8 byte aligned as defined by the spec, so
114    // insert any padding bytes as needed.
115    let fixed_header = IGVM_VHS_VARIABLE_HEADER {
116        typ: header_type,
117        length: header
118            .len()
119            .try_into()
120            .expect("header data must fit in u32"),
121    };
122
123    let align_up_iter = std::iter::repeat_n(&0u8, align_8(header.len()) - header.len());
124
125    variable_headers.extend_from_slice(fixed_header.as_bytes());
126    variable_headers.extend_from_slice(header);
127    variable_headers.extend(align_up_iter);
128
129    debug_assert!(variable_headers.len() % 8 == 0);
130}
131
132/// The serializer for headers with file data. This serializer deduplicates data
133/// seen before, by handing out a file offset to already serialized data
134/// sections that match.
135pub struct FileDataSerializer {
136    /// The current file_offset for the next section of data returned.
137    file_offset: usize,
138    /// The serialized file data.
139    file_data: Vec<u8>,
140    /// The map of file data to offset mapping data seen before.
141    file_data_map: HashMap<Vec<u8>, u32>,
142}
143
144impl FileDataSerializer {
145    /// Create a new instance of the file data serializer, with the given
146    /// starting file_offset.
147    pub fn new(file_offset: usize) -> Self {
148        Self {
149            file_offset,
150            file_data: Vec::new(),
151            file_data_map: HashMap::new(),
152        }
153    }
154
155    /// Take the serialized file data as a `Vec<u8>`.
156    pub fn take(self) -> Vec<u8> {
157        self.file_data
158    }
159
160    /// Write the given file_data to the serializer. Returns the file_offset to
161    /// be encoded into the header.
162    pub fn write_file_data(&mut self, file_data: &[u8]) -> u32 {
163        if let Some(offset) = self.file_data_map.get(file_data) {
164            return *offset;
165        }
166
167        let offset = self.file_offset;
168        self.file_offset += file_data.len();
169        self.file_data.extend_from_slice(file_data);
170
171        let offset: u32 = offset.try_into().expect("file data offset must fit in u32");
172        self.file_data_map.insert(file_data.to_vec(), offset);
173
174        offset
175    }
176}
177
178/// Represents a structure in an IGVM variable header section, platform
179/// structure.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub enum IgvmPlatformHeader {
182    SupportedPlatform(IGVM_VHS_SUPPORTED_PLATFORM),
183}
184
185impl IgvmPlatformHeader {
186    /// Get the in file variable header size of the given type.
187    fn header_size(&self) -> usize {
188        let additional = match self {
189            IgvmPlatformHeader::SupportedPlatform(platform) => size_of_val(platform),
190        };
191
192        size_of::<IGVM_VHS_VARIABLE_HEADER>() + additional
193    }
194
195    /// Get the [`IgvmVariableHeaderType`] for the platform header.
196    #[cfg(feature = "igvm-c")]
197    #[cfg_attr(docsrs, doc(cfg(feature = "igvm-c")))]
198    fn header_type(&self) -> IgvmVariableHeaderType {
199        match self {
200            IgvmPlatformHeader::SupportedPlatform(_) => {
201                IgvmVariableHeaderType::IGVM_VHT_SUPPORTED_PLATFORM
202            }
203        }
204    }
205
206    /// Checks if this header contains valid state.
207    fn validate(&self) -> Result<(), BinaryHeaderError> {
208        match self {
209            IgvmPlatformHeader::SupportedPlatform(info) => {
210                // Only one compatibility_mask value can be set.
211                if info.compatibility_mask.count_ones() != 1 {
212                    return Err(BinaryHeaderError::InvalidCompatibilityMask);
213                }
214
215                // Highest vtl must be 0 or 2.
216                if info.highest_vtl != 0 && info.highest_vtl != 2 {
217                    return Err(BinaryHeaderError::InvalidVtl);
218                }
219
220                // Platform type must be valid.
221                match info.platform_type {
222                    IgvmPlatformType::NATIVE => {
223                        if info.platform_version != IGVM_NATIVE_PLATFORM_VERSION {
224                            return Err(BinaryHeaderError::InvalidPlatformVersion);
225                        }
226                        if info.shared_gpa_boundary != 0 {
227                            return Err(BinaryHeaderError::InvalidSharedGpaBoundary);
228                        }
229                    }
230                    IgvmPlatformType::VSM_ISOLATION => {
231                        if info.platform_version != IGVM_VSM_ISOLATION_PLATFORM_VERSION {
232                            return Err(BinaryHeaderError::InvalidPlatformVersion);
233                        }
234
235                        if info.shared_gpa_boundary != 0 {
236                            return Err(BinaryHeaderError::InvalidSharedGpaBoundary);
237                        }
238                    }
239                    IgvmPlatformType::SEV_SNP => {
240                        if info.platform_version != IGVM_SEV_SNP_PLATFORM_VERSION {
241                            return Err(BinaryHeaderError::InvalidPlatformVersion);
242                        }
243
244                        // TODO: shared gpa boundary req?
245                    }
246
247                    IgvmPlatformType::TDX => {
248                        if info.platform_version != IGVM_TDX_PLATFORM_VERSION {
249                            return Err(BinaryHeaderError::InvalidPlatformVersion);
250                        }
251                        // TODO: shared gpa boundary req?
252                    }
253                    IgvmPlatformType::SEV => {
254                        if info.platform_version != IGVM_SEV_PLATFORM_VERSION {
255                            return Err(BinaryHeaderError::InvalidPlatformVersion);
256                        }
257                        // TODO: shared gpa boundary req?
258                    }
259                    IgvmPlatformType::SEV_ES => {
260                        if info.platform_version != IGVM_SEV_ES_PLATFORM_VERSION {
261                            return Err(BinaryHeaderError::InvalidPlatformVersion);
262                        }
263                        // TODO: shared gpa boundary req?
264                    }
265                    _ => {
266                        return Err(BinaryHeaderError::InvalidPlatformType);
267                    }
268                }
269
270                Ok(())
271            }
272        }
273    }
274
275    /// Create a new [`IgvmPlatformHeader`] from the binary slice provided.
276    /// Returns the remaining slice of unused bytes.
277    fn new_from_binary_split(
278        mut variable_headers: &[u8],
279    ) -> Result<(Self, &[u8]), BinaryHeaderError> {
280        let header = read_header::<IGVM_VHS_VARIABLE_HEADER>(&mut variable_headers)?;
281
282        if header.typ == IgvmVariableHeaderType::IGVM_VHT_SUPPORTED_PLATFORM
283            && header.length == size_of::<IGVM_VHS_SUPPORTED_PLATFORM>() as u32
284        {
285            let header = IgvmPlatformHeader::SupportedPlatform(read_header(&mut variable_headers)?);
286            header.validate()?;
287
288            Ok((header, variable_headers))
289        } else {
290            Err(BinaryHeaderError::InvalidVariableHeaderType)
291        }
292    }
293
294    /// Write the binary representation of the header and any associated file
295    /// data to the supplied variable_headers and file data vectors.
296    /// file_data_offset points to the start of the data section to be encoded
297    /// in the variable header if this data has a file data component.
298    #[cfg(feature = "igvm-c")]
299    #[cfg_attr(docsrs, doc(cfg(feature = "igvm-c")))]
300    fn write_binary_header(&self, variable_headers: &mut Vec<u8>) -> Result<(), BinaryHeaderError> {
301        // Only serialize this header if valid.
302        self.validate()?;
303
304        match self {
305            IgvmPlatformHeader::SupportedPlatform(platform) => {
306                append_header(
307                    platform,
308                    IgvmVariableHeaderType::IGVM_VHT_SUPPORTED_PLATFORM,
309                    variable_headers,
310                );
311            }
312        }
313        Ok(())
314    }
315}
316
317/// Represents a structure in an IGVM variable header section, initialization
318/// structure.
319#[derive(Debug, Clone, PartialEq, Eq)]
320pub enum IgvmInitializationHeader {
321    GuestPolicy {
322        policy: u64,
323        compatibility_mask: u32,
324    },
325    RelocatableRegion {
326        compatibility_mask: u32,
327        relocation_alignment: u64,
328        relocation_region_gpa: u64,
329        relocation_region_size: u64,
330        minimum_relocation_gpa: u64,
331        maximum_relocation_gpa: u64,
332        is_vtl2: bool,
333        apply_rip_offset: bool,
334        apply_gdtr_offset: bool,
335        vp_index: u16,
336        vtl: Vtl,
337    },
338    /// Represents an [IGVM_VHS_PAGE_TABLE_RELOCATION].
339    PageTableRelocationRegion {
340        compatibility_mask: u32,
341        gpa: u64,
342        size: u64,
343        used_size: u64,
344        vp_index: u16,
345        vtl: Vtl,
346    },
347}
348
349impl IgvmInitializationHeader {
350    /// Get the in file variable header size of the given type.
351    fn header_size(&self) -> usize {
352        let additional = match self {
353            IgvmInitializationHeader::GuestPolicy { .. } => size_of::<IGVM_VHS_GUEST_POLICY>(),
354            IgvmInitializationHeader::RelocatableRegion { .. } => {
355                size_of::<IGVM_VHS_RELOCATABLE_REGION>()
356            }
357            IgvmInitializationHeader::PageTableRelocationRegion { .. } => {
358                size_of::<IGVM_VHS_PAGE_TABLE_RELOCATION>()
359            }
360        };
361
362        size_of::<IGVM_VHS_VARIABLE_HEADER>() + additional
363    }
364
365    /// Get the [`IgvmVariableHeaderType`] for the initialization header.
366    #[cfg(feature = "igvm-c")]
367    #[cfg_attr(docsrs, doc(cfg(feature = "igvm-c")))]
368    fn header_type(&self) -> IgvmVariableHeaderType {
369        match self {
370            IgvmInitializationHeader::GuestPolicy { .. } => {
371                IgvmVariableHeaderType::IGVM_VHT_GUEST_POLICY
372            }
373            IgvmInitializationHeader::RelocatableRegion { .. } => {
374                IgvmVariableHeaderType::IGVM_VHT_RELOCATABLE_REGION
375            }
376            IgvmInitializationHeader::PageTableRelocationRegion { .. } => {
377                IgvmVariableHeaderType::IGVM_VHT_PAGE_TABLE_RELOCATION_REGION
378            }
379        }
380    }
381
382    /// Checks if this header contains valid state.
383    fn validate(&self) -> Result<(), BinaryHeaderError> {
384        match self {
385            IgvmInitializationHeader::GuestPolicy {
386                policy: _,
387                compatibility_mask: _,
388            } => {
389                // TODO: check policy bits?
390                Ok(())
391            }
392            IgvmInitializationHeader::RelocatableRegion {
393                compatibility_mask: _,
394                relocation_alignment,
395                relocation_region_gpa,
396                relocation_region_size,
397                minimum_relocation_gpa,
398                maximum_relocation_gpa,
399                is_vtl2: _,
400                apply_rip_offset: _,
401                apply_gdtr_offset: _,
402                vp_index: _,
403                vtl: _,
404            } => {
405                if relocation_region_size % PAGE_SIZE_4K != 0 {
406                    return Err(BinaryHeaderError::RelocationSize);
407                }
408
409                if relocation_alignment % PAGE_SIZE_4K != 0 {
410                    return Err(BinaryHeaderError::RelocationAlignment);
411                }
412
413                if relocation_region_gpa % relocation_alignment != 0 {
414                    return Err(BinaryHeaderError::RelocationAddress(*relocation_region_gpa));
415                }
416
417                if minimum_relocation_gpa % relocation_alignment != 0 {
418                    return Err(BinaryHeaderError::RelocationAddress(
419                        *minimum_relocation_gpa,
420                    ));
421                }
422
423                if maximum_relocation_gpa % relocation_alignment != 0 {
424                    return Err(BinaryHeaderError::RelocationAddress(
425                        *maximum_relocation_gpa,
426                    ));
427                }
428
429                Ok(())
430            }
431            IgvmInitializationHeader::PageTableRelocationRegion {
432                compatibility_mask: _,
433                gpa,
434                size,
435                used_size,
436                vp_index: _,
437                vtl: _,
438            } => {
439                if gpa % PAGE_SIZE_4K != 0 {
440                    return Err(BinaryHeaderError::UnalignedAddress(*gpa));
441                }
442
443                if size % PAGE_SIZE_4K != 0 {
444                    return Err(BinaryHeaderError::UnalignedSize(*size));
445                }
446
447                if used_size % PAGE_SIZE_4K != 0 {
448                    return Err(BinaryHeaderError::UnalignedSize(*used_size));
449                }
450
451                if used_size > size {
452                    return Err(BinaryHeaderError::InvalidPageTableRegionSize);
453                }
454
455                Ok(())
456            }
457        }
458    }
459
460    /// Create a new [`IgvmInitializationHeader`] from the binary slice provided.
461    /// Returns the remaining slice of unused bytes.
462    fn new_from_binary_split(
463        mut variable_headers: &[u8],
464    ) -> Result<(Self, &[u8]), BinaryHeaderError> {
465        let IGVM_VHS_VARIABLE_HEADER { typ, length } =
466            read_header::<IGVM_VHS_VARIABLE_HEADER>(&mut variable_headers)?;
467
468        tracing::trace!(typ = ?typ, len = ?length, "trying to parse typ, len");
469
470        let length = length as usize;
471
472        let header = match typ {
473            IgvmVariableHeaderType::IGVM_VHT_GUEST_POLICY
474                if length == size_of::<IGVM_VHS_GUEST_POLICY>() =>
475            {
476                let IGVM_VHS_GUEST_POLICY {
477                    policy,
478                    compatibility_mask,
479                    reserved,
480                } = read_header(&mut variable_headers)?;
481
482                if reserved != 0 {
483                    return Err(BinaryHeaderError::ReservedNotZero);
484                }
485
486                IgvmInitializationHeader::GuestPolicy {
487                    policy,
488                    compatibility_mask,
489                }
490            }
491            IgvmVariableHeaderType::IGVM_VHT_RELOCATABLE_REGION
492                if length == size_of::<IGVM_VHS_RELOCATABLE_REGION>() =>
493            {
494                let IGVM_VHS_RELOCATABLE_REGION {
495                    compatibility_mask,
496                    flags,
497                    relocation_alignment,
498                    relocation_region_gpa,
499                    relocation_region_size,
500                    minimum_relocation_gpa,
501                    maximum_relocation_gpa,
502                    vp_index,
503                    vtl,
504                } = read_header(&mut variable_headers)?;
505
506                let is_vtl2 = flags & IGVM_VHF_RELOCATABLE_REGION_IS_VTL2
507                    == IGVM_VHF_RELOCATABLE_REGION_IS_VTL2;
508                let apply_gdtr_offset = flags & IGVM_VHF_RELOCATABLE_REGION_APPLY_GDTR
509                    == IGVM_VHF_RELOCATABLE_REGION_APPLY_GDTR;
510                let apply_rip_offset = flags & IGVM_VHF_RELOCATABLE_REGION_APPLY_RIP
511                    == IGVM_VHF_RELOCATABLE_REGION_APPLY_RIP;
512
513                IgvmInitializationHeader::RelocatableRegion {
514                    compatibility_mask,
515                    relocation_alignment,
516                    relocation_region_gpa,
517                    relocation_region_size,
518                    minimum_relocation_gpa,
519                    maximum_relocation_gpa,
520                    is_vtl2,
521                    apply_gdtr_offset,
522                    apply_rip_offset,
523                    vp_index,
524                    vtl: vtl.try_into().map_err(|_| BinaryHeaderError::InvalidVtl)?,
525                }
526            }
527            IgvmVariableHeaderType::IGVM_VHT_PAGE_TABLE_RELOCATION_REGION
528                if length == size_of::<IGVM_VHS_PAGE_TABLE_RELOCATION>() =>
529            {
530                let IGVM_VHS_PAGE_TABLE_RELOCATION {
531                    gpa,
532                    size,
533                    used_size,
534                    compatibility_mask,
535                    reserved,
536                    vp_index,
537                    vtl,
538                } = read_header(&mut variable_headers)?;
539
540                if reserved != 0 {
541                    return Err(BinaryHeaderError::ReservedNotZero);
542                }
543
544                IgvmInitializationHeader::PageTableRelocationRegion {
545                    compatibility_mask,
546                    gpa,
547                    size,
548                    used_size,
549                    vp_index,
550                    vtl: vtl.try_into().map_err(|_| BinaryHeaderError::InvalidVtl)?,
551                }
552            }
553
554            _ => return Err(BinaryHeaderError::InvalidVariableHeaderType),
555        };
556
557        header.validate()?;
558        Ok((header, variable_headers))
559    }
560
561    /// Returns the associated compatibility mask with the header, if any.
562    fn compatibility_mask(&self) -> Option<u32> {
563        use IgvmInitializationHeader::*;
564
565        match self {
566            GuestPolicy {
567                compatibility_mask, ..
568            } => Some(*compatibility_mask),
569            RelocatableRegion {
570                compatibility_mask, ..
571            } => Some(*compatibility_mask),
572            PageTableRelocationRegion {
573                compatibility_mask, ..
574            } => Some(*compatibility_mask),
575        }
576    }
577
578    fn write_binary_header(&self, variable_headers: &mut Vec<u8>) -> Result<(), BinaryHeaderError> {
579        // Only serialize this header if valid.
580        self.validate()?;
581
582        match self {
583            IgvmInitializationHeader::GuestPolicy {
584                policy,
585                compatibility_mask,
586            } => {
587                let info = IGVM_VHS_GUEST_POLICY {
588                    policy: *policy,
589                    compatibility_mask: *compatibility_mask,
590                    reserved: 0,
591                };
592
593                append_header(
594                    &info,
595                    IgvmVariableHeaderType::IGVM_VHT_GUEST_POLICY,
596                    variable_headers,
597                );
598            }
599            IgvmInitializationHeader::RelocatableRegion {
600                compatibility_mask,
601                relocation_alignment,
602                relocation_region_gpa,
603                relocation_region_size,
604                minimum_relocation_gpa,
605                maximum_relocation_gpa,
606                is_vtl2,
607                apply_rip_offset,
608                apply_gdtr_offset,
609                vp_index,
610                vtl,
611            } => {
612                let mut flags = 0;
613
614                if *is_vtl2 {
615                    flags |= IGVM_VHF_RELOCATABLE_REGION_IS_VTL2;
616                }
617
618                if *apply_rip_offset {
619                    flags |= IGVM_VHF_RELOCATABLE_REGION_APPLY_RIP;
620                }
621
622                if *apply_gdtr_offset {
623                    flags |= IGVM_VHF_RELOCATABLE_REGION_APPLY_GDTR;
624                }
625
626                let info = IGVM_VHS_RELOCATABLE_REGION {
627                    compatibility_mask: *compatibility_mask,
628                    relocation_alignment: *relocation_alignment,
629                    relocation_region_gpa: *relocation_region_gpa,
630                    relocation_region_size: *relocation_region_size,
631                    flags,
632                    minimum_relocation_gpa: *minimum_relocation_gpa,
633                    maximum_relocation_gpa: *maximum_relocation_gpa,
634                    vp_index: *vp_index,
635                    vtl: *vtl as u8,
636                };
637
638                append_header(
639                    &info,
640                    IgvmVariableHeaderType::IGVM_VHT_RELOCATABLE_REGION,
641                    variable_headers,
642                );
643            }
644            IgvmInitializationHeader::PageTableRelocationRegion {
645                compatibility_mask,
646                gpa,
647                size,
648                used_size,
649                vp_index,
650                vtl,
651            } => {
652                let info = IGVM_VHS_PAGE_TABLE_RELOCATION {
653                    gpa: *gpa,
654                    size: *size,
655                    used_size: *used_size,
656                    compatibility_mask: *compatibility_mask,
657                    reserved: 0,
658                    vp_index: *vp_index,
659                    vtl: *vtl as u8,
660                };
661
662                append_header(
663                    &info,
664                    IgvmVariableHeaderType::IGVM_VHT_PAGE_TABLE_RELOCATION_REGION,
665                    variable_headers,
666                );
667            }
668        }
669
670        Ok(())
671    }
672}
673
674/// Represents a structure in an IGVM variable header section, directive
675/// structure.
676#[derive(Debug, Clone, PartialEq, Eq)]
677pub enum IgvmDirectiveHeader {
678    PageData {
679        gpa: u64,
680        compatibility_mask: u32,
681        flags: IgvmPageDataFlags,
682        data_type: IgvmPageDataType,
683        data: Vec<u8>,
684    },
685    ParameterArea {
686        number_of_bytes: u64,
687        parameter_area_index: u32,
688        initial_data: Vec<u8>,
689    },
690    VpCount(IGVM_VHS_PARAMETER),
691    EnvironmentInfo(IGVM_VHS_PARAMETER),
692    Srat(IGVM_VHS_PARAMETER),
693    Madt(IGVM_VHS_PARAMETER),
694    Slit(IGVM_VHS_PARAMETER),
695    Pptt(IGVM_VHS_PARAMETER),
696    MmioRanges(IGVM_VHS_PARAMETER),
697    MemoryMap(IGVM_VHS_PARAMETER),
698    CommandLine(IGVM_VHS_PARAMETER),
699    DeviceTree(IGVM_VHS_PARAMETER),
700    RequiredMemory {
701        gpa: u64,
702        compatibility_mask: u32,
703        number_of_bytes: u32,
704        vtl2_protectable: bool,
705    },
706    SnpVpContext {
707        gpa: u64,
708        compatibility_mask: u32,
709        vp_index: u16,
710        vmsa: Box<SevVmsa>,
711    },
712    X64NativeVpContext {
713        compatibility_mask: u32,
714        vp_index: u16,
715        context: Box<IgvmNativeVpContextX64>,
716    },
717    /// Represents VP context for the BSP only.
718    X64VbsVpContext {
719        vtl: Vtl,
720        registers: Vec<X86Register>,
721        compatibility_mask: u32,
722    },
723    AArch64VbsVpContext {
724        vtl: Vtl,
725        registers: Vec<AArch64Register>,
726        compatibility_mask: u32,
727    },
728    ParameterInsert(IGVM_VHS_PARAMETER_INSERT),
729    ErrorRange {
730        gpa: u64,
731        compatibility_mask: u32,
732        size_bytes: u32,
733    },
734    SnpIdBlock {
735        compatibility_mask: u32,
736        author_key_enabled: u8,
737        reserved: [u8; 3],
738        ld: [u8; 48],
739        family_id: [u8; 16],
740        image_id: [u8; 16],
741        version: u32,
742        guest_svn: u32,
743        id_key_algorithm: u32,
744        author_key_algorithm: u32,
745        id_key_signature: Box<IGVM_VHS_SNP_ID_BLOCK_SIGNATURE>,
746        id_public_key: Box<IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY>,
747        author_key_signature: Box<IGVM_VHS_SNP_ID_BLOCK_SIGNATURE>,
748        author_public_key: Box<IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY>,
749    },
750    VbsMeasurement {
751        compatibility_mask: u32,
752        version: u32,
753        product_id: u32,
754        module_id: u32,
755        security_version: u32,
756        policy_flags: u32,
757        boot_digest_algo: u32,
758        signing_algo: u32,
759        boot_measurement_digest: Box<[u8; 64]>,
760        signature: Box<[u8; 256]>,
761        public_key: Box<[u8; 512]>,
762    },
763}
764
765impl fmt::Display for IgvmDirectiveHeader {
766    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767        match self {
768            IgvmDirectiveHeader::PageData {
769                gpa,
770                compatibility_mask,
771                flags,
772                data_type,
773                data: _,
774            } => {
775                writeln!(f, "PageData {{")?;
776                writeln!(f, "\t\tgpa: {:#X}", gpa)?;
777                writeln!(f, "\t\tcompatibility_mask: {:#X}", compatibility_mask)?;
778                writeln!(f, "\t\tflags: {:?}", flags)?;
779                writeln!(f, "\t\tdata_type: {:?}", data_type)?;
780                write!(f, "}}")?;
781                Ok(())
782            }
783            IgvmDirectiveHeader::SnpIdBlock {
784                compatibility_mask,
785                author_key_enabled,
786                reserved: _,
787                ld,
788                family_id,
789                image_id,
790                version,
791                guest_svn,
792                id_key_algorithm,
793                author_key_algorithm,
794                id_key_signature,
795                id_public_key,
796                author_key_signature,
797                author_public_key,
798            } => {
799                writeln!(f, "IGVM_VHS_SNP_ID_BLOCK {{")?;
800                writeln!(f, "\t\tcompatibility_mask: {:#X}", compatibility_mask)?;
801                writeln!(f, "\t\tauthor_key_enabled: {:#X}", author_key_enabled)?;
802                writeln!(f, "\t\tld: {}", hex::encode_upper(ld))?;
803                writeln!(f, "\t\tfamily_id: {:#X}", family_id[0])?;
804                writeln!(f, "\t\timage_id: {:#X}", image_id[0])?;
805                writeln!(f, "\t\tversion: {:#X}", version)?;
806                writeln!(f, "\t\tguest_svn: {:#X}", guest_svn)?;
807                writeln!(f, "\t\tid_key_algorithm: {:#X}", id_key_algorithm)?;
808                writeln!(f, "\t\tauthor_key_algorithm: {:#X}", author_key_algorithm)?;
809                writeln!(
810                    f,
811                    "\t\tid_block_signature R: 0x{}",
812                    hex::encode_upper(id_key_signature.r_comp)
813                )?;
814                writeln!(
815                    f,
816                    "\t\tid_block_signature S: 0x{}",
817                    hex::encode_upper(id_key_signature.s_comp)
818                )?;
819                writeln!(
820                    f,
821                    "\t\tid_public_key qx: 0x{}",
822                    hex::encode_upper(id_public_key.qx)
823                )?;
824                writeln!(
825                    f,
826                    "\t\tid_public_key qy: 0x{}",
827                    hex::encode_upper(id_public_key.qy)
828                )?;
829                writeln!(f, "\t\tid_public_key curve: {:#X}", id_public_key.curve)?;
830                if *author_key_enabled == 0x1 {
831                    writeln!(
832                        f,
833                        "\t\tauthor_block_signature R: 0x{}",
834                        hex::encode_upper(author_key_signature.r_comp)
835                    )?;
836                    writeln!(
837                        f,
838                        "\t\tauthor_block_signature S: 0x{}",
839                        hex::encode_upper(author_key_signature.s_comp)
840                    )?;
841                    writeln!(
842                        f,
843                        "\t\tauthor_public_key qx: 0x{}",
844                        hex::encode_upper(id_public_key.qx)
845                    )?;
846                    writeln!(
847                        f,
848                        "\t\tauthor_public_key qy: 0x{}",
849                        hex::encode_upper(author_public_key.qy)
850                    )?;
851                    writeln!(
852                        f,
853                        "\t\tauthor_public_key curve: {:#X}",
854                        author_public_key.curve
855                    )?;
856                }
857                write!(f, "}}")?;
858                Ok(())
859            }
860            IgvmDirectiveHeader::VbsMeasurement {
861                compatibility_mask,
862                version,
863                product_id,
864                module_id,
865                security_version,
866                policy_flags,
867                boot_digest_algo,
868                signing_algo,
869                boot_measurement_digest,
870                signature,
871                public_key,
872            } => {
873                writeln!(f, "IGVM_VHS_VBS_MEASUREMENT {{")?;
874                writeln!(f, "\tcompatibility_mask: {:#X}", compatibility_mask)?;
875                writeln!(f, "\tversion: {:#X}", version)?;
876                writeln!(f, "\tproduct_id: {:#X}", product_id)?;
877                writeln!(f, "\tmodule_id: {:#X}", module_id)?;
878                writeln!(f, "\tsecurity_version: {:#X}", security_version)?;
879                writeln!(f, "\tpolicy_flags: {:#X}", policy_flags)?;
880                writeln!(f, "\tboot_digest_algo: {:#X}", boot_digest_algo)?;
881                writeln!(f, "\tsigning_algo: {:#X}", signing_algo)?;
882                writeln!(
883                    f,
884                    "\tboot_measurement_digest: {}",
885                    hex::encode_upper(boot_measurement_digest.as_ref())
886                )?;
887                writeln!(f, "\tsignature: {}", hex::encode_upper(signature.as_ref()))?;
888                writeln!(
889                    f,
890                    "\tpublic_key: {}",
891                    hex::encode_upper(public_key.as_ref())
892                )?;
893                write!(f, "}}")?;
894                Ok(())
895            }
896            other => write!(f, "{:#X?}", other),
897        }
898    }
899}
900
901/// Binary serialization errors when converting a typed Rust
902/// [`IgvmDirectiveHeader`] to the corresponding IGVM binary format or vice
903/// versa.
904#[derive(Debug, Error)]
905pub enum BinaryHeaderError {
906    #[error("address {0} is not aligned")]
907    UnalignedAddress(u64),
908    #[error("size {0} is not aligned")]
909    UnalignedSize(u64),
910    #[error("data is an invalid size")]
911    InvalidDataSize,
912    #[error("invalid variable header size")]
913    InvalidVariableHeaderSize,
914    #[error("invalid variable header type")]
915    InvalidVariableHeaderType,
916    #[error("invalid page data type")]
917    InvalidPageDataType,
918    #[error("invalid vp context platform type")]
919    InvalidVpContextPlatformType,
920    #[error("invalid vmsa")]
921    InvalidVmsa,
922    #[error("invalid VP context")]
923    InvalidContext,
924    #[error("invalid compatibility mask")]
925    InvalidCompatibilityMask,
926    #[error("invalid vtl")]
927    InvalidVtl,
928    #[error("invalid platform type")]
929    InvalidPlatformType,
930    #[error("invalid platform version")]
931    InvalidPlatformVersion,
932    #[error("invalid shared gpa boundary")]
933    InvalidSharedGpaBoundary,
934    #[error("reserved values not zero")]
935    ReservedNotZero,
936    #[error("VBS vp context has no registers")]
937    NoVbsVpContextRegisters,
938    #[error("relocation region size not aligned to 4k")]
939    RelocationSize,
940    #[error("relocation alignment not aligned to 4k")]
941    RelocationAlignment,
942    #[error("relocation address not aligned to alignement")]
943    RelocationAddress(u64),
944    #[error("invalid page table entry size")]
945    InvalidPageTableRegionSize,
946    #[error("unsupported x64 register")]
947    UnsupportedX64Register(#[from] registers::UnsupportedRegister<HvX64RegisterName>),
948    #[error("unsupported AArch64 register")]
949    UnsupportedAArch64Register(#[from] registers::UnsupportedRegister<HvArm64RegisterName>),
950}
951
952impl IgvmDirectiveHeader {
953    /// Get the binary variable header size of the given type.
954    fn header_size(&self) -> usize {
955        let additional = match self {
956            IgvmDirectiveHeader::PageData { .. } => size_of::<IGVM_VHS_PAGE_DATA>(),
957            IgvmDirectiveHeader::ParameterArea { .. } => size_of::<IGVM_VHS_PARAMETER_AREA>(),
958            IgvmDirectiveHeader::VpCount(param) => size_of_val(param),
959            IgvmDirectiveHeader::EnvironmentInfo(param) => size_of_val(param),
960            IgvmDirectiveHeader::Srat(param) => size_of_val(param),
961            IgvmDirectiveHeader::Madt(param) => size_of_val(param),
962            IgvmDirectiveHeader::Slit(param) => size_of_val(param),
963            IgvmDirectiveHeader::Pptt(param) => size_of_val(param),
964            IgvmDirectiveHeader::MmioRanges(param) => size_of_val(param),
965            IgvmDirectiveHeader::MemoryMap(param) => size_of_val(param),
966            IgvmDirectiveHeader::CommandLine(param) => size_of_val(param),
967            IgvmDirectiveHeader::DeviceTree(param) => size_of_val(param),
968            IgvmDirectiveHeader::RequiredMemory { .. } => size_of::<IGVM_VHS_REQUIRED_MEMORY>(),
969            IgvmDirectiveHeader::SnpVpContext { .. } => size_of::<IGVM_VHS_VP_CONTEXT>(),
970            IgvmDirectiveHeader::X64NativeVpContext { .. } => size_of::<IGVM_VHS_VP_CONTEXT>(),
971            IgvmDirectiveHeader::X64VbsVpContext { .. } => size_of::<IGVM_VHS_VP_CONTEXT>(),
972            IgvmDirectiveHeader::AArch64VbsVpContext { .. } => size_of::<IGVM_VHS_VP_CONTEXT>(),
973            IgvmDirectiveHeader::ParameterInsert(param) => size_of_val(param),
974            IgvmDirectiveHeader::ErrorRange { .. } => size_of::<IGVM_VHS_ERROR_RANGE>(),
975            IgvmDirectiveHeader::SnpIdBlock { .. } => size_of::<IGVM_VHS_SNP_ID_BLOCK>(),
976            IgvmDirectiveHeader::VbsMeasurement { .. } => size_of::<IGVM_VHS_VBS_MEASUREMENT>(),
977        };
978
979        align_8(size_of::<IGVM_VHS_VARIABLE_HEADER>() + additional)
980    }
981
982    /// Get the [`IgvmVariableHeaderType`] for the directive header.
983    #[cfg(feature = "igvm-c")]
984    #[cfg_attr(docsrs, doc(cfg(feature = "igvm-c")))]
985    fn header_type(&self) -> IgvmVariableHeaderType {
986        match self {
987            IgvmDirectiveHeader::PageData { .. } => IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA,
988            IgvmDirectiveHeader::ParameterArea { .. } => {
989                IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA
990            }
991            IgvmDirectiveHeader::VpCount(_) => IgvmVariableHeaderType::IGVM_VHT_VP_COUNT_PARAMETER,
992            IgvmDirectiveHeader::Srat(_) => IgvmVariableHeaderType::IGVM_VHT_SRAT,
993            IgvmDirectiveHeader::Madt(_) => IgvmVariableHeaderType::IGVM_VHT_MADT,
994            IgvmDirectiveHeader::Slit(_) => IgvmVariableHeaderType::IGVM_VHT_SLIT,
995            IgvmDirectiveHeader::Pptt(_) => IgvmVariableHeaderType::IGVM_VHT_PPTT,
996            IgvmDirectiveHeader::MmioRanges(_) => IgvmVariableHeaderType::IGVM_VHT_MMIO_RANGES,
997            IgvmDirectiveHeader::MemoryMap(_) => IgvmVariableHeaderType::IGVM_VHT_MEMORY_MAP,
998            IgvmDirectiveHeader::CommandLine(_) => IgvmVariableHeaderType::IGVM_VHT_COMMAND_LINE,
999            IgvmDirectiveHeader::DeviceTree(_) => IgvmVariableHeaderType::IGVM_VHT_DEVICE_TREE,
1000            IgvmDirectiveHeader::RequiredMemory { .. } => {
1001                IgvmVariableHeaderType::IGVM_VHT_REQUIRED_MEMORY
1002            }
1003            IgvmDirectiveHeader::SnpVpContext { .. } => IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
1004            IgvmDirectiveHeader::X64NativeVpContext { .. } => {
1005                IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT
1006            }
1007            IgvmDirectiveHeader::X64VbsVpContext { .. } => {
1008                IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT
1009            }
1010            IgvmDirectiveHeader::AArch64VbsVpContext { .. } => {
1011                IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT
1012            }
1013            IgvmDirectiveHeader::ParameterInsert(_) => {
1014                IgvmVariableHeaderType::IGVM_VHT_PARAMETER_INSERT
1015            }
1016            IgvmDirectiveHeader::ErrorRange { .. } => IgvmVariableHeaderType::IGVM_VHT_ERROR_RANGE,
1017            IgvmDirectiveHeader::SnpIdBlock { .. } => IgvmVariableHeaderType::IGVM_VHT_SNP_ID_BLOCK,
1018            IgvmDirectiveHeader::VbsMeasurement { .. } => {
1019                IgvmVariableHeaderType::IGVM_VHT_VBS_MEASUREMENT
1020            }
1021            IgvmDirectiveHeader::EnvironmentInfo(_) => {
1022                IgvmVariableHeaderType::IGVM_VHT_ENVIRONMENT_INFO_PARAMETER
1023            }
1024        }
1025    }
1026
1027    /// Write the binary representation of the header and any associated file
1028    /// data to the supplied variable_headers and file data vectors.
1029    /// file_data_offset points to the start of the data section to be encoded
1030    /// in the variable header if this data has a file data component.
1031    pub fn write_binary_header(
1032        &self,
1033        variable_headers: &mut Vec<u8>,
1034        file_data: &mut FileDataSerializer,
1035    ) -> Result<(), BinaryHeaderError> {
1036        // Only serialize this header if valid.
1037        self.validate()?;
1038
1039        match self {
1040            IgvmDirectiveHeader::PageData {
1041                gpa,
1042                compatibility_mask,
1043                flags,
1044                data_type,
1045                data,
1046            } => {
1047                let file_offset = if data.is_empty() {
1048                    // No data means a file offset of 0.
1049                    0
1050                } else {
1051                    // Pad data out to 4K if smaller. It must not be larger than
1052                    // 4K.
1053                    //
1054                    // TODO: Support 2MB page data
1055                    assert!(data.len() as u64 <= PAGE_SIZE_4K);
1056
1057                    let align_up_iter =
1058                        std::iter::repeat_n(&0u8, PAGE_SIZE_4K as usize - data.len());
1059                    let data: Vec<u8> = data.iter().chain(align_up_iter).copied().collect();
1060                    file_data.write_file_data(&data)
1061                };
1062
1063                let info = IGVM_VHS_PAGE_DATA {
1064                    gpa: *gpa,
1065                    compatibility_mask: *compatibility_mask,
1066                    file_offset,
1067                    flags: *flags,
1068                    data_type: *data_type,
1069                    reserved: 0,
1070                };
1071
1072                append_header(
1073                    &info,
1074                    IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA,
1075                    variable_headers,
1076                );
1077            }
1078            IgvmDirectiveHeader::ParameterArea {
1079                number_of_bytes,
1080                parameter_area_index,
1081                initial_data,
1082            } => {
1083                assert_eq!(number_of_bytes % PAGE_SIZE_4K, 0);
1084
1085                let file_offset = if initial_data.is_empty() {
1086                    // No data means a file offset of 0.
1087                    0
1088                } else {
1089                    // Pad data out to number_of_bytes if smaller.
1090                    assert!(initial_data.len() as u64 <= *number_of_bytes);
1091
1092                    let align_up_iter =
1093                        std::iter::repeat_n(&0u8, *number_of_bytes as usize - initial_data.len());
1094                    let data: Vec<u8> = initial_data.iter().chain(align_up_iter).copied().collect();
1095                    file_data.write_file_data(&data)
1096                };
1097
1098                let info = IGVM_VHS_PARAMETER_AREA {
1099                    number_of_bytes: *number_of_bytes,
1100                    parameter_area_index: *parameter_area_index,
1101                    file_offset,
1102                };
1103
1104                append_header(
1105                    &info,
1106                    IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA,
1107                    variable_headers,
1108                );
1109            }
1110            IgvmDirectiveHeader::VpCount(param) => {
1111                append_header(
1112                    param,
1113                    IgvmVariableHeaderType::IGVM_VHT_VP_COUNT_PARAMETER,
1114                    variable_headers,
1115                );
1116            }
1117            IgvmDirectiveHeader::EnvironmentInfo(param) => {
1118                append_header(
1119                    param,
1120                    IgvmVariableHeaderType::IGVM_VHT_ENVIRONMENT_INFO_PARAMETER,
1121                    variable_headers,
1122                );
1123            }
1124            IgvmDirectiveHeader::Srat(param) => {
1125                append_header(
1126                    param,
1127                    IgvmVariableHeaderType::IGVM_VHT_SRAT,
1128                    variable_headers,
1129                );
1130            }
1131            IgvmDirectiveHeader::Madt(param) => {
1132                append_header(
1133                    param,
1134                    IgvmVariableHeaderType::IGVM_VHT_MADT,
1135                    variable_headers,
1136                );
1137            }
1138            IgvmDirectiveHeader::Slit(param) => {
1139                append_header(
1140                    param,
1141                    IgvmVariableHeaderType::IGVM_VHT_SLIT,
1142                    variable_headers,
1143                );
1144            }
1145            IgvmDirectiveHeader::Pptt(param) => {
1146                append_header(
1147                    param,
1148                    IgvmVariableHeaderType::IGVM_VHT_PPTT,
1149                    variable_headers,
1150                );
1151            }
1152            IgvmDirectiveHeader::MmioRanges(param) => {
1153                append_header(
1154                    param,
1155                    IgvmVariableHeaderType::IGVM_VHT_MMIO_RANGES,
1156                    variable_headers,
1157                );
1158            }
1159            IgvmDirectiveHeader::MemoryMap(param) => {
1160                append_header(
1161                    param,
1162                    IgvmVariableHeaderType::IGVM_VHT_MEMORY_MAP,
1163                    variable_headers,
1164                );
1165            }
1166            IgvmDirectiveHeader::CommandLine(param) => {
1167                append_header(
1168                    param,
1169                    IgvmVariableHeaderType::IGVM_VHT_COMMAND_LINE,
1170                    variable_headers,
1171                );
1172            }
1173            IgvmDirectiveHeader::DeviceTree(param) => {
1174                append_header(
1175                    param,
1176                    IgvmVariableHeaderType::IGVM_VHT_DEVICE_TREE,
1177                    variable_headers,
1178                );
1179            }
1180            IgvmDirectiveHeader::RequiredMemory {
1181                gpa,
1182                compatibility_mask,
1183                number_of_bytes,
1184                vtl2_protectable,
1185            } => {
1186                // GPA and size must be 4k aligned.
1187                assert_eq!(gpa % PAGE_SIZE_4K, 0);
1188                assert_eq!(*number_of_bytes as u64 % PAGE_SIZE_4K, 0);
1189
1190                let info = IGVM_VHS_REQUIRED_MEMORY {
1191                    gpa: *gpa,
1192                    compatibility_mask: *compatibility_mask,
1193                    number_of_bytes: *number_of_bytes,
1194                    flags: RequiredMemoryFlags::new().with_vtl2_protectable(*vtl2_protectable),
1195                    reserved: 0,
1196                };
1197
1198                append_header(
1199                    &info,
1200                    IgvmVariableHeaderType::IGVM_VHT_REQUIRED_MEMORY,
1201                    variable_headers,
1202                );
1203            }
1204            IgvmDirectiveHeader::SnpVpContext {
1205                gpa,
1206                compatibility_mask,
1207                vp_index,
1208                vmsa,
1209            } => {
1210                // GPA must be 4k aligned.
1211                assert_eq!(gpa % PAGE_SIZE_4K, 0);
1212
1213                // Pad file data to 4K.
1214                let align_up_iter =
1215                    std::iter::repeat_n(&0u8, PAGE_SIZE_4K as usize - vmsa.as_bytes().len());
1216                let data: Vec<u8> = vmsa
1217                    .as_bytes()
1218                    .iter()
1219                    .chain(align_up_iter)
1220                    .copied()
1221                    .collect();
1222                let file_offset = file_data.write_file_data(&data);
1223
1224                let info = IGVM_VHS_VP_CONTEXT {
1225                    gpa: u64_le::new(*gpa),
1226                    compatibility_mask: *compatibility_mask,
1227                    file_offset,
1228                    vp_index: *vp_index,
1229                    reserved: 0,
1230                };
1231
1232                append_header(
1233                    &info,
1234                    IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
1235                    variable_headers,
1236                );
1237            }
1238            IgvmDirectiveHeader::X64NativeVpContext {
1239                compatibility_mask,
1240                vp_index,
1241                context,
1242            } => {
1243                // Pad file data to 4K.
1244                let align_up_iter =
1245                    std::iter::repeat_n(&0u8, PAGE_SIZE_4K as usize - context.as_bytes().len());
1246                let data: Vec<u8> = context
1247                    .as_bytes()
1248                    .iter()
1249                    .chain(align_up_iter)
1250                    .copied()
1251                    .collect();
1252                let file_offset = file_data.write_file_data(&data);
1253
1254                let info = IGVM_VHS_VP_CONTEXT {
1255                    gpa: 0.into(),
1256                    compatibility_mask: *compatibility_mask,
1257                    file_offset,
1258                    vp_index: *vp_index,
1259                    reserved: 0,
1260                };
1261
1262                append_header(
1263                    &info,
1264                    IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
1265                    variable_headers,
1266                );
1267            }
1268            IgvmDirectiveHeader::X64VbsVpContext {
1269                vtl,
1270                registers,
1271                compatibility_mask,
1272            } => {
1273                // Build the serialized file data.
1274                let mut data = Vec::new();
1275                let header = VbsVpContextHeader {
1276                    register_count: registers
1277                        .len()
1278                        .try_into()
1279                        .expect("reg count must fit in u32"),
1280                };
1281                data.extend_from_slice(header.as_bytes());
1282
1283                for register in registers {
1284                    let vbs_reg = register.into_vbs_vp_context_reg(*vtl);
1285                    data.extend_from_slice(vbs_reg.as_bytes());
1286                }
1287
1288                let file_offset = file_data.write_file_data(&data);
1289
1290                let info = IGVM_VHS_VP_CONTEXT {
1291                    gpa: 0.into(),
1292                    compatibility_mask: *compatibility_mask,
1293                    file_offset,
1294                    vp_index: 0,
1295                    reserved: 0,
1296                };
1297
1298                append_header(
1299                    &info,
1300                    IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
1301                    variable_headers,
1302                );
1303            }
1304            IgvmDirectiveHeader::AArch64VbsVpContext {
1305                vtl,
1306                registers,
1307                compatibility_mask,
1308            } => {
1309                let mut data = Vec::new();
1310
1311                // Build the serialized file data.
1312                let header = VbsVpContextHeader {
1313                    register_count: registers
1314                        .len()
1315                        .try_into()
1316                        .expect("reg count must fit in u32"),
1317                };
1318                data.extend_from_slice(header.as_bytes());
1319
1320                for register in registers {
1321                    let vbs_reg = register.into_vbs_vp_context_reg(*vtl);
1322                    data.extend_from_slice(vbs_reg.as_bytes());
1323                }
1324
1325                let file_offset = file_data.write_file_data(&data);
1326
1327                let info = IGVM_VHS_VP_CONTEXT {
1328                    gpa: 0.into(),
1329                    compatibility_mask: *compatibility_mask,
1330                    file_offset,
1331                    vp_index: 0,
1332                    reserved: 0,
1333                };
1334
1335                append_header(
1336                    &info,
1337                    IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
1338                    variable_headers,
1339                );
1340            }
1341            IgvmDirectiveHeader::ParameterInsert(param) => {
1342                // GPA must be 4k aligned.
1343                assert_eq!(param.gpa % PAGE_SIZE_4K, 0);
1344
1345                append_header(
1346                    param,
1347                    IgvmVariableHeaderType::IGVM_VHT_PARAMETER_INSERT,
1348                    variable_headers,
1349                );
1350            }
1351            IgvmDirectiveHeader::ErrorRange { .. } => {
1352                todo!("append ErrorRange")
1353            }
1354            IgvmDirectiveHeader::SnpIdBlock {
1355                compatibility_mask,
1356                author_key_enabled,
1357                reserved,
1358                ld,
1359                family_id,
1360                image_id,
1361                version,
1362                guest_svn,
1363                id_key_algorithm,
1364                author_key_algorithm,
1365                id_key_signature,
1366                id_public_key,
1367                author_key_signature,
1368                author_public_key,
1369            } => {
1370                let id_block = IGVM_VHS_SNP_ID_BLOCK {
1371                    compatibility_mask: *compatibility_mask,
1372                    author_key_enabled: *author_key_enabled,
1373                    reserved: *reserved,
1374                    ld: *ld,
1375                    family_id: *family_id,
1376                    image_id: *image_id,
1377                    version: *version,
1378                    guest_svn: *guest_svn,
1379                    id_key_algorithm: *id_key_algorithm,
1380                    author_key_algorithm: *author_key_algorithm,
1381                    id_key_signature: **id_key_signature,
1382                    id_public_key: **id_public_key,
1383                    author_key_signature: **author_key_signature,
1384                    author_public_key: **author_public_key,
1385                };
1386                append_header(
1387                    &id_block,
1388                    IgvmVariableHeaderType::IGVM_VHT_SNP_ID_BLOCK,
1389                    variable_headers,
1390                );
1391            }
1392            IgvmDirectiveHeader::VbsMeasurement {
1393                compatibility_mask,
1394                version,
1395                product_id,
1396                module_id,
1397                security_version,
1398                policy_flags,
1399                boot_digest_algo,
1400                signing_algo,
1401                boot_measurement_digest,
1402                signature,
1403                public_key,
1404            } => {
1405                let vbs_measurement = IGVM_VHS_VBS_MEASUREMENT {
1406                    compatibility_mask: *compatibility_mask,
1407                    version: *version,
1408                    product_id: *product_id,
1409                    module_id: *module_id,
1410                    security_version: *security_version,
1411                    policy_flags: *policy_flags,
1412                    boot_digest_algo: *boot_digest_algo,
1413                    signing_algo: *signing_algo,
1414                    boot_measurement_digest: **boot_measurement_digest,
1415                    signature: **signature,
1416                    public_key: **public_key,
1417                };
1418                append_header(
1419                    &vbs_measurement,
1420                    IgvmVariableHeaderType::IGVM_VHT_VBS_MEASUREMENT,
1421                    variable_headers,
1422                )
1423            }
1424        }
1425
1426        Ok(())
1427    }
1428
1429    /// Returns the associated compatibility mask with the header, if any.
1430    pub fn compatibility_mask(&self) -> Option<u32> {
1431        use IgvmDirectiveHeader::*;
1432
1433        match &self {
1434            PageData {
1435                compatibility_mask, ..
1436            } => Some(*compatibility_mask),
1437            ParameterArea { .. } => None,
1438            VpCount(_) => None,
1439            EnvironmentInfo(_) => None,
1440            Srat(_) => None,
1441            Madt(_) => None,
1442            Slit(_) => None,
1443            Pptt(_) => None,
1444            MmioRanges(_) => None,
1445            MemoryMap(_) => None,
1446            CommandLine(_) => None,
1447            DeviceTree(_) => None,
1448            RequiredMemory {
1449                compatibility_mask, ..
1450            } => Some(*compatibility_mask),
1451            SnpVpContext {
1452                compatibility_mask, ..
1453            } => Some(*compatibility_mask),
1454            X64NativeVpContext {
1455                compatibility_mask, ..
1456            } => Some(*compatibility_mask),
1457            X64VbsVpContext {
1458                compatibility_mask, ..
1459            } => Some(*compatibility_mask),
1460            AArch64VbsVpContext {
1461                compatibility_mask, ..
1462            } => Some(*compatibility_mask),
1463            ParameterInsert(info) => Some(info.compatibility_mask),
1464            ErrorRange {
1465                compatibility_mask, ..
1466            } => Some(*compatibility_mask),
1467            SnpIdBlock {
1468                compatibility_mask, ..
1469            } => Some(*compatibility_mask),
1470            VbsMeasurement {
1471                compatibility_mask, ..
1472            } => Some(*compatibility_mask),
1473        }
1474    }
1475
1476    /// Returns a mutable reference to the associated compatibility mask with
1477    /// the header, if any.
1478    pub fn compatibility_mask_mut(&mut self) -> Option<&mut u32> {
1479        use IgvmDirectiveHeader::*;
1480
1481        match self {
1482            PageData {
1483                compatibility_mask, ..
1484            } => Some(compatibility_mask),
1485            ParameterArea { .. } => None,
1486            VpCount(_) => None,
1487            EnvironmentInfo(_) => None,
1488            Srat(_) => None,
1489            Madt(_) => None,
1490            Slit(_) => None,
1491            Pptt(_) => None,
1492            MmioRanges(_) => None,
1493            MemoryMap(_) => None,
1494            CommandLine(_) => None,
1495            DeviceTree(_) => None,
1496            RequiredMemory {
1497                compatibility_mask, ..
1498            } => Some(compatibility_mask),
1499            SnpVpContext {
1500                compatibility_mask, ..
1501            } => Some(compatibility_mask),
1502            X64NativeVpContext {
1503                compatibility_mask, ..
1504            } => Some(compatibility_mask),
1505            X64VbsVpContext {
1506                compatibility_mask, ..
1507            } => Some(compatibility_mask),
1508            AArch64VbsVpContext {
1509                compatibility_mask, ..
1510            } => Some(compatibility_mask),
1511            ParameterInsert(info) => Some(&mut info.compatibility_mask),
1512            ErrorRange {
1513                compatibility_mask, ..
1514            } => Some(compatibility_mask),
1515            SnpIdBlock {
1516                compatibility_mask, ..
1517            } => Some(compatibility_mask),
1518            VbsMeasurement {
1519                compatibility_mask, ..
1520            } => Some(compatibility_mask),
1521        }
1522    }
1523
1524    /// Returns if `self` is equivalent to `other`, with equivalence being the
1525    /// headers match other than compatibility mask.
1526    pub fn equivalent(&self, other: &Self) -> bool {
1527        match (self, other) {
1528            (
1529                IgvmDirectiveHeader::PageData {
1530                    gpa: a_gpa,
1531                    flags: a_flags,
1532                    data_type: a_data_type,
1533                    data: a_data,
1534                    compatibility_mask: _,
1535                },
1536                IgvmDirectiveHeader::PageData {
1537                    gpa: b_gpa,
1538                    flags: b_flags,
1539                    data_type: b_data_type,
1540                    data: b_data,
1541                    compatibility_mask: _,
1542                },
1543            ) => {
1544                a_gpa == b_gpa
1545                    && a_flags == b_flags
1546                    && a_data_type == b_data_type
1547                    && a_data == b_data
1548            }
1549            (
1550                IgvmDirectiveHeader::RequiredMemory {
1551                    gpa: a_gpa,
1552                    number_of_bytes: a_number_of_bytes,
1553                    vtl2_protectable: a_vtl2_protectable,
1554                    compatibility_mask: _,
1555                },
1556                IgvmDirectiveHeader::RequiredMemory {
1557                    gpa: b_gpa,
1558                    number_of_bytes: b_number_of_bytes,
1559                    vtl2_protectable: b_vtl2_protectable,
1560                    compatibility_mask: _,
1561                },
1562            ) => {
1563                a_gpa == b_gpa
1564                    && a_number_of_bytes == b_number_of_bytes
1565                    && a_vtl2_protectable == b_vtl2_protectable
1566            }
1567            // TODO: other headers with compat masks
1568            _ => self == other,
1569        }
1570    }
1571
1572    /// Checks if this header contains valid state.
1573    fn validate(&self) -> Result<(), BinaryHeaderError> {
1574        match self {
1575            IgvmDirectiveHeader::PageData {
1576                gpa,
1577                compatibility_mask: _,
1578                flags: _,
1579                data_type,
1580                data,
1581            } => {
1582                // TODO: support 2MB pages
1583
1584                // GPA must be aligned.
1585                if gpa % PAGE_SIZE_4K != 0 {
1586                    return Err(BinaryHeaderError::UnalignedAddress(*gpa));
1587                }
1588
1589                // Data type must be valid type.
1590                match *data_type {
1591                    IgvmPageDataType::NORMAL
1592                    | IgvmPageDataType::SECRETS
1593                    | IgvmPageDataType::CPUID_DATA
1594                    | IgvmPageDataType::CPUID_XF => {}
1595                    _ => return Err(BinaryHeaderError::InvalidPageDataType),
1596                }
1597
1598                // Data must be less than 4K.
1599                if data.len() > PAGE_SIZE_4K as usize {
1600                    return Err(BinaryHeaderError::InvalidDataSize);
1601                }
1602            }
1603            IgvmDirectiveHeader::ParameterArea {
1604                number_of_bytes,
1605                parameter_area_index: _,
1606                initial_data,
1607            } => {
1608                if number_of_bytes % PAGE_SIZE_4K != 0 {
1609                    return Err(BinaryHeaderError::UnalignedSize(*number_of_bytes));
1610                }
1611
1612                if initial_data.len() > *number_of_bytes as usize {
1613                    return Err(BinaryHeaderError::InvalidDataSize);
1614                }
1615            }
1616            // Parameter usage is validated by the IgvmFile functions, as more
1617            // info is needed than just this header.
1618            IgvmDirectiveHeader::VpCount(_)
1619            | IgvmDirectiveHeader::EnvironmentInfo(_)
1620            | IgvmDirectiveHeader::Srat(_)
1621            | IgvmDirectiveHeader::Madt(_)
1622            | IgvmDirectiveHeader::Slit(_)
1623            | IgvmDirectiveHeader::Pptt(_)
1624            | IgvmDirectiveHeader::MmioRanges(_)
1625            | IgvmDirectiveHeader::MemoryMap(_)
1626            | IgvmDirectiveHeader::CommandLine(_)
1627            | IgvmDirectiveHeader::DeviceTree(_) => {}
1628            IgvmDirectiveHeader::RequiredMemory {
1629                gpa,
1630                compatibility_mask: _,
1631                number_of_bytes,
1632                vtl2_protectable: _,
1633            } => {
1634                if gpa % PAGE_SIZE_4K != 0 {
1635                    return Err(BinaryHeaderError::UnalignedAddress(*gpa));
1636                }
1637
1638                if *number_of_bytes as u64 % PAGE_SIZE_4K != 0 {
1639                    return Err(BinaryHeaderError::UnalignedSize(*number_of_bytes as u64));
1640                }
1641            }
1642            IgvmDirectiveHeader::SnpVpContext {
1643                gpa,
1644                compatibility_mask: _,
1645                vp_index: _,
1646                vmsa: _,
1647            } => {
1648                if gpa % PAGE_SIZE_4K != 0 {
1649                    return Err(BinaryHeaderError::UnalignedAddress(*gpa));
1650                }
1651            }
1652            IgvmDirectiveHeader::X64NativeVpContext {
1653                compatibility_mask: _,
1654                vp_index: _,
1655                context: _,
1656            } => {}
1657            IgvmDirectiveHeader::X64VbsVpContext {
1658                vtl: _,
1659                registers: _,
1660                compatibility_mask: _,
1661            } => {}
1662            IgvmDirectiveHeader::AArch64VbsVpContext {
1663                vtl: _,
1664                registers: _,
1665                compatibility_mask: _,
1666            } => {}
1667            IgvmDirectiveHeader::ParameterInsert(param) => {
1668                if param.gpa % PAGE_SIZE_4K != 0 {
1669                    return Err(BinaryHeaderError::UnalignedAddress(param.gpa));
1670                }
1671            }
1672            IgvmDirectiveHeader::ErrorRange { gpa, .. } => {
1673                // GPA must be aligned.
1674                if gpa % PAGE_SIZE_4K != 0 {
1675                    return Err(BinaryHeaderError::UnalignedAddress(*gpa));
1676                }
1677            }
1678            //TODO: validate SNP
1679            IgvmDirectiveHeader::SnpIdBlock { .. } => {}
1680            //TODO: validate VBS
1681            IgvmDirectiveHeader::VbsMeasurement { .. } => {}
1682        }
1683
1684        Ok(())
1685    }
1686
1687    /// Create a new [`IgvmDirectiveHeader`] from a binary representation, with
1688    /// the following slices representing the variable headers and file data
1689    /// sections of the IGVM file.
1690    ///
1691    /// Returns the remaining variable_headers slice after this header is
1692    /// constructed.
1693    fn new_from_binary_split<'a>(
1694        revision: IgvmRevision,
1695        mut variable_headers: &'a [u8],
1696        file_data: &'a [u8],
1697        file_data_start: u32,
1698        compatibility_mask_to_platforms: impl Fn(u32) -> Option<IgvmPlatformType>,
1699    ) -> Result<(Self, &'a [u8]), BinaryHeaderError> {
1700        // First read the fixed header.
1701        let IGVM_VHS_VARIABLE_HEADER { typ, length } = read_header(&mut variable_headers)?;
1702
1703        tracing::trace!(typ = ?typ, len = ?length, "trying to parse typ, len");
1704
1705        let length = length as usize;
1706        // Extract file data from a given file offest with the given size. File
1707        // offset of 0 results in no data.
1708        let extract_file_data =
1709            |file_offset: u32, size: usize| -> Result<Vec<u8>, BinaryHeaderError> {
1710                if file_offset == 0 {
1711                    return Ok(Vec::new());
1712                }
1713
1714                let start = (file_offset - file_data_start) as usize;
1715                let end = start + size;
1716
1717                file_data
1718                    .get(start..end)
1719                    .ok_or(BinaryHeaderError::InvalidDataSize)
1720                    .map(|slice| slice.to_vec())
1721            };
1722        let header = match typ {
1723            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA
1724                if length == size_of::<IGVM_VHS_PARAMETER_AREA>() =>
1725            {
1726                let IGVM_VHS_PARAMETER_AREA {
1727                    file_offset,
1728                    number_of_bytes,
1729                    parameter_area_index,
1730                } = read_header(&mut variable_headers)?;
1731
1732                let data = extract_file_data(file_offset, number_of_bytes as usize)?;
1733
1734                IgvmDirectiveHeader::ParameterArea {
1735                    number_of_bytes,
1736                    parameter_area_index,
1737                    initial_data: data,
1738                }
1739            }
1740            IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA
1741                if length == size_of::<IGVM_VHS_PAGE_DATA>() =>
1742            {
1743                let IGVM_VHS_PAGE_DATA {
1744                    gpa,
1745                    compatibility_mask,
1746                    flags,
1747                    data_type,
1748                    file_offset,
1749                    reserved,
1750                } = read_header(&mut variable_headers)?;
1751
1752                // TODO: only 4K data supported
1753                let data = extract_file_data(file_offset, PAGE_SIZE_4K as usize)?;
1754
1755                if reserved != 0 {
1756                    return Err(BinaryHeaderError::ReservedNotZero);
1757                }
1758
1759                IgvmDirectiveHeader::PageData {
1760                    gpa,
1761                    compatibility_mask,
1762                    flags,
1763                    data_type,
1764                    data,
1765                }
1766            }
1767            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_INSERT
1768                if length == size_of::<IGVM_VHS_PARAMETER_INSERT>() =>
1769            {
1770                IgvmDirectiveHeader::ParameterInsert(read_header(&mut variable_headers)?)
1771            }
1772            IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT
1773                if length == size_of::<IGVM_VHS_VP_CONTEXT>() =>
1774            {
1775                // Clone the reference here as we manually advance the slice by
1776                // the aligned size, not the size of the structure.
1777                let header = read_header::<IGVM_VHS_VP_CONTEXT>(&mut &*variable_headers)?;
1778
1779                // Advance variable_headers by aligned up size
1780                let aligned_size = align_8(size_of::<IGVM_VHS_VP_CONTEXT>());
1781                variable_headers = variable_headers
1782                    .get(aligned_size..)
1783                    .ok_or(BinaryHeaderError::InvalidVariableHeaderSize)?;
1784
1785                match compatibility_mask_to_platforms(header.compatibility_mask) {
1786                    Some(IgvmPlatformType::VSM_ISOLATION) => {
1787                        // First read the VbsVpContextHeader at file offset
1788                        let start = (header.file_offset - file_data_start) as usize;
1789                        let (VbsVpContextHeader { register_count }, remaining_data) =
1790                            VbsVpContextHeader::read_from_prefix(&file_data[start..])
1791                                .map_err(|_| BinaryHeaderError::InvalidDataSize)?; // todo: zerocopy: map_err
1792
1793                        let mut registers: Vec<VbsVpContextRegister> = Vec::new();
1794                        let mut vp_vtl: Option<u8> = None;
1795                        let mut remaining_data = remaining_data;
1796
1797                        for _ in 0..register_count {
1798                            let reg = match VbsVpContextRegister::read_from_prefix(remaining_data) {
1799                                Ok((reg, slice)) => {
1800                                    remaining_data = slice;
1801                                    reg
1802                                }
1803                                Err(_) => return Err(BinaryHeaderError::InvalidDataSize), // todo: zerocopy: map_err
1804                            };
1805
1806                            registers.push(reg);
1807
1808                            // TODO: single vtl expected
1809                            match vp_vtl {
1810                                Some(vtl) => assert_eq!(vtl, reg.vtl),
1811                                None => vp_vtl = Some(reg.vtl),
1812                            }
1813                        }
1814
1815                        // TODO: only bsp supported
1816                        let vp_index = header.vp_index;
1817                        assert_eq!(vp_index, 0);
1818
1819                        let vtl = vp_vtl
1820                            .ok_or(BinaryHeaderError::NoVbsVpContextRegisters)?
1821                            .try_into()
1822                            .map_err(|_| BinaryHeaderError::InvalidVtl)?;
1823
1824                        match revision.arch() {
1825                            Arch::X64 => {
1826                                let registers: Result<Vec<X86Register>, _> = registers
1827                                    .iter()
1828                                    .map(|reg| X86Register::try_from(*reg))
1829                                    .collect();
1830
1831                                IgvmDirectiveHeader::X64VbsVpContext {
1832                                    vtl,
1833                                    registers: registers?,
1834                                    compatibility_mask: header.compatibility_mask,
1835                                }
1836                            }
1837                            Arch::AArch64 => {
1838                                let registers: Result<Vec<AArch64Register>, _> = registers
1839                                    .iter()
1840                                    .map(|reg| AArch64Register::try_from(*reg))
1841                                    .collect();
1842
1843                                IgvmDirectiveHeader::AArch64VbsVpContext {
1844                                    vtl,
1845                                    registers: registers?,
1846                                    compatibility_mask: header.compatibility_mask,
1847                                }
1848                            }
1849                        }
1850                    }
1851                    Some(IgvmPlatformType::SEV_SNP) | Some(IgvmPlatformType::SEV_ES) => {
1852                        // Read the VMSA which is stored as 4K file data.
1853                        let start = (header.file_offset - file_data_start) as usize;
1854                        if file_data.len() < start {
1855                            return Err(BinaryHeaderError::InvalidDataSize);
1856                        }
1857
1858                        let data = file_data
1859                            .get(start..)
1860                            .and_then(|x| x.get(..PAGE_SIZE_4K as usize))
1861                            .ok_or(BinaryHeaderError::InvalidDataSize)?;
1862
1863                        // Copy the VMSA bytes into the VMSA, and validate the remaining bytes are 0.
1864                        // todo: zerocopy: as of 0.8, can recover from allocation failure
1865                        let mut vmsa = SevVmsa::new_box_zeroed().unwrap();
1866                        let (vmsa_slice, remaining) = data.split_at(size_of::<SevVmsa>());
1867                        vmsa.as_mut_bytes().copy_from_slice(vmsa_slice);
1868                        if remaining.iter().any(|b| *b != 0) {
1869                            return Err(BinaryHeaderError::InvalidVmsa);
1870                        }
1871
1872                        IgvmDirectiveHeader::SnpVpContext {
1873                            gpa: header.gpa.into(),
1874                            compatibility_mask: header.compatibility_mask,
1875                            vp_index: header.vp_index,
1876                            vmsa,
1877                        }
1878                    }
1879                    Some(IgvmPlatformType::NATIVE) | Some(IgvmPlatformType::SEV) => {
1880                        // Read the context which is stored as 4K file data.
1881                        let start = (header.file_offset - file_data_start) as usize;
1882                        if file_data.len() < start {
1883                            return Err(BinaryHeaderError::InvalidDataSize);
1884                        }
1885
1886                        let data = file_data
1887                            .get(start..)
1888                            .and_then(|x| x.get(..PAGE_SIZE_4K as usize))
1889                            .ok_or(BinaryHeaderError::InvalidDataSize)?;
1890
1891                        // Copy the context bytes into the context structure,
1892                        // and validate the remaining bytes are 0.
1893                        // todo: zerocopy: as of 0.8, can recover from allocation failure
1894                        let mut context = IgvmNativeVpContextX64::new_box_zeroed().unwrap();
1895                        let (context_slice, remaining) =
1896                            data.split_at(size_of::<IgvmNativeVpContextX64>());
1897                        context.as_mut_bytes().copy_from_slice(context_slice);
1898                        if remaining.iter().any(|b| *b != 0) {
1899                            return Err(BinaryHeaderError::InvalidContext);
1900                        }
1901
1902                        IgvmDirectiveHeader::X64NativeVpContext {
1903                            compatibility_mask: header.compatibility_mask,
1904                            vp_index: header.vp_index,
1905                            context,
1906                        }
1907                    }
1908                    _ => {
1909                        // Unsupported compatibility mask or isolation type
1910                        return Err(BinaryHeaderError::InvalidVpContextPlatformType);
1911                    }
1912                }
1913            }
1914            IgvmVariableHeaderType::IGVM_VHT_REQUIRED_MEMORY
1915                if length == size_of::<IGVM_VHS_REQUIRED_MEMORY>() =>
1916            {
1917                let IGVM_VHS_REQUIRED_MEMORY {
1918                    gpa,
1919                    compatibility_mask,
1920                    number_of_bytes,
1921                    flags,
1922                    reserved,
1923                } = read_header(&mut variable_headers)?;
1924
1925                if reserved != 0 {
1926                    return Err(BinaryHeaderError::ReservedNotZero);
1927                }
1928
1929                let vtl2_protectable = flags.vtl2_protectable();
1930
1931                IgvmDirectiveHeader::RequiredMemory {
1932                    gpa,
1933                    compatibility_mask,
1934                    number_of_bytes,
1935                    vtl2_protectable,
1936                }
1937            }
1938            IgvmVariableHeaderType::IGVM_VHT_VP_COUNT_PARAMETER
1939                if length == size_of::<IGVM_VHS_PARAMETER>() =>
1940            {
1941                IgvmDirectiveHeader::VpCount(read_header(&mut variable_headers)?)
1942            }
1943            IgvmVariableHeaderType::IGVM_VHT_ENVIRONMENT_INFO_PARAMETER
1944                if length == size_of::<IGVM_VHS_PARAMETER>() =>
1945            {
1946                IgvmDirectiveHeader::EnvironmentInfo(read_header(&mut variable_headers)?)
1947            }
1948            IgvmVariableHeaderType::IGVM_VHT_SRAT if length == size_of::<IGVM_VHS_PARAMETER>() => {
1949                IgvmDirectiveHeader::Srat(read_header(&mut variable_headers)?)
1950            }
1951            IgvmVariableHeaderType::IGVM_VHT_MADT if length == size_of::<IGVM_VHS_PARAMETER>() => {
1952                IgvmDirectiveHeader::Madt(read_header(&mut variable_headers)?)
1953            }
1954            IgvmVariableHeaderType::IGVM_VHT_SLIT if length == size_of::<IGVM_VHS_PARAMETER>() => {
1955                IgvmDirectiveHeader::Slit(read_header(&mut variable_headers)?)
1956            }
1957            IgvmVariableHeaderType::IGVM_VHT_PPTT if length == size_of::<IGVM_VHS_PARAMETER>() => {
1958                IgvmDirectiveHeader::Pptt(read_header(&mut variable_headers)?)
1959            }
1960            IgvmVariableHeaderType::IGVM_VHT_MMIO_RANGES
1961                if length == size_of::<IGVM_VHS_PARAMETER>() =>
1962            {
1963                IgvmDirectiveHeader::MmioRanges(read_header(&mut variable_headers)?)
1964            }
1965            IgvmVariableHeaderType::IGVM_VHT_SNP_ID_BLOCK
1966                if length == size_of::<IGVM_VHS_SNP_ID_BLOCK>() =>
1967            {
1968                let IGVM_VHS_SNP_ID_BLOCK {
1969                    compatibility_mask,
1970                    author_key_enabled,
1971                    reserved,
1972                    ld,
1973                    family_id,
1974                    image_id,
1975                    version,
1976                    guest_svn,
1977                    id_key_algorithm,
1978                    author_key_algorithm,
1979                    id_key_signature,
1980                    id_public_key,
1981                    author_key_signature,
1982                    author_public_key,
1983                } = read_header(&mut variable_headers)?;
1984                IgvmDirectiveHeader::SnpIdBlock {
1985                    compatibility_mask,
1986                    author_key_enabled,
1987                    reserved,
1988                    ld,
1989                    family_id,
1990                    image_id,
1991                    version,
1992                    guest_svn,
1993                    id_key_algorithm,
1994                    author_key_algorithm,
1995                    id_key_signature: Box::new(id_key_signature),
1996                    id_public_key: Box::new(id_public_key),
1997                    author_key_signature: Box::new(author_key_signature),
1998                    author_public_key: Box::new(author_public_key),
1999                }
2000            }
2001            IgvmVariableHeaderType::IGVM_VHT_VBS_MEASUREMENT
2002                if length == size_of::<IGVM_VHS_VBS_MEASUREMENT>() =>
2003            {
2004                let IGVM_VHS_VBS_MEASUREMENT {
2005                    compatibility_mask,
2006                    version,
2007                    product_id,
2008                    module_id,
2009                    security_version,
2010                    policy_flags,
2011                    boot_digest_algo,
2012                    signing_algo,
2013                    boot_measurement_digest,
2014                    signature,
2015                    public_key,
2016                } = read_header(&mut variable_headers)?;
2017                IgvmDirectiveHeader::VbsMeasurement {
2018                    compatibility_mask,
2019                    version,
2020                    product_id,
2021                    module_id,
2022                    security_version,
2023                    policy_flags,
2024                    boot_digest_algo,
2025                    signing_algo,
2026                    boot_measurement_digest: Box::new(boot_measurement_digest),
2027                    signature: Box::new(signature),
2028                    public_key: Box::new(public_key),
2029                }
2030            }
2031            IgvmVariableHeaderType::IGVM_VHT_MEMORY_MAP
2032                if length == size_of::<IGVM_VHS_PARAMETER>() =>
2033            {
2034                IgvmDirectiveHeader::MemoryMap(read_header(&mut variable_headers)?)
2035            }
2036            IgvmVariableHeaderType::IGVM_VHT_ERROR_RANGE
2037                if length == size_of::<IGVM_VHS_ERROR_RANGE>() =>
2038            {
2039                let IGVM_VHS_ERROR_RANGE {
2040                    gpa,
2041                    compatibility_mask,
2042                    size_bytes,
2043                } = read_header(&mut variable_headers)?;
2044                IgvmDirectiveHeader::ErrorRange {
2045                    gpa,
2046                    compatibility_mask,
2047                    size_bytes,
2048                }
2049            }
2050            IgvmVariableHeaderType::IGVM_VHT_COMMAND_LINE
2051                if length == size_of::<IGVM_VHS_PARAMETER>() =>
2052            {
2053                IgvmDirectiveHeader::CommandLine(read_header(&mut variable_headers)?)
2054            }
2055            IgvmVariableHeaderType::IGVM_VHT_DEVICE_TREE
2056                if length == size_of::<IGVM_VHS_PARAMETER>() =>
2057            {
2058                IgvmDirectiveHeader::DeviceTree(read_header(&mut variable_headers)?)
2059            }
2060            _ => return Err(BinaryHeaderError::InvalidVariableHeaderType),
2061        };
2062
2063        header.validate()?;
2064        Ok((header, variable_headers))
2065    }
2066}
2067
2068#[derive(Debug, Error)]
2069pub enum Error {
2070    #[error("no valid platform headers")]
2071    NoPlatformHeaders,
2072    #[error("file data section too large")]
2073    FileDataSectionTooLarge,
2074    #[error("variable header section too large")]
2075    VariableHeaderSectionTooLarge,
2076    #[error("total file size too large")]
2077    TotalFileSizeTooLarge,
2078    #[error("invalid binary platform header")]
2079    InvalidBinaryPlatformHeader(#[source] BinaryHeaderError),
2080    #[error("invalid binary initialization header")]
2081    InvalidBinaryInitializationHeader(#[source] BinaryHeaderError),
2082    #[error("invalid binary directive header")]
2083    InvalidBinaryDirectiveHeader(#[source] BinaryHeaderError),
2084    #[error("multiple platform headers with the same isolation type")]
2085    MultiplePlatformHeadersWithSameIsolation,
2086    #[error("invalid parameter area index")]
2087    InvalidParameterAreaIndex,
2088    #[error("invalid platform type")]
2089    InvalidPlatformType,
2090    #[error("no free compatibility masks")]
2091    NoFreeCompatibilityMasks,
2092    #[error("invalid fixed header")]
2093    InvalidFixedHeader,
2094    #[error("invalid binary variable header section")]
2095    InvalidBinaryVariableHeaderSection,
2096    #[error("invalid checksum in fixed header, expected {expected} was {header_value}")]
2097    InvalidChecksum { expected: u32, header_value: u32 },
2098    #[error("page table relocation header specified twice for a compatibiltiy mask")]
2099    MultiplePageTableRelocationHeaders,
2100    #[error("relocation regions overlap")]
2101    RelocationRegionsOverlap,
2102    #[error("parameter insert inside page table region")]
2103    ParameterInsertInsidePageTableRegion,
2104    #[error("no matching vp context for vp index and vtl")]
2105    NoMatchingVpContext,
2106    #[error("platform {platform:?} not supported on architecture {arch:?}")]
2107    PlatformArchUnsupported {
2108        arch: Arch,
2109        platform: igvm_defs::IgvmPlatformType,
2110    },
2111    #[error("invalid header type {header_type} on arch {arch:?}")]
2112    InvalidHeaderArch { arch: Arch, header_type: String },
2113    #[error("page size of 0x{0:x} unsupported")]
2114    UnsupportedPageSize(u32),
2115    #[error("invalid fixed header arch")]
2116    InvalidFixedHeaderArch(u32),
2117    #[error("merged igvm files are not the same revision")]
2118    MergeRevision,
2119}
2120
2121/// Architecture for an IGVM file.
2122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2123pub enum Arch {
2124    X64,
2125    AArch64,
2126}
2127
2128impl From<Arch> for IgvmArchitecture {
2129    fn from(value: Arch) -> Self {
2130        match value {
2131            Arch::X64 => IgvmArchitecture::X64,
2132            Arch::AArch64 => IgvmArchitecture::AARCH64,
2133        }
2134    }
2135}
2136
2137/// Format revision for an IGVM file.
2138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2139pub enum IgvmRevision {
2140    V1,
2141    V2 {
2142        /// Architecture for the IGVM file.
2143        arch: Arch,
2144        /// Page size for the IGVM file.
2145        page_size: u32,
2146    },
2147}
2148
2149impl IgvmRevision {
2150    fn arch(&self) -> Arch {
2151        match self {
2152            IgvmRevision::V1 => Arch::X64,
2153            IgvmRevision::V2 { arch, .. } => *arch,
2154        }
2155    }
2156
2157    fn page_size(&self) -> u64 {
2158        match self {
2159            IgvmRevision::V1 => PAGE_SIZE_4K,
2160            IgvmRevision::V2 { page_size, .. } => *page_size as u64,
2161        }
2162    }
2163
2164    fn fixed_header_size(&self) -> usize {
2165        match self {
2166            IgvmRevision::V1 => size_of::<IGVM_FIXED_HEADER>(),
2167            IgvmRevision::V2 { .. } => size_of::<IGVM_FIXED_HEADER_V2>(),
2168        }
2169    }
2170}
2171
2172/// An in-memory IGVM file that can be used to load a guest, or serialized to
2173/// the binary format.
2174#[derive(Debug, Clone)]
2175pub struct IgvmFile {
2176    revision: IgvmRevision,
2177    platform_headers: Vec<IgvmPlatformHeader>,
2178    initialization_headers: Vec<IgvmInitializationHeader>,
2179    directive_headers: Vec<IgvmDirectiveHeader>,
2180}
2181
2182impl fmt::Display for IgvmFile {
2183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2184        writeln!(f, "{:#X?}", self.platform_headers)?;
2185        writeln!(f, "{:#X?}", self.initialization_headers)?;
2186        for h in &self.directive_headers {
2187            writeln!(f, "{}", h)?;
2188        }
2189        Ok(())
2190    }
2191}
2192
2193/// Represents information about an IGVM relocatable region.
2194#[derive(Debug, Clone)]
2195pub struct IgvmRelocatableRegion {
2196    pub base_gpa: u64,
2197    pub size: u64,
2198    pub relocation_alignment: u64,
2199    pub minimum_relocation_gpa: u64,
2200    pub maximum_relocation_gpa: u64,
2201    pub is_vtl2: bool,
2202    pub apply_rip_offset: bool,
2203    pub apply_gdtr_offset: bool,
2204    pub vp_index: u16,
2205    pub vtl: Vtl,
2206}
2207
2208impl IgvmRelocatableRegion {
2209    /// Check if a gpa is contained within this relocatable region.
2210    pub fn contains(&self, gpa: u64) -> bool {
2211        let end = self.base_gpa + self.size;
2212        gpa >= self.base_gpa && gpa < end
2213    }
2214
2215    /// Check if a relocation_base gpa is valid or not.
2216    pub fn relocation_base_valid(&self, relocation_base: u64) -> bool {
2217        // New base must be aligned, and start and end must live within the
2218        // acceptable relocation range.
2219        let start = relocation_base;
2220        let end = relocation_base + self.size;
2221
2222        if start % self.relocation_alignment != 0 {
2223            tracing::debug!("base is not aligned");
2224            false
2225        } else if start < self.minimum_relocation_gpa {
2226            tracing::debug!("base is too low");
2227            false
2228        } else if end > self.maximum_relocation_gpa {
2229            tracing::debug!("end is too high");
2230            false
2231        } else {
2232            true
2233        }
2234    }
2235}
2236
2237#[derive(Debug, Clone)]
2238struct VpIdentifier {
2239    compatibility_mask: u32,
2240    vp_index: u16,
2241    vtl: Vtl,
2242}
2243
2244#[derive(Debug, Clone)]
2245struct PageTableRegion {
2246    compatibility_mask: u32,
2247    gpa: u64,
2248    size: u64,
2249}
2250
2251#[derive(Debug, Clone)]
2252struct DirectiveHeaderValidationInfo {
2253    used_vp_idents: Vec<VpIdentifier>,
2254    page_table_regions: Vec<PageTableRegion>,
2255}
2256
2257fn extract_individual_masks(mut compatibility_mask: u32) -> Vec<u32> {
2258    let mut masks = Vec::new();
2259    while compatibility_mask != 0 {
2260        let single_mask = 1 << compatibility_mask.trailing_zeros();
2261        masks.push(single_mask);
2262        compatibility_mask &= !single_mask;
2263    }
2264    masks
2265}
2266
2267/// Represents an IGVM fixed header.
2268#[derive(Debug, Clone)]
2269enum FixedHeader {
2270    V1(IGVM_FIXED_HEADER),
2271    V2(IGVM_FIXED_HEADER_V2),
2272}
2273
2274impl FixedHeader {
2275    /// Get the fixed header as raw bytes.
2276    fn as_bytes(&self) -> &[u8] {
2277        match self {
2278            FixedHeader::V1(raw) => raw.as_bytes(),
2279            FixedHeader::V2(raw) => raw.as_bytes(),
2280        }
2281    }
2282
2283    fn set_total_file_size(&mut self, size: u32) {
2284        match self {
2285            FixedHeader::V1(raw) => raw.total_file_size = size,
2286            FixedHeader::V2(raw) => raw.total_file_size = size,
2287        }
2288    }
2289
2290    fn set_checksum(&mut self, checksum: u32) {
2291        match self {
2292            FixedHeader::V1(raw) => raw.checksum = checksum,
2293            FixedHeader::V2(raw) => raw.checksum = checksum,
2294        }
2295    }
2296
2297    fn magic(&self) -> u32 {
2298        match self {
2299            FixedHeader::V1(raw) => raw.magic,
2300            FixedHeader::V2(raw) => raw.magic,
2301        }
2302    }
2303
2304    fn format_version(&self) -> u32 {
2305        match self {
2306            FixedHeader::V1(raw) => raw.format_version,
2307            FixedHeader::V2(raw) => raw.format_version,
2308        }
2309    }
2310
2311    fn total_file_size(&self) -> u32 {
2312        match self {
2313            FixedHeader::V1(raw) => raw.total_file_size,
2314            FixedHeader::V2(raw) => raw.total_file_size,
2315        }
2316    }
2317
2318    fn variable_header_offset(&self) -> u32 {
2319        match self {
2320            FixedHeader::V1(raw) => raw.variable_header_offset,
2321            FixedHeader::V2(raw) => raw.variable_header_offset,
2322        }
2323    }
2324
2325    fn variable_header_size(&self) -> u32 {
2326        match self {
2327            FixedHeader::V1(raw) => raw.variable_header_size,
2328            FixedHeader::V2(raw) => raw.variable_header_size,
2329        }
2330    }
2331
2332    fn checksum(&self) -> u32 {
2333        match self {
2334            FixedHeader::V1(raw) => raw.checksum,
2335            FixedHeader::V2(raw) => raw.checksum,
2336        }
2337    }
2338}
2339
2340impl IgvmFile {
2341    /// Check if the given platform headers are valid.
2342    ///
2343    /// Validates that:
2344    /// - There is at least 1 platform header
2345    /// - Each isolation type is valid
2346    /// - Each isolation type is only used once
2347    /// - Isolation type is consistent with arch
2348    fn validate_platform_headers<'a>(
2349        revision: IgvmRevision,
2350        platform_headers: impl Iterator<Item = &'a IgvmPlatformHeader>,
2351    ) -> Result<(), Error> {
2352        let mut at_least_one = false;
2353        let mut isolation_types = HashMap::new();
2354
2355        for header in platform_headers {
2356            at_least_one = true;
2357            header
2358                .validate()
2359                .map_err(Error::InvalidBinaryPlatformHeader)?;
2360
2361            match header {
2362                IgvmPlatformHeader::SupportedPlatform(info) => {
2363                    match info.platform_type {
2364                        IgvmPlatformType::VSM_ISOLATION => {}
2365                        IgvmPlatformType::SEV_SNP
2366                        | IgvmPlatformType::TDX
2367                        | IgvmPlatformType::NATIVE
2368                        | IgvmPlatformType::SEV
2369                        | IgvmPlatformType::SEV_ES => {
2370                            if revision.arch() != Arch::X64 {
2371                                return Err(Error::PlatformArchUnsupported {
2372                                    arch: revision.arch(),
2373                                    platform: info.platform_type,
2374                                });
2375                            }
2376                        }
2377                        _ => return Err(Error::InvalidPlatformType),
2378                    }
2379
2380                    if let Some(prev) = isolation_types.insert(info.platform_type, info) {
2381                        tracing::trace!(
2382                            current = ?info,
2383                            prev = ?prev,
2384                            "current platform header conflicts with previous duplicate header"
2385                        );
2386                        return Err(Error::MultiplePlatformHeadersWithSameIsolation);
2387                    }
2388                }
2389            }
2390        }
2391
2392        if !at_least_one {
2393            Err(Error::NoPlatformHeaders)
2394        } else {
2395            Ok(())
2396        }
2397    }
2398
2399    /// Check if the given initialization headers are valid.
2400    ///
2401    /// Returns additional info used to validate directive headers.
2402    fn validate_initialization_headers(
2403        revision: IgvmRevision,
2404        initialization_headers: &[IgvmInitializationHeader],
2405    ) -> Result<DirectiveHeaderValidationInfo, Error> {
2406        let mut page_table_masks = 0;
2407        let mut used_vp_idents: Vec<VpIdentifier> = Vec::new();
2408        let mut reloc_regions: HashMap<u32, RangeMap<u64, ()>> = HashMap::new();
2409        let mut page_table_regions = Vec::new();
2410
2411        let mut check_region_overlap =
2412            |compatibility_mask: u32, start: u64, size: u64| -> Result<(), Error> {
2413                for mask in extract_individual_masks(compatibility_mask) {
2414                    let regions = match reloc_regions.get_mut(&mask) {
2415                        Some(value) => value,
2416                        None => {
2417                            reloc_regions.insert(mask, RangeMap::new());
2418                            reloc_regions.get_mut(&mask).expect("just inserted")
2419                        }
2420                    };
2421
2422                    if !regions.insert(start..=start + size - 1, ()) {
2423                        return Err(Error::RelocationRegionsOverlap);
2424                    }
2425                }
2426
2427                Ok(())
2428            };
2429
2430        for header in initialization_headers {
2431            // Each individual header needs to be valid.
2432            header
2433                .validate()
2434                .map_err(Error::InvalidBinaryInitializationHeader)?;
2435
2436            // Do the following additional validation:
2437            //  - A page table relocation header may only be specified once per
2438            //    compatability mask.
2439            //  - Relocation regions and page table regions for a given
2440            //    compatibility mask may not overlap.
2441            //  - Keep track of which page table regions, vp_index, and vtls for
2442            //    use in validating directive headers.
2443            match header {
2444                IgvmInitializationHeader::PageTableRelocationRegion {
2445                    compatibility_mask,
2446                    gpa,
2447                    size,
2448                    used_size: _,
2449                    vp_index,
2450                    vtl,
2451                } => {
2452                    if !matches!(revision.arch(), Arch::X64 | Arch::AArch64) {
2453                        return Err(Error::InvalidHeaderArch {
2454                            arch: revision.arch(),
2455                            header_type: "PageTableRelocationRegion".into(),
2456                        });
2457                    }
2458
2459                    // Header can be only specified once per compatibility mask
2460                    if compatibility_mask & page_table_masks != 0 {
2461                        return Err(Error::MultiplePageTableRelocationHeaders);
2462                    }
2463                    page_table_masks |= compatibility_mask;
2464
2465                    check_region_overlap(*compatibility_mask, *gpa, *size)?;
2466
2467                    used_vp_idents.push(VpIdentifier {
2468                        compatibility_mask: *compatibility_mask,
2469                        vp_index: *vp_index,
2470                        vtl: *vtl,
2471                    });
2472                    page_table_regions.push(PageTableRegion {
2473                        compatibility_mask: *compatibility_mask,
2474                        gpa: *gpa,
2475                        size: *size,
2476                    })
2477                }
2478                IgvmInitializationHeader::RelocatableRegion {
2479                    compatibility_mask,
2480                    relocation_alignment: _,
2481                    relocation_region_gpa,
2482                    relocation_region_size,
2483                    minimum_relocation_gpa: _,
2484                    maximum_relocation_gpa: _,
2485                    is_vtl2: _,
2486                    apply_rip_offset: _,
2487                    apply_gdtr_offset: _,
2488                    vp_index,
2489                    vtl,
2490                } => {
2491                    if !matches!(revision.arch(), Arch::X64 | Arch::AArch64) {
2492                        return Err(Error::InvalidHeaderArch {
2493                            arch: revision.arch(),
2494                            header_type: "RelocatableRegion".into(),
2495                        });
2496                    }
2497
2498                    check_region_overlap(
2499                        *compatibility_mask,
2500                        *relocation_region_gpa,
2501                        *relocation_region_size,
2502                    )?;
2503
2504                    used_vp_idents.push(VpIdentifier {
2505                        compatibility_mask: *compatibility_mask,
2506                        vp_index: *vp_index,
2507                        vtl: *vtl,
2508                    })
2509                }
2510                // TODO: validate SNP policy compatibility mask specifies SNP
2511                _ => {}
2512            }
2513        }
2514
2515        Ok(DirectiveHeaderValidationInfo {
2516            used_vp_idents,
2517            page_table_regions,
2518        })
2519    }
2520
2521    /// Check if the given directive headers are valid.
2522    ///
2523    /// Validates that:
2524    ///  - Parameter indicies are declared first in a parameter area
2525    ///  - Parameter indicies are not declared more than once
2526    ///
2527    /// TODO: compatability masks? vp contexts match isolation arch? individual
2528    /// header alignment?
2529    fn validate_directive_headers(
2530        revision: IgvmRevision,
2531        directive_headers: &[IgvmDirectiveHeader],
2532        mut validation_info: DirectiveHeaderValidationInfo,
2533    ) -> Result<(), Error> {
2534        #[derive(PartialEq, Eq)]
2535        enum ParameterAreaState {
2536            Allocated,
2537            Inserted,
2538        }
2539        let mut parameter_areas: BTreeMap<u32, ParameterAreaState> = BTreeMap::new();
2540
2541        // TODO: validate parameter usage offset falls within parameter area size
2542
2543        for header in directive_headers {
2544            header
2545                .validate()
2546                .map_err(Error::InvalidBinaryDirectiveHeader)?;
2547
2548            match header {
2549                IgvmDirectiveHeader::PageData { .. } => {}
2550                IgvmDirectiveHeader::ParameterArea {
2551                    parameter_area_index,
2552                    ..
2553                } => {
2554                    // This must be the first use of this parameter index
2555                    if parameter_areas
2556                        .insert(*parameter_area_index, ParameterAreaState::Allocated)
2557                        .is_some()
2558                    {
2559                        return Err(Error::InvalidParameterAreaIndex);
2560                    }
2561                }
2562                IgvmDirectiveHeader::VpCount(info)
2563                | IgvmDirectiveHeader::EnvironmentInfo(info)
2564                | IgvmDirectiveHeader::Srat(info)
2565                | IgvmDirectiveHeader::Madt(info)
2566                | IgvmDirectiveHeader::Slit(info)
2567                | IgvmDirectiveHeader::Pptt(info)
2568                | IgvmDirectiveHeader::MmioRanges(info)
2569                | IgvmDirectiveHeader::MemoryMap(info)
2570                | IgvmDirectiveHeader::CommandLine(info)
2571                | IgvmDirectiveHeader::DeviceTree(info) => {
2572                    match parameter_areas.get(&info.parameter_area_index) {
2573                        Some(ParameterAreaState::Allocated) => {}
2574                        _ => return Err(Error::InvalidParameterAreaIndex),
2575                    }
2576                }
2577                IgvmDirectiveHeader::RequiredMemory { .. } => {}
2578                IgvmDirectiveHeader::SnpVpContext { .. } => {
2579                    // TODO: Validate vp info for SNP. Need max enabled VTL for given platform as that's the
2580                    //       which VTL this vmsa refers to.
2581                }
2582                IgvmDirectiveHeader::X64NativeVpContext {
2583                    compatibility_mask: _,
2584                    context: _,
2585                    vp_index: _,
2586                } => {
2587                    if revision.arch() != Arch::X64 {
2588                        return Err(Error::InvalidHeaderArch {
2589                            arch: revision.arch(),
2590                            header_type: "X64VbsVpContext".into(),
2591                        });
2592                    }
2593                }
2594                IgvmDirectiveHeader::X64VbsVpContext {
2595                    vtl,
2596                    registers: _,
2597                    compatibility_mask,
2598                } => {
2599                    if revision.arch() != Arch::X64 {
2600                        return Err(Error::InvalidHeaderArch {
2601                            arch: revision.arch(),
2602                            header_type: "X64VbsVpContext".into(),
2603                        });
2604                    }
2605
2606                    // Remove all vp identifiers that refer to this vp context. The vp_index is 0.
2607                    validation_info.used_vp_idents.retain(|ident| {
2608                        !((ident.compatibility_mask & compatibility_mask != 0)
2609                            && ident.vp_index == 0
2610                            && ident.vtl == *vtl)
2611                    })
2612                }
2613                IgvmDirectiveHeader::AArch64VbsVpContext {
2614                    vtl,
2615                    registers: _,
2616                    compatibility_mask,
2617                } => {
2618                    if revision.arch() != Arch::AArch64 {
2619                        return Err(Error::InvalidHeaderArch {
2620                            arch: revision.arch(),
2621                            header_type: "AArch64VbsVpContext".into(),
2622                        });
2623                    }
2624
2625                    // Remove all vp identifiers that refer to this vp context. The vp_index is 0.
2626                    validation_info.used_vp_idents.retain(|ident| {
2627                        !((ident.compatibility_mask & compatibility_mask != 0)
2628                            && ident.vp_index == 0
2629                            && ident.vtl == *vtl)
2630                    })
2631                }
2632                IgvmDirectiveHeader::ParameterInsert(info) => {
2633                    match parameter_areas.get_mut(&info.parameter_area_index) {
2634                        Some(state) if *state == ParameterAreaState::Allocated => {
2635                            // Parameter index can no longer be used again.
2636                            *state = ParameterAreaState::Inserted;
2637                        }
2638                        _ => return Err(Error::InvalidParameterAreaIndex),
2639                    }
2640
2641                    // Cannot insert within a page table region
2642                    if validation_info.page_table_regions.iter().any(|region| {
2643                        let start = region.gpa;
2644                        let end = region.gpa + region.size;
2645                        (region.compatibility_mask & info.compatibility_mask != 0)
2646                            && info.gpa >= start
2647                            && info.gpa < end
2648                    }) {
2649                        return Err(Error::ParameterInsertInsidePageTableRegion);
2650                    }
2651                }
2652                IgvmDirectiveHeader::ErrorRange { .. } => {} // TODO: Validate ErrorRange
2653                IgvmDirectiveHeader::SnpIdBlock { .. } => {} // TODO: Validate Snp
2654                IgvmDirectiveHeader::VbsMeasurement { .. } => {} // TODO: Validate Vbs
2655            }
2656        }
2657
2658        if !validation_info.used_vp_idents.is_empty() {
2659            return Err(Error::NoMatchingVpContext);
2660        }
2661
2662        Ok(())
2663    }
2664
2665    /// Serialize this IGVM file into the binary format, into the supplied
2666    /// output Vec.
2667    pub fn serialize(&self, output: &mut Vec<u8>) -> Result<(), Error> {
2668        IgvmFile::validate_platform_headers(self.revision, self.platform_headers.iter())?;
2669
2670        // Build the variable header and file data section by looping through each header type.
2671        // First, calculate the starting data file offset relative to the rest of the file.
2672        let mut variable_header_section_size = 0;
2673        for header in self.platform_headers.iter() {
2674            variable_header_section_size += header.header_size();
2675        }
2676        for header in self.initialization_headers.iter() {
2677            variable_header_section_size += header.header_size();
2678        }
2679        for header in self.directive_headers.iter() {
2680            variable_header_section_size += header.header_size();
2681        }
2682
2683        // TODO: All headers should be 8 byte aligned. Const assert and downgrade this to a debug_assert instead?
2684        assert_eq!(variable_header_section_size % 8, 0);
2685
2686        // The file data section starts after the fixed header and variable header section.
2687        let file_data_section_start =
2688            self.revision.fixed_header_size() + variable_header_section_size;
2689
2690        let mut variable_header_binary = Vec::new();
2691
2692        // Add platform headers
2693        for header in &self.platform_headers {
2694            match header {
2695                IgvmPlatformHeader::SupportedPlatform(platform) => {
2696                    let header = IGVM_VHS_VARIABLE_HEADER {
2697                        typ: IgvmVariableHeaderType::IGVM_VHT_SUPPORTED_PLATFORM,
2698                        length: platform.as_bytes().len() as u32,
2699                    };
2700                    variable_header_binary.extend_from_slice(header.as_bytes());
2701                    variable_header_binary.extend_from_slice(platform.as_bytes());
2702                }
2703            }
2704        }
2705
2706        // Add initialization headers
2707        for header in &self.initialization_headers {
2708            header
2709                .write_binary_header(&mut variable_header_binary)
2710                .map_err(Error::InvalidBinaryDirectiveHeader)?;
2711            assert_eq!(variable_header_binary.len() % 8, 0);
2712        }
2713
2714        // dedup file data
2715        let mut file_data = FileDataSerializer::new(file_data_section_start);
2716
2717        // Add directive headers
2718        for header in &self.directive_headers {
2719            header
2720                .write_binary_header(&mut variable_header_binary, &mut file_data)
2721                .map_err(Error::InvalidBinaryDirectiveHeader)?;
2722            // TODO: All structure definitions should be 8 byte aligned. Should const assert defs instead and
2723            //       downgrade this or leave this assert in?
2724            assert_eq!(variable_header_binary.len() % 8, 0);
2725        }
2726
2727        assert_eq!(variable_header_section_size, variable_header_binary.len());
2728
2729        let mut fixed_header = match self.revision {
2730            IgvmRevision::V1 => FixedHeader::V1(IGVM_FIXED_HEADER {
2731                magic: IGVM_MAGIC_VALUE,
2732                format_version: IGVM_FORMAT_VERSION_1,
2733                variable_header_offset: size_of::<IGVM_FIXED_HEADER>() as u32,
2734                variable_header_size: variable_header_binary
2735                    .len()
2736                    .try_into()
2737                    .map_err(|_| Error::VariableHeaderSectionTooLarge)?,
2738                total_file_size: 0,
2739                checksum: 0,
2740            }),
2741            IgvmRevision::V2 { arch, page_size } => FixedHeader::V2(IGVM_FIXED_HEADER_V2 {
2742                magic: IGVM_MAGIC_VALUE,
2743                format_version: IGVM_FORMAT_VERSION_2,
2744                variable_header_offset: size_of::<IGVM_FIXED_HEADER_V2>() as u32,
2745                variable_header_size: variable_header_binary
2746                    .len()
2747                    .try_into()
2748                    .map_err(|_| Error::VariableHeaderSectionTooLarge)?,
2749                total_file_size: 0,
2750                checksum: 0,
2751                architecture: arch.into(),
2752                page_size,
2753            }),
2754        };
2755
2756        let mut file_data = file_data.take();
2757        // Create the fixed header
2758        let total_file_size =
2759            fixed_header.as_bytes().len() + variable_header_binary.len() + file_data.len();
2760
2761        fixed_header.set_total_file_size(
2762            total_file_size
2763                .try_into()
2764                .map_err(|_| Error::TotalFileSizeTooLarge)?,
2765        );
2766
2767        // Calculate the checksum which consists of a CRC32 of just the fixed and variable headers.
2768        // It does not include the file data.
2769        let mut hasher = crc32fast::Hasher::new();
2770        hasher.update(fixed_header.as_bytes());
2771        hasher.update(&variable_header_binary);
2772        let checksum = hasher.finalize();
2773        fixed_header.set_checksum(checksum);
2774
2775        // Flatten into a single vector representing the whole file
2776        output.extend_from_slice(fixed_header.as_bytes());
2777        output.append(&mut variable_header_binary);
2778        output.append(&mut file_data);
2779
2780        Ok(())
2781    }
2782
2783    /// Create a new [`IgvmFile`] from the given headers.
2784    pub fn new(
2785        revision: IgvmRevision,
2786        platform_headers: Vec<IgvmPlatformHeader>,
2787        initialization_headers: Vec<IgvmInitializationHeader>,
2788        directive_headers: Vec<IgvmDirectiveHeader>,
2789    ) -> Result<Self, Error> {
2790        // TODO: support non 4K page sizes.
2791        if revision.page_size() != PAGE_SIZE_4K {
2792            return Err(Error::UnsupportedPageSize(revision.page_size() as u32));
2793        }
2794
2795        Self::validate_platform_headers(revision, platform_headers.iter())?;
2796        let validation_info =
2797            Self::validate_initialization_headers(revision, &initialization_headers)?;
2798        Self::validate_directive_headers(revision, &directive_headers, validation_info)?;
2799
2800        Ok(Self {
2801            revision,
2802            platform_headers,
2803            initialization_headers,
2804            directive_headers,
2805        })
2806    }
2807
2808    /// Create a new [`IgvmFile`] from a serialized binary representation. An
2809    /// optional filter can be specified to filter headers that do not apply to
2810    /// the given isolation platform.
2811    pub fn new_from_binary(
2812        file: &[u8],
2813        isolation_filter: Option<IsolationType>,
2814    ) -> Result<Self, Error> {
2815        let total_size = file.len();
2816
2817        // Read the IGVM fixed header
2818        let mut fixed_header = FixedHeader::V1(
2819            IGVM_FIXED_HEADER::read_from_prefix(file)
2820                .map_err(|_| Error::InvalidFixedHeader)?
2821                .0, // todo: zerocopy: map_err
2822        );
2823
2824        if fixed_header.magic() != IGVM_MAGIC_VALUE {
2825            return Err(Error::InvalidFixedHeader);
2826        }
2827
2828        let revision = match fixed_header.format_version() {
2829            IGVM_FORMAT_VERSION_1 => IgvmRevision::V1,
2830            IGVM_FORMAT_VERSION_2 => {
2831                let v2 = IGVM_FIXED_HEADER_V2::read_from_prefix(file)
2832                    .map_err(|_| Error::InvalidFixedHeader)?
2833                    .0; // todo: zerocopy: map_err
2834
2835                let arch = match v2.architecture {
2836                    IgvmArchitecture::X64 => Arch::X64,
2837                    IgvmArchitecture::AARCH64 => Arch::AArch64,
2838                    _ => return Err(Error::InvalidFixedHeader),
2839                };
2840
2841                if v2.page_size != PAGE_SIZE_4K as u32 {
2842                    return Err(Error::UnsupportedPageSize(v2.page_size));
2843                }
2844
2845                let revision = IgvmRevision::V2 {
2846                    arch,
2847                    page_size: v2.page_size,
2848                };
2849
2850                fixed_header = FixedHeader::V2(v2);
2851                revision
2852            }
2853            _ => return Err(Error::InvalidFixedHeader),
2854        };
2855
2856        if fixed_header.total_file_size() as usize != total_size {
2857            return Err(Error::InvalidFixedHeader);
2858        }
2859
2860        let variable_header_start = fixed_header.variable_header_offset() as usize;
2861
2862        if variable_header_start >= total_size {
2863            return Err(Error::InvalidFixedHeader);
2864        }
2865
2866        let file_data_start =
2867            fixed_header.variable_header_offset() + fixed_header.variable_header_size();
2868
2869        if file_data_start as usize >= total_size {
2870            return Err(Error::InvalidFixedHeader);
2871        }
2872
2873        // Split file into variable headers and file data
2874        let (mut variable_headers, file_data) =
2875            file[variable_header_start..].split_at(fixed_header.variable_header_size() as usize);
2876
2877        // Validate the checksum. The checksum is calculated with the
2878        // fixed_header and variable header section, with the fixed header
2879        // checksum field set to zero.
2880        let mut fixed_header_calculate_checksum = fixed_header.clone();
2881        fixed_header_calculate_checksum.set_checksum(0);
2882        let mut hasher = crc32fast::Hasher::new();
2883        hasher.update(fixed_header_calculate_checksum.as_bytes());
2884        hasher.update(variable_headers);
2885        let checksum = hasher.finalize();
2886
2887        if fixed_header.checksum() != checksum {
2888            return Err(Error::InvalidChecksum {
2889                expected: checksum,
2890                header_value: fixed_header.checksum(),
2891            });
2892        }
2893
2894        #[derive(PartialEq, Eq)]
2895        enum VariableHeaderParsingStage {
2896            Platform,
2897            Initialization,
2898            Directive,
2899        }
2900
2901        let mut parsing_stage = VariableHeaderParsingStage::Platform;
2902        let mut platform_headers = Vec::new();
2903        let mut initialization_headers = Vec::new();
2904        let mut directive_headers = Vec::new();
2905        let mut filter_mask: u32 = 0xFFFFFFFF;
2906        let isolation_filter: Option<IgvmPlatformType> = isolation_filter.map(|typ| typ.into());
2907        let mut mask_map: HashMap<u32, IgvmPlatformType> = HashMap::new();
2908
2909        while !variable_headers.is_empty() {
2910            // Peek the next fixed variable header to determine what kind of header to parse
2911            match IGVM_VHS_VARIABLE_HEADER::read_from_prefix(variable_headers)
2912                .ok()
2913                .map(|h| h.0)
2914            {
2915                Some(header) if IGVM_VHT_RANGE_PLATFORM.contains(&header.typ.0) => {
2916                    if parsing_stage != VariableHeaderParsingStage::Platform {
2917                        // Only legal to parse platform headers before other types
2918                        return Err(Error::InvalidBinaryVariableHeaderSection);
2919                    }
2920
2921                    let (header, new_slice) =
2922                        IgvmPlatformHeader::new_from_binary_split(variable_headers)
2923                            .map_err(Error::InvalidBinaryPlatformHeader)?;
2924
2925                    match header {
2926                        IgvmPlatformHeader::SupportedPlatform(info) => {
2927                            // Setup filter_mask based on isolation_filter
2928                            if let Some(filter_type) = isolation_filter {
2929                                if filter_type == info.platform_type {
2930                                    filter_mask = info.compatibility_mask;
2931                                }
2932                            }
2933
2934                            mask_map.insert(info.compatibility_mask, info.platform_type);
2935                        }
2936                    }
2937
2938                    platform_headers.push(header);
2939                    variable_headers = new_slice;
2940                }
2941                Some(header) if IGVM_VHT_RANGE_INIT.contains(&header.typ.0) => {
2942                    match parsing_stage {
2943                        VariableHeaderParsingStage::Platform => {
2944                            parsing_stage = VariableHeaderParsingStage::Initialization
2945                        }
2946                        VariableHeaderParsingStage::Initialization => {}
2947                        VariableHeaderParsingStage::Directive => {
2948                            return Err(Error::InvalidBinaryVariableHeaderSection);
2949                        }
2950                    }
2951
2952                    let (header, new_slice) =
2953                        IgvmInitializationHeader::new_from_binary_split(variable_headers)
2954                            .map_err(Error::InvalidBinaryInitializationHeader)?;
2955
2956                    variable_headers = new_slice;
2957
2958                    if let Some(mask) = header.compatibility_mask() {
2959                        if mask & filter_mask == 0 {
2960                            // Skip this header, does not apply to the isolation filter
2961                            continue;
2962                        }
2963                    }
2964
2965                    initialization_headers.push(header);
2966                }
2967                Some(header) if IGVM_VHT_RANGE_DIRECTIVE.contains(&header.typ.0) => {
2968                    match parsing_stage {
2969                        VariableHeaderParsingStage::Platform
2970                        | VariableHeaderParsingStage::Initialization => {
2971                            parsing_stage = VariableHeaderParsingStage::Directive
2972                        }
2973                        VariableHeaderParsingStage::Directive => {}
2974                    }
2975
2976                    let compatibility_mask_to_platforms =
2977                        |mask: u32| -> Option<IgvmPlatformType> { mask_map.get(&mask).copied() };
2978
2979                    let (header, new_slice) = IgvmDirectiveHeader::new_from_binary_split(
2980                        revision,
2981                        variable_headers,
2982                        file_data,
2983                        file_data_start,
2984                        compatibility_mask_to_platforms,
2985                    )
2986                    .map_err(Error::InvalidBinaryDirectiveHeader)?;
2987
2988                    variable_headers = new_slice;
2989
2990                    if let Some(mask) = header.compatibility_mask() {
2991                        if mask & filter_mask == 0 {
2992                            // Skip this header, does not apply to the isolation filter
2993                            continue;
2994                        }
2995                    }
2996
2997                    directive_headers.push(header);
2998                }
2999                other => {
3000                    eprintln!("invalid header: {:?}", Some(other));
3001                    return Err(Error::InvalidBinaryVariableHeaderSection);
3002                }
3003            }
3004        }
3005
3006        IgvmFile::new(
3007            revision,
3008            platform_headers,
3009            initialization_headers,
3010            directive_headers,
3011        )
3012    }
3013
3014    /// Get the platform headers in this file.
3015    pub fn platforms(&self) -> &[IgvmPlatformHeader] {
3016        self.platform_headers.as_slice()
3017    }
3018
3019    /// Get the initialization headers in this file.
3020    pub fn initializations(&self) -> &[IgvmInitializationHeader] {
3021        self.initialization_headers.as_slice()
3022    }
3023
3024    /// Get the directive headers in this file.
3025    pub fn directives(&self) -> &[IgvmDirectiveHeader] {
3026        self.directive_headers.as_slice()
3027    }
3028
3029    /// Get the relocation regions and page table builder in this file for a
3030    /// given compatibility mask. If relocation is not supported, None is
3031    /// returned.
3032    pub fn relocations(
3033        &self,
3034        compatibility_mask: u32,
3035    ) -> (
3036        Option<Vec<IgvmRelocatableRegion>>,
3037        Option<PageTableRelocationBuilder>,
3038    ) {
3039        let mut regions = Vec::new();
3040        let mut page_table_fixup = None;
3041
3042        for header in &self.initialization_headers {
3043            if let Some(mask) = header.compatibility_mask() {
3044                if mask & compatibility_mask != compatibility_mask {
3045                    continue;
3046                }
3047            }
3048
3049            match header {
3050                IgvmInitializationHeader::RelocatableRegion {
3051                    compatibility_mask: _,
3052                    relocation_alignment,
3053                    relocation_region_gpa,
3054                    relocation_region_size,
3055                    minimum_relocation_gpa,
3056                    maximum_relocation_gpa,
3057                    is_vtl2,
3058                    apply_rip_offset,
3059                    apply_gdtr_offset,
3060                    vp_index,
3061                    vtl,
3062                } => {
3063                    regions.push(IgvmRelocatableRegion {
3064                        base_gpa: *relocation_region_gpa,
3065                        relocation_alignment: *relocation_alignment,
3066                        size: *relocation_region_size,
3067                        minimum_relocation_gpa: *minimum_relocation_gpa,
3068                        maximum_relocation_gpa: *maximum_relocation_gpa,
3069                        is_vtl2: *is_vtl2,
3070                        apply_rip_offset: *apply_rip_offset,
3071                        apply_gdtr_offset: *apply_gdtr_offset,
3072                        vp_index: *vp_index,
3073                        vtl: *vtl,
3074                    });
3075                }
3076                IgvmInitializationHeader::PageTableRelocationRegion {
3077                    compatibility_mask: _,
3078                    gpa,
3079                    size,
3080                    used_size,
3081                    vp_index,
3082                    vtl,
3083                } => {
3084                    assert!(page_table_fixup.is_none());
3085                    page_table_fixup = Some(PageTableRelocationBuilder::new(
3086                        *gpa, *size, *used_size, *vp_index, *vtl,
3087                    ))
3088                }
3089                _ => {}
3090            }
3091        }
3092
3093        let regions = if !regions.is_empty() {
3094            Some(regions)
3095        } else {
3096            None
3097        };
3098        (regions, page_table_fixup)
3099    }
3100
3101    /// Merge a vec of directive headers into this file. This attempts to
3102    /// deduplicate headers by merging compatibility masks. This merge is stable
3103    /// and does not modify the order of either self or other, leaving
3104    /// measurements intact.
3105    ///
3106    /// This method is O(n*m) where n is the number of headers in self, and m is
3107    /// the number of headers in other_directives.
3108    ///
3109    /// TODO: Is there a way to speed this up without breaking stability? This
3110    /// is slow for large number of headers.
3111    fn merge_dedup_directives(
3112        &mut self,
3113        other_directives: Vec<IgvmDirectiveHeader>,
3114    ) -> Result<(), Error> {
3115        let mut insert_index = 0;
3116        'outer: for other_header in other_directives {
3117            // Limit the search space to the earliest possible insertion point
3118            // that would not break relative ordering.
3119            for (index, header) in self.directive_headers[insert_index..]
3120                .iter_mut()
3121                .enumerate()
3122                .rev()
3123            {
3124                if header.equivalent(&other_header) {
3125                    match (
3126                        header.compatibility_mask_mut(),
3127                        other_header.compatibility_mask(),
3128                    ) {
3129                        (Some(header_mask), Some(other_header_mask)) => {
3130                            debug_assert!(*header_mask & other_header_mask == 0);
3131                            *header_mask |= other_header_mask
3132                        }
3133                        (None, None) => {}
3134                        _ => unreachable!(),
3135                    }
3136                    // Search now ends after this merged header. Since we
3137                    // limited the slice earlier, we add the index + 1 to the
3138                    // overall starting point.
3139                    insert_index += index + 1;
3140                    continue 'outer;
3141                }
3142            }
3143            // Unable to merge the header into an existing header, append at the
3144            // specified index and update the end of search index.
3145            self.directive_headers.insert(insert_index, other_header);
3146            insert_index += 1;
3147        }
3148
3149        Ok(())
3150    }
3151
3152    /// Internal implementation of merge used by public methods. Has different
3153    /// toggles for different behavior. This method is a stable merge.
3154    ///
3155    /// `dedup_directives` will attempt to deduplicate directives, which is
3156    /// O(n^2) on the number of headers.
3157    fn merge_internal(&mut self, mut other: IgvmFile, dedup_directives: bool) -> Result<(), Error> {
3158        // Individual validation on each IgvmFile should have already been done.
3159        // Validate the combination of both is valid.
3160        #[cfg(debug_assertions)]
3161        {
3162            debug_assert!(Self::validate_platform_headers(
3163                self.revision,
3164                self.platform_headers.iter()
3165            )
3166            .is_ok());
3167            debug_assert!(Self::validate_platform_headers(
3168                other.revision,
3169                other.platform_headers.iter()
3170            )
3171            .is_ok());
3172            let self_info =
3173                Self::validate_initialization_headers(self.revision, &self.initialization_headers)
3174                    .expect("valid file");
3175            let other_info = Self::validate_initialization_headers(
3176                other.revision,
3177                &other.initialization_headers,
3178            )
3179            .expect("valid file");
3180            debug_assert!(Self::validate_directive_headers(
3181                self.revision,
3182                &self.directive_headers,
3183                self_info
3184            )
3185            .is_ok());
3186            debug_assert!(Self::validate_directive_headers(
3187                other.revision,
3188                &other.directive_headers,
3189                other_info
3190            )
3191            .is_ok());
3192        }
3193
3194        if self.revision != other.revision {
3195            return Err(Error::MergeRevision);
3196        }
3197
3198        Self::validate_platform_headers(
3199            self.revision,
3200            self.platform_headers
3201                .iter()
3202                .chain(other.platform_headers.iter()),
3203        )?;
3204
3205        // Check the platform headers for each file to see if they need to be
3206        // fixed up or not. Do this by first checking which masks are used in
3207        // `self`, and then creating a fixup map for `other`.
3208        let mut used_compatibility_masks =
3209            self.platform_headers
3210                .iter()
3211                .fold(0, |mask, header| match header {
3212                    IgvmPlatformHeader::SupportedPlatform(platform) => {
3213                        mask | platform.compatibility_mask
3214                    }
3215                });
3216        let mut fixup_masks_map = HashMap::new();
3217        for header in &other.platform_headers {
3218            match header {
3219                IgvmPlatformHeader::SupportedPlatform(platform) => {
3220                    if platform.compatibility_mask & used_compatibility_masks != 0 {
3221                        // Find the next free compatibility mask
3222                        let free_bit = used_compatibility_masks.trailing_ones();
3223
3224                        if free_bit > 32 {
3225                            // This can never be reached, as there aren't 32
3226                            // different isolation architectures and the earlier
3227                            // validation of platform headers should have
3228                            // failed. But return an error anyways if this case
3229                            // is ever reached.
3230                            return Err(Error::NoFreeCompatibilityMasks);
3231                        }
3232
3233                        let new_mask = 1u32 << free_bit;
3234                        used_compatibility_masks |= new_mask;
3235
3236                        assert!(fixup_masks_map
3237                            .insert(platform.compatibility_mask, new_mask)
3238                            .is_none());
3239                    }
3240                }
3241            }
3242        }
3243
3244        let fixup_masks_map = fixup_masks_map;
3245        let fixup_mask_all_bits = fixup_masks_map.iter().fold(0, |mask, entry| mask | entry.0);
3246
3247        let fixup_mask = |mask: &mut u32| {
3248            let mut bits_to_be_fixed: u32 = *mask & fixup_mask_all_bits;
3249            while bits_to_be_fixed != 0 {
3250                let old_mask = 1 << bits_to_be_fixed.trailing_zeros();
3251                let new_mask = fixup_masks_map
3252                    .get(&old_mask)
3253                    .expect("old_mask should always be present");
3254                *mask = (!old_mask & *mask) | new_mask;
3255                bits_to_be_fixed &= !old_mask;
3256            }
3257        };
3258
3259        // Fixup parameter area indices by first seeing which ones are used in
3260        // `self`.
3261        let mut used_parameter_indices = BTreeSet::new();
3262        let mut fixup_parameter_index_map: BTreeMap<u32, u32> = BTreeMap::new();
3263        for header in &self.directive_headers {
3264            use IgvmDirectiveHeader::*;
3265            if let ParameterArea {
3266                parameter_area_index,
3267                ..
3268            } = header
3269            {
3270                // Self should never use a parameter area twice, as the IgvmFile
3271                // should always be valid.
3272                assert!(
3273                    used_parameter_indices.insert(*parameter_area_index),
3274                    "invalid igvm file, parameter index used twice"
3275                );
3276            }
3277        }
3278
3279        // NOTE: This could be optimized by using some sort of interval tree and
3280        //       merging consecutive usages into adjacent ranges, then iterating
3281        //       through ranges to find the first available gap. However, the
3282        //       current loaders do not use that many parameter areas, so linear
3283        //       search shouldn't be very slow as the map is small.
3284        let allocate_new_parameter_index =
3285            |used_parameter_indices: &mut BTreeSet<u32>| -> Result<u32, Error> {
3286                // new index must fit into a u32
3287                let mut new_index: u32 = used_parameter_indices
3288                    .len()
3289                    .try_into()
3290                    .map_err(|_| Error::InvalidParameterAreaIndex)?;
3291                for (index, val) in used_parameter_indices.iter().enumerate() {
3292                    let index = index as u32;
3293                    if index != *val {
3294                        new_index = index;
3295                        break;
3296                    }
3297                }
3298                assert!(used_parameter_indices.insert(new_index));
3299                Ok(new_index)
3300            };
3301
3302        let fixup_parameter_index =
3303            |index: &mut u32, fixup_parameter_index_map: &BTreeMap<u32, u32>| {
3304                // A None entry means that this paramter index does not need to
3305                // be fixed up
3306                if let Some(new_index) = fixup_parameter_index_map.get(index) {
3307                    *index = *new_index;
3308                }
3309            };
3310
3311        // Fixup all compatibility masks in platform and init headers.
3312        for header in &mut other.platform_headers {
3313            match header {
3314                IgvmPlatformHeader::SupportedPlatform(platform) => {
3315                    fixup_mask(&mut platform.compatibility_mask)
3316                }
3317            }
3318        }
3319
3320        for header in &mut other.initialization_headers {
3321            match header {
3322                IgvmInitializationHeader::GuestPolicy {
3323                    policy: _,
3324                    compatibility_mask,
3325                } => fixup_mask(compatibility_mask),
3326                IgvmInitializationHeader::RelocatableRegion {
3327                    compatibility_mask, ..
3328                } => fixup_mask(compatibility_mask),
3329                IgvmInitializationHeader::PageTableRelocationRegion {
3330                    compatibility_mask, ..
3331                } => fixup_mask(compatibility_mask),
3332            }
3333        }
3334
3335        // Fixup all compatibility masks and parameter area index usage in
3336        // directive headers.
3337        for header in &mut other.directive_headers {
3338            use IgvmDirectiveHeader::*;
3339
3340            if let Some(mask) = header.compatibility_mask_mut() {
3341                fixup_mask(mask);
3342            }
3343
3344            match header {
3345                RequiredMemory { .. }
3346                | PageData { .. }
3347                | SnpVpContext { .. }
3348                | X64NativeVpContext { .. }
3349                | ErrorRange { .. }
3350                | SnpIdBlock { .. }
3351                | VbsMeasurement { .. }
3352                | X64VbsVpContext { .. }
3353                | AArch64VbsVpContext { .. } => {}
3354                ParameterArea {
3355                    parameter_area_index,
3356                    ..
3357                } => {
3358                    if used_parameter_indices.contains(parameter_area_index) {
3359                        // If the parameter area conflicts with ones in self,
3360                        // replace it according to the map. An existing fixup
3361                        // entry means this index was used twice, and `other` is
3362                        // invalid.
3363                        match fixup_parameter_index_map.get(parameter_area_index) {
3364                            Some(_) => panic!("igvm file is invalid, parameter index used twice"),
3365                            None => {
3366                                let new_index =
3367                                    allocate_new_parameter_index(&mut used_parameter_indices)?;
3368                                assert!(fixup_parameter_index_map
3369                                    .insert(*parameter_area_index, new_index)
3370                                    .is_none());
3371                                *parameter_area_index = new_index;
3372                            }
3373                        }
3374                    }
3375                }
3376                VpCount(info)
3377                | EnvironmentInfo(info)
3378                | Srat(info)
3379                | Madt(info)
3380                | Slit(info)
3381                | Pptt(info)
3382                | MmioRanges(info)
3383                | MemoryMap(info)
3384                | CommandLine(info)
3385                | DeviceTree(info) => {
3386                    fixup_parameter_index(
3387                        &mut info.parameter_area_index,
3388                        &fixup_parameter_index_map,
3389                    );
3390                }
3391                ParameterInsert(insert) => {
3392                    fixup_parameter_index(
3393                        &mut insert.parameter_area_index,
3394                        &fixup_parameter_index_map,
3395                    );
3396                }
3397            }
3398        }
3399
3400        // Non-directive headers are just appeneded to the current file.
3401        self.platform_headers.append(&mut other.platform_headers);
3402        self.initialization_headers
3403            .append(&mut other.initialization_headers);
3404
3405        // Dedup directives if requested.
3406        if dedup_directives {
3407            self.merge_dedup_directives(other.directive_headers)?;
3408        } else {
3409            self.directive_headers.append(&mut other.directive_headers);
3410        }
3411
3412        Ok(())
3413    }
3414
3415    /// Merge the `other` [`IgvmFile`] into `self`.
3416    ///
3417    /// This will change compatabilty masks of `other` if any conflict with the
3418    /// current file.
3419    ///
3420    /// Parameter area indices will be changed to avoid any conflicts. While
3421    /// it's technically possible to merge parameter areas, it would require
3422    /// each parameter usage within that parameter area match exactly between
3423    /// different platforms due to only the final parameter insert having a
3424    /// compatibility mask.
3425    ///
3426    /// To preserve all potential measurements in both `self` and `other`,
3427    /// merging is stable and will not modify the relative order of directives
3428    /// in both IGVM files.
3429    ///
3430    /// The runtime of this function is O(n*m) where n is the number of headers
3431    /// in self, and m is the number of headers in other.
3432    pub fn merge(&mut self, other: IgvmFile) -> Result<(), Error> {
3433        self.merge_internal(other, true)
3434    }
3435
3436    /// Merge `other` into self, only fixing up compatibilty masks and parameter
3437    /// area indices as necessary, like [`Self::merge`]. No deduplication of
3438    /// headers will be done.
3439    ///
3440    /// The runtime of this function is O(n+m) where n is the number of headers
3441    /// in self, and m is the number of headers in other.
3442    pub fn merge_simple(&mut self, other: IgvmFile) -> Result<(), Error> {
3443        self.merge_internal(other, false)
3444    }
3445}
3446
3447#[cfg(test)]
3448mod tests {
3449    use super::*;
3450    use crate::hv_defs::HvArm64RegisterName;
3451    use crate::hv_defs::HvRegisterValue;
3452
3453    fn new_platform(
3454        compatibility_mask: u32,
3455        platform_type: IgvmPlatformType,
3456    ) -> IgvmPlatformHeader {
3457        IgvmPlatformHeader::SupportedPlatform(IGVM_VHS_SUPPORTED_PLATFORM {
3458            compatibility_mask,
3459            highest_vtl: 0,
3460            platform_type,
3461            platform_version: 1,
3462            shared_gpa_boundary: 0,
3463        })
3464    }
3465
3466    fn new_page_data(page: u64, compatibility_mask: u32, data: &[u8]) -> IgvmDirectiveHeader {
3467        IgvmDirectiveHeader::PageData {
3468            gpa: page * PAGE_SIZE_4K,
3469            compatibility_mask,
3470            flags: IgvmPageDataFlags::new(),
3471            data_type: IgvmPageDataType::NORMAL,
3472            data: data.to_vec(),
3473        }
3474    }
3475
3476    fn assert_igvm_equal(a: &IgvmFile, b: &IgvmFile) {
3477        assert_eq!(a.revision, b.revision);
3478
3479        for (a, b) in a.platform_headers.iter().zip(b.platform_headers.iter()) {
3480            assert_eq!(a, b);
3481        }
3482
3483        for (a, b) in a
3484            .initialization_headers
3485            .iter()
3486            .zip(b.initialization_headers.iter())
3487        {
3488            assert_eq!(a, b);
3489        }
3490
3491        for (a, b) in a.directive_headers.iter().zip(b.directive_headers.iter()) {
3492            assert_eq!(a, b);
3493        }
3494    }
3495
3496    fn new_parameter_area(index: u32) -> IgvmDirectiveHeader {
3497        IgvmDirectiveHeader::ParameterArea {
3498            number_of_bytes: 4096,
3499            parameter_area_index: index,
3500            initial_data: vec![],
3501        }
3502    }
3503
3504    fn new_parameter_usage(index: u32) -> IgvmDirectiveHeader {
3505        IgvmDirectiveHeader::VpCount(IGVM_VHS_PARAMETER {
3506            parameter_area_index: index,
3507            byte_offset: 0,
3508        })
3509    }
3510
3511    fn new_parameter_insert(page: u64, index: u32, mask: u32) -> IgvmDirectiveHeader {
3512        IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT {
3513            gpa: page * PAGE_SIZE_4K,
3514            parameter_area_index: index,
3515            compatibility_mask: mask,
3516        })
3517    }
3518
3519    mod new_from_binary {
3520        use super::*;
3521        // TODO: test individual or extend existing individual tests to match.
3522        //       pending refactor though for bad get_* methods to take mut vector instead
3523
3524        // test basic
3525        #[test]
3526        fn test_basic() {
3527            let data1 = vec![1; PAGE_SIZE_4K as usize];
3528            let data2 = vec![2; PAGE_SIZE_4K as usize];
3529            let data3 = vec![3; PAGE_SIZE_4K as usize];
3530            let data4 = vec![4; PAGE_SIZE_4K as usize];
3531            let file = IgvmFile {
3532                revision: IgvmRevision::V1,
3533                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3534                initialization_headers: vec![],
3535                directive_headers: vec![
3536                    new_page_data(0, 1, &data1),
3537                    new_page_data(1, 1, &data2),
3538                    new_page_data(2, 1, &data3),
3539                    new_page_data(4, 1, &data4),
3540                    new_page_data(10, 1, &data1),
3541                    new_page_data(11, 1, &data2),
3542                    new_page_data(12, 1, &data3),
3543                    new_page_data(14, 1, &data4),
3544                    new_parameter_area(0),
3545                    new_parameter_usage(0),
3546                    new_parameter_insert(20, 0, 1),
3547                ],
3548            };
3549            let mut binary_file = Vec::new();
3550            file.serialize(&mut binary_file).unwrap();
3551
3552            let deserialized_binary_file = IgvmFile::new_from_binary(&binary_file, None).unwrap();
3553            assert_igvm_equal(&file, &deserialized_binary_file);
3554        }
3555
3556        #[test]
3557        fn test_basic_v2() {
3558            let data1 = vec![1; PAGE_SIZE_4K as usize];
3559            let data2 = vec![2; PAGE_SIZE_4K as usize];
3560            let data3 = vec![3; PAGE_SIZE_4K as usize];
3561            let data4 = vec![4; PAGE_SIZE_4K as usize];
3562            let file = IgvmFile {
3563                revision: IgvmRevision::V2 {
3564                    arch: Arch::X64,
3565                    page_size: PAGE_SIZE_4K as u32,
3566                },
3567                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3568                initialization_headers: vec![],
3569                directive_headers: vec![
3570                    new_page_data(0, 1, &data1),
3571                    new_page_data(1, 1, &data2),
3572                    new_page_data(2, 1, &data3),
3573                    new_page_data(4, 1, &data4),
3574                    new_page_data(10, 1, &data1),
3575                    new_page_data(11, 1, &data2),
3576                    new_page_data(12, 1, &data3),
3577                    new_page_data(14, 1, &data4),
3578                    new_parameter_area(0),
3579                    new_parameter_usage(0),
3580                    new_parameter_insert(20, 0, 1),
3581                    IgvmDirectiveHeader::X64VbsVpContext {
3582                        vtl: Vtl::Vtl0,
3583                        registers: vec![X86Register::R12(0x1234)],
3584                        compatibility_mask: 0x1,
3585                    },
3586                ],
3587            };
3588            let mut binary_file = Vec::new();
3589            file.serialize(&mut binary_file).unwrap();
3590
3591            let deserialized_binary_file = IgvmFile::new_from_binary(&binary_file, None).unwrap();
3592            assert_igvm_equal(&file, &deserialized_binary_file);
3593        }
3594
3595        #[test]
3596        fn test_basic_v2_aarch64() {
3597            let data1 = vec![1; PAGE_SIZE_4K as usize];
3598            let data2 = vec![2; PAGE_SIZE_4K as usize];
3599            let data3 = vec![3; PAGE_SIZE_4K as usize];
3600            let data4 = vec![4; PAGE_SIZE_4K as usize];
3601            let file = IgvmFile {
3602                revision: IgvmRevision::V2 {
3603                    arch: Arch::AArch64,
3604                    page_size: PAGE_SIZE_4K as u32,
3605                },
3606                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3607                initialization_headers: vec![],
3608                directive_headers: vec![
3609                    new_page_data(0, 1, &data1),
3610                    new_page_data(1, 1, &data2),
3611                    new_page_data(2, 1, &data3),
3612                    new_page_data(4, 1, &data4),
3613                    new_page_data(10, 1, &data1),
3614                    new_page_data(11, 1, &data2),
3615                    new_page_data(12, 1, &data3),
3616                    new_page_data(14, 1, &data4),
3617                    new_parameter_area(0),
3618                    new_parameter_usage(0),
3619                    new_parameter_insert(20, 0, 1),
3620                    IgvmDirectiveHeader::AArch64VbsVpContext {
3621                        vtl: Vtl::Vtl0,
3622                        registers: vec![AArch64Register::X0(0x1234)],
3623                        compatibility_mask: 0x1,
3624                    },
3625                ],
3626            };
3627            let mut binary_file = Vec::new();
3628            file.serialize(&mut binary_file).unwrap();
3629
3630            let deserialized_binary_file = IgvmFile::new_from_binary(&binary_file, None).unwrap();
3631            assert_igvm_equal(&file, &deserialized_binary_file);
3632        }
3633
3634        // test platform filter works correctly
3635        // test state transition checks enforce correct ordering
3636    }
3637
3638    mod merge {
3639        use super::*;
3640
3641        // test merge function
3642        #[test]
3643        fn test_merge_basic() {
3644            let data1 = vec![1; PAGE_SIZE_4K as usize];
3645            let data2 = vec![2; PAGE_SIZE_4K as usize];
3646            let data3 = vec![3; PAGE_SIZE_4K as usize];
3647            let data4 = vec![4; PAGE_SIZE_4K as usize];
3648            let mut a = IgvmFile {
3649                revision: IgvmRevision::V1,
3650                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3651                initialization_headers: vec![],
3652                directive_headers: vec![
3653                    new_page_data(0, 1, &data1),
3654                    new_page_data(1, 1, &data2),
3655                    new_page_data(2, 1, &data3),
3656                    new_page_data(4, 1, &data4),
3657                    new_page_data(10, 1, &data1),
3658                    new_page_data(11, 1, &data2),
3659                    new_page_data(12, 1, &data3),
3660                    new_page_data(14, 1, &data4),
3661                ],
3662            };
3663            let b = IgvmFile {
3664                revision: IgvmRevision::V1,
3665                platform_headers: vec![new_platform(0x1, IgvmPlatformType::SEV_SNP)],
3666                initialization_headers: vec![],
3667                directive_headers: vec![
3668                    new_page_data(0, 1, &data1),
3669                    new_page_data(1, 1, &data2),
3670                    new_page_data(2, 1, &data3),
3671                    new_page_data(4, 1, &data4),
3672                    new_page_data(20, 1, &data1),
3673                    new_page_data(21, 1, &data2),
3674                    new_page_data(22, 1, &data3),
3675                    new_page_data(24, 1, &data4),
3676                ],
3677            };
3678            let merged = IgvmFile {
3679                revision: IgvmRevision::V1,
3680                platform_headers: vec![
3681                    new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
3682                    new_platform(0x2, IgvmPlatformType::SEV_SNP),
3683                ],
3684                initialization_headers: vec![],
3685                directive_headers: vec![
3686                    new_page_data(0, 3, &data1),
3687                    new_page_data(1, 3, &data2),
3688                    new_page_data(2, 3, &data3),
3689                    new_page_data(4, 3, &data4),
3690                    new_page_data(20, 2, &data1),
3691                    new_page_data(21, 2, &data2),
3692                    new_page_data(22, 2, &data3),
3693                    new_page_data(24, 2, &data4),
3694                    new_page_data(10, 1, &data1),
3695                    new_page_data(11, 1, &data2),
3696                    new_page_data(12, 1, &data3),
3697                    new_page_data(14, 1, &data4),
3698                ],
3699            };
3700
3701            a.merge(b).unwrap();
3702            assert_igvm_equal(&a, &merged);
3703        }
3704
3705        #[test]
3706        fn test_merge_simple() {
3707            let data1 = vec![1; PAGE_SIZE_4K as usize];
3708            let data2 = vec![2; PAGE_SIZE_4K as usize];
3709            let data3 = vec![3; PAGE_SIZE_4K as usize];
3710            let data4 = vec![4; PAGE_SIZE_4K as usize];
3711            let mut a = IgvmFile {
3712                revision: IgvmRevision::V1,
3713                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3714                initialization_headers: vec![],
3715                directive_headers: vec![
3716                    new_page_data(0, 1, &data1),
3717                    new_page_data(1, 1, &data2),
3718                    new_page_data(2, 1, &data3),
3719                    new_page_data(4, 1, &data4),
3720                    new_page_data(10, 1, &data1),
3721                    new_page_data(11, 1, &data2),
3722                    new_page_data(12, 1, &data3),
3723                    new_page_data(14, 1, &data4),
3724                ],
3725            };
3726            let b = IgvmFile {
3727                revision: IgvmRevision::V1,
3728                platform_headers: vec![new_platform(0x1, IgvmPlatformType::SEV_SNP)],
3729                initialization_headers: vec![],
3730                directive_headers: vec![
3731                    new_page_data(0, 1, &data1),
3732                    new_page_data(1, 1, &data2),
3733                    new_page_data(2, 1, &data3),
3734                    new_page_data(4, 1, &data4),
3735                    new_page_data(20, 1, &data1),
3736                    new_page_data(21, 1, &data2),
3737                    new_page_data(22, 1, &data3),
3738                    new_page_data(24, 1, &data4),
3739                ],
3740            };
3741            let merged = IgvmFile {
3742                revision: IgvmRevision::V1,
3743                platform_headers: vec![
3744                    new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
3745                    new_platform(0x2, IgvmPlatformType::SEV_SNP),
3746                ],
3747                initialization_headers: vec![],
3748                directive_headers: vec![
3749                    // original vsm headers
3750                    new_page_data(0, 1, &data1),
3751                    new_page_data(1, 1, &data2),
3752                    new_page_data(2, 1, &data3),
3753                    new_page_data(4, 1, &data4),
3754                    new_page_data(10, 1, &data1),
3755                    new_page_data(11, 1, &data2),
3756                    new_page_data(12, 1, &data3),
3757                    new_page_data(14, 1, &data4),
3758                    // snp headers with new mask
3759                    new_page_data(0, 2, &data1),
3760                    new_page_data(1, 2, &data2),
3761                    new_page_data(2, 2, &data3),
3762                    new_page_data(4, 2, &data4),
3763                    new_page_data(20, 2, &data1),
3764                    new_page_data(21, 2, &data2),
3765                    new_page_data(22, 2, &data3),
3766                    new_page_data(24, 2, &data4),
3767                ],
3768            };
3769
3770            a.merge_simple(b).unwrap();
3771            assert_igvm_equal(&a, &merged);
3772        }
3773
3774        // test multiple platform headers with masks gets fixed up
3775        #[test]
3776        fn test_multiple_compat_masks() {
3777            let data1 = vec![1; PAGE_SIZE_4K as usize];
3778            let data2 = vec![2; PAGE_SIZE_4K as usize];
3779
3780            // merge igvm file with all 3 isolation types, two with compat mask 0x1 and third with compat mask 0x2
3781            let mut a = IgvmFile {
3782                revision: IgvmRevision::V1,
3783                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3784                initialization_headers: vec![],
3785                directive_headers: vec![new_page_data(0, 1, &data1), new_page_data(1, 1, &data2)],
3786            };
3787            let b = IgvmFile {
3788                revision: IgvmRevision::V1,
3789                platform_headers: vec![new_platform(0x1, IgvmPlatformType::SEV_SNP)],
3790                initialization_headers: vec![],
3791                directive_headers: vec![new_page_data(0, 1, &data1), new_page_data(1, 1, &data2)],
3792            };
3793            let c = IgvmFile {
3794                revision: IgvmRevision::V1,
3795                platform_headers: vec![new_platform(0x2, IgvmPlatformType::TDX)],
3796                initialization_headers: vec![],
3797                directive_headers: vec![new_page_data(0, 2, &data1), new_page_data(1, 2, &data2)],
3798            };
3799            let merged = IgvmFile {
3800                revision: IgvmRevision::V1,
3801                platform_headers: vec![
3802                    new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
3803                    new_platform(0x2, IgvmPlatformType::SEV_SNP),
3804                    new_platform(0x4, IgvmPlatformType::TDX),
3805                ],
3806                initialization_headers: vec![],
3807                directive_headers: vec![new_page_data(0, 7, &data1), new_page_data(1, 7, &data2)],
3808            };
3809            a.merge(b).unwrap();
3810            a.merge(c).unwrap();
3811            assert_igvm_equal(&a, &merged);
3812        }
3813
3814        // test page imports to same page but different data do not merge
3815        #[test]
3816        fn test_merge_page_data_should_not_merge() {
3817            let data1 = vec![1; PAGE_SIZE_4K as usize];
3818            let data2 = vec![2; PAGE_SIZE_4K as usize];
3819
3820            let mut a = IgvmFile {
3821                revision: IgvmRevision::V1,
3822                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3823                initialization_headers: vec![],
3824                directive_headers: vec![new_page_data(0, 1, &data2), new_page_data(1, 1, &data1)],
3825            };
3826            let b = IgvmFile {
3827                revision: IgvmRevision::V1,
3828                platform_headers: vec![new_platform(0x1, IgvmPlatformType::SEV_SNP)],
3829                initialization_headers: vec![],
3830                directive_headers: vec![new_page_data(0, 1, &data1), new_page_data(1, 1, &data2)],
3831            };
3832            let merged = IgvmFile {
3833                revision: IgvmRevision::V1,
3834                platform_headers: vec![
3835                    new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
3836                    new_platform(0x2, IgvmPlatformType::SEV_SNP),
3837                ],
3838                initialization_headers: vec![],
3839                directive_headers: vec![
3840                    new_page_data(0, 2, &data1),
3841                    new_page_data(1, 2, &data2),
3842                    new_page_data(0, 1, &data2),
3843                    new_page_data(1, 1, &data1),
3844                ],
3845            };
3846
3847            a.merge(b).unwrap();
3848            assert_igvm_equal(&a, &merged);
3849        }
3850
3851        #[test]
3852        fn test_merge_stable_ordering() {
3853            // test stable ordering
3854            //      test headers that could be merged but would violate stable ordering
3855            //      test first header matches, very last header matches, all subsequent should just be appended (could possibly match)
3856            let data1 = vec![1; PAGE_SIZE_4K as usize];
3857            let data2 = vec![2; PAGE_SIZE_4K as usize];
3858            let data3 = vec![3; PAGE_SIZE_4K as usize];
3859            let data4 = vec![4; PAGE_SIZE_4K as usize];
3860            let mut a = IgvmFile {
3861                revision: IgvmRevision::V1,
3862                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3863                initialization_headers: vec![],
3864                directive_headers: vec![
3865                    new_page_data(0, 1, &data1),
3866                    new_page_data(1, 1, &data2),
3867                    new_page_data(2, 1, &data3),
3868                    new_page_data(4, 1, &data4),
3869                    new_page_data(10, 1, &data1),
3870                    new_page_data(11, 1, &data2),
3871                    new_page_data(12, 1, &data3),
3872                    new_page_data(14, 1, &data4),
3873                ],
3874            };
3875            let b = IgvmFile {
3876                revision: IgvmRevision::V1,
3877                platform_headers: vec![new_platform(0x1, IgvmPlatformType::SEV_SNP)],
3878                initialization_headers: vec![],
3879                directive_headers: vec![
3880                    new_page_data(0, 1, &data1),
3881                    new_page_data(14, 1, &data4),
3882                    new_page_data(1, 1, &data2),
3883                    new_page_data(2, 1, &data3),
3884                    new_page_data(4, 1, &data4),
3885                    new_page_data(10, 1, &data1),
3886                    new_page_data(11, 1, &data2),
3887                    new_page_data(12, 1, &data3),
3888                ],
3889            };
3890            let merged = IgvmFile {
3891                revision: IgvmRevision::V1,
3892                platform_headers: vec![
3893                    new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
3894                    new_platform(0x2, IgvmPlatformType::SEV_SNP),
3895                ],
3896                initialization_headers: vec![],
3897                directive_headers: vec![
3898                    new_page_data(0, 3, &data1),
3899                    new_page_data(1, 1, &data2),
3900                    new_page_data(2, 1, &data3),
3901                    new_page_data(4, 1, &data4),
3902                    new_page_data(10, 1, &data1),
3903                    new_page_data(11, 1, &data2),
3904                    new_page_data(12, 1, &data3),
3905                    new_page_data(14, 3, &data4),
3906                    new_page_data(1, 2, &data2),
3907                    new_page_data(2, 2, &data3),
3908                    new_page_data(4, 2, &data4),
3909                    new_page_data(10, 2, &data1),
3910                    new_page_data(11, 2, &data2),
3911                    new_page_data(12, 2, &data3),
3912                ],
3913            };
3914            a.merge(b).unwrap();
3915            assert_igvm_equal(&a, &merged);
3916        }
3917
3918        #[test]
3919        fn test_merge_parameter_areas() {
3920            // test parameter page indexes get changed since merges are not supported
3921            //      test basic parameter usage
3922            //      test gaps in parameter indices in src and dest
3923            //      test parameter areas that do not overlap
3924
3925            // basic parameter area merging
3926            let mut a = IgvmFile {
3927                revision: IgvmRevision::V1,
3928                platform_headers: vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
3929                initialization_headers: vec![],
3930                directive_headers: vec![
3931                    new_parameter_area(1),
3932                    new_parameter_area(2),
3933                    new_parameter_area(7),
3934                    new_parameter_usage(1),
3935                    new_parameter_usage(2),
3936                    new_parameter_usage(7),
3937                    new_parameter_insert(1, 1, 1),
3938                    new_parameter_insert(2, 2, 1),
3939                    new_parameter_insert(10, 7, 1),
3940                ],
3941            };
3942
3943            let b = IgvmFile {
3944                revision: IgvmRevision::V1,
3945                platform_headers: vec![new_platform(0x1, IgvmPlatformType::SEV_SNP)],
3946                initialization_headers: vec![],
3947                directive_headers: vec![
3948                    new_parameter_area(1),
3949                    new_parameter_area(2),
3950                    new_parameter_area(10),
3951                    new_parameter_usage(1),
3952                    new_parameter_usage(2),
3953                    new_parameter_usage(10),
3954                    new_parameter_insert(1, 1, 1),
3955                    new_parameter_insert(4, 2, 1),
3956                    new_parameter_insert(12, 10, 1),
3957                ],
3958            };
3959
3960            let merged = IgvmFile {
3961                revision: IgvmRevision::V1,
3962                platform_headers: vec![
3963                    new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
3964                    new_platform(0x2, IgvmPlatformType::SEV_SNP),
3965                ],
3966                initialization_headers: vec![],
3967                directive_headers: vec![
3968                    new_parameter_area(0),
3969                    new_parameter_area(3),
3970                    new_parameter_area(10),
3971                    new_parameter_usage(0),
3972                    new_parameter_usage(3),
3973                    new_parameter_usage(10),
3974                    new_parameter_insert(1, 0, 2),
3975                    new_parameter_insert(4, 3, 2),
3976                    new_parameter_insert(12, 10, 2),
3977                    new_parameter_area(1),
3978                    new_parameter_area(2),
3979                    new_parameter_area(7),
3980                    new_parameter_usage(1),
3981                    new_parameter_usage(2),
3982                    new_parameter_usage(7),
3983                    new_parameter_insert(1, 1, 1),
3984                    new_parameter_insert(2, 2, 1),
3985                    new_parameter_insert(10, 7, 1),
3986                ],
3987            };
3988
3989            a.merge(b).unwrap();
3990            assert_igvm_equal(&a, &merged);
3991        }
3992    }
3993
3994    // TODO: test validate platform headers
3995    //       test validate directive headers
3996    //
3997    //       test headers equivalent function
3998
3999    /// Test a variable header matches the supplied args. Also tests that the header deserialized returns the original
4000    /// header.
4001    fn test_variable_header<T: IntoBytes + Immutable + KnownLayout>(
4002        revision: IgvmRevision,
4003        header: IgvmDirectiveHeader,
4004        file_data_offset: u32,
4005        header_type: IgvmVariableHeaderType,
4006        expected_variable_binary_header: T,
4007        expected_file_data: Option<Vec<u8>>,
4008        platform_to_report: Option<IgvmPlatformType>,
4009    ) {
4010        let mut binary_header = Vec::new();
4011        let mut file_data = FileDataSerializer::new(file_data_offset as usize);
4012
4013        header
4014            .write_binary_header(&mut binary_header, &mut file_data)
4015            .unwrap();
4016
4017        let file_data = file_data.take();
4018
4019        let common_header = IGVM_VHS_VARIABLE_HEADER::read_from_prefix(&binary_header[..])
4020            .expect("variable header must be present")
4021            .0;
4022
4023        assert_eq!(common_header.typ, header_type);
4024        assert_eq!(
4025            align_8(common_header.length as usize),
4026            size_of_val(&expected_variable_binary_header)
4027        );
4028        assert_eq!(
4029            &binary_header[size_of_val(&common_header)..],
4030            expected_variable_binary_header.as_bytes()
4031        );
4032
4033        match &expected_file_data {
4034            Some(data) => assert_eq!(data, &file_data),
4035            None => assert!(file_data.is_empty()),
4036        }
4037
4038        let (reserialized_header, remaining) = IgvmDirectiveHeader::new_from_binary_split(
4039            revision,
4040            &binary_header,
4041            &file_data,
4042            file_data_offset,
4043            |_mask| platform_to_report,
4044        )
4045        .unwrap();
4046        assert!(remaining.is_empty());
4047
4048        // Reserialized header should match the initial supplied one, with some differences for data padding.
4049        match (&header, &reserialized_header) {
4050            (
4051                IgvmDirectiveHeader::PageData {
4052                    gpa: a_gpa,
4053                    flags: a_flags,
4054                    data_type: a_data_type,
4055                    data: a_data,
4056                    compatibility_mask: a_compmask,
4057                },
4058                IgvmDirectiveHeader::PageData {
4059                    gpa: b_gpa,
4060                    flags: b_flags,
4061                    data_type: b_data_type,
4062                    data: b_data,
4063                    compatibility_mask: b_compmask,
4064                },
4065            ) => {
4066                assert!(
4067                    a_gpa == b_gpa
4068                        && a_flags == b_flags
4069                        && a_data_type == b_data_type
4070                        && a_compmask == b_compmask
4071                );
4072
4073                // data might not be the same length, as it gets padded out.
4074                for i in 0..b_data.len() {
4075                    if i < a_data.len() {
4076                        assert_eq!(a_data[i], b_data[i]);
4077                    } else {
4078                        assert_eq!(0, b_data[i]);
4079                    }
4080                }
4081            }
4082            (
4083                IgvmDirectiveHeader::ParameterArea {
4084                    number_of_bytes: a_number_of_bytes,
4085                    parameter_area_index: a_parameter_area_index,
4086                    initial_data: a_initial_data,
4087                },
4088                IgvmDirectiveHeader::ParameterArea {
4089                    number_of_bytes: b_number_of_bytes,
4090                    parameter_area_index: b_parameter_area_index,
4091                    initial_data: b_initial_data,
4092                },
4093            ) => {
4094                assert!(
4095                    a_number_of_bytes == b_number_of_bytes
4096                        && a_parameter_area_index == b_parameter_area_index
4097                );
4098
4099                // initial_data might be padded out just like page data.
4100                for i in 0..b_initial_data.len() {
4101                    if i < a_initial_data.len() {
4102                        assert_eq!(a_initial_data[i], b_initial_data[i]);
4103                    } else {
4104                        assert_eq!(0, b_initial_data[i]);
4105                    }
4106                }
4107            }
4108            _ => assert_eq!(header, reserialized_header),
4109        }
4110    }
4111
4112    // Test get binary header for each type.
4113    #[test]
4114    fn test_page_data() {
4115        let gpa = 0x12 * PAGE_SIZE_4K;
4116        let file_data_offset = 0x12340;
4117
4118        // Test empty page data
4119        let header = IgvmDirectiveHeader::PageData {
4120            gpa,
4121            compatibility_mask: 0,
4122            flags: IgvmPageDataFlags::new(),
4123            data_type: IgvmPageDataType::NORMAL,
4124            data: vec![],
4125        };
4126        let expected_header = IGVM_VHS_PAGE_DATA {
4127            gpa,
4128            ..FromZeros::new_zeroed()
4129        };
4130        test_variable_header(
4131            IgvmRevision::V1,
4132            header,
4133            file_data_offset,
4134            IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA,
4135            expected_header,
4136            None,
4137            None,
4138        );
4139
4140        // Test sub 4k page data
4141        let mut data = vec![1, 2, 3, 4, 5, 4, 3, 2, 1];
4142        let header = IgvmDirectiveHeader::PageData {
4143            gpa,
4144            compatibility_mask: 0,
4145            flags: IgvmPageDataFlags::new(),
4146            data_type: IgvmPageDataType::NORMAL,
4147            data: data.clone(),
4148        };
4149        let expected_header = IGVM_VHS_PAGE_DATA {
4150            gpa,
4151            file_offset: file_data_offset,
4152            ..FromZeros::new_zeroed()
4153        };
4154        data.resize(PAGE_SIZE_4K as usize, 0);
4155        let expected_file_data = Some(data);
4156        test_variable_header(
4157            IgvmRevision::V1,
4158            header,
4159            file_data_offset,
4160            IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA,
4161            expected_header,
4162            expected_file_data,
4163            None,
4164        );
4165
4166        // Test exactly 4k page data
4167        let data: Vec<u8> = (0..PAGE_SIZE_4K).map(|x| (x % 255) as u8).collect();
4168        let header = IgvmDirectiveHeader::PageData {
4169            gpa,
4170            compatibility_mask: 0,
4171            flags: IgvmPageDataFlags::new(),
4172            data_type: IgvmPageDataType::NORMAL,
4173            data: data.clone(),
4174        };
4175        let expected_header = IGVM_VHS_PAGE_DATA {
4176            gpa,
4177            file_offset: file_data_offset,
4178            ..FromZeros::new_zeroed()
4179        };
4180        let expected_file_data = Some(data);
4181        test_variable_header(
4182            IgvmRevision::V1,
4183            header,
4184            file_data_offset,
4185            IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA,
4186            expected_header,
4187            expected_file_data,
4188            None,
4189        );
4190    }
4191
4192    #[test]
4193    fn test_page_data_dedup() {
4194        // test two page datas with same contents refer to the same file offset
4195        let gpa = 0x12 * PAGE_SIZE_4K;
4196        let file_data_offset = 0x12340;
4197        let data = vec![1, 2, 3, 4, 5, 4, 3, 2, 1];
4198        let mut file_data = FileDataSerializer::new(file_data_offset);
4199
4200        let header = IgvmDirectiveHeader::PageData {
4201            gpa,
4202            compatibility_mask: 0,
4203            flags: IgvmPageDataFlags::new(),
4204            data_type: IgvmPageDataType::NORMAL,
4205            data: data.clone(),
4206        };
4207
4208        let mut first = Vec::new();
4209        let mut second = Vec::new();
4210        let mut third = Vec::new();
4211
4212        header
4213            .write_binary_header(&mut first, &mut file_data)
4214            .unwrap();
4215        header
4216            .write_binary_header(&mut second, &mut file_data)
4217            .unwrap();
4218        header
4219            .write_binary_header(&mut third, &mut file_data)
4220            .unwrap();
4221
4222        let mut different = Vec::new();
4223        let header = IgvmDirectiveHeader::PageData {
4224            gpa,
4225            compatibility_mask: 0,
4226            flags: IgvmPageDataFlags::new(),
4227            data_type: IgvmPageDataType::NORMAL,
4228            data: vec![5, 5, 5, 5, 5],
4229        };
4230        header
4231            .write_binary_header(&mut different, &mut file_data)
4232            .unwrap();
4233
4234        let read_raw_header = |data: &[u8]| {
4235            let (_, data) = data.split_at(size_of::<IGVM_VHS_VARIABLE_HEADER>());
4236            IGVM_VHS_PAGE_DATA::read_from_prefix(data).unwrap().0
4237        };
4238
4239        let first = read_raw_header(&first);
4240        let second = read_raw_header(&second);
4241        let third = read_raw_header(&third);
4242        let different = read_raw_header(&different);
4243
4244        assert_eq!(first.file_offset, second.file_offset);
4245        assert_eq!(second.file_offset, third.file_offset);
4246        assert!(different.file_offset != first.file_offset);
4247        assert_eq!(file_data.file_data_map.len(), 2);
4248    }
4249
4250    #[test]
4251    fn test_page_data_over_4k() {
4252        // TODO: once we support 2MB page datas, this will need to be fixed.
4253        let size = PAGE_SIZE_4K as usize + 1;
4254        let header = IgvmDirectiveHeader::PageData {
4255            gpa: 0,
4256            compatibility_mask: 1,
4257            flags: IgvmPageDataFlags::new(),
4258            data_type: IgvmPageDataType::NORMAL,
4259            data: vec![0; size],
4260        };
4261
4262        match header.write_binary_header(&mut Vec::new(), &mut FileDataSerializer::new(0)) {
4263            Err(BinaryHeaderError::InvalidDataSize) => {}
4264            _ => {
4265                panic!("invalid serialization")
4266            }
4267        }
4268    }
4269
4270    #[test]
4271    fn test_parameter_area() {
4272        let file_data_offset = 1234;
4273
4274        // Test single page
4275        let raw_header = IGVM_VHS_PARAMETER_AREA {
4276            number_of_bytes: PAGE_SIZE_4K,
4277            parameter_area_index: 2,
4278            file_offset: 0,
4279        };
4280
4281        let header = IgvmDirectiveHeader::ParameterArea {
4282            number_of_bytes: PAGE_SIZE_4K,
4283            parameter_area_index: 2,
4284            initial_data: Vec::new(),
4285        };
4286        test_variable_header(
4287            IgvmRevision::V1,
4288            header,
4289            0,
4290            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA,
4291            raw_header,
4292            None,
4293            None,
4294        );
4295
4296        let raw_header = IGVM_VHS_PARAMETER_AREA {
4297            number_of_bytes: PAGE_SIZE_4K,
4298            parameter_area_index: 2,
4299            file_offset: file_data_offset,
4300        };
4301        let mut file_data = vec![1, 2, 3, 4, 5, 0, 1];
4302        let header = IgvmDirectiveHeader::ParameterArea {
4303            number_of_bytes: PAGE_SIZE_4K,
4304            parameter_area_index: 2,
4305            initial_data: file_data.clone(),
4306        };
4307        file_data.resize(PAGE_SIZE_4K as usize, 0);
4308        test_variable_header(
4309            IgvmRevision::V1,
4310            header,
4311            file_data_offset,
4312            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA,
4313            raw_header,
4314            Some(file_data),
4315            None,
4316        );
4317
4318        // Test multi page
4319        let raw_header = IGVM_VHS_PARAMETER_AREA {
4320            number_of_bytes: 123 * PAGE_SIZE_4K,
4321            parameter_area_index: 2,
4322            file_offset: 0,
4323        };
4324
4325        let header = IgvmDirectiveHeader::ParameterArea {
4326            number_of_bytes: 123 * PAGE_SIZE_4K,
4327            parameter_area_index: 2,
4328            initial_data: Vec::new(),
4329        };
4330        test_variable_header(
4331            IgvmRevision::V1,
4332            header,
4333            0,
4334            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA,
4335            raw_header,
4336            None,
4337            None,
4338        );
4339
4340        let raw_header = IGVM_VHS_PARAMETER_AREA {
4341            number_of_bytes: 123 * PAGE_SIZE_4K,
4342            parameter_area_index: 2,
4343            file_offset: file_data_offset,
4344        };
4345        let mut file_data: Vec<u8> = (0..(PAGE_SIZE_4K + 1482))
4346            .map(|x| (x % 255) as u8)
4347            .collect();
4348        let header = IgvmDirectiveHeader::ParameterArea {
4349            number_of_bytes: 123 * PAGE_SIZE_4K,
4350            parameter_area_index: 2,
4351            initial_data: file_data.clone(),
4352        };
4353        file_data.resize(123 * PAGE_SIZE_4K as usize, 0);
4354        test_variable_header(
4355            IgvmRevision::V1,
4356            header,
4357            file_data_offset,
4358            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA,
4359            raw_header,
4360            Some(file_data),
4361            None,
4362        );
4363    }
4364
4365    #[test]
4366    fn test_parameter_area_bad_size() {
4367        // Non page size number of bytes
4368        let header = IgvmDirectiveHeader::ParameterArea {
4369            number_of_bytes: 1234,
4370            parameter_area_index: 0,
4371            initial_data: Vec::new(),
4372        };
4373
4374        assert!(matches!(
4375            header.write_binary_header(&mut Vec::new(), &mut FileDataSerializer::new(0)),
4376            Err(BinaryHeaderError::UnalignedSize(1234))
4377        ));
4378    }
4379
4380    /// Generate a test function to test an IGVM parameter directive type.
4381    macro_rules! test_igvm_parameter {
4382        ($test_name:ident($directive:path, $header_type:path)) => {
4383            #[test]
4384            fn $test_name() {
4385                // Byte offset 0
4386                let raw_header = IGVM_VHS_PARAMETER {
4387                    parameter_area_index: 1,
4388                    byte_offset: 0,
4389                };
4390                let header = $directive(raw_header);
4391                test_variable_header(
4392                    IgvmRevision::V1,
4393                    header,
4394                    0,
4395                    $header_type,
4396                    raw_header,
4397                    None,
4398                    None,
4399                );
4400
4401                // Byte offset 1234
4402                let raw_header = IGVM_VHS_PARAMETER {
4403                    parameter_area_index: 0,
4404                    byte_offset: 1234,
4405                };
4406                let header = $directive(raw_header);
4407                test_variable_header(
4408                    IgvmRevision::V1,
4409                    header,
4410                    0,
4411                    $header_type,
4412                    raw_header,
4413                    None,
4414                    None,
4415                );
4416            }
4417        };
4418    }
4419
4420    test_igvm_parameter!(test_vp_count(
4421        IgvmDirectiveHeader::VpCount,
4422        IgvmVariableHeaderType::IGVM_VHT_VP_COUNT_PARAMETER
4423    ));
4424
4425    test_igvm_parameter!(test_environment_info(
4426        IgvmDirectiveHeader::EnvironmentInfo,
4427        IgvmVariableHeaderType::IGVM_VHT_ENVIRONMENT_INFO_PARAMETER
4428    ));
4429
4430    test_igvm_parameter!(test_srat(
4431        IgvmDirectiveHeader::Srat,
4432        IgvmVariableHeaderType::IGVM_VHT_SRAT
4433    ));
4434
4435    test_igvm_parameter!(test_madt(
4436        IgvmDirectiveHeader::Madt,
4437        IgvmVariableHeaderType::IGVM_VHT_MADT
4438    ));
4439
4440    test_igvm_parameter!(test_slit(
4441        IgvmDirectiveHeader::Slit,
4442        IgvmVariableHeaderType::IGVM_VHT_SLIT
4443    ));
4444
4445    test_igvm_parameter!(test_pptt(
4446        IgvmDirectiveHeader::Pptt,
4447        IgvmVariableHeaderType::IGVM_VHT_PPTT
4448    ));
4449
4450    test_igvm_parameter!(test_mmio_ranges(
4451        IgvmDirectiveHeader::MmioRanges,
4452        IgvmVariableHeaderType::IGVM_VHT_MMIO_RANGES
4453    ));
4454
4455    test_igvm_parameter!(test_memory_map(
4456        IgvmDirectiveHeader::MemoryMap,
4457        IgvmVariableHeaderType::IGVM_VHT_MEMORY_MAP
4458    ));
4459
4460    test_igvm_parameter!(test_command_line(
4461        IgvmDirectiveHeader::CommandLine,
4462        IgvmVariableHeaderType::IGVM_VHT_COMMAND_LINE
4463    ));
4464
4465    #[test]
4466    fn test_required_memory() {
4467        let gpa = 0x1234 * PAGE_SIZE_4K;
4468        let number_of_bytes = 0x4567 * PAGE_SIZE_4K as u32;
4469        let compatibility_mask = 0x1;
4470        let vtl2_protectable = true;
4471        let flags = RequiredMemoryFlags::new().with_vtl2_protectable(true);
4472        let raw_header = IGVM_VHS_REQUIRED_MEMORY {
4473            gpa,
4474            number_of_bytes,
4475            compatibility_mask,
4476            flags,
4477            ..FromZeros::new_zeroed()
4478        };
4479
4480        let header = IgvmDirectiveHeader::RequiredMemory {
4481            gpa,
4482            number_of_bytes,
4483            compatibility_mask,
4484            vtl2_protectable,
4485        };
4486        test_variable_header(
4487            IgvmRevision::V1,
4488            header,
4489            0,
4490            IgvmVariableHeaderType::IGVM_VHT_REQUIRED_MEMORY,
4491            raw_header,
4492            None,
4493            None,
4494        );
4495
4496        let gpa = 24 * 1024 * 1024;
4497        let number_of_bytes = 64 * 1024 * 1024;
4498        let compatibility_mask = 0x1;
4499        let flags = RequiredMemoryFlags::new();
4500        let raw_header = IGVM_VHS_REQUIRED_MEMORY {
4501            gpa,
4502            number_of_bytes,
4503            compatibility_mask,
4504            flags,
4505            ..FromZeros::new_zeroed()
4506        };
4507
4508        let header = IgvmDirectiveHeader::RequiredMemory {
4509            gpa,
4510            number_of_bytes,
4511            compatibility_mask,
4512            vtl2_protectable: false,
4513        };
4514        test_variable_header(
4515            IgvmRevision::V1,
4516            header,
4517            0,
4518            IgvmVariableHeaderType::IGVM_VHT_REQUIRED_MEMORY,
4519            raw_header,
4520            None,
4521            None,
4522        );
4523    }
4524
4525    #[test]
4526    fn test_required_memory_unaligned_gpa() {
4527        let gpa = 0x1234;
4528
4529        let header = IgvmDirectiveHeader::RequiredMemory {
4530            gpa,
4531            number_of_bytes: 0x4567 * PAGE_SIZE_4K as u32,
4532            compatibility_mask: 0x1,
4533            vtl2_protectable: true,
4534        };
4535        match header.write_binary_header(&mut Vec::new(), &mut FileDataSerializer::new(0)) {
4536            Err(BinaryHeaderError::UnalignedAddress(err_gpa)) => {
4537                assert_eq!(gpa, err_gpa);
4538            }
4539            _ => panic!("invalid serialization"),
4540        }
4541    }
4542
4543    #[test]
4544    fn test_required_memory_unaligned_size() {
4545        let size = 0x4567;
4546
4547        let header = IgvmDirectiveHeader::RequiredMemory {
4548            gpa: 0x1234 * PAGE_SIZE_4K,
4549            number_of_bytes: size,
4550            compatibility_mask: 0x1,
4551            vtl2_protectable: true,
4552        };
4553        match header.write_binary_header(&mut Vec::new(), &mut FileDataSerializer::new(0)) {
4554            Err(BinaryHeaderError::UnalignedSize(err_size)) => {
4555                assert_eq!(size as u64, err_size);
4556            }
4557            _ => panic!("invalid serialization"),
4558        }
4559    }
4560
4561    #[test]
4562    fn test_parameter_insert() {
4563        let raw_header = IGVM_VHS_PARAMETER_INSERT {
4564            gpa: 0x1234 * PAGE_SIZE_4K,
4565            compatibility_mask: 0x1,
4566            parameter_area_index: 0x10,
4567        };
4568
4569        let header = IgvmDirectiveHeader::ParameterInsert(raw_header);
4570        test_variable_header(
4571            IgvmRevision::V1,
4572            header,
4573            1234,
4574            IgvmVariableHeaderType::IGVM_VHT_PARAMETER_INSERT,
4575            raw_header,
4576            None,
4577            None,
4578        );
4579    }
4580
4581    #[test]
4582    fn test_parameter_insert_unaligned_gpa() {
4583        let gpa = 0x1234;
4584        let raw_header = IGVM_VHS_PARAMETER_INSERT {
4585            gpa,
4586            compatibility_mask: 0x1,
4587            parameter_area_index: 0x10,
4588        };
4589
4590        let header = IgvmDirectiveHeader::ParameterInsert(raw_header);
4591        match header.write_binary_header(&mut Vec::new(), &mut FileDataSerializer::new(0)) {
4592            Err(BinaryHeaderError::UnalignedAddress(err_gpa)) => {
4593                assert_eq!(gpa, err_gpa);
4594            }
4595            _ => panic!("invalid serialization"),
4596        }
4597    }
4598
4599    #[test]
4600    fn test_aarch64_vbs_vp_context() {
4601        let raw_header = IGVM_VHS_VP_CONTEXT {
4602            gpa: 0.into(),
4603            compatibility_mask: 0x1,
4604            file_offset: 1234,
4605            vp_index: 0,
4606            reserved: 0,
4607        };
4608
4609        let mut raw_header_bytes: [u8; 24] = [0; 24];
4610        raw_header_bytes[..raw_header.as_bytes().len()].copy_from_slice(raw_header.as_bytes());
4611
4612        let reg_list = [
4613            VbsVpContextRegister {
4614                vtl: 0,
4615                register_name: HvArm64RegisterName::XPc.0.into(),
4616                mbz: [0; 11],
4617                register_value: HvRegisterValue::from(0x1234u64).0.to_ne_bytes(),
4618            },
4619            VbsVpContextRegister {
4620                vtl: 0,
4621                register_name: HvArm64RegisterName::X1.0.into(),
4622                mbz: [0; 11],
4623                register_value: HvRegisterValue::from(0x5678u64).0.to_ne_bytes(),
4624            },
4625        ];
4626
4627        let reg_header = VbsVpContextHeader { register_count: 2 };
4628        let mut file_data: Vec<u8> = Vec::new();
4629        file_data.extend_from_slice(reg_header.as_bytes());
4630        file_data.extend_from_slice(reg_list.as_bytes());
4631
4632        let header = IgvmDirectiveHeader::AArch64VbsVpContext {
4633            vtl: Vtl::Vtl0,
4634            registers: vec![AArch64Register::Pc(0x1234), AArch64Register::X1(0x5678)],
4635            compatibility_mask: 0x1,
4636        };
4637
4638        test_variable_header(
4639            IgvmRevision::V2 {
4640                arch: Arch::AArch64,
4641                page_size: PAGE_SIZE_4K as u32,
4642            },
4643            header,
4644            1234,
4645            IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
4646            raw_header_bytes,
4647            Some(file_data),
4648            Some(IgvmPlatformType::VSM_ISOLATION),
4649        )
4650    }
4651
4652    #[test]
4653    fn test_snp_vp_context() {
4654        let raw_header = IGVM_VHS_VP_CONTEXT {
4655            gpa: 0x1234000.into(),
4656            compatibility_mask: 0x1,
4657            file_offset: 1234,
4658            vp_index: 0xabcd,
4659            reserved: 0,
4660        };
4661
4662        let mut raw_header_bytes: [u8; 24] = [0; 24];
4663        raw_header_bytes[..raw_header.as_bytes().len()].copy_from_slice(raw_header.as_bytes());
4664
4665        let mut vmsa = SevVmsa::new_box_zeroed().unwrap();
4666        // Set a couple of random values so it's not just all 0s.
4667        vmsa.cr2 = 42;
4668        vmsa.ldtr.attrib = 92;
4669
4670        let mut file_data: Vec<u8> = vmsa.as_bytes().to_vec();
4671        file_data.resize(PAGE_SIZE_4K.try_into().unwrap(), 0);
4672
4673        let header = IgvmDirectiveHeader::SnpVpContext {
4674            gpa: 0x1234000,
4675            compatibility_mask: 0x1,
4676            vp_index: 0xabcd,
4677            vmsa,
4678        };
4679
4680        test_variable_header(
4681            IgvmRevision::V2 {
4682                arch: Arch::X64,
4683                page_size: PAGE_SIZE_4K as u32,
4684            },
4685            header,
4686            1234,
4687            IgvmVariableHeaderType::IGVM_VHT_VP_CONTEXT,
4688            raw_header_bytes,
4689            Some(file_data),
4690            Some(IgvmPlatformType::SEV_SNP),
4691        )
4692    }
4693
4694    // Test serialize and deserialize
4695}