use crate::error::{Result, VhdxError};
pub const HEADER1_OFFSET: u64 = 0x0001_0000; pub const HEADER2_OFFSET: u64 = 0x0002_0000; pub const REGION_TABLE1_OFFSET: u64 = 0x0003_0000; pub const REGION_TABLE2_OFFSET: u64 = 0x0004_0000;
pub const HEADER_SIGNATURE: &[u8; 4] = b"head";
pub const HEADER_SIZE: usize = 4096;
#[derive(Debug, Clone)]
pub struct VhdxHeader {
pub sequence_number: u64,
pub log_guid: [u8; 16],
pub log_offset: u64,
pub log_length: u32,
}
pub fn parse_active_header(data: &[u8]) -> Result<VhdxHeader> {
let h1 = parse_one_header(data, HEADER1_OFFSET as usize);
let h2 = parse_one_header(data, HEADER2_OFFSET as usize);
match (h1, h2) {
(Ok(a), Ok(b)) => {
if a.sequence_number >= b.sequence_number {
Ok(a)
} else {
Ok(b)
}
}
(Ok(a), Err(_)) => Ok(a),
(Err(_), Ok(b)) => Ok(b),
(Err(_), Err(_)) => Err(VhdxError::NoValidHeader),
}
}
fn parse_one_header(data: &[u8], offset: usize) -> Result<VhdxHeader> {
let end = offset
.checked_add(HEADER_SIZE)
.ok_or(VhdxError::NoValidHeader)?;
if data.len() < end {
return Err(VhdxError::NoValidHeader);
}
let slice = &data[offset..end];
if &slice[0..4] != HEADER_SIGNATURE {
return Err(VhdxError::NoValidHeader);
}
if !verify_crc32c(slice) {
return Err(VhdxError::NoValidHeader);
}
let sequence_number = u64::from_le_bytes(slice[8..16].try_into().unwrap());
let log_guid: [u8; 16] = slice[48..64].try_into().unwrap();
let log_length = u32::from_le_bytes(slice[68..72].try_into().unwrap());
let log_offset = u64::from_le_bytes(slice[72..80].try_into().unwrap());
Ok(VhdxHeader {
sequence_number,
log_guid,
log_offset,
log_length,
})
}
fn verify_crc32c(block: &[u8]) -> bool {
let stored = u32::from_le_bytes(block[4..8].try_into().unwrap());
let mut buf = block.to_vec();
buf[4..8].fill(0);
crc32c(&buf) == stored
}
pub fn crc32c(data: &[u8]) -> u32 {
const POLY: u32 = 0x82F6_3B78;
let mut crc: u32 = 0xFFFF_FFFF;
for &byte in data {
crc ^= u32::from(byte);
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ POLY;
} else {
crc >>= 1;
}
}
}
crc ^ 0xFFFF_FFFF
}