1use crate::error::{Result, VhdError};
8use crate::read::{be_u32, be_u64};
9
10pub const FOOTER_SIZE: usize = 512;
11pub const COOKIE: &[u8; 8] = b"conectix";
12pub const CURRENT_VERSION: u32 = 0x0001_0000;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DiskType {
17 Fixed = 2,
18 Dynamic = 3,
19 }
21
22#[derive(Debug, Clone)]
24pub struct VhdFooter {
25 pub disk_type: DiskType,
26 pub current_size: u64, pub original_size: u64, pub data_offset: u64, }
30
31impl VhdFooter {
32 pub fn parse(data: &[u8]) -> Result<Self> {
34 if data.len() < FOOTER_SIZE {
35 return Err(VhdError::FileTooSmall);
36 }
37 let footer = &data[data.len() - FOOTER_SIZE..];
38
39 if &footer[0..8] != COOKIE {
41 return Err(VhdError::BadCookie);
42 }
43
44 let version = be_u32(footer, 12);
46 if version != CURRENT_VERSION {
47 return Err(VhdError::UnsupportedVersion(version));
48 }
49
50 let data_offset = be_u64(footer, 16);
52
53 let current_size = be_u64(footer, 48);
56 let original_size = be_u64(footer, 40);
58
59 let disk_type_raw = be_u32(footer, 60);
61 let disk_type = match disk_type_raw {
62 2 => DiskType::Fixed,
63 3 => DiskType::Dynamic,
64 4 => return Err(VhdError::DifferencingNotSupported),
65 other => return Err(VhdError::UnknownDiskType(other)),
66 };
67
68 let stored_checksum = be_u32(footer, 64);
70 let computed = checksum(footer);
71 if stored_checksum != computed {
72 return Err(VhdError::ChecksumMismatch {
73 expected: stored_checksum,
74 actual: computed,
75 });
76 }
77
78 Ok(VhdFooter {
79 disk_type,
80 current_size,
81 original_size,
82 data_offset,
83 })
84 }
85}
86
87fn checksum(footer: &[u8]) -> u32 {
89 let mut sum: u32 = 0;
90 for (i, &byte) in footer.iter().enumerate() {
91 if (64..68).contains(&i) {
93 continue;
94 }
95 sum = sum.wrapping_add(u32::from(byte));
96 }
97 !sum
98}
99
100#[cfg(any(test, feature = "test-helpers"))]
107pub fn test_fixed_footer(virtual_size: u64) -> Vec<u8> {
108 let mut footer = vec![0u8; FOOTER_SIZE];
109
110 footer[0..8].copy_from_slice(COOKIE);
112 footer[8..12].copy_from_slice(&0x0000_0002u32.to_be_bytes());
114 footer[12..16].copy_from_slice(&CURRENT_VERSION.to_be_bytes());
116 footer[16..24].copy_from_slice(&0xFFFF_FFFF_FFFF_FFFFu64.to_be_bytes());
118 footer[40..48].copy_from_slice(&virtual_size.to_be_bytes());
120 footer[48..56].copy_from_slice(&virtual_size.to_be_bytes());
122 footer[60..64].copy_from_slice(&2u32.to_be_bytes());
124 let cs = checksum(&footer);
126 footer[64..68].copy_from_slice(&cs.to_be_bytes());
127
128 footer
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 fn base() -> Vec<u8> {
136 test_fixed_footer(1024)
137 }
138
139 #[test]
140 fn too_small_is_file_too_small() {
141 assert!(matches!(
142 VhdFooter::parse(&[0u8; 100]),
143 Err(VhdError::FileTooSmall)
144 ));
145 }
146
147 #[test]
148 fn bad_cookie_rejected() {
149 let mut f = base();
150 f[0] = b'X';
151 assert!(matches!(VhdFooter::parse(&f), Err(VhdError::BadCookie)));
152 }
153
154 #[test]
155 fn unsupported_version_rejected() {
156 let mut f = base();
157 f[12..16].copy_from_slice(&0x0002_0000u32.to_be_bytes());
158 assert!(matches!(
159 VhdFooter::parse(&f),
160 Err(VhdError::UnsupportedVersion(0x0002_0000))
161 ));
162 }
163
164 #[test]
165 fn differencing_rejected() {
166 let mut f = base();
167 f[60..64].copy_from_slice(&4u32.to_be_bytes());
168 assert!(matches!(
169 VhdFooter::parse(&f),
170 Err(VhdError::DifferencingNotSupported)
171 ));
172 }
173
174 #[test]
175 fn unknown_disk_type_rejected() {
176 let mut f = base();
177 f[60..64].copy_from_slice(&99u32.to_be_bytes());
178 assert!(matches!(
179 VhdFooter::parse(&f),
180 Err(VhdError::UnknownDiskType(99))
181 ));
182 }
183
184 #[test]
185 fn checksum_mismatch_rejected() {
186 let mut f = base();
187 f[100] ^= 0xFF; assert!(matches!(
189 VhdFooter::parse(&f),
190 Err(VhdError::ChecksumMismatch { .. })
191 ));
192 }
193}