Skip to main content

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}