1use crate::bytes::{le_u32, le_u64};
2use crate::error::{Result, VhdxError};
3use crate::header::crc32c;
4
5pub const REGION_TABLE_SIGNATURE: &[u8; 4] = b"regi";
6pub const REGION_ENTRY_SIZE: usize = 32;
7pub const REGION_TABLE_CRC_COVERAGE: usize = 65536;
9pub const MB: u64 = 0x0010_0000;
11
12pub const BAT_GUID: [u8; 16] = [
14 0x66, 0x77, 0xC2, 0x2D, 0x23, 0xF6, 0x00, 0x42, 0x9D, 0x64, 0x11, 0x5E, 0x9B, 0xFD, 0x4A, 0x08,
15];
16
17pub const METADATA_GUID: [u8; 16] = [
19 0x06, 0xA2, 0x7C, 0x8B, 0x90, 0x47, 0x9A, 0x4B, 0xB8, 0xFE, 0x57, 0x5F, 0x05, 0x0F, 0x88, 0x6E,
20];
21
22#[derive(Debug, Clone)]
23pub struct RegionEntry {
24 #[allow(dead_code)]
25 pub guid: [u8; 16],
26 pub file_offset: u64,
27 pub length: u32,
28}
29
30#[derive(Debug, Clone)]
31pub struct RegionTable {
32 pub bat: RegionEntry,
33 pub metadata: RegionEntry,
34}
35
36const REGION_ENTRY_COUNT_MAX: usize = 2048;
42
43pub fn parse_region_table(data: &[u8], offset: usize, container_len: u64) -> Result<RegionTable> {
51 if data.len() < offset + 16 {
52 return Err(VhdxError::InvalidRegionTable);
53 }
54 let slice = &data[offset..];
55 if &slice[0..4] != REGION_TABLE_SIGNATURE {
56 return Err(VhdxError::InvalidRegionTable);
57 }
58 let stored_crc = le_u32(slice, 4);
59 let mut buf = slice[..65536.min(slice.len())].to_vec();
60 buf.resize(65536, 0);
61 buf[4..8].fill(0);
62 if crc32c(&buf) != stored_crc {
63 return Err(VhdxError::InvalidRegionTable);
64 }
65 let entry_count = (le_u32(slice, 8) as usize).min(REGION_ENTRY_COUNT_MAX);
66 let mut bat: Option<RegionEntry> = None;
67 let mut metadata: Option<RegionEntry> = None;
68 for i in 0..entry_count {
69 let base = 16 + i * REGION_ENTRY_SIZE;
70 if base + REGION_ENTRY_SIZE > slice.len() {
71 break;
72 }
73 let mut guid = [0u8; 16];
74 guid.copy_from_slice(&slice[base..base + 16]);
75 let file_offset = le_u64(slice, base + 16);
76 let length = le_u32(slice, base + 24);
77 let region_end = file_offset
78 .checked_add(u64::from(length))
79 .ok_or(VhdxError::OffsetOutOfBounds)?;
80 if region_end > container_len {
81 return Err(VhdxError::OffsetOutOfBounds);
82 }
83 let entry = RegionEntry {
84 guid,
85 file_offset,
86 length,
87 };
88 if guid == BAT_GUID {
89 bat = Some(entry);
90 } else if guid == METADATA_GUID {
91 metadata = Some(entry);
92 }
93 }
94 Ok(RegionTable {
95 bat: bat.ok_or(VhdxError::BatRegionMissing)?,
96 metadata: metadata.ok_or(VhdxError::MetadataRegionMissing)?,
97 })
98}