Skip to main content

vhdx/
header.rs

1use crate::error::{Result, VhdxError};
2
3/// Offsets per MS-VHDX spec §2.1 — each block occupies a 64 KB slot within
4/// the first 1 MB of the file (the "header section").
5pub const HEADER1_OFFSET: u64 = 0x0001_0000; // 64 KB
6pub const HEADER2_OFFSET: u64 = 0x0002_0000; // 128 KB
7pub const REGION_TABLE1_OFFSET: u64 = 0x0003_0000; // 192 KB
8pub const REGION_TABLE2_OFFSET: u64 = 0x0004_0000; // 256 KB
9
10pub const HEADER_SIGNATURE: &[u8; 4] = b"head";
11pub const HEADER_SIZE: usize = 4096;
12
13/// Parsed VHDX header (the active copy with highest valid sequence number).
14#[derive(Debug, Clone)]
15pub struct VhdxHeader {
16    pub sequence_number: u64,
17    pub log_guid: [u8; 16],
18    pub log_offset: u64,
19    pub log_length: u32,
20}
21
22/// Select the active header from a raw byte slice covering both header copies.
23/// Returns the header with the highest sequence number whose CRC32C is valid.
24pub fn parse_active_header(data: &[u8]) -> Result<VhdxHeader> {
25    let h1 = parse_one_header(data, HEADER1_OFFSET as usize);
26    let h2 = parse_one_header(data, HEADER2_OFFSET as usize);
27    match (h1, h2) {
28        (Ok(a), Ok(b)) => {
29            if a.sequence_number >= b.sequence_number {
30                Ok(a)
31            } else {
32                Ok(b)
33            }
34        }
35        (Ok(a), Err(_)) => Ok(a),
36        (Err(_), Ok(b)) => Ok(b),
37        (Err(_), Err(_)) => Err(VhdxError::NoValidHeader),
38    }
39}
40
41fn parse_one_header(data: &[u8], offset: usize) -> Result<VhdxHeader> {
42    let end = offset
43        .checked_add(HEADER_SIZE)
44        .ok_or(VhdxError::NoValidHeader)?;
45    if data.len() < end {
46        return Err(VhdxError::NoValidHeader);
47    }
48    let slice = &data[offset..end];
49    if &slice[0..4] != HEADER_SIGNATURE {
50        return Err(VhdxError::NoValidHeader);
51    }
52    if !verify_crc32c(slice) {
53        return Err(VhdxError::NoValidHeader);
54    }
55    let sequence_number = u64::from_le_bytes(slice[8..16].try_into().unwrap());
56    let log_guid: [u8; 16] = slice[48..64].try_into().unwrap();
57    let log_length = u32::from_le_bytes(slice[68..72].try_into().unwrap());
58    let log_offset = u64::from_le_bytes(slice[72..80].try_into().unwrap());
59    Ok(VhdxHeader {
60        sequence_number,
61        log_guid,
62        log_offset,
63        log_length,
64    })
65}
66
67fn verify_crc32c(block: &[u8]) -> bool {
68    let stored = u32::from_le_bytes(block[4..8].try_into().unwrap());
69    let mut buf = block.to_vec();
70    buf[4..8].fill(0);
71    crc32c(&buf) == stored
72}
73
74pub fn crc32c(data: &[u8]) -> u32 {
75    const POLY: u32 = 0x82F6_3B78;
76    let mut crc: u32 = 0xFFFF_FFFF;
77    for &byte in data {
78        crc ^= u32::from(byte);
79        for _ in 0..8 {
80            if crc & 1 != 0 {
81                crc = (crc >> 1) ^ POLY;
82            } else {
83                crc >>= 1;
84            }
85        }
86    }
87    crc ^ 0xFFFF_FFFF
88}