vhdx/validation/
parent.rs1use super::{
2 BAT_REGION_GUID, Error, Guid, Header, METADATA_REGION_GUID, Result, SpecValidator,
3 ValidationIssue,
4};
5
6impl SpecValidator {
7 pub fn validate_parent_locator(&self) -> Result<Vec<ValidationIssue>> {
13 let mut issues = Vec::new();
14 let Some(meta_data) = self.metadata_region() else {
15 return Ok(issues);
16 };
17
18 let meta = crate::metadata::Metadata::new(meta_data)?;
19 let Ok(locator) = meta.items().parent_locator() else {
20 return Ok(issues);
21 };
22 Self::validate_parent_locator_keys(&locator, &mut issues)?;
23
24 Ok(issues)
25 }
26
27 fn validate_parent_locator_keys(
28 locator: &crate::metadata::ParentLocator<'_>, issues: &mut Vec<ValidationIssue>,
29 ) -> Result<Option<Guid>> {
30 let kv_data = locator.key_value_data();
31 let mut has_parent_linkage = false;
32 let mut has_path = false;
33 let mut parent_linkage_guid: Option<Guid> = None;
34 for kv in locator.entries() {
35 let key = kv.key(kv_data)?;
36 match key.as_str() {
37 "parent_linkage" => {
38 has_parent_linkage = true;
39 if let Ok(value) = kv.value(kv_data) {
40 parent_linkage_guid = parse_guid_from_braced_string(&value);
41 }
42 }
43 "parent_linkage2" => {
44 Self::push_issue(
45 issues,
46 ValidationIssue::new(
47 "parent_locator",
48 "PARENT_LOCATOR_LINKAGE2_CONFLICT",
49 "parent_linkage2 present",
50 "MS-VHDX/2.6.2.6.3",
51 ),
52 );
53 return Err(Error::ParentLocatorLinkage2Conflict);
54 }
55 "relative_path" | "volume_path" | "absolute_win32_path" => {
56 has_path = true;
57 }
58 _ => {}
59 }
60 }
61 if !has_parent_linkage {
62 Self::push_issue(
63 issues,
64 ValidationIssue::new(
65 "parent_locator",
66 "PARENT_LOCATOR_MISSING_LINKAGE",
67 "parent_linkage key not found",
68 "MS-VHDX/2.6.2.6.3",
69 ),
70 );
71 return Err(Error::ParentLocatorMissingLinkage);
72 }
73 if parent_linkage_guid.is_none() {
74 Self::push_issue(
75 issues,
76 ValidationIssue::new(
77 "parent_locator",
78 "PARENT_LOCATOR_FORMAT_ERROR",
79 "parent_linkage value is not a valid GUID format",
80 "VALEXT",
81 ),
82 );
83 return Err(Error::InvalidParentLocator(
84 "parent_linkage value is not a valid GUID format".into(),
85 ));
86 }
87 if !has_path {
88 Self::push_issue(
89 issues,
90 ValidationIssue::new(
91 "parent_locator",
92 "PARENT_LOCATOR_NO_VALID_PATH",
93 "no valid parent path (relative_path/volume_path/absolute_win32_path)",
94 "MS-VHDX/2.6.2.6.3",
95 ),
96 );
97 return Err(Error::ParentNotFound);
98 }
99 Ok(parent_linkage_guid)
100 }
101
102 pub(super) fn parse_header(&self) -> Result<Header<'_>> {
108 Header::new(&self.data)
109 }
110
111 pub(super) fn log_region(&self) -> Option<&[u8]> {
113 let header = self.parse_header().ok()?;
114 let current = header.header(0).ok()?;
115 let log_offset = usize::try_from(current.log_offset()).ok()?;
116 let log_length = usize::try_from(current.log_length()).ok()?;
117
118 if log_offset == 0 && log_length == 0 {
119 return None;
120 }
121
122 let end = log_offset.checked_add(log_length)?;
123 if end > self.data.len() {
124 return None;
125 }
126
127 Some(&self.data[log_offset..end])
128 }
129
130 pub(super) fn region_for_guid(&self, guid: &Guid) -> Option<&[u8]> {
132 let header = self.parse_header().ok()?;
133 let rt = header.region_table(0).ok()?;
134 for entry in rt.entries() {
135 if entry.guid() == *guid {
136 let offset = usize::try_from(entry.file_offset()).ok()?;
137 let length = usize::try_from(entry.length()).ok()?;
138 let end = offset.checked_add(length)?;
139 if end <= self.data.len() {
140 return Some(&self.data[offset..end]);
141 }
142 }
143 }
144 None
145 }
146
147 pub(super) fn bat_region(&self) -> Option<&[u8]> {
149 self.region_for_guid(&BAT_REGION_GUID)
150 }
151
152 pub(super) fn metadata_region(&self) -> Option<&[u8]> {
154 self.region_for_guid(&METADATA_REGION_GUID)
155 }
156
157 pub(super) fn has_parent(&self) -> bool {
159 if let Some(meta_data) = self.metadata_region()
160 && let Ok(meta) = crate::metadata::Metadata::new(meta_data)
161 && let Ok(fp) = meta.items().file_parameters()
162 {
163 return fp.has_parent();
164 }
165 false
166 }
167
168 pub(super) fn current_log_guid(header: &Header<'_>) -> Result<Guid> {
170 let current = header.header(0)?;
171 Ok(current.log_guid())
172 }
173
174 pub(super) fn chunk_ratio(&self) -> u64 {
176 let block_size = u64::from(self.block_size());
177 let logical_sector_size = u64::from(self.logical_sector_size());
178 if block_size == 0 || logical_sector_size == 0 {
179 return 0;
180 }
181 crate::common::compute_chunk_ratio(block_size, logical_sector_size)
182 }
183
184 pub(super) fn block_size(&self) -> u32 {
186 if let Some(meta_data) = self.metadata_region()
187 && let Ok(meta) = crate::metadata::Metadata::new(meta_data)
188 && let Ok(fp) = meta.items().file_parameters()
189 {
190 return fp.block_size();
191 }
192 0
193 }
194
195 pub(super) fn logical_sector_size(&self) -> u32 {
197 if let Some(meta_data) = self.metadata_region()
198 && let Ok(meta) = crate::metadata::Metadata::new(meta_data)
199 && let Ok(lss) = meta.items().logical_sector_size()
200 {
201 return lss;
202 }
203 0
204 }
205
206 pub(super) fn virtual_disk_size(&self) -> u64 {
208 if let Some(meta_data) = self.metadata_region()
209 && let Ok(meta) = crate::metadata::Metadata::new(meta_data)
210 && let Ok(vds) = meta.items().virtual_disk_size()
211 {
212 return vds;
213 }
214 0
215 }
216}
217
218fn parse_guid_from_braced_string(s: &str) -> Option<Guid> {
227 let s = s.trim();
228 let inner = s.strip_prefix('{').and_then(|s| s.strip_suffix('}'))?;
230 let hex: String = inner.chars().filter(|c| *c != '-').collect();
232 if hex.len() != 32 {
233 return None;
234 }
235 let mut bytes = [0u8; 16];
236 for i in 0..16 {
237 let byte_str = &hex[i * 2..i * 2 + 2];
238 bytes[i] = u8::from_str_radix(byte_str, 16).ok()?;
239 }
240 Some(Guid::from_bytes(bytes))
241}