1use std::fs::File;
46use std::io::{Read, Seek, SeekFrom};
47use std::path::Path;
48
49pub const ESE_FILE_SIGNATURE: u32 = 0x89AB_CDEF;
51
52pub mod file_type {
54 pub const DATABASE: u32 = 0;
55 pub const STREAMING_FILE: u32 = 1;
56}
57
58pub mod page_flag {
60 pub const ROOT: u32 = 0x0000_0001;
62 pub const LEAF: u32 = 0x0000_0002;
64 pub const PARENT: u32 = 0x0000_0004;
66 pub const EMPTY: u32 = 0x0000_0008;
68 pub const SPACE_TREE: u32 = 0x0000_0020;
70 pub const INDEX: u32 = 0x0000_0040;
72 pub const LONG_VALUE: u32 = 0x0000_0080;
74 pub const NEW_RECORD_FORMAT: u32 = 0x0000_2000;
76 pub const SCRUBBED: u32 = 0x0000_4000;
78 pub const NEW_CHECKSUM_FORMAT: u32 = 0x0000_8000;
80}
81
82pub struct EseFile {
85 file: File,
86 format_version: u32,
87 format_revision: u32,
88 page_size: u32,
89 file_type: u32,
90 previous_incremental_backup_log_gen: u32,
91 previous_full_backup_log_gen: u32,
92 current_full_backup_log_gen: u32,
93}
94
95impl EseFile {
96 pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
99 let mut file = File::open(path).map_err(Error::Io)?;
100 let mut hdr = [0u8; 668];
101 file.read_exact(&mut hdr).map_err(Error::Io)?;
102 let parsed = Self::parse_header_fields(&hdr)?;
103 Ok(EseFile {
104 file,
105 format_version: parsed.format_version,
106 format_revision: parsed.format_revision,
107 page_size: parsed.page_size,
108 file_type: parsed.file_type,
109 previous_incremental_backup_log_gen: parsed.previous_incremental_backup_log_gen,
110 previous_full_backup_log_gen: parsed.previous_full_backup_log_gen,
111 current_full_backup_log_gen: parsed.current_full_backup_log_gen,
112 })
113 }
114
115 pub fn parse_header(hdr: &[u8]) -> Result<HeaderFields, Error> {
118 Self::parse_header_fields(hdr)
119 }
120
121 fn parse_header_fields(hdr: &[u8]) -> Result<HeaderFields, Error> {
122 if hdr.len() < 668 {
123 return Err(Error::Format(format!(
124 "header too short: {} bytes (want 668)",
125 hdr.len()
126 )));
127 }
128 let magic = u32::from_le_bytes([hdr[4], hdr[5], hdr[6], hdr[7]]);
129 if magic != ESE_FILE_SIGNATURE {
130 return Err(Error::Format(format!(
131 "bad ESE magic: got {magic:#010x}, want {ESE_FILE_SIGNATURE:#010x}"
132 )));
133 }
134 let format_version = u32::from_le_bytes([hdr[8], hdr[9], hdr[10], hdr[11]]);
135 let file_type = u32::from_le_bytes([hdr[12], hdr[13], hdr[14], hdr[15]]);
136 let page_size = u32::from_le_bytes([hdr[236], hdr[237], hdr[238], hdr[239]]);
137 if ![2048, 4096, 8192, 16384, 32768].contains(&page_size) {
138 return Err(Error::Format(format!("odd page size: {page_size}")));
139 }
140 let format_revision = u32::from_le_bytes([hdr[240], hdr[241], hdr[242], hdr[243]]);
141 let previous_full_backup_log_gen =
150 u32::from_le_bytes([hdr[256], hdr[257], hdr[258], hdr[259]]);
151 let previous_incremental_backup_log_gen =
152 u32::from_le_bytes([hdr[268], hdr[269], hdr[270], hdr[271]]);
153 let current_full_backup_log_gen =
154 u32::from_le_bytes([hdr[280], hdr[281], hdr[282], hdr[283]]);
155 Ok(HeaderFields {
156 format_version,
157 format_revision,
158 page_size,
159 file_type,
160 previous_full_backup_log_gen,
161 previous_incremental_backup_log_gen,
162 current_full_backup_log_gen,
163 })
164 }
165
166 pub fn format_version(&self) -> u32 {
167 self.format_version
168 }
169 pub fn format_revision(&self) -> u32 {
170 self.format_revision
171 }
172 pub fn page_size(&self) -> u32 {
173 self.page_size
174 }
175 pub fn file_type(&self) -> u32 {
176 self.file_type
177 }
178 pub fn previous_full_backup_log_gen(&self) -> u32 {
179 self.previous_full_backup_log_gen
180 }
181 pub fn previous_incremental_backup_log_gen(&self) -> u32 {
182 self.previous_incremental_backup_log_gen
183 }
184 pub fn current_full_backup_log_gen(&self) -> u32 {
185 self.current_full_backup_log_gen
186 }
187
188 pub fn page(&mut self, page_number: u32) -> Result<RawPage, Error> {
191 let offset = page_number as u64 * self.page_size as u64;
192 self.file.seek(SeekFrom::Start(offset)).map_err(Error::Io)?;
193 let mut buf = vec![0u8; self.page_size as usize];
194 self.file.read_exact(&mut buf).map_err(Error::Io)?;
195 RawPage::parse(page_number, &buf, self.page_size)
196 }
197}
198
199#[derive(Debug, Clone, Copy)]
202#[non_exhaustive]
203pub struct HeaderFields {
204 pub format_version: u32,
205 pub format_revision: u32,
206 pub page_size: u32,
207 pub file_type: u32,
208 pub previous_full_backup_log_gen: u32,
209 pub previous_incremental_backup_log_gen: u32,
210 pub current_full_backup_log_gen: u32,
211}
212
213#[derive(Clone)]
216#[non_exhaustive]
217pub struct RawPage {
218 pub page_number: u32,
220 pub page_flags: u32,
222 pub previous_page: u32,
224 pub next_page: u32,
226 pub father_data_page: u32,
228 pub object_id: u32,
230 pub available_data_size: u16,
232 pub available_page_tag: u16,
234 pub bytes: Vec<u8>,
236}
237
238impl std::fmt::Debug for RawPage {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 f.debug_struct("RawPage")
241 .field("page_number", &self.page_number)
242 .field("page_flags", &format_args!("{:#010x}", self.page_flags))
243 .field("previous_page", &self.previous_page)
244 .field("next_page", &self.next_page)
245 .field("father_data_page", &self.father_data_page)
246 .field("object_id", &self.object_id)
247 .field("available_data_size", &self.available_data_size)
248 .field("available_page_tag", &self.available_page_tag)
249 .field("bytes_len", &self.bytes.len())
250 .finish()
251 }
252}
253
254impl RawPage {
255 pub fn parse(page_number: u32, bytes: &[u8], page_size: u32) -> Result<Self, Error> {
276 if bytes.len() as u32 != page_size {
277 return Err(Error::Format(format!(
278 "page {page_number}: got {} bytes, expected {page_size}",
279 bytes.len()
280 )));
281 }
282 if page_size < 48 {
283 return Err(Error::Format(format!("page size too small: {page_size}")));
284 }
285 let previous_page = u32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
286 let next_page = u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]);
287 let father_data_page = u32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]);
288 let available_data_size = u16::from_le_bytes([bytes[32], bytes[33]]);
289 let available_page_tag = u16::from_le_bytes([bytes[38], bytes[39]]);
290 let page_flags = u32::from_le_bytes([bytes[40], bytes[41], bytes[42], bytes[43]]);
291 let object_id = u32::from_le_bytes([bytes[44], bytes[45], bytes[46], bytes[47]]);
292 Ok(RawPage {
293 page_number,
294 page_flags,
295 previous_page,
296 next_page,
297 father_data_page,
298 object_id,
299 available_data_size,
300 available_page_tag,
301 bytes: bytes.to_vec(),
302 })
303 }
304
305 pub fn has_flag(&self, flag: u32) -> bool {
307 (self.page_flags & flag) == flag
308 }
309
310 pub fn is_leaf(&self) -> bool {
312 self.has_flag(page_flag::LEAF)
313 }
314
315 pub fn is_root(&self) -> bool {
317 self.has_flag(page_flag::ROOT)
318 }
319}
320
321#[derive(Debug)]
323#[non_exhaustive]
324pub enum Error {
325 Io(std::io::Error),
326 Format(String),
327}
328
329impl std::fmt::Display for Error {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 match self {
332 Error::Io(e) => write!(f, "io: {e}"),
333 Error::Format(s) => write!(f, "format: {s}"),
334 }
335 }
336}
337
338impl std::error::Error for Error {}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 fn synthetic_header(page_size: u32) -> Vec<u8> {
345 let mut hdr = vec![0u8; 668];
346 hdr[4..8].copy_from_slice(&ESE_FILE_SIGNATURE.to_le_bytes());
347 hdr[8..12].copy_from_slice(&620u32.to_le_bytes()); hdr[12..16].copy_from_slice(&file_type::DATABASE.to_le_bytes());
349 hdr[236..240].copy_from_slice(&page_size.to_le_bytes());
350 hdr[240..244].copy_from_slice(&17u32.to_le_bytes()); hdr
352 }
353
354 #[test]
355 fn header_accepts_valid_synthetic() {
356 let hdr = synthetic_header(8192);
357 let h = EseFile::parse_header(&hdr).unwrap();
358 assert_eq!(h.page_size, 8192);
359 assert_eq!(h.format_version, 620);
360 assert_eq!(h.format_revision, 17);
361 assert_eq!(h.file_type, file_type::DATABASE);
362 }
363
364 #[test]
365 fn header_rejects_short() {
366 assert!(matches!(
367 EseFile::parse_header(&[0u8; 300]).unwrap_err(),
368 Error::Format(_)
369 ));
370 }
371
372 #[test]
373 fn header_rejects_bad_magic() {
374 let mut hdr = synthetic_header(8192);
375 hdr[4..8].copy_from_slice(&0xdeadbeefu32.to_le_bytes());
376 assert!(matches!(
377 EseFile::parse_header(&hdr).unwrap_err(),
378 Error::Format(_)
379 ));
380 }
381
382 #[test]
383 fn header_rejects_odd_page_size() {
384 let hdr = synthetic_header(1234);
385 assert!(matches!(
386 EseFile::parse_header(&hdr).unwrap_err(),
387 Error::Format(_)
388 ));
389 }
390
391 #[test]
392 fn page_flags_have_expected_bits() {
393 assert_eq!(page_flag::ROOT, 0x1);
396 assert_eq!(page_flag::LEAF, 0x2);
397 assert_eq!(page_flag::PARENT, 0x4);
398 assert_eq!(page_flag::LONG_VALUE, 0x80);
399 }
400
401 #[test]
402 fn raw_page_parse_reads_header_fields() {
403 let mut bytes = vec![0u8; 4096];
404 bytes[20..24].copy_from_slice(&100u32.to_le_bytes()); bytes[24..28].copy_from_slice(&102u32.to_le_bytes()); bytes[28..32].copy_from_slice(&2u32.to_le_bytes()); bytes[32..34].copy_from_slice(&512u16.to_le_bytes()); bytes[38..40].copy_from_slice(&5u16.to_le_bytes()); bytes[40..44]
410 .copy_from_slice(&(page_flag::LEAF | page_flag::NEW_RECORD_FORMAT).to_le_bytes());
411 bytes[44..48].copy_from_slice(&42u32.to_le_bytes()); let page = RawPage::parse(101, &bytes, 4096).unwrap();
413 assert_eq!(page.page_number, 101);
414 assert_eq!(page.previous_page, 100);
415 assert_eq!(page.next_page, 102);
416 assert_eq!(page.available_data_size, 512);
417 assert_eq!(page.available_page_tag, 5);
418 assert_eq!(page.object_id, 42);
419 assert!(page.is_leaf());
420 assert!(!page.is_root());
421 assert!(page.has_flag(page_flag::NEW_RECORD_FORMAT));
422 }
423
424 #[test]
425 fn raw_page_rejects_size_mismatch() {
426 let bytes = vec![0u8; 100];
427 assert!(matches!(
428 RawPage::parse(1, &bytes, 4096).unwrap_err(),
429 Error::Format(_)
430 ));
431 }
432}