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