1use super::{Error, PayloadBlockState, Result, SectorBitmapState, SpecValidator, ValidationIssue};
2
3impl SpecValidator {
4 pub fn validate_bat(&self) -> Result<Vec<ValidationIssue>> {
21 let mut issues = Vec::new();
22 let Some(bat_data) = self.bat_region() else {
23 return Ok(issues);
24 };
25
26 let chunk_ratio = self.chunk_ratio();
27 if chunk_ratio == 0 {
28 return Ok(issues); }
30
31 let bat = crate::bat::Bat::new(bat_data, chunk_ratio);
32 let has_parent = self.has_parent();
33 let block_size = u64::from(self.block_size());
34
35 self.validate_bat_entry_count(&bat, block_size, &mut issues)?;
36 let mut seen_offsets = std::collections::HashSet::new();
37 for entry in bat.entries() {
38 Self::validate_bat_entry(
39 entry,
40 has_parent,
41 block_size,
42 &mut seen_offsets,
43 &mut issues,
44 )?;
45 }
46 Self::validate_bat_sector_bitmap_consistency(&bat, has_parent, chunk_ratio, &mut issues);
47
48 Ok(issues)
49 }
50
51 fn validate_bat_entry_count(
52 &self, bat: &crate::bat::Bat<'_>, block_size: u64, issues: &mut Vec<ValidationIssue>,
53 ) -> Result<()> {
54 let virtual_disk_size = self.virtual_disk_size();
55 if virtual_disk_size > 0 && block_size > 0 {
56 let min_entries = virtual_disk_size.div_ceil(block_size);
57 if bat.len() < usize::try_from(min_entries).expect("minimum BAT entries fit usize") {
58 Self::push_issue(
59 issues,
60 ValidationIssue::new(
61 "bat",
62 "BAT_ENTRY_COUNT_INSUFFICIENT",
63 format!(
64 "BAT has {} entries but virtual disk requires at least {}",
65 bat.len(),
66 min_entries
67 ),
68 "MS-VHDX/2.5",
69 ),
70 );
71 return Err(Error::BatEntryCountInsufficient {
72 actual: bat.len() as u64,
73 expected: min_entries,
74 });
75 }
76 }
77 Ok(())
78 }
79
80 fn validate_bat_entry(
81 entry: crate::bat::BatEntry<'_>, has_parent: bool, block_size: u64,
82 seen_offsets: &mut std::collections::HashSet<u64>, issues: &mut Vec<ValidationIssue>,
83 ) -> Result<()> {
84 let raw_state = entry.raw_state();
85 if entry.is_sector_bitmap() {
86 return Self::validate_bat_sector_bitmap_entry(raw_state, entry, has_parent, issues);
87 }
88 Self::validate_bat_payload_entry(
89 raw_state,
90 entry,
91 has_parent,
92 block_size,
93 seen_offsets,
94 issues,
95 )
96 }
97
98 fn validate_bat_sector_bitmap_entry(
99 raw_state: u8, entry: crate::bat::BatEntry<'_>, has_parent: bool,
100 issues: &mut Vec<ValidationIssue>,
101 ) -> Result<()> {
102 let Some(sb_state) = entry.sector_bitmap_state() else {
103 Self::push_issue(
104 issues,
105 ValidationIssue::new(
106 "bat",
107 "BAT_SECTOR_BITMAP_INVALID_STATE",
108 format!("invalid sector bitmap state: {raw_state}"),
109 "MS-VHDX/2.5.1.2",
110 ),
111 );
112 return Err(Error::InvalidSectorBitmapState(raw_state));
113 };
114 if !has_parent && sb_state != SectorBitmapState::NotPresent {
115 Self::push_issue(
116 issues,
117 ValidationIssue::new(
118 "bat",
119 "BAT_ENTRY_STATE_MISMATCH",
120 "sector bitmap state not NotPresent on non-differencing disk".to_string(),
121 "MS-VHDX/2.5.1.1",
122 ),
123 );
124 return Err(Error::StateMismatch {
125 state: raw_state,
126 description: "sector bitmap state not NotPresent on non-differencing disk".into(),
127 });
128 }
129 Ok(())
130 }
131
132 fn validate_bat_payload_entry(
133 raw_state: u8, entry: crate::bat::BatEntry<'_>, has_parent: bool, block_size: u64,
134 seen_offsets: &mut std::collections::HashSet<u64>, issues: &mut Vec<ValidationIssue>,
135 ) -> Result<()> {
136 let Some(p_state) = entry.payload_state() else {
137 Self::push_issue(
138 issues,
139 ValidationIssue::new(
140 "bat",
141 "BAT_ENTRY_INVALID_STATE",
142 format!("invalid payload block state: {raw_state}"),
143 "MS-VHDX/2.5.1.1",
144 ),
145 );
146 return Err(Error::InvalidBlockState(raw_state));
147 };
148 Self::validate_bat_payload_state_for_disk_type(raw_state, p_state, has_parent, issues)?;
149 Self::validate_bat_payload_offset_alignment(entry, p_state, block_size, issues)?;
150 Self::validate_bat_payload_offset_uniqueness(entry, p_state, seen_offsets, issues)
151 }
152
153 fn validate_bat_payload_offset_alignment(
154 entry: crate::bat::BatEntry<'_>, p_state: PayloadBlockState, block_size: u64,
155 issues: &mut Vec<ValidationIssue>,
156 ) -> Result<()> {
157 match p_state {
158 PayloadBlockState::FullyPresent | PayloadBlockState::PartiallyPresent => {
159 let offset_mb = entry.file_offset_mb();
160 if block_size > 0 && offset_mb != 0 {
161 let offset_bytes = offset_mb * 1024 * 1024;
162 if !offset_bytes.is_multiple_of(block_size) {
163 Self::push_issue(
164 issues,
165 ValidationIssue::new(
166 "bat",
167 "BAT_ENTRY_FILE_OFFSET_UNALIGNED",
168 format!(
169 "payload block file offset {offset_mb} MB ({offset_bytes} bytes) not aligned to block size {block_size}"
170 ),
171 "MS-VHDX/2.5",
172 ),
173 );
174 return Err(Error::BatFileOffsetUnaligned {
175 offset_mb,
176 block_size: u32::try_from(block_size).unwrap_or(u32::MAX),
177 });
178 }
179 }
180 }
181 _ => {}
182 }
183 Ok(())
184 }
185
186 fn validate_bat_payload_state_for_disk_type(
187 raw_state: u8, p_state: PayloadBlockState, has_parent: bool,
188 issues: &mut Vec<ValidationIssue>,
189 ) -> Result<()> {
190 if !has_parent {
191 match p_state {
192 PayloadBlockState::Unmapped | PayloadBlockState::PartiallyPresent => {
193 Self::push_issue(
194 issues,
195 ValidationIssue::new(
196 "bat",
197 "BAT_ENTRY_STATE_MISMATCH",
198 "payload state Unmapped/PartiallyPresent on non-differencing disk"
199 .to_string(),
200 "MS-VHDX/2.5.1.1",
201 ),
202 );
203 return Err(Error::StateMismatch {
204 state: raw_state,
205 description:
206 "payload state Unmapped/PartiallyPresent on non-differencing disk"
207 .into(),
208 });
209 }
210 _ => {}
211 }
212 }
213 Ok(())
214 }
215
216 fn validate_bat_payload_offset_uniqueness(
217 entry: crate::bat::BatEntry<'_>, p_state: PayloadBlockState,
218 seen_offsets: &mut std::collections::HashSet<u64>, issues: &mut Vec<ValidationIssue>,
219 ) -> Result<()> {
220 match p_state {
221 PayloadBlockState::FullyPresent | PayloadBlockState::PartiallyPresent => {
222 let offset_mb = entry.file_offset_mb();
223 if offset_mb != 0 && !seen_offsets.insert(offset_mb) {
224 Self::push_issue(
225 issues,
226 ValidationIssue::new(
227 "bat",
228 "BAT_FILE_OFFSET_DUPLICATE",
229 format!("duplicate file_offset_mb {offset_mb} in BAT"),
230 "MS-VHDX/2.5",
231 ),
232 );
233 return Err(Error::BatFileOffsetDuplicate { offset_mb });
234 }
235 }
236 _ => {}
237 }
238 Ok(())
239 }
240
241 fn validate_bat_sector_bitmap_consistency(
242 bat: &crate::bat::Bat<'_>, has_parent: bool, chunk_ratio: u64,
243 issues: &mut Vec<ValidationIssue>,
244 ) {
245 if !has_parent {
246 return;
247 }
248 let stride = chunk_ratio + 1;
249 let total_entries = bat.len() as u64;
250 let num_chunks = total_entries / stride;
251 for chunk_idx in 0..num_chunks {
252 if !Self::chunk_has_partially_present_payload(
253 bat,
254 chunk_idx,
255 stride,
256 chunk_ratio,
257 total_entries,
258 ) {
259 continue;
260 }
261 let sb_bat_idx = chunk_idx * stride + chunk_ratio;
262 if sb_bat_idx >= total_entries {
263 break;
264 }
265 let Ok(sb_entry) = bat.entry(sb_bat_idx) else {
266 break;
267 };
268 let sb_state = sb_entry.sector_bitmap_state();
269 if !matches!(sb_state, Some(crate::bat::SectorBitmapState::Present)) {
270 Self::push_issue(
271 issues,
272 ValidationIssue::new(
273 "bat",
274 "BAT_SECTOR_BITMAP_INVALID_STATE",
275 format!(
276 "chunk {chunk_idx}: payload entry is PartiallyPresent but sector bitmap state is {sb_state:?}"
277 ),
278 "MS-VHDX/2.5.1.2",
279 ),
280 );
281 }
282 }
283 }
284
285 fn chunk_has_partially_present_payload(
286 bat: &crate::bat::Bat<'_>, chunk_idx: u64, stride: u64, chunk_ratio: u64,
287 total_entries: u64,
288 ) -> bool {
289 for payload_offset_in_chunk in 0..chunk_ratio {
290 let payload_bat_idx = chunk_idx * stride + payload_offset_in_chunk;
291 if payload_bat_idx >= total_entries {
292 break;
293 }
294 let Ok(payload_entry) = bat.entry(payload_bat_idx) else {
295 continue;
296 };
297 if !payload_entry.is_sector_bitmap()
298 && let Some(crate::bat::PayloadBlockState::PartiallyPresent) =
299 payload_entry.payload_state()
300 {
301 return true;
302 }
303 }
304 false
305 }
306}