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