1use std::fmt;
28
29pub const FORMAT_VERSION_V1: u16 = 1;
34
35pub const FORMAT_VERSION_V2: u16 = 2;
38
39pub const FORMAT_VERSION: u16 = FORMAT_VERSION_V2;
42
43pub const PAGE_HEADER_SIZE: usize = 5;
45
46const FLAG_POINTER: u8 = 0b0000_0001;
51const FLAG_COMPRESSED: u8 = 0b0000_0010;
52const FLAG_RESERVED_MASK: u8 = !(FLAG_POINTER | FLAG_COMPRESSED);
53
54#[repr(u8)]
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum PageType {
59 Free = 0,
61 Leaf = 1,
63 Internal = 2,
65 Overflow = 3,
69}
70
71impl PageType {
72 pub fn from_byte(b: u8) -> Result<Self, PageFormatError> {
75 match b {
76 0 => Ok(PageType::Free),
77 1 => Ok(PageType::Leaf),
78 2 => Ok(PageType::Internal),
79 3 => Ok(PageType::Overflow),
80 other => Err(PageFormatError::UnknownPageType(other)),
81 }
82 }
83
84 #[inline]
86 pub fn to_byte(self) -> u8 {
87 self as u8
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
94pub struct LeafCellFlags {
95 pub is_pointer: bool,
98 pub is_compressed: bool,
101}
102
103impl LeafCellFlags {
104 pub const INLINE_RAW: Self = LeafCellFlags {
107 is_pointer: false,
108 is_compressed: false,
109 };
110
111 pub fn to_byte(self) -> u8 {
113 let mut b = 0u8;
114 if self.is_pointer {
115 b |= FLAG_POINTER;
116 }
117 if self.is_compressed {
118 b |= FLAG_COMPRESSED;
119 }
120 b
121 }
122
123 pub fn from_byte(b: u8) -> Result<Self, PageFormatError> {
126 if b & FLAG_RESERVED_MASK != 0 {
127 return Err(PageFormatError::UnknownCellFlags(b));
128 }
129 Ok(LeafCellFlags {
130 is_pointer: b & FLAG_POINTER != 0,
131 is_compressed: b & FLAG_COMPRESSED != 0,
132 })
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct PageHeader {
140 pub version: u16,
141 pub page_type: PageType,
142 pub cell_count: u16,
143}
144
145impl PageHeader {
146 pub fn new(page_type: PageType, cell_count: u16) -> Self {
148 Self {
149 version: FORMAT_VERSION,
150 page_type,
151 cell_count,
152 }
153 }
154
155 pub fn encode(&self, out: &mut [u8]) -> Result<(), PageFormatError> {
157 if out.len() < PAGE_HEADER_SIZE {
158 return Err(PageFormatError::ShortBuffer {
159 need: PAGE_HEADER_SIZE,
160 got: out.len(),
161 });
162 }
163 out[0..2].copy_from_slice(&self.version.to_le_bytes());
164 out[2] = self.page_type.to_byte();
165 out[3..5].copy_from_slice(&self.cell_count.to_le_bytes());
166 Ok(())
167 }
168
169 pub fn decode(bytes: &[u8]) -> Result<Self, PageFormatError> {
173 if bytes.len() < PAGE_HEADER_SIZE {
174 return Err(PageFormatError::ShortBuffer {
175 need: PAGE_HEADER_SIZE,
176 got: bytes.len(),
177 });
178 }
179 let version = u16::from_le_bytes([bytes[0], bytes[1]]);
180 if version == 0 || version > FORMAT_VERSION {
181 return Err(PageFormatError::UnsupportedVersion(version));
182 }
183 let page_type = PageType::from_byte(bytes[2])?;
184 let cell_count = u16::from_le_bytes([bytes[3], bytes[4]]);
185 Ok(Self {
186 version,
187 page_type,
188 cell_count,
189 })
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct LeafCell<'a> {
198 pub flags: LeafCellFlags,
199 pub key: &'a [u8],
200 pub payload: &'a [u8],
201}
202
203pub fn encode_leaf_cell_v2(
206 flags: LeafCellFlags,
207 key: &[u8],
208 payload: &[u8],
209 out: &mut Vec<u8>,
210) -> Result<(), PageFormatError> {
211 if key.len() > u16::MAX as usize {
212 return Err(PageFormatError::FieldTooLarge {
213 field: "key",
214 len: key.len(),
215 });
216 }
217 if payload.len() > u32::MAX as usize {
218 return Err(PageFormatError::FieldTooLarge {
219 field: "payload",
220 len: payload.len(),
221 });
222 }
223 out.reserve(1 + 2 + 4 + key.len() + payload.len());
224 out.push(flags.to_byte());
225 out.extend_from_slice(&(key.len() as u16).to_le_bytes());
226 out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
227 out.extend_from_slice(key);
228 out.extend_from_slice(payload);
229 Ok(())
230}
231
232pub fn encode_leaf_cell_v1(
237 key: &[u8],
238 payload: &[u8],
239 out: &mut Vec<u8>,
240) -> Result<(), PageFormatError> {
241 if key.len() > u16::MAX as usize {
242 return Err(PageFormatError::FieldTooLarge {
243 field: "key",
244 len: key.len(),
245 });
246 }
247 if payload.len() > u32::MAX as usize {
248 return Err(PageFormatError::FieldTooLarge {
249 field: "payload",
250 len: payload.len(),
251 });
252 }
253 out.reserve(2 + 4 + key.len() + payload.len());
254 out.extend_from_slice(&(key.len() as u16).to_le_bytes());
255 out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
256 out.extend_from_slice(key);
257 out.extend_from_slice(payload);
258 Ok(())
259}
260
261pub fn decode_leaf_cell(
270 version: u16,
271 bytes: &[u8],
272) -> Result<(LeafCell<'_>, usize), PageFormatError> {
273 match version {
274 FORMAT_VERSION_V1 => decode_leaf_cell_v1(bytes),
275 FORMAT_VERSION_V2 => decode_leaf_cell_v2(bytes),
276 other => Err(PageFormatError::UnsupportedVersion(other)),
277 }
278}
279
280fn decode_leaf_cell_v1(bytes: &[u8]) -> Result<(LeafCell<'_>, usize), PageFormatError> {
281 if bytes.len() < 6 {
282 return Err(PageFormatError::TruncatedCell);
283 }
284 let key_len = u16::from_le_bytes([bytes[0], bytes[1]]) as usize;
285 let payload_len = u32::from_le_bytes([bytes[2], bytes[3], bytes[4], bytes[5]]) as usize;
286 let total = 6 + key_len + payload_len;
287 if bytes.len() < total {
288 return Err(PageFormatError::TruncatedCell);
289 }
290 let key = &bytes[6..6 + key_len];
291 let payload = &bytes[6 + key_len..6 + key_len + payload_len];
292 Ok((
293 LeafCell {
294 flags: LeafCellFlags::INLINE_RAW,
295 key,
296 payload,
297 },
298 total,
299 ))
300}
301
302fn decode_leaf_cell_v2(bytes: &[u8]) -> Result<(LeafCell<'_>, usize), PageFormatError> {
303 if bytes.len() < 7 {
304 return Err(PageFormatError::TruncatedCell);
305 }
306 let flags = LeafCellFlags::from_byte(bytes[0])?;
307 let key_len = u16::from_le_bytes([bytes[1], bytes[2]]) as usize;
308 let payload_len = u32::from_le_bytes([bytes[3], bytes[4], bytes[5], bytes[6]]) as usize;
309 let total = 7 + key_len + payload_len;
310 if bytes.len() < total {
311 return Err(PageFormatError::TruncatedCell);
312 }
313 let key = &bytes[7..7 + key_len];
314 let payload = &bytes[7 + key_len..7 + key_len + payload_len];
315 Ok((
316 LeafCell {
317 flags,
318 key,
319 payload,
320 },
321 total,
322 ))
323}
324
325#[derive(Debug, PartialEq, Eq)]
327pub enum PageFormatError {
328 UnknownPageType(u8),
329 UnknownCellFlags(u8),
330 UnsupportedVersion(u16),
331 ShortBuffer { need: usize, got: usize },
332 TruncatedCell,
333 FieldTooLarge { field: &'static str, len: usize },
334}
335
336impl fmt::Display for PageFormatError {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 match self {
339 PageFormatError::UnknownPageType(b) => write!(f, "unknown page type byte: {}", b),
340 PageFormatError::UnknownCellFlags(b) => {
341 write!(f, "unknown leaf-cell flag bits: 0b{:08b}", b)
342 }
343 PageFormatError::UnsupportedVersion(v) => {
344 write!(f, "unsupported page format version: {}", v)
345 }
346 PageFormatError::ShortBuffer { need, got } => {
347 write!(f, "buffer too small: need {} bytes, got {}", need, got)
348 }
349 PageFormatError::TruncatedCell => write!(f, "leaf cell truncated"),
350 PageFormatError::FieldTooLarge { field, len } => {
351 write!(f, "{} length {} exceeds on-disk encoding limit", field, len)
352 }
353 }
354 }
355}
356
357impl std::error::Error for PageFormatError {}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn format_version_constant_is_v2() {
365 assert_eq!(FORMAT_VERSION, 2);
369 assert_eq!(FORMAT_VERSION_V1, 1);
370 assert_eq!(FORMAT_VERSION_V2, 2);
371 }
372
373 #[test]
374 fn page_header_round_trips_overflow_type() {
375 let header = PageHeader::new(PageType::Overflow, 0);
378 let mut buf = [0u8; PAGE_HEADER_SIZE];
379 header.encode(&mut buf).expect("encode");
380 let decoded = PageHeader::decode(&buf).expect("decode");
381 assert_eq!(decoded, header);
382 assert_eq!(decoded.page_type, PageType::Overflow);
383 assert_eq!(decoded.version, FORMAT_VERSION_V2);
384 }
385
386 #[test]
387 fn page_header_round_trips_every_type() {
388 for pt in [
389 PageType::Free,
390 PageType::Leaf,
391 PageType::Internal,
392 PageType::Overflow,
393 ] {
394 let header = PageHeader::new(pt, 42);
395 let mut buf = [0u8; PAGE_HEADER_SIZE];
396 header.encode(&mut buf).unwrap();
397 let decoded = PageHeader::decode(&buf).unwrap();
398 assert_eq!(decoded.page_type, pt);
399 assert_eq!(decoded.cell_count, 42);
400 }
401 }
402
403 #[test]
404 fn page_header_rejects_unknown_type_byte() {
405 let mut buf = [0u8; PAGE_HEADER_SIZE];
406 buf[0..2].copy_from_slice(&FORMAT_VERSION_V2.to_le_bytes());
407 buf[2] = 99;
408 assert_eq!(
409 PageHeader::decode(&buf).unwrap_err(),
410 PageFormatError::UnknownPageType(99)
411 );
412 }
413
414 #[test]
415 fn page_header_rejects_version_newer_than_known() {
416 let mut buf = [0u8; PAGE_HEADER_SIZE];
417 buf[0..2].copy_from_slice(&7u16.to_le_bytes());
418 buf[2] = PageType::Leaf.to_byte();
419 assert_eq!(
420 PageHeader::decode(&buf).unwrap_err(),
421 PageFormatError::UnsupportedVersion(7)
422 );
423 }
424
425 #[test]
426 fn page_header_rejects_version_zero() {
427 let mut buf = [0u8; PAGE_HEADER_SIZE];
428 buf[2] = PageType::Leaf.to_byte();
429 assert_eq!(
430 PageHeader::decode(&buf).unwrap_err(),
431 PageFormatError::UnsupportedVersion(0)
432 );
433 }
434
435 #[test]
436 fn page_header_decode_rejects_short_buffer() {
437 let buf = [0u8; PAGE_HEADER_SIZE - 1];
438 assert!(matches!(
439 PageHeader::decode(&buf),
440 Err(PageFormatError::ShortBuffer { .. })
441 ));
442 }
443
444 #[test]
445 fn leaf_cell_flags_byte_round_trip() {
446 for is_pointer in [false, true] {
447 for is_compressed in [false, true] {
448 let flags = LeafCellFlags {
449 is_pointer,
450 is_compressed,
451 };
452 let b = flags.to_byte();
453 assert_eq!(LeafCellFlags::from_byte(b).unwrap(), flags);
454 }
455 }
456 }
457
458 #[test]
459 fn leaf_cell_flags_reject_reserved_bits() {
460 for reserved in [0b0000_0100u8, 0b1000_0000, 0xFF] {
464 assert_eq!(
465 LeafCellFlags::from_byte(reserved).unwrap_err(),
466 PageFormatError::UnknownCellFlags(reserved)
467 );
468 }
469 }
470
471 #[test]
472 fn all_four_leaf_cell_shapes_round_trip() {
473 let key = b"vec:42".as_slice();
476 let payload = b"\xDE\xAD\xBE\xEF\x00\x01\x02\x03".as_slice();
477 for flags in [
478 LeafCellFlags {
479 is_pointer: false,
480 is_compressed: false,
481 },
482 LeafCellFlags {
483 is_pointer: false,
484 is_compressed: true,
485 },
486 LeafCellFlags {
487 is_pointer: true,
488 is_compressed: false,
489 },
490 LeafCellFlags {
491 is_pointer: true,
492 is_compressed: true,
493 },
494 ] {
495 let mut buf = Vec::new();
496 encode_leaf_cell_v2(flags, key, payload, &mut buf).unwrap();
497 let (cell, consumed) = decode_leaf_cell(FORMAT_VERSION_V2, &buf).unwrap();
498 assert_eq!(consumed, buf.len(), "consumed must equal encoded size");
499 assert_eq!(cell.flags, flags);
500 assert_eq!(cell.key, key);
501 assert_eq!(cell.payload, payload);
502 }
503 }
504
505 #[test]
506 fn v1_cell_reads_as_inline_raw() {
507 let key = b"legacy-key".as_slice();
510 let payload = b"\x00\xFF\x10\x20\x30".as_slice();
511 let mut buf = Vec::new();
512 encode_leaf_cell_v1(key, payload, &mut buf).unwrap();
513 let (cell, consumed) = decode_leaf_cell(FORMAT_VERSION_V1, &buf).unwrap();
514 assert_eq!(consumed, buf.len());
515 assert_eq!(cell.flags, LeafCellFlags::INLINE_RAW);
516 assert!(!cell.flags.is_pointer);
517 assert!(!cell.flags.is_compressed);
518 assert_eq!(cell.key, key);
519 assert_eq!(cell.payload, payload);
520 }
521
522 #[test]
523 fn v1_stream_of_cells_decodes_byte_identically() {
524 let cells: Vec<(&[u8], &[u8])> = vec![
528 (b"k0", b"v0"),
529 (b"k1", b"\x00\x01\x02"),
530 (b"", b"empty-key"),
531 (b"large", &[0xABu8; 300][..]),
532 ];
533 let mut buf = Vec::new();
534 for (k, v) in &cells {
535 encode_leaf_cell_v1(k, v, &mut buf).unwrap();
536 }
537 let mut cursor = 0;
538 for (k, v) in &cells {
539 let (cell, n) = decode_leaf_cell(FORMAT_VERSION_V1, &buf[cursor..]).unwrap();
540 assert_eq!(cell.flags, LeafCellFlags::INLINE_RAW);
541 assert_eq!(cell.key, *k);
542 assert_eq!(cell.payload, *v);
543 cursor += n;
544 }
545 assert_eq!(cursor, buf.len(), "stream fully consumed");
546 }
547
548 #[test]
549 fn freshly_created_page_writes_v2_header() {
550 let header = PageHeader::new(PageType::Leaf, 0);
554 assert_eq!(header.version, FORMAT_VERSION_V2);
555 let mut buf = [0u8; PAGE_HEADER_SIZE];
556 header.encode(&mut buf).unwrap();
557 assert_eq!(u16::from_le_bytes([buf[0], buf[1]]), FORMAT_VERSION_V2);
558 }
559
560 #[test]
561 fn v1_page_still_reads_after_partial_rewrites_in_place() {
562 let originals: Vec<(Vec<u8>, Vec<u8>)> = vec![
569 (b"orig-a".to_vec(), b"value-a".to_vec()),
570 (b"orig-b".to_vec(), b"value-b".to_vec()),
571 (b"orig-c".to_vec(), b"value-c".to_vec()),
572 ];
573 let mut page_bytes = Vec::new();
574 let v1_header = PageHeader {
576 version: FORMAT_VERSION_V1,
577 page_type: PageType::Leaf,
578 cell_count: originals.len() as u16,
579 };
580 let mut hdr_buf = [0u8; PAGE_HEADER_SIZE];
581 v1_header.encode(&mut hdr_buf).unwrap();
582 page_bytes.extend_from_slice(&hdr_buf);
583
584 let mut cell_offsets = Vec::new();
587 for (k, v) in &originals {
588 cell_offsets.push(page_bytes.len());
589 encode_leaf_cell_v1(k, v, &mut page_bytes).unwrap();
590 }
591
592 let new_value = b"VALUE-B"; assert_eq!(new_value.len(), originals[1].1.len());
598 let rewrite_start = cell_offsets[1] + 2 + 4 + originals[1].0.len();
599 page_bytes[rewrite_start..rewrite_start + new_value.len()].copy_from_slice(new_value);
600
601 let header = PageHeader::decode(&page_bytes[..PAGE_HEADER_SIZE]).unwrap();
605 assert_eq!(header.version, FORMAT_VERSION_V1);
606 let mut cursor = PAGE_HEADER_SIZE;
607 let expected: Vec<(&[u8], &[u8])> = vec![
608 (&originals[0].0, &originals[0].1),
609 (&originals[1].0, new_value),
610 (&originals[2].0, &originals[2].1),
611 ];
612 for (k, v) in expected {
613 let (cell, n) = decode_leaf_cell(header.version, &page_bytes[cursor..]).unwrap();
614 assert_eq!(cell.flags, LeafCellFlags::INLINE_RAW);
615 assert_eq!(cell.key, k);
616 assert_eq!(cell.payload, v);
617 cursor += n;
618 }
619 assert_eq!(cursor, page_bytes.len());
620 }
621
622 #[test]
623 fn page_type_byte_values_are_stable() {
624 assert_eq!(PageType::Free.to_byte(), 0);
627 assert_eq!(PageType::Leaf.to_byte(), 1);
628 assert_eq!(PageType::Internal.to_byte(), 2);
629 assert_eq!(PageType::Overflow.to_byte(), 3);
630 }
631
632 #[test]
633 fn decode_leaf_cell_rejects_truncation() {
634 let mut buf = Vec::new();
635 encode_leaf_cell_v2(LeafCellFlags::INLINE_RAW, b"abc", b"xyz", &mut buf).unwrap();
636 for trunc in 0..buf.len() {
637 assert_eq!(
638 decode_leaf_cell(FORMAT_VERSION_V2, &buf[..trunc]).unwrap_err(),
639 PageFormatError::TruncatedCell,
640 "truncation at {} bytes must be rejected",
641 trunc
642 );
643 }
644 }
645
646 #[test]
647 fn decode_leaf_cell_unknown_version_rejected() {
648 let buf = [0u8; 16];
649 assert_eq!(
650 decode_leaf_cell(99, &buf).unwrap_err(),
651 PageFormatError::UnsupportedVersion(99)
652 );
653 }
654
655 #[test]
656 fn encoded_v2_cell_has_flag_byte_then_v1_layout() {
657 let mut v2 = Vec::new();
661 encode_leaf_cell_v2(
662 LeafCellFlags {
663 is_pointer: true,
664 is_compressed: false,
665 },
666 b"k",
667 b"p",
668 &mut v2,
669 )
670 .unwrap();
671 let mut v1 = Vec::new();
672 encode_leaf_cell_v1(b"k", b"p", &mut v1).unwrap();
673 assert_eq!(v2[0], FLAG_POINTER);
674 assert_eq!(&v2[1..], &v1[..]);
675 }
676}