1use std::io::{Read, Seek, SeekFrom};
16
17use crate::error::VmdkError;
18
19pub const SE_CONST_MAGIC: u64 = 0x0000_0000_CAFE_BABE;
21
22pub const SE_VERSION: u64 = 0x0000_0002_0000_0001;
24
25pub const SE_GRAIN_SECTORS: u64 = 8;
27
28pub const SE_GT_SECTORS: u64 = 64;
30
31pub const SE_GTES_PER_GT: u64 = 4096;
33
34const SECTOR_SIZE: u64 = 512;
35
36pub const SE_GD_ALLOC_MASK: u64 = 0xffff_ffff_0000_0000;
39pub const SE_GD_ALLOC_FLAG: u64 = 0x1000_0000_0000_0000;
40pub const SE_GD_INDEX_MASK: u64 = 0x0000_0000_ffff_ffff;
42pub const SE_GTE_TYPE_MASK: u64 = 0xf000_0000_0000_0000;
44pub const SE_GTE_TYPE_ALLOCATED: u64 = 0x3000_0000_0000_0000;
45pub const SE_GTE_TYPE_UNMAPPED: u64 = 0x1000_0000_0000_0000; pub const SE_GTE_TYPE_ZERO: u64 = 0x2000_0000_0000_0000; pub fn se_gte_grain_index(gte: u64) -> u64 {
50 ((gte & 0x0fff_0000_0000_0000) >> 48) | ((gte & 0x0000_ffff_ffff_ffff) << 12)
51}
52
53pub struct SeConstHeader {
55 pub capacity: u64, pub grain_size: u64, pub gd_offset: u64, pub gt_offset: u64, pub grains_offset: u64, }
61
62impl SeConstHeader {
63 pub fn parse(data: &[u8]) -> Result<Self, VmdkError> {
65 if data.len() < 208 {
66 return Err(VmdkError::FileTooSmall);
67 }
68 let magic = u64::from_le_bytes(data[0..8].try_into().expect("8 bytes"));
69 if magic != SE_CONST_MAGIC {
70 return Err(VmdkError::BadMagic);
71 }
72 let version = u64::from_le_bytes(data[8..16].try_into().expect("8 bytes"));
73 if version != SE_VERSION {
74 return Err(VmdkError::UnsupportedVersion(version as u32));
75 }
76 let capacity = u64::from_le_bytes(data[16..24].try_into().expect("8 bytes"));
77 let grain_size = u64::from_le_bytes(data[24..32].try_into().expect("8 bytes"));
78 if grain_size != SE_GRAIN_SECTORS {
79 return Err(VmdkError::FieldOutOfRange {
80 field: "grain_size",
81 value: grain_size,
82 reason: "must equal the seSparse fixed grain size (8 sectors)",
83 });
84 }
85 let grain_table_size = u64::from_le_bytes(data[32..40].try_into().expect("8 bytes"));
86 if grain_table_size != SE_GT_SECTORS {
87 return Err(VmdkError::FieldOutOfRange {
88 field: "grain_table_size",
89 value: grain_table_size,
90 reason: "must equal the seSparse fixed grain-table size",
91 });
92 }
93 let gd_offset = u64::from_le_bytes(data[128..136].try_into().expect("8 bytes"));
95 let gt_offset = u64::from_le_bytes(data[144..152].try_into().expect("8 bytes"));
96 let grains_offset = u64::from_le_bytes(data[192..200].try_into().expect("8 bytes"));
97
98 Ok(SeConstHeader {
99 capacity,
100 grain_size,
101 gd_offset,
102 gt_offset,
103 grains_offset,
104 })
105 }
106}
107
108pub(crate) fn open_sesparse<R: Read + Seek>(
113 mut reader: R,
114) -> Result<(Vec<u64>, u64, u64), VmdkError> {
115 let mut hdr_bytes = [0u8; 512];
116 reader.read_exact(&mut hdr_bytes)?;
117 let hdr = SeConstHeader::parse(&hdr_bytes)?;
118
119 let grain_size_bytes = hdr.grain_size * SECTOR_SIZE;
120
121 let num_grains = hdr.capacity.div_ceil(hdr.grain_size);
123 let num_gts = num_grains.div_ceil(SE_GTES_PER_GT);
124 let num_gts = num_gts.max(1);
126
127 let gd_bytes = num_gts * 8; const MAX_SESP_GD: u64 = 16 * 1024 * 1024;
129 if gd_bytes > MAX_SESP_GD {
130 return Err(VmdkError::FieldOutOfRange {
131 field: "grain_directory",
132 value: gd_bytes,
133 reason: "exceeds the 16 MiB cap",
134 });
135 }
136
137 let gd_offset_bytes = hdr.gd_offset * SECTOR_SIZE;
138 reader.seek(SeekFrom::Start(gd_offset_bytes))?;
139 let mut buf = vec![0u8; gd_bytes as usize];
140 reader.read_exact(&mut buf)?;
141
142 let grain_dir = buf
143 .chunks_exact(8)
144 .map(|c| u64::from_le_bytes(c.try_into().expect("8 bytes")))
145 .collect();
146
147 Ok((grain_dir, grain_size_bytes, hdr.grains_offset))
148}
149
150#[cfg(test)]
156mod tests {
157 use super::*;
158
159 fn make_sesparse_header(capacity: u64) -> Vec<u8> {
160 let mut h = vec![0u8; 512];
161 h[0..8].copy_from_slice(&SE_CONST_MAGIC.to_le_bytes());
162 h[8..16].copy_from_slice(&SE_VERSION.to_le_bytes());
163 h[16..24].copy_from_slice(&capacity.to_le_bytes());
164 h[24..32].copy_from_slice(&SE_GRAIN_SECTORS.to_le_bytes()); h[32..40].copy_from_slice(&SE_GT_SECTORS.to_le_bytes()); h[80..88].copy_from_slice(&2u64.to_le_bytes());
168 h[128..136].copy_from_slice(&10u64.to_le_bytes()); h[136..144].copy_from_slice(&1u64.to_le_bytes());
172 h[144..152].copy_from_slice(&11u64.to_le_bytes());
174 h[192..200].copy_from_slice(&75u64.to_le_bytes());
176 h
177 }
178
179 #[test]
180 fn sesparse_header_parse_ok() {
181 let h = make_sesparse_header(4096);
182 let hdr = SeConstHeader::parse(&h).expect("parse");
183 assert_eq!(hdr.capacity, 4096);
184 assert_eq!(hdr.grain_size, 8);
185 assert_eq!(hdr.gd_offset, 10);
186 assert_eq!(hdr.gt_offset, 11);
187 }
188
189 #[test]
190 fn sesparse_wrong_magic_rejected() {
191 let h = vec![0u8; 512];
192 assert!(matches!(SeConstHeader::parse(&h), Err(VmdkError::BadMagic)));
193 }
194
195 #[test]
196 fn sesparse_wrong_version_rejected() {
197 let mut h = make_sesparse_header(8);
198 h[8..16].copy_from_slice(&0u64.to_le_bytes()); assert!(matches!(
200 SeConstHeader::parse(&h),
201 Err(VmdkError::UnsupportedVersion(_))
202 ));
203 }
204
205 #[test]
206 fn sesparse_wrong_grain_size_rejected() {
207 let mut h = make_sesparse_header(8);
208 h[24..32].copy_from_slice(&16u64.to_le_bytes()); assert!(matches!(
210 SeConstHeader::parse(&h),
211 Err(VmdkError::FieldOutOfRange {
212 field: "grain_size",
213 ..
214 })
215 ));
216 }
217
218 #[test]
219 fn sesparse_short_buffer_rejected() {
220 assert!(matches!(
221 SeConstHeader::parse(&[0u8; 100]),
222 Err(VmdkError::FileTooSmall)
223 ));
224 }
225
226 #[test]
227 fn sesparse_wrong_grain_table_size_rejected() {
228 let mut h = make_sesparse_header(8);
229 h[32..40].copy_from_slice(&128u64.to_le_bytes()); assert!(matches!(
231 SeConstHeader::parse(&h),
232 Err(VmdkError::FieldOutOfRange {
233 field: "grain_table_size",
234 ..
235 })
236 ));
237 }
238
239 #[test]
240 fn sesparse_grain_directory_too_large_rejected() {
241 let h = make_sesparse_header(100_000_000_000);
243 assert!(matches!(
244 open_sesparse(std::io::Cursor::new(h)),
245 Err(VmdkError::FieldOutOfRange {
246 field: "grain_directory",
247 ..
248 })
249 ));
250 }
251}