1use crate::bytes::{le_u16, le_u32, le_u64};
2use crate::error::{Result, VhdxError};
3
4pub const METADATA_TABLE_SIGNATURE: &[u8; 8] = b"metadata";
5
6const BLOCK_SIZE_MIN: u32 = 1 << 20; const BLOCK_SIZE_MAX: u32 = 256 << 20; const VALID_SECTOR_SIZES: [u32; 2] = [512, 4096];
9const VIRTUAL_DISK_SIZE_MAX: u64 = 64 * (1u64 << 40); pub const GUID_FILE_PARAMETERS: [u8; 16] = [
13 0x37, 0x67, 0xA1, 0xCA, 0x36, 0xFA, 0x43, 0x4D, 0xB3, 0xB6, 0x33, 0xF0, 0xAA, 0x44, 0xE7, 0x6B,
14];
15pub const GUID_VIRTUAL_DISK_SIZE: [u8; 16] = [
16 0x24, 0x42, 0xA5, 0x2F, 0x1B, 0xCD, 0x76, 0x48, 0xB2, 0x11, 0x5B, 0xE0, 0x7A, 0x6C, 0xE3, 0x2C,
17];
18pub const GUID_VIRTUAL_DISK_SIZE_QEMU_COMPAT: [u8; 16] = [
21 0x24, 0x42, 0xA5, 0x2F, 0x1B, 0xCD, 0x76, 0x48, 0xB2, 0x11, 0x5D, 0xBE, 0xD8, 0x3B, 0xF4, 0xB8,
22];
23pub const GUID_LOGICAL_SECTOR_SIZE: [u8; 16] = [
24 0x1D, 0xBF, 0x41, 0x81, 0x6F, 0xA9, 0x09, 0x47, 0xBA, 0x47, 0xF2, 0x33, 0xA8, 0xFA, 0xAB, 0x5F,
25];
26pub const GUID_PHYSICAL_SECTOR_SIZE: [u8; 16] = [
27 0xC7, 0x48, 0xA3, 0xCD, 0x5D, 0x44, 0x71, 0x44, 0x9C, 0xC9, 0xE9, 0x88, 0x52, 0x51, 0xC5, 0x56,
28];
29pub const GUID_VIRTUAL_DISK_ID: [u8; 16] = [
30 0xAB, 0x12, 0xCA, 0xBE, 0xE6, 0xB2, 0x23, 0x45, 0x93, 0xEF, 0xC3, 0x09, 0xE0, 0x00, 0xC7, 0x46,
31];
32pub const GUID_PARENT_LOCATOR: [u8; 16] = [
33 0x2D, 0x5F, 0xD3, 0xA8, 0x0B, 0xB3, 0x4D, 0x45, 0xAB, 0xF7, 0xD3, 0xD8, 0x48, 0x34, 0xAB, 0x0C,
34];
35
36#[derive(Debug, Clone)]
37pub struct VhdxMetadata {
38 pub block_size: u32,
40 pub has_parent: bool,
42 pub virtual_disk_size: u64,
44 pub logical_sector_size: u32,
46}
47
48impl VhdxMetadata {
49 pub fn chunk_ratio(&self) -> u64 {
52 (1u64 << 23) * u64::from(self.logical_sector_size) / u64::from(self.block_size)
53 }
54
55 pub fn validate(&self) -> Result<()> {
56 if self.block_size < BLOCK_SIZE_MIN || self.block_size > BLOCK_SIZE_MAX {
57 return Err(VhdxError::InvalidMetadata(
58 "BlockSize must be in [1 MB, 256 MB]",
59 ));
60 }
61 if self.block_size.count_ones() != 1 {
62 return Err(VhdxError::InvalidMetadata(
63 "BlockSize must be a power of two",
64 ));
65 }
66 if !VALID_SECTOR_SIZES.contains(&self.logical_sector_size) {
67 return Err(VhdxError::InvalidMetadata(
68 "LogicalSectorSize must be 512 or 4096",
69 ));
70 }
71 if self.virtual_disk_size == 0 {
72 return Err(VhdxError::InvalidMetadata("VirtualDiskSize cannot be zero"));
73 }
74 if self.virtual_disk_size > VIRTUAL_DISK_SIZE_MAX {
75 return Err(VhdxError::InvalidMetadata(
76 "VirtualDiskSize exceeds the 64 TiB spec limit",
77 ));
78 }
79 if self.virtual_disk_size % u64::from(self.logical_sector_size) != 0 {
80 return Err(VhdxError::InvalidMetadata(
81 "VirtualDiskSize must be a multiple of LogicalSectorSize",
82 ));
83 }
84 Ok(())
85 }
86}
87
88pub fn parse_metadata(data: &[u8], region_offset: u64, region_len: u32) -> Result<VhdxMetadata> {
89 let start = region_offset as usize;
90 let end = start + region_len as usize;
91 if data.len() < end || end < start + 8 {
92 return Err(VhdxError::MetadataMissing("region out of bounds"));
93 }
94 let region = &data[start..end];
95 if ®ion[0..8] != METADATA_TABLE_SIGNATURE {
96 return Err(VhdxError::MetadataMissing("bad metadata signature"));
97 }
98 let entry_count = le_u16(region, 10) as usize;
99
100 let mut block_size: Option<u32> = None;
101 let mut has_parent = false;
102 let mut virtual_disk_size: Option<u64> = None;
103 let mut logical_sector_size: Option<u32> = None;
104
105 for i in 0..entry_count {
106 let base = 32 + i * 32;
107 if base + 32 > region.len() {
108 break;
109 }
110 let mut guid = [0u8; 16];
111 guid.copy_from_slice(®ion[base..base + 16]);
112 let item_offset = le_u32(region, base + 16) as usize;
113 let item_len = le_u32(region, base + 20) as usize;
114
115 let data_start = start + item_offset;
116 let data_end = data_start + item_len;
117 if data.len() < data_end {
118 continue;
119 }
120 let item_data = &data[data_start..data_end];
121
122 if guid == GUID_FILE_PARAMETERS && item_data.len() >= 8 {
123 block_size = Some(le_u32(item_data, 0));
124 let flags = le_u32(item_data, 4);
125 has_parent = flags & 2 != 0;
126 } else if (guid == GUID_VIRTUAL_DISK_SIZE || guid == GUID_VIRTUAL_DISK_SIZE_QEMU_COMPAT)
127 && item_data.len() >= 8
128 {
129 virtual_disk_size = Some(le_u64(item_data, 0));
130 } else if guid == GUID_LOGICAL_SECTOR_SIZE && item_data.len() >= 4 {
131 logical_sector_size = Some(le_u32(item_data, 0));
132 }
133 }
134
135 Ok(VhdxMetadata {
136 block_size: block_size.ok_or(VhdxError::MetadataMissing("BlockSize"))?,
137 has_parent,
138 virtual_disk_size: virtual_disk_size
139 .ok_or(VhdxError::MetadataMissing("VirtualDiskSize"))?,
140 logical_sector_size: logical_sector_size.unwrap_or(512),
141 })
142}