1use crate::EseError;
4
5pub const PAGE_SIZE: usize = 4096;
7
8pub const PAGE_FLAG_ROOT: u32 = 0x0001;
10pub const PAGE_FLAG_LEAF: u32 = 0x0002;
12pub const PAGE_FLAG_PARENT: u32 = 0x0004;
14pub const PAGE_FLAG_EMPTY: u32 = 0x0008;
16pub const PAGE_FLAG_SPACE_TREE: u32 = 0x0020;
18pub const PAGE_FLAG_LONG_VALUE: u32 = 0x0400;
20
21#[derive(Debug, Clone)]
23pub struct EsePageHeader {
24 pub prev_page: Option<u32>,
26 pub next_page: Option<u32>,
28 pub fdp_object_id: u32,
30 pub available_data_size: u16,
32 pub available_uncommitted: u16,
34 pub available_data_offset: u16,
36 pub available_page_tag_count: u16,
38 pub page_flags: u32,
40}
41
42#[derive(Debug, Clone)]
44pub struct EsePage {
45 pub page_number: u32,
47 pub data: Vec<u8>,
49}
50
51impl EsePage {
52 pub const HEADER_SIZE: usize = 40;
54
55 pub fn parse_header(&self) -> Result<EsePageHeader, EseError> {
74 let d = &self.data;
75 if d.len() < Self::HEADER_SIZE {
76 return Err(EseError::Corrupt {
77 page: self.page_number,
78 detail: format!("page header too short: {} < {}", d.len(), Self::HEADER_SIZE),
79 });
80 }
81
82 let prev_raw = u32::from_le_bytes([d[0x10], d[0x11], d[0x12], d[0x13]]);
83 let next_raw = u32::from_le_bytes([d[0x14], d[0x15], d[0x16], d[0x17]]);
84 let fdp_object_id = u32::from_le_bytes([d[0x18], d[0x19], d[0x1A], d[0x1B]]);
85 let available_data_size = u16::from_le_bytes([d[0x1C], d[0x1D]]);
86 let available_uncommitted = u16::from_le_bytes([d[0x1E], d[0x1F]]);
87 let available_data_offset = u16::from_le_bytes([d[0x20], d[0x21]]);
88 let available_page_tag_count = u16::from_le_bytes([d[0x22], d[0x23]]);
89 let page_flags = u32::from_le_bytes([d[0x24], d[0x25], d[0x26], d[0x27]]);
90
91 Ok(EsePageHeader {
92 prev_page: if prev_raw == 0xFFFF_FFFF {
93 None
94 } else {
95 Some(prev_raw)
96 },
97 next_page: if next_raw == 0xFFFF_FFFF {
98 None
99 } else {
100 Some(next_raw)
101 },
102 fdp_object_id,
103 available_data_size,
104 available_uncommitted,
105 available_data_offset,
106 available_page_tag_count,
107 page_flags,
108 })
109 }
110
111 pub fn tags(&self) -> Result<Vec<(u16, u16)>, EseError> {
124 let hdr = self.parse_header()?;
125 let count = hdr.available_page_tag_count as usize;
126 let page_size = self.data.len();
127 if page_size < count * 4 {
128 return Err(EseError::TagArrayOverflow {
129 page: self.page_number,
130 });
131 }
132 let mut tags = Vec::with_capacity(count);
133 for i in 0..count {
134 let tag_offset = page_size - (i + 1) * 4;
135 let raw = u32::from_le_bytes([
136 self.data[tag_offset],
137 self.data[tag_offset + 1],
138 self.data[tag_offset + 2],
139 self.data[tag_offset + 3],
140 ]);
141 let value_size = (raw & 0x1FFF) as u16; let value_offset = ((raw >> 16) & 0x1FFF) as u16; let end = Self::HEADER_SIZE + usize::from(value_offset) + usize::from(value_size);
149 if end > self.data.len() {
150 return Err(EseError::RecordTooShort {
151 page: self.page_number,
152 tag: i,
153 got: self.data.len(),
154 need: end,
155 });
156 }
157 tags.push((value_offset, value_size));
158 }
159 Ok(tags)
160 }
161
162 pub fn raw_data_area(&self) -> Result<&[u8], EseError> {
174 let hdr = self.parse_header()?;
175 let tag_count = hdr.available_page_tag_count as usize;
176 let tag_array_bytes = tag_count.saturating_mul(4);
177 let tag_array_start = self.data.len().saturating_sub(tag_array_bytes);
178 let start = Self::HEADER_SIZE.min(tag_array_start);
179 Ok(&self.data[start..tag_array_start])
180 }
181
182 pub fn record_data(&self, index: usize) -> Result<&[u8], EseError> {
191 let tags = self.tags()?;
192 if index >= tags.len() {
193 return Err(EseError::Corrupt {
194 page: self.page_number,
195 detail: format!("tag index {index} out of range (tag count: {})", tags.len()),
196 });
197 }
198 let (offset, size) = tags[index];
199 let start = Self::HEADER_SIZE + offset as usize;
201 let end = start + size as usize;
202 if end > self.data.len() {
203 return Err(EseError::RecordTooShort {
204 page: self.page_number,
205 tag: index,
206 got: self.data.len(),
207 need: end,
208 });
209 }
210 Ok(&self.data[start..end])
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
221 fn parse_header_page_flags_leaf() {
222 let mut data = vec![0u8; 4096];
223 data[0x24..0x28].copy_from_slice(&PAGE_FLAG_LEAF.to_le_bytes());
225 let page = EsePage {
226 page_number: 1,
227 data,
228 };
229 let hdr = page.parse_header().expect("parse header");
230 assert_eq!(hdr.page_flags, PAGE_FLAG_LEAF);
231 assert!(hdr.page_flags & PAGE_FLAG_LEAF != 0);
232 }
233
234 #[test]
235 fn page_flag_constants_match_ese_spec() {
236 assert_eq!(PAGE_FLAG_ROOT, 0x0001);
237 assert_eq!(PAGE_FLAG_LEAF, 0x0002);
238 assert_eq!(PAGE_FLAG_PARENT, 0x0004);
239 assert_eq!(PAGE_FLAG_EMPTY, 0x0008);
240 assert_eq!(PAGE_FLAG_SPACE_TREE, 0x0020);
241 assert_eq!(PAGE_FLAG_LONG_VALUE, 0x0400);
242 }
243
244 #[test]
245 fn parse_header_prev_next_links() {
246 let mut data = vec![0u8; 4096];
247 data[0x10..0x14].copy_from_slice(&5u32.to_le_bytes()); data[0x14..0x18].copy_from_slice(&7u32.to_le_bytes()); let page = EsePage {
250 page_number: 3,
251 data,
252 };
253 let hdr = page.parse_header().expect("parse header");
254 assert_eq!(hdr.prev_page, Some(5));
255 assert_eq!(hdr.next_page, Some(7));
256 }
257
258 #[test]
259 fn parse_header_none_when_0xffffffff() {
260 let mut data = vec![0u8; 4096];
261 data[0x10..0x14].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); data[0x14..0x18].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); let page = EsePage {
264 page_number: 1,
265 data,
266 };
267 let hdr = page.parse_header().expect("parse header");
268 assert_eq!(hdr.prev_page, None);
269 assert_eq!(hdr.next_page, None);
270 }
271
272 #[test]
273 fn parse_header_short_buffer_returns_err() {
274 let data = vec![0u8; 20]; let page = EsePage {
276 page_number: 1,
277 data,
278 };
279 let result = page.parse_header();
280 assert!(result.is_err(), "short buffer must return Err");
281 }
282
283 #[test]
286 fn parse_header_tag_count_at_vista_offset_0x22() {
287 let mut data = vec![0u8; 4096];
290 data[0x22..0x24].copy_from_slice(&5u16.to_le_bytes());
291 let page = EsePage { page_number: 1, data };
292 let hdr = page.parse_header().expect("parse header");
293 assert_eq!(hdr.available_page_tag_count, 5,
294 "tag count must be read from offset 0x22 per the Vista+ ESE format");
295 }
296
297 #[test]
298 fn parse_header_flags_at_vista_offset_0x24() {
299 let mut data = vec![0u8; 4096];
301 data[0x24..0x28].copy_from_slice(&PAGE_FLAG_LEAF.to_le_bytes());
302 let page = EsePage { page_number: 1, data };
303 let hdr = page.parse_header().expect("parse header");
304 assert!(hdr.page_flags & PAGE_FLAG_LEAF != 0,
305 "LEAF flag must be read from offset 0x24 per the Vista+ ESE format");
306 }
307
308 #[test]
309 fn parse_header_prev_next_at_vista_offsets_0x10_0x14() {
310 let mut data = vec![0u8; 4096];
312 data[0x10..0x14].copy_from_slice(&5u32.to_le_bytes());
313 data[0x14..0x18].copy_from_slice(&9u32.to_le_bytes());
314 let page = EsePage { page_number: 1, data };
315 let hdr = page.parse_header().expect("parse header");
316 assert_eq!(hdr.prev_page, Some(5));
317 assert_eq!(hdr.next_page, Some(9));
318 }
319
320 fn make_page_with_records(page_size: usize, records: &[&[u8]]) -> Vec<u8> {
329 const HEADER_SIZE: usize = 40;
330 let mut d = vec![0u8; page_size];
331 let tag_count = u16::try_from(1 + records.len()).unwrap_or(u16::MAX);
332 d[0x22..0x24].copy_from_slice(&tag_count.to_le_bytes());
334 d[0x24..0x28].copy_from_slice(&PAGE_FLAG_LEAF.to_le_bytes());
335
336 write_tag(&mut d, page_size, 0, 0, 40);
338
339 let mut cur_relative: u16 = 0;
341 for (i, rec) in records.iter().enumerate() {
342 let tag_idx = i + 1;
343 let rec_size = u16::try_from(rec.len()).unwrap_or(u16::MAX);
344 let absolute_start = HEADER_SIZE + usize::from(cur_relative);
345 d[absolute_start..absolute_start + rec.len()].copy_from_slice(rec);
346 write_tag(&mut d, page_size, tag_idx, cur_relative, rec_size);
347 cur_relative += rec_size;
348 }
349 d
350 }
351
352 fn write_tag(page: &mut [u8], page_size: usize, tag_idx: usize, offset: u16, size: u16) {
353 let raw: u32 = (u32::from(size) & 0x1FFF) | ((u32::from(offset) & 0x1FFF) << 16);
355 let pos = page_size - (tag_idx + 1) * 4;
356 page[pos..pos + 4].copy_from_slice(&raw.to_le_bytes());
357 }
358
359 #[test]
360 fn tags_count_matches_header() {
361 let data = make_page_with_records(4096, &[b"hello", b"world"]);
363 let page = EsePage {
364 page_number: 1,
365 data,
366 };
367 let tags = page.tags().expect("tags");
368 assert_eq!(tags.len(), 3);
369 }
370
371 #[test]
372 fn tags_first_record_offset_and_size() {
373 let data = make_page_with_records(4096, &[b"AAAA", b"BBBB"]);
374 let page = EsePage {
375 page_number: 1,
376 data,
377 };
378 let tags = page.tags().expect("tags");
379 assert_eq!(tags[1], (0, 4));
381 }
382
383 #[test]
384 fn record_data_first_matches() {
385 let data = make_page_with_records(4096, &[b"AAAA", b"BBBB"]);
386 let page = EsePage {
387 page_number: 1,
388 data,
389 };
390 let rec = page.record_data(1).expect("record 1");
391 assert_eq!(rec, b"AAAA");
392 }
393
394 #[test]
395 fn record_data_second_matches() {
396 let data = make_page_with_records(4096, &[b"AAAA", b"BBBB"]);
397 let page = EsePage {
398 page_number: 1,
399 data,
400 };
401 let rec = page.record_data(2).expect("record 2");
402 assert_eq!(rec, b"BBBB");
403 }
404
405 #[test]
406 fn record_data_beyond_count_returns_err() {
407 let data = make_page_with_records(4096, &[b"X", b"Y"]);
408 let page = EsePage {
409 page_number: 1,
410 data,
411 };
412 let result = page.record_data(5);
413 assert!(result.is_err(), "index beyond tag count must return Err");
414 }
415
416 #[test]
422 fn tags_real_ese_format_size_in_low_bits_offset_in_high_bits() {
423 let mut data = vec![0u8; 4096];
427 let sentinel = [0xDE, 0xAD, 0xBE, 0xEFu8];
428 data[40..44].copy_from_slice(&sentinel); data[0x22..0x24].copy_from_slice(&2u16.to_le_bytes()); data[0x24..0x28].copy_from_slice(&PAGE_FLAG_LEAF.to_le_bytes());
432 data[0x10..0x14].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
433 data[0x14..0x18].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
434 data[4096 - 4..4096].copy_from_slice(&40u32.to_le_bytes());
436 data[4096 - 8..4096 - 4].copy_from_slice(&4u32.to_le_bytes());
438
439 let page = EsePage { page_number: 1, data };
440 let (tag_offset, tag_size) = page.tags().expect("tags")[1];
441 assert_eq!(tag_size, 4, "cb_ (size) must be read from LOW 13 bits");
442 assert_eq!(tag_offset, 0, "ib_ (offset) must be read from HIGH 13 bits");
443 assert_eq!(
444 page.record_data(1).expect("record_data"),
445 &sentinel,
446 "real ESE tag: size in LOW bits, offset in HIGH bits"
447 );
448 }
449
450 #[test]
451 fn record_data_real_ese_nonsequential_offset() {
452 let mut data = vec![0u8; 4096];
455 let rec1 = [0xAAu8; 4];
456 let rec2 = [0xBBu8; 4];
457 data[40..44].copy_from_slice(&rec1); data[48..52].copy_from_slice(&rec2); data[0x22..0x24].copy_from_slice(&3u16.to_le_bytes()); data[0x24..0x28].copy_from_slice(&PAGE_FLAG_LEAF.to_le_bytes());
462 data[0x10..0x14].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
463 data[0x14..0x18].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
464 data[4096 - 4..4096].copy_from_slice(&40u32.to_le_bytes());
466 data[4096 - 8..4096 - 4].copy_from_slice(&4u32.to_le_bytes());
468 data[4096 - 12..4096 - 8].copy_from_slice(&((8u32 << 16) | 4u32).to_le_bytes());
470
471 let page = EsePage { page_number: 1, data };
472 assert_eq!(
473 page.record_data(1).expect("rec1"),
474 &rec1,
475 "record 1 at offset=0"
476 );
477 assert_eq!(
478 page.record_data(2).expect("rec2"),
479 &rec2,
480 "record 2 at non-sequential offset=8"
481 );
482 }
483
484 #[test]
487 fn record_data_treats_tag_offset_as_relative_to_header_end() {
488 let mut data = vec![0u8; 4096];
497 let sentinel = [0xCA, 0xFE, 0xBA, 0xBEu8];
498 data[42..46].copy_from_slice(&sentinel);
500
501 let tag_count: u16 = 2;
502 data[0x22..0x24].copy_from_slice(&tag_count.to_le_bytes());
503 data[0x24..0x28].copy_from_slice(&PAGE_FLAG_LEAF.to_le_bytes());
504 data[0x10..0x14].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
505 data[0x14..0x18].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
506
507 let tag0: u32 = 40u32;
510 data[4096 - 4..4096].copy_from_slice(&tag0.to_le_bytes());
511
512 let tag1: u32 = 4u32 | (2u32 << 16);
514 data[4096 - 8..4096 - 4].copy_from_slice(&tag1.to_le_bytes());
515
516 let page = EsePage { page_number: 1, data };
517 let rec = page.record_data(1).expect("record_data(1)");
518 assert_eq!(
519 rec,
520 &sentinel,
521 "record_data must add HEADER_SIZE(40) to tag offset; \
522 relative offset 2 must read from absolute byte 42, not byte 2"
523 );
524 }
525}