use crate::error::{Result, VhdxError};
use crate::header::crc32c;
pub const REGION_TABLE_SIGNATURE: &[u8; 4] = b"regi";
pub const REGION_ENTRY_SIZE: usize = 32;
pub const REGION_TABLE_CRC_COVERAGE: usize = 65536;
pub const MB: u64 = 0x0010_0000;
pub const BAT_GUID: [u8; 16] = [
0x66, 0x77, 0xC2, 0x2D, 0x23, 0xF6, 0x00, 0x42, 0x9D, 0x64, 0x11, 0x5E, 0x9B, 0xFD, 0x4A, 0x08,
];
pub const METADATA_GUID: [u8; 16] = [
0x06, 0xA2, 0x7C, 0x8B, 0x90, 0x47, 0x9A, 0x4B, 0xB8, 0xFE, 0x57, 0x5F, 0x05, 0x0F, 0x88, 0x6E,
];
#[derive(Debug, Clone)]
pub struct RegionEntry {
#[allow(dead_code)]
pub guid: [u8; 16],
pub file_offset: u64,
pub length: u32,
}
#[derive(Debug, Clone)]
pub struct RegionTable {
pub bat: RegionEntry,
pub metadata: RegionEntry,
}
const REGION_ENTRY_COUNT_MAX: usize = 2048;
pub fn parse_region_table(data: &[u8], offset: usize) -> Result<RegionTable> {
if data.len() < offset + 16 {
return Err(VhdxError::InvalidRegionTable);
}
let slice = &data[offset..];
if &slice[0..4] != REGION_TABLE_SIGNATURE {
return Err(VhdxError::InvalidRegionTable);
}
let stored_crc = u32::from_le_bytes(slice[4..8].try_into().unwrap());
let mut buf = slice[..65536.min(slice.len())].to_vec();
buf.resize(65536, 0);
buf[4..8].fill(0);
if crc32c(&buf) != stored_crc {
return Err(VhdxError::InvalidRegionTable);
}
let entry_count =
(u32::from_le_bytes(slice[8..12].try_into().unwrap()) as usize).min(REGION_ENTRY_COUNT_MAX);
let container_len = data.len();
let mut bat: Option<RegionEntry> = None;
let mut metadata: Option<RegionEntry> = None;
for i in 0..entry_count {
let base = 16 + i * REGION_ENTRY_SIZE;
if base + REGION_ENTRY_SIZE > slice.len() {
break;
}
let mut guid = [0u8; 16];
guid.copy_from_slice(&slice[base..base + 16]);
let file_offset = u64::from_le_bytes(slice[base + 16..base + 24].try_into().unwrap());
let length = u32::from_le_bytes(slice[base + 24..base + 28].try_into().unwrap());
let region_end = file_offset
.checked_add(u64::from(length))
.ok_or(VhdxError::OffsetOutOfBounds)?;
if region_end as usize > container_len {
return Err(VhdxError::OffsetOutOfBounds);
}
let entry = RegionEntry {
guid,
file_offset,
length,
};
if guid == BAT_GUID {
bat = Some(entry);
} else if guid == METADATA_GUID {
metadata = Some(entry);
}
}
Ok(RegionTable {
bat: bat.ok_or(VhdxError::BatRegionMissing)?,
metadata: metadata.ok_or(VhdxError::MetadataRegionMissing)?,
})
}