Skip to main content

vhdx/validation/
header.rs

1use super::{
2    Error, Guid, Header, HeaderStructure, MIB, Result, SignaturePosition, SpecValidator,
3    ValidationIssue,
4};
5
6impl SpecValidator {
7    /// Validate the header section.
8    ///
9    /// Checks:
10    /// - File type identifier signature ("vhdxfile")
11    /// - Header 1 and Header 2 signatures, CRC-32C, version
12    /// - Sequence number comparison (both headers valid)
13    /// - `LogGuid` consistency between headers
14    ///
15    /// # Errors
16    ///
17    /// Returns an error when required header invariants fail.
18    ///
19    /// # Panics
20    ///
21    /// Panics on internal invariant violations where code unwraps a known error
22    /// branch after `is_ok()` checks.
23    pub fn validate_header(&self) -> Result<Vec<ValidationIssue>> {
24        let mut issues = Vec::new();
25        let header = self.parse_header()?;
26        Self::validate_file_type_identifier(&header, &mut issues)?;
27        Self::validate_header_pair(&header, &mut issues)?;
28        Self::validate_log_alignment(&header, &mut issues)?;
29
30        Ok(issues)
31    }
32
33    fn validate_file_type_identifier(
34        header: &Header<'_>, issues: &mut Vec<ValidationIssue>,
35    ) -> Result<()> {
36        let ft = header.file_type();
37        if ft.signature() == b"vhdxfile" {
38            return Ok(());
39        }
40        Self::push_issue(
41            issues,
42            ValidationIssue::new(
43                "header",
44                "HEADER_FILE_TYPE_ID_INVALID",
45                format!(
46                    "invalid signature at offset 0: expected \"vhdxfile\", found {:?}",
47                    std::str::from_utf8(ft.signature()).unwrap_or("<binary>")
48                ),
49                "MS-VHDX/2.2.1",
50            ),
51        );
52        Err(Error::InvalidSignature {
53            position: SignaturePosition::FileTypeIdentifier,
54            expected: *b"vhdxfile",
55            found: *ft.signature(),
56        })
57    }
58
59    fn validate_header_pair(header: &Header<'_>, issues: &mut Vec<ValidationIssue>) -> Result<()> {
60        let v1 = header
61            .header(1)
62            .and_then(|h| Self::validate_single_header(Ok(h)));
63        let v2 = header
64            .header(2)
65            .and_then(|h| Self::validate_single_header(Ok(h)));
66        let h1_valid = v1.is_ok();
67        let h2_valid = v2.is_ok();
68        if !h1_valid && !h2_valid {
69            Self::push_header_issue(issues, 1, v1.as_ref().err().unwrap());
70            Self::push_header_issue(issues, 2, v2.as_ref().err().unwrap());
71            return Err(Error::CorruptedHeader("both headers are invalid".into()));
72        }
73        if !h1_valid {
74            Self::push_header_issue(issues, 1, v1.as_ref().err().unwrap());
75        }
76        if !h2_valid {
77            Self::push_header_issue(issues, 2, v2.as_ref().err().unwrap());
78        }
79        if h1_valid && h2_valid {
80            Self::validate_header_pair_consistency(header, issues, &v1.unwrap(), &v2.unwrap())?;
81        }
82        Ok(())
83    }
84
85    fn validate_header_pair_consistency(
86        header: &Header<'_>, issues: &mut Vec<ValidationIssue>, v1: &HeaderStructure<'_>,
87        v2: &HeaderStructure<'_>,
88    ) -> Result<()> {
89        if v1.sequence_number() == v2.sequence_number() {
90            Self::push_issue(
91                issues,
92                ValidationIssue::new(
93                    "header",
94                    "HEADER_SEQUENCE_NUMBER_INVALID",
95                    "both headers have same sequence number",
96                    "MS-VHDX/2.2.2",
97                ),
98            );
99            return Err(Error::HeaderSequenceNumberInvalid {
100                sequence_number_1: v1.sequence_number(),
101                sequence_number_2: v2.sequence_number(),
102            });
103        }
104        let log_guid = Self::current_log_guid(header)?;
105        if log_guid == v1.log_guid() && log_guid == v2.log_guid() {
106            return Ok(());
107        }
108        Self::push_issue(
109            issues,
110            ValidationIssue::new(
111                "header",
112                "HEADER_LOG_GUID_MISMATCH",
113                "LogGuid differs between headers",
114                "MS-VHDX/2.2.2",
115            ),
116        );
117        Err(Error::HeaderLogGuidMismatch {
118            header1_log_guid: v1.log_guid(),
119            header2_log_guid: v2.log_guid(),
120        })
121    }
122
123    fn validate_log_alignment(
124        header: &Header<'_>, issues: &mut Vec<ValidationIssue>,
125    ) -> Result<()> {
126        let current = header.header(0)?;
127        let log_offset = current.log_offset();
128        let log_length = current.log_length();
129        if log_length > 0 && u64::from(log_length) % u64::from(MIB) != 0 {
130            Self::push_issue(
131                issues,
132                ValidationIssue::new(
133                    "header",
134                    "HEADER_LOG_LENGTH_NOT_ALIGNED",
135                    format!("log_length {log_length} is not a multiple of 1MB"),
136                    "MS-VHDX/2.2.2",
137                ),
138            );
139            return Err(Error::HeaderLogNotAligned {
140                field: "log_length".to_string(),
141                value: u64::from(log_length),
142            });
143        }
144        if log_offset > 0 && log_offset % u64::from(MIB) != 0 {
145            Self::push_issue(
146                issues,
147                ValidationIssue::new(
148                    "header",
149                    "HEADER_LOG_OFFSET_NOT_ALIGNED",
150                    format!("log_offset {log_offset} is not a multiple of 1MB"),
151                    "MS-VHDX/2.2.2",
152                ),
153            );
154            return Err(Error::HeaderLogNotAligned {
155                field: "log_offset".to_string(),
156                value: log_offset,
157            });
158        }
159        Ok(())
160    }
161
162    /// Validate a single header structure (signature, CRC, version, `log_version`).
163    fn validate_single_header(result: Result<HeaderStructure<'_>>) -> Result<HeaderStructure<'_>> {
164        let mut issues = Vec::new();
165        let h = result?;
166
167        // Signature check is done by Header::validate_header_at (returns
168        // CorruptedHeader on mismatch). Version and log_version are additional
169        // checks performed here.
170
171        // Version must be 1
172        if h.version() != 1 {
173            Self::push_issue(
174                &mut issues,
175                ValidationIssue::new(
176                    "header",
177                    "HEADER_VERSION_UNSUPPORTED",
178                    format!("version {} is not supported (expected 1)", h.version()),
179                    "MS-VHDX/2.2.2",
180                ),
181            );
182            return Err(Error::UnsupportedVersion {
183                version: h.version(),
184            });
185        }
186
187        // Log version must be 0 (MS-VHDX ยง2.2.2: MUST NOT continue UNLESS LogGuid==0)
188        if h.log_version() != 0 && h.log_guid() != Guid::zero() {
189            Self::push_issue(
190                &mut issues,
191                ValidationIssue::new(
192                    "header",
193                    "HEADER_LOG_VERSION_UNSUPPORTED",
194                    format!(
195                        "log version {} is not supported (expected 0)",
196                        h.log_version()
197                    ),
198                    "MS-VHDX/2.2.2",
199                ),
200            );
201            return Err(Error::UnsupportedLogVersion {
202                version: h.log_version(),
203            });
204        }
205
206        Ok(h)
207    }
208}