use crate::error::{Result, VhdError};
use crate::read::{be_u32, be_u64};
pub const FOOTER_SIZE: usize = 512;
pub const COOKIE: &[u8; 8] = b"conectix";
pub const CURRENT_VERSION: u32 = 0x0001_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiskType {
Fixed = 2,
Dynamic = 3,
}
#[derive(Debug, Clone)]
pub struct VhdFooter {
pub disk_type: DiskType,
pub current_size: u64, pub original_size: u64, pub data_offset: u64, }
impl VhdFooter {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < FOOTER_SIZE {
return Err(VhdError::FileTooSmall);
}
let footer = &data[data.len() - FOOTER_SIZE..];
if &footer[0..8] != COOKIE {
return Err(VhdError::BadCookie);
}
let version = be_u32(footer, 12);
if version != CURRENT_VERSION {
return Err(VhdError::UnsupportedVersion(version));
}
let data_offset = be_u64(footer, 16);
let current_size = be_u64(footer, 48);
let original_size = be_u64(footer, 40);
let disk_type_raw = be_u32(footer, 60);
let disk_type = match disk_type_raw {
2 => DiskType::Fixed,
3 => DiskType::Dynamic,
4 => return Err(VhdError::DifferencingNotSupported),
other => return Err(VhdError::UnknownDiskType(other)),
};
let stored_checksum = be_u32(footer, 64);
let computed = checksum(footer);
if stored_checksum != computed {
return Err(VhdError::ChecksumMismatch {
expected: stored_checksum,
actual: computed,
});
}
Ok(VhdFooter {
disk_type,
current_size,
original_size,
data_offset,
})
}
}
fn checksum(footer: &[u8]) -> u32 {
let mut sum: u32 = 0;
for (i, &byte) in footer.iter().enumerate() {
if (64..68).contains(&i) {
continue;
}
sum = sum.wrapping_add(u32::from(byte));
}
!sum
}
#[cfg(any(test, feature = "test-helpers"))]
pub fn test_fixed_footer(virtual_size: u64) -> Vec<u8> {
let mut footer = vec![0u8; FOOTER_SIZE];
footer[0..8].copy_from_slice(COOKIE);
footer[8..12].copy_from_slice(&0x0000_0002u32.to_be_bytes());
footer[12..16].copy_from_slice(&CURRENT_VERSION.to_be_bytes());
footer[16..24].copy_from_slice(&0xFFFF_FFFF_FFFF_FFFFu64.to_be_bytes());
footer[40..48].copy_from_slice(&virtual_size.to_be_bytes());
footer[48..56].copy_from_slice(&virtual_size.to_be_bytes());
footer[60..64].copy_from_slice(&2u32.to_be_bytes());
let cs = checksum(&footer);
footer[64..68].copy_from_slice(&cs.to_be_bytes());
footer
}
#[cfg(test)]
mod tests {
use super::*;
fn base() -> Vec<u8> {
test_fixed_footer(1024)
}
#[test]
fn too_small_is_file_too_small() {
assert!(matches!(
VhdFooter::parse(&[0u8; 100]),
Err(VhdError::FileTooSmall)
));
}
#[test]
fn bad_cookie_rejected() {
let mut f = base();
f[0] = b'X';
assert!(matches!(VhdFooter::parse(&f), Err(VhdError::BadCookie)));
}
#[test]
fn unsupported_version_rejected() {
let mut f = base();
f[12..16].copy_from_slice(&0x0002_0000u32.to_be_bytes());
assert!(matches!(
VhdFooter::parse(&f),
Err(VhdError::UnsupportedVersion(0x0002_0000))
));
}
#[test]
fn differencing_rejected() {
let mut f = base();
f[60..64].copy_from_slice(&4u32.to_be_bytes());
assert!(matches!(
VhdFooter::parse(&f),
Err(VhdError::DifferencingNotSupported)
));
}
#[test]
fn unknown_disk_type_rejected() {
let mut f = base();
f[60..64].copy_from_slice(&99u32.to_be_bytes());
assert!(matches!(
VhdFooter::parse(&f),
Err(VhdError::UnknownDiskType(99))
));
}
#[test]
fn checksum_mismatch_rejected() {
let mut f = base();
f[100] ^= 0xFF; assert!(matches!(
VhdFooter::parse(&f),
Err(VhdError::ChecksumMismatch { .. })
));
}
}