vhdx/validation/core.rs
1//! VHDX specification compliance validator (read-only).
2//!
3//! `SpecValidator` performs structural validation against MS-VHDX and
4//! companion standards. All validation is non-destructive and does not
5//! modify file state.
6//!
7//! # Standard references
8//!
9//! - MS-VHDX (baseline specification)
10//! - MS-VHDX-`校验扩展标准` (this module's error code dictionary)
11//! - MS-VHDX-`宽松扩展标准` (permissive validation, RELAX)
12//! - MS-VHDX-`只读扩展标准` (read-only semantics, ROEXT)
13
14use crate::error::{Error, Result, SignaturePosition};
15use std::sync::Arc;
16
17// ---------------------------------------------------------------------------
18// ValidationIssue – structured diagnostic output
19// ---------------------------------------------------------------------------
20
21/// A structured validation issue for diagnostics and auditing.
22///
23/// Each issue carries a standardised error code, a human-readable message,
24/// and a reference to the relevant section of the MS-VHDX specification
25/// (or companion standard).
26#[derive(Debug, Clone)]
27pub struct ValidationIssue {
28 /// Validation phase (e.g. `"header"`, `"bat"`, `"log"`).
29 section: &'static str,
30 /// Standardised error code (e.g. `"HEADER_SIGNATURE_INVALID"`).
31 code: &'static str,
32 /// Human-readable description, SHOULD include context values.
33 message: String,
34 /// Specification reference (e.g. `"MS-VHDX/2.2"`).
35 spec_ref: &'static str,
36}
37
38impl ValidationIssue {
39 /// Create a new validation issue.
40 pub(crate) fn new(
41 section: &'static str, code: &'static str, message: impl Into<String>,
42 spec_ref: &'static str,
43 ) -> Self {
44 Self {
45 section,
46 code,
47 message: message.into(),
48 spec_ref,
49 }
50 }
51
52 /// Validation phase (e.g. `"header"`, `"bat"`, `"log"`).
53 #[must_use]
54 pub fn section(&self) -> &'static str {
55 self.section
56 }
57
58 /// Standardised error code.
59 #[must_use]
60 pub fn code(&self) -> &'static str {
61 self.code
62 }
63
64 /// Human-readable description.
65 #[must_use]
66 pub fn message(&self) -> String {
67 self.message.clone()
68 }
69
70 /// Specification reference.
71 #[must_use]
72 pub fn spec_ref(&self) -> &'static str {
73 self.spec_ref
74 }
75}
76
77// ---------------------------------------------------------------------------
78// SpecValidator
79// ---------------------------------------------------------------------------
80
81/// VHDX specification compliance validator.
82///
83/// Holds a snapshot of the file data buffer and performs read-only
84/// structural checks against MS-VHDX and companion standards.
85///
86/// # Construction
87///
88/// Typically constructed with the file's full data buffer and configuration
89/// flags (strict mode, whether the disk is differencing).
90pub struct SpecValidator {
91 /// Full file data buffer (must include header, log, BAT, metadata regions).
92 pub(super) data: Arc<[u8]>,
93 /// Whether strict validation mode is enabled.
94 pub(super) strict: bool,
95}
96
97impl SpecValidator {
98 /// Create a new `SpecValidator`.
99 ///
100 /// `data` must be at least 1 MB (the header section). For full validation
101 /// it should include the log, BAT, and metadata regions.
102 #[cfg(test)]
103 pub(crate) fn new(data: &[u8], strict: bool) -> Self {
104 Self {
105 data: Arc::from(data),
106 strict,
107 }
108 }
109
110 /// Create a `SpecValidator` from a `Medium` reference.
111 ///
112 /// Collects all cached region buffers (header, log, BAT, metadata) from the
113 /// file and assembles them into a contiguous view at their correct file offsets,
114 /// so that region lookups by absolute offset work correctly.
115 ///
116 /// The returned validator owns an `Arc<[u8]>` snapshot cloned from the
117 /// `Medium`'s internal `validator_buf` cache, which is built lazily on first access.
118 pub(crate) fn from_file<T>(file: &mut crate::medium::Medium<T>) -> Result<Self>
119 where
120 T: std::io::Read + std::io::Seek,
121 {
122 let strict = file.is_strict();
123 let data = file.validator_buf()?;
124 Ok(Self { data, strict })
125 }
126
127 /// Push a non-fatal validation issue into the collection.
128 pub(super) fn push_issue(issues: &mut Vec<ValidationIssue>, issue: ValidationIssue) {
129 issues.push(issue);
130 }
131
132 /// Map a header validation [`Error`] to the correct [`ValidationIssue`] and push it.
133 ///
134 /// Distinguishes `InvalidSignature` (header position) from `InvalidChecksum` and
135 /// version errors, producing the appropriate per-header issue code so that each
136 /// invalid header gets a *specific* diagnostic rather than the generic
137 /// `HEADER_SEQUENCE_NUMBER_INVALID` catch-all.
138 pub(super) fn push_header_issue(
139 issues: &mut Vec<ValidationIssue>, header_idx: u32, err: &Error,
140 ) {
141 let issue = match err {
142 Error::InvalidSignature {
143 position: SignaturePosition::Header,
144 ..
145 } => ValidationIssue::new(
146 "header",
147 "HEADER_SIGNATURE_INVALID",
148 format!("header {header_idx} signature error: {err}"),
149 "MS-VHDX/2.2.2",
150 ),
151 Error::InvalidChecksum { .. } => ValidationIssue::new(
152 "header",
153 "HEADER_CHECKSUM_MISMATCH",
154 format!("header {header_idx} checksum error: {err}"),
155 "MS-VHDX/2.2.2",
156 ),
157 Error::UnsupportedVersion { version } => ValidationIssue::new(
158 "header",
159 "HEADER_VERSION_UNSUPPORTED",
160 format!("header {header_idx} version {version} is not supported (expected 1)"),
161 "MS-VHDX/2.2.2",
162 ),
163 Error::UnsupportedLogVersion { version } => ValidationIssue::new(
164 "header",
165 "HEADER_LOG_VERSION_UNSUPPORTED",
166 format!("header {header_idx} log version {version} is not supported (expected 0)"),
167 "MS-VHDX/2.2.2",
168 ),
169 _ => ValidationIssue::new(
170 "header",
171 "HEADER_CORRUPTED",
172 format!("header {header_idx} error: {err}"),
173 "MS-VHDX/2.2.2",
174 ),
175 };
176 issues.push(issue);
177 }
178
179 /// Run all structural validations.
180 ///
181 /// Calls each sub-validation in order. Returns the first error encountered.
182 ///
183 /// # Errors
184 ///
185 /// Returns the first hard validation error from a sub-validation stage.
186 pub fn validate_file(&self) -> Result<Vec<ValidationIssue>> {
187 let mut issues = Vec::new();
188 issues.extend(self.validate_header()?);
189 issues.extend(self.validate_region_table()?);
190 issues.extend(self.validate_log()?);
191 issues.extend(self.validate_bat()?);
192 issues.extend(self.validate_metadata()?);
193 issues.extend(self.validate_required_metadata_items()?);
194 if self.has_parent() {
195 issues.extend(self.validate_parent_locator()?);
196 }
197 Ok(issues)
198 }
199}