pub const SECTOR_SIZE: u64 = 512;
pub const VMDK4_MAGIC: u32 = 0x564D_444B;
pub const VERSION_BASE: u32 = 1;
pub const VERSION_ZEROED_GRAIN: u32 = 2;
pub const VERSION_STREAM_OPTIMIZED: u32 = 3;
pub const GD_AT_END: u64 = u64::MAX;
pub const VMDK4_FLAG_VALID_NEWLINE: u32 = 0x0000_0001;
pub const VMDK4_FLAG_USE_RGD: u32 = 0x0000_0002;
pub const VMDK4_FLAG_ZERO_GRAIN: u32 = 0x0000_0004;
pub const VMDK4_FLAG_COMPRESSED: u32 = 0x0001_0000;
pub const VMDK4_FLAG_MARKERS: u32 = 0x0002_0000;
pub const COMPRESSION_NONE: u16 = 0;
pub const COMPRESSION_DEFLATE: u16 = 1;
pub const GTE_SPARSE: u32 = 0;
pub const GTE_ZEROED: u32 = 1;
pub const GRAIN_MARKER_HEADER_SIZE: u64 = 12;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Vmdk4HeaderOffsets {
pub magic: u64, pub version: u64, pub flags: u64, pub capacity: u64, pub grain_size: u64, pub descriptor_offset: u64, pub descriptor_size: u64, pub num_gtes_per_gt: u64, pub rgd_offset: u64, pub gd_offset: u64, pub overhead: u64, pub compress_algorithm: u64, }
pub const VMDK4_HEADER_OFFSETS: Vmdk4HeaderOffsets = Vmdk4HeaderOffsets {
magic: 0x00,
version: 0x04,
flags: 0x08,
capacity: 0x0C,
grain_size: 0x14,
descriptor_offset: 0x1C,
descriptor_size: 0x24,
num_gtes_per_gt: 0x2C,
rgd_offset: 0x30,
gd_offset: 0x38,
overhead: 0x40,
compress_algorithm: 0x4D,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct GrainMarkerOffsets {
pub lba: u64, pub data_size: u64, }
pub const GRAIN_MARKER_OFFSETS: GrainMarkerOffsets = GrainMarkerOffsets {
lba: 0x00,
data_size: 0x08,
};
pub const COWD_MAGIC: u32 = 0x434F_5744;
pub const COWD_GRAIN_DIRECTORY_SECTOR: u32 = 4;
pub const COWD_GTES_PER_GRAIN_TABLE: u32 = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CowdHeaderOffsets {
pub magic: u64, pub version: u64, pub flags: u64, pub capacity: u64, pub grain_size: u64, pub gd_offset: u64, pub gd_entries: u64, pub next_free: u64, }
pub const COWD_HEADER_OFFSETS: CowdHeaderOffsets = CowdHeaderOffsets {
magic: 0x00,
version: 0x04,
flags: 0x08,
capacity: 0x0C,
grain_size: 0x10,
gd_offset: 0x14,
gd_entries: 0x18,
next_free: 0x1C,
};
pub const SESPARSE_CONST_MAGIC: u64 = 0x0000_0000_CAFE_BABE;
pub const SESPARSE_VOLATILE_MAGIC: u64 = 0x0000_0000_CAFE_CAFE;
pub const SESPARSE_VERSION: u64 = 0x0000_0002_0000_0001;
pub const SESPARSE_GRAIN_SECTORS: u64 = 8;
pub const SESPARSE_GRAIN_TABLE_SECTORS: u64 = 64;
pub const SESPARSE_GTES_PER_GRAIN_TABLE: u64 = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SeSparseHeaderOffsets {
pub magic: u64, pub version: u64, pub capacity: u64, pub grain_size: u64, pub grain_table_size: u64, pub grain_dir_offset: u64, pub grain_tables_offset: u64, pub grains_offset: u64, }
pub const SESPARSE_HEADER_OFFSETS: SeSparseHeaderOffsets = SeSparseHeaderOffsets {
magic: 0x00,
version: 0x08,
capacity: 0x10,
grain_size: 0x18,
grain_table_size: 0x20,
grain_dir_offset: 0x80,
grain_tables_offset: 0x90,
grains_offset: 0xC0,
};
pub const SESPARSE_GD_ALLOC_MASK: u64 = 0xFFFF_FFFF_0000_0000;
pub const SESPARSE_GD_ALLOC_FLAG: u64 = 0x1000_0000_0000_0000;
pub const SESPARSE_GD_INDEX_MASK: u64 = 0x0000_0000_FFFF_FFFF;
pub const SESPARSE_GTE_TYPE_MASK: u64 = 0xF000_0000_0000_0000;
pub const SESPARSE_GTE_UNALLOCATED: u64 = 0x0000_0000_0000_0000;
pub const SESPARSE_GTE_SCSI_UNMAPPED: u64 = 0x1000_0000_0000_0000; pub const SESPARSE_GTE_ZERO: u64 = 0x2000_0000_0000_0000; pub const SESPARSE_GTE_ALLOCATED: u64 = 0x3000_0000_0000_0000;
#[must_use]
pub const fn sesparse_decode_grain_index(entry: u64) -> u64 {
((entry & 0x0FFF_0000_0000_0000) >> 48) | ((entry & 0x0000_FFFF_FFFF_FFFF) << 12)
}
#[must_use]
pub const fn sesparse_encode_allocated_grain(grain_index: u64) -> u64 {
let lo12 = grain_index & 0x0000_0000_0000_0FFF;
let hi = (grain_index >> 12) & 0x0000_FFFF_FFFF_FFFF;
SESPARSE_GTE_ALLOCATED | (lo12 << 48) | hi
}
pub const CREATE_TYPES: &[&str] = &[
"monolithicSparse",
"monolithicFlat",
"twoGbMaxExtentSparse",
"twoGbMaxExtentFlat",
"vmfs",
"vmfsPreallocated",
"vmfsEagerZeroedThick",
"vmfsSparse",
"vmfsThin",
"seSparse",
"streamOptimized",
"vmfsRDM",
"vmfsRaw",
"vmfsRawDeviceMap",
"vmfsPassthroughRawDeviceMap",
"fullDevice",
"partitionedDevice",
"custom",
];
pub const EXTENT_TYPES: &[&str] = &["FLAT", "VMFS", "VMFSRAW", "ZERO", "SPARSE", "VMFSSPARSE", "SESPARSE"];
pub const EXTENT_ACCESS_MODES: &[&str] = &["RW", "RDONLY", "NOACCESS"];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vmdk4_magic_is_kdmv() {
assert_eq!(VMDK4_MAGIC, 0x564D_444B);
assert_eq!(&VMDK4_MAGIC.to_le_bytes(), b"KDMV");
}
#[test]
fn cowd_magic_is_cowd_big_endian() {
assert_eq!(COWD_MAGIC, 0x434F_5744);
assert_eq!(&COWD_MAGIC.to_be_bytes(), b"COWD");
}
#[test]
fn sesparse_magics_and_version() {
assert_eq!(SESPARSE_CONST_MAGIC, 0x0000_0000_CAFE_BABE);
assert_eq!(SESPARSE_VOLATILE_MAGIC, 0x0000_0000_CAFE_CAFE);
assert_eq!(SESPARSE_VERSION, 0x0000_0002_0000_0001);
}
#[test]
fn header_versions() {
assert_eq!(VERSION_BASE, 1);
assert_eq!(VERSION_ZEROED_GRAIN, 2);
assert_eq!(VERSION_STREAM_OPTIMIZED, 3);
}
#[test]
fn sector_size_and_gd_at_end_sentinel() {
assert_eq!(SECTOR_SIZE, 512);
assert_eq!(GD_AT_END, u64::MAX);
}
#[test]
fn vmdk4_header_offsets() {
assert_eq!(VMDK4_HEADER_OFFSETS.magic, 0x00);
assert_eq!(VMDK4_HEADER_OFFSETS.version, 0x04);
assert_eq!(VMDK4_HEADER_OFFSETS.flags, 0x08);
assert_eq!(VMDK4_HEADER_OFFSETS.capacity, 0x0C);
assert_eq!(VMDK4_HEADER_OFFSETS.grain_size, 0x14);
assert_eq!(VMDK4_HEADER_OFFSETS.descriptor_offset, 0x1C);
assert_eq!(VMDK4_HEADER_OFFSETS.descriptor_size, 0x24);
assert_eq!(VMDK4_HEADER_OFFSETS.num_gtes_per_gt, 0x2C);
assert_eq!(VMDK4_HEADER_OFFSETS.rgd_offset, 0x30);
assert_eq!(VMDK4_HEADER_OFFSETS.gd_offset, 0x38);
assert_eq!(VMDK4_HEADER_OFFSETS.overhead, 0x40);
assert_eq!(VMDK4_HEADER_OFFSETS.compress_algorithm, 0x4D);
}
#[test]
fn vmdk4_flags_and_compression() {
assert_eq!(VMDK4_FLAG_VALID_NEWLINE, 0x0000_0001);
assert_eq!(VMDK4_FLAG_USE_RGD, 0x0000_0002);
assert_eq!(VMDK4_FLAG_ZERO_GRAIN, 0x0000_0004);
assert_eq!(VMDK4_FLAG_COMPRESSED, 0x0001_0000);
assert_eq!(VMDK4_FLAG_MARKERS, 0x0002_0000);
assert_eq!(COMPRESSION_NONE, 0);
assert_eq!(COMPRESSION_DEFLATE, 1);
}
#[test]
fn grain_marker_layout() {
assert_eq!(GRAIN_MARKER_HEADER_SIZE, 12);
assert_eq!(GRAIN_MARKER_OFFSETS.lba, 0x00);
assert_eq!(GRAIN_MARKER_OFFSETS.data_size, 0x08);
}
#[test]
fn grain_table_entry_sentinels() {
assert_eq!(GTE_SPARSE, 0);
assert_eq!(GTE_ZEROED, 1);
}
#[test]
fn cowd_layout() {
assert_eq!(COWD_HEADER_OFFSETS.magic, 0x00);
assert_eq!(COWD_HEADER_OFFSETS.version, 0x04);
assert_eq!(COWD_HEADER_OFFSETS.capacity, 0x0C);
assert_eq!(COWD_HEADER_OFFSETS.grain_size, 0x10);
assert_eq!(COWD_HEADER_OFFSETS.gd_offset, 0x14);
assert_eq!(COWD_HEADER_OFFSETS.gd_entries, 0x18);
assert_eq!(COWD_GRAIN_DIRECTORY_SECTOR, 4);
assert_eq!(COWD_GTES_PER_GRAIN_TABLE, 4096);
}
#[test]
fn sesparse_const_header_layout() {
assert_eq!(SESPARSE_HEADER_OFFSETS.magic, 0x00);
assert_eq!(SESPARSE_HEADER_OFFSETS.version, 0x08);
assert_eq!(SESPARSE_HEADER_OFFSETS.capacity, 0x10);
assert_eq!(SESPARSE_HEADER_OFFSETS.grain_size, 0x18);
assert_eq!(SESPARSE_HEADER_OFFSETS.grain_table_size, 0x20);
assert_eq!(SESPARSE_HEADER_OFFSETS.grain_dir_offset, 0x80);
assert_eq!(SESPARSE_HEADER_OFFSETS.grain_tables_offset, 0x90);
assert_eq!(SESPARSE_HEADER_OFFSETS.grains_offset, 0xC0);
assert_eq!(SESPARSE_GRAIN_SECTORS, 8);
assert_eq!(SESPARSE_GRAIN_TABLE_SECTORS, 64);
assert_eq!(SESPARSE_GTES_PER_GRAIN_TABLE, 4096);
}
#[test]
fn sesparse_grain_directory_encoding() {
assert_eq!(SESPARSE_GD_ALLOC_MASK, 0xFFFF_FFFF_0000_0000);
assert_eq!(SESPARSE_GD_ALLOC_FLAG, 0x1000_0000_0000_0000);
assert_eq!(SESPARSE_GD_INDEX_MASK, 0x0000_0000_FFFF_FFFF);
}
#[test]
fn sesparse_grain_table_entry_types() {
assert_eq!(SESPARSE_GTE_TYPE_MASK, 0xF000_0000_0000_0000);
assert_eq!(SESPARSE_GTE_UNALLOCATED, 0x0000_0000_0000_0000);
assert_eq!(SESPARSE_GTE_SCSI_UNMAPPED, 0x1000_0000_0000_0000);
assert_eq!(SESPARSE_GTE_ZERO, 0x2000_0000_0000_0000);
assert_eq!(SESPARSE_GTE_ALLOCATED, 0x3000_0000_0000_0000);
}
#[test]
fn sesparse_grain_index_bit_rotation_round_trips() {
for idx in [0u64, 1, 42, 0xFFF, 0x1000, 0x000F_FFFF, 0x0FFF_FFFF_FFFF] {
let entry = sesparse_encode_allocated_grain(idx);
assert_eq!(entry & SESPARSE_GTE_TYPE_MASK, SESPARSE_GTE_ALLOCATED);
assert_eq!(sesparse_decode_grain_index(entry), idx, "idx {idx:#x}");
}
}
#[test]
fn create_type_enumeration_is_complete() {
assert_eq!(CREATE_TYPES.len(), 18);
for t in [
"monolithicSparse",
"monolithicFlat",
"twoGbMaxExtentSparse",
"twoGbMaxExtentFlat",
"vmfs",
"vmfsPreallocated",
"vmfsEagerZeroedThick",
"vmfsSparse",
"vmfsThin",
"seSparse",
"streamOptimized",
"vmfsRDM",
"vmfsRaw",
"vmfsRawDeviceMap",
"vmfsPassthroughRawDeviceMap",
"fullDevice",
"partitionedDevice",
"custom",
] {
assert!(CREATE_TYPES.contains(&t), "missing createType {t}");
}
}
#[test]
fn extent_type_and_access_enumeration() {
for t in ["FLAT", "VMFS", "VMFSRAW", "ZERO", "SPARSE", "VMFSSPARSE", "SESPARSE"] {
assert!(EXTENT_TYPES.contains(&t), "missing extent type {t}");
}
assert_eq!(EXTENT_ACCESS_MODES, &["RW", "RDONLY", "NOACCESS"]);
}
}