1#![forbid(unsafe_code)]
29
30use crate::error::{Error, Result};
31use crate::pager::page::{Page, PAGE_SIZE, PAGE_TRAILER_SIZE};
32
33pub const PAGE_TYPE_BTREE_INTERNAL: u8 = 0x02;
36pub const PAGE_TYPE_BTREE_LEAF: u8 = 0x03;
38
39pub const NODE_HEADER_SIZE: usize = 24;
41
42const OFF_PAGE_TYPE: usize = 0;
44const OFF_LEVEL: usize = 1;
45const OFF_KEY_COUNT: usize = 2;
46const OFF_FREE_SPACE_OFF: usize = 4;
47const OFF_NEXT_SIBLING: usize = 8;
48const OFF_RESERVED: usize = 16;
49
50pub const LEAF_SLOT_BYTES: usize = 4;
53
54pub const INTERNAL_SLOT_BYTES: usize = 12;
57
58pub const INTERNAL_LEFTMOST_CHILD_BYTES: usize = 8;
61
62const PAYLOAD_OFFSET: usize = NODE_HEADER_SIZE;
65
66const PAYLOAD_END: usize = PAGE_SIZE - PAGE_TRAILER_SIZE;
69
70pub const LEAF_SLOT_CAP: usize = (PAYLOAD_END - PAYLOAD_OFFSET) / LEAF_SLOT_BYTES;
73
74pub const INTERNAL_SLOT_CAP: usize =
77 (PAYLOAD_END - PAYLOAD_OFFSET - INTERNAL_LEFTMOST_CHILD_BYTES) / INTERNAL_SLOT_BYTES;
78
79#[must_use]
82pub const fn max_key_len() -> usize {
83 PAGE_SIZE / 4
84}
85
86#[must_use]
89pub const fn max_inline_value(key_len: usize) -> usize {
90 let base = PAYLOAD_END - PAYLOAD_OFFSET - LEAF_SLOT_BYTES;
93 let varints = 9 + 9;
95 if key_len + varints >= base {
96 0
97 } else {
98 base - key_len - varints
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum NodeKind {
105 Internal,
107 Leaf,
109}
110
111#[derive(Debug, Clone)]
113pub struct LeafEntry {
114 pub key: Vec<u8>,
116 pub value: Vec<u8>,
118}
119
120#[derive(Debug, Clone)]
124pub struct InternalEntry {
125 pub key: Vec<u8>,
127}
128
129pub type ChildEntry = u64;
134
135#[derive(Debug, Clone)]
142pub struct DecodedNode {
143 pub kind: NodeKind,
145 pub level: u8,
147 pub next_sibling: u64,
150 pub children: Vec<ChildEntry>,
154 pub leaves: Vec<LeafEntry>,
156 pub internals: Vec<InternalEntry>,
158}
159
160impl DecodedNode {
161 #[must_use]
165 pub fn occupied_bytes(&self) -> usize {
166 match self.kind {
167 NodeKind::Leaf => {
168 let slot_bytes = self.leaves.len() * LEAF_SLOT_BYTES;
169 let heap_bytes: usize = self
170 .leaves
171 .iter()
172 .map(|e| {
173 varint_len(u64_from_usize(e.key.len()))
174 + e.key.len()
175 + varint_len(u64_from_usize(e.value.len()))
176 + e.value.len()
177 })
178 .sum();
179 slot_bytes + heap_bytes
180 }
181 NodeKind::Internal => {
182 let slot_bytes = self.internals.len() * INTERNAL_SLOT_BYTES;
183 let heap_bytes: usize = self
184 .internals
185 .iter()
186 .map(|e| varint_len(u64_from_usize(e.key.len())) + e.key.len())
187 .sum();
188 INTERNAL_LEFTMOST_CHILD_BYTES + slot_bytes + heap_bytes
189 }
190 }
191 }
192
193 #[must_use]
195 pub fn free_bytes(&self) -> usize {
196 let cap = PAYLOAD_END - PAYLOAD_OFFSET;
197 cap.saturating_sub(self.occupied_bytes())
198 }
199}
200
201pub fn encode_node(node: &DecodedNode, page: &mut Page) -> Result<()> {
208 validate_node_release(node)?;
209 let buf = page.as_bytes_mut();
210 buf.fill(0);
211 write_node_header(buf, node);
212 match node.kind {
213 NodeKind::Leaf => encode_leaf_body(buf, node)?,
214 NodeKind::Internal => encode_internal_body(buf, node)?,
215 }
216 crate::pager::checksum::write_page_trailer(page);
220 Ok(())
221}
222
223fn write_node_header(buf: &mut [u8; PAGE_SIZE], node: &DecodedNode) {
224 let page_type = match node.kind {
225 NodeKind::Leaf => PAGE_TYPE_BTREE_LEAF,
226 NodeKind::Internal => PAGE_TYPE_BTREE_INTERNAL,
227 };
228 buf[OFF_PAGE_TYPE] = page_type;
229 buf[OFF_LEVEL] = node.level;
230 let key_count = match node.kind {
231 NodeKind::Leaf => node.leaves.len(),
232 NodeKind::Internal => node.internals.len(),
233 };
234 let kc16 = u16_from_usize(key_count);
238 buf[OFF_KEY_COUNT..OFF_KEY_COUNT + 2].copy_from_slice(&kc16.to_le_bytes());
239 let heap_bytes: usize = match node.kind {
243 NodeKind::Leaf => node
244 .leaves
245 .iter()
246 .map(|e| {
247 varint_len(u64_from_usize(e.key.len()))
248 + e.key.len()
249 + varint_len(u64_from_usize(e.value.len()))
250 + e.value.len()
251 })
252 .sum(),
253 NodeKind::Internal => node
254 .internals
255 .iter()
256 .map(|e| varint_len(u64_from_usize(e.key.len())) + e.key.len())
257 .sum(),
258 };
259 let fso = u32_from_usize(PAYLOAD_END.saturating_sub(heap_bytes));
260 buf[OFF_FREE_SPACE_OFF..OFF_FREE_SPACE_OFF + 4].copy_from_slice(&fso.to_le_bytes());
261 buf[OFF_NEXT_SIBLING..OFF_NEXT_SIBLING + 8].copy_from_slice(&node.next_sibling.to_le_bytes());
262 let _ = OFF_RESERVED;
264}
265
266fn encode_leaf_body(buf: &mut [u8; PAGE_SIZE], node: &DecodedNode) -> Result<()> {
267 let mut heap_cursor = PAYLOAD_END;
268 let mut slot_off = PAYLOAD_OFFSET;
269 for entry in &node.leaves {
270 let entry_len = varint_len(u64_from_usize(entry.key.len()))
271 + entry.key.len()
272 + varint_len(u64_from_usize(entry.value.len()))
273 + entry.value.len();
274 if heap_cursor < entry_len + slot_off + LEAF_SLOT_BYTES {
275 return Err(Error::BTreeInvariantViolated {
276 reason: "leaf encode: slot dir and heap collide",
277 });
278 }
279 heap_cursor -= entry_len;
280 let mut cur = heap_cursor;
281 let kl = u64_from_usize(entry.key.len());
282 cur += write_varint(&mut buf[cur..], kl);
283 buf[cur..cur + entry.key.len()].copy_from_slice(&entry.key);
284 cur += entry.key.len();
285 let vl = u64_from_usize(entry.value.len());
286 cur += write_varint(&mut buf[cur..], vl);
287 buf[cur..cur + entry.value.len()].copy_from_slice(&entry.value);
288 let off32 = u32_from_usize(heap_cursor);
289 buf[slot_off..slot_off + LEAF_SLOT_BYTES].copy_from_slice(&off32.to_le_bytes());
290 slot_off += LEAF_SLOT_BYTES;
291 }
292 Ok(())
293}
294
295fn encode_internal_body(buf: &mut [u8; PAGE_SIZE], node: &DecodedNode) -> Result<()> {
296 if node.children.len() != node.internals.len() + 1 {
297 return Err(Error::BTreeInvariantViolated {
298 reason: "internal node has children.len() != pivots+1",
299 });
300 }
301 let leftmost = node.children[0];
302 buf[PAYLOAD_OFFSET..PAYLOAD_OFFSET + INTERNAL_LEFTMOST_CHILD_BYTES]
303 .copy_from_slice(&leftmost.to_le_bytes());
304 let mut heap_cursor = PAYLOAD_END;
305 let mut slot_off = PAYLOAD_OFFSET + INTERNAL_LEFTMOST_CHILD_BYTES;
306 for (i, pivot) in node.internals.iter().enumerate() {
307 let right_child = node.children[i + 1];
308 let entry_len = varint_len(u64_from_usize(pivot.key.len())) + pivot.key.len();
309 if heap_cursor < entry_len + slot_off + INTERNAL_SLOT_BYTES {
310 return Err(Error::BTreeInvariantViolated {
311 reason: "internal encode: slot dir and heap collide",
312 });
313 }
314 heap_cursor -= entry_len;
315 let mut cur = heap_cursor;
316 cur += write_varint(&mut buf[cur..], u64_from_usize(pivot.key.len()));
317 buf[cur..cur + pivot.key.len()].copy_from_slice(&pivot.key);
318 let off32 = u32_from_usize(heap_cursor);
319 buf[slot_off..slot_off + 4].copy_from_slice(&off32.to_le_bytes());
320 buf[slot_off + 4..slot_off + INTERNAL_SLOT_BYTES]
321 .copy_from_slice(&right_child.to_le_bytes());
322 slot_off += INTERNAL_SLOT_BYTES;
323 }
324 Ok(())
325}
326
327pub fn decode_node(buf: &[u8; PAGE_SIZE]) -> Result<DecodedNode> {
336 let page_type = buf[OFF_PAGE_TYPE];
337 let kind = match page_type {
338 PAGE_TYPE_BTREE_LEAF => NodeKind::Leaf,
339 PAGE_TYPE_BTREE_INTERNAL => NodeKind::Internal,
340 _ => return Err(Error::Corruption { page_id: 0 }),
341 };
342 let level = buf[OFF_LEVEL];
343 let key_count = u16::from_le_bytes([buf[OFF_KEY_COUNT], buf[OFF_KEY_COUNT + 1]]) as usize;
344 let next_sibling = u64::from_le_bytes([
345 buf[OFF_NEXT_SIBLING],
346 buf[OFF_NEXT_SIBLING + 1],
347 buf[OFF_NEXT_SIBLING + 2],
348 buf[OFF_NEXT_SIBLING + 3],
349 buf[OFF_NEXT_SIBLING + 4],
350 buf[OFF_NEXT_SIBLING + 5],
351 buf[OFF_NEXT_SIBLING + 6],
352 buf[OFF_NEXT_SIBLING + 7],
353 ]);
354 let mut node = DecodedNode {
355 kind,
356 level,
357 next_sibling,
358 children: Vec::new(),
359 leaves: Vec::new(),
360 internals: Vec::new(),
361 };
362 match kind {
363 NodeKind::Leaf => decode_leaf_body(buf, &mut node, key_count)?,
364 NodeKind::Internal => decode_internal_body(buf, &mut node, key_count)?,
365 }
366 validate_node_release(&node)?;
367 Ok(node)
368}
369
370fn decode_leaf_body(buf: &[u8; PAGE_SIZE], node: &mut DecodedNode, key_count: usize) -> Result<()> {
371 if key_count > LEAF_SLOT_CAP {
372 return Err(Error::Corruption { page_id: 0 });
373 }
374 node.leaves.reserve(key_count);
375 let mut prev_key: Option<Vec<u8>> = None;
376 for i in 0..key_count {
377 let slot_off = PAYLOAD_OFFSET + i * LEAF_SLOT_BYTES;
378 if slot_off + LEAF_SLOT_BYTES > PAYLOAD_END {
379 return Err(Error::Corruption { page_id: 0 });
380 }
381 let entry_off = u32::from_le_bytes([
382 buf[slot_off],
383 buf[slot_off + 1],
384 buf[slot_off + 2],
385 buf[slot_off + 3],
386 ]) as usize;
387 if !(PAYLOAD_OFFSET..PAYLOAD_END).contains(&entry_off) {
388 return Err(Error::Corruption { page_id: 0 });
389 }
390 let (key, after_key) = read_length_prefixed(buf, entry_off)?;
391 let (value, _) = read_length_prefixed(buf, after_key)?;
392 if key.len() > max_key_len() {
393 return Err(Error::Corruption { page_id: 0 });
394 }
395 let key_vec = key.to_vec();
396 if let Some(prev) = &prev_key {
397 if key_vec.as_slice() <= prev.as_slice() {
398 return Err(Error::Corruption { page_id: 0 });
399 }
400 }
401 prev_key = Some(key_vec.clone());
402 node.leaves.push(LeafEntry {
403 key: key_vec,
404 value: value.to_vec(),
405 });
406 }
407 Ok(())
408}
409
410fn decode_internal_body(
411 buf: &[u8; PAGE_SIZE],
412 node: &mut DecodedNode,
413 key_count: usize,
414) -> Result<()> {
415 if key_count > INTERNAL_SLOT_CAP {
416 return Err(Error::Corruption { page_id: 0 });
417 }
418 let leftmost = read_u64(buf, PAYLOAD_OFFSET);
419 if leftmost == 0 {
420 return Err(Error::Corruption { page_id: 0 });
421 }
422 node.children.reserve(key_count + 1);
423 node.internals.reserve(key_count);
424 node.children.push(leftmost);
425 let mut prev_key: Option<Vec<u8>> = None;
426 for i in 0..key_count {
427 let (key_vec, right_child) = decode_internal_slot(buf, i)?;
428 if let Some(prev) = &prev_key {
429 if key_vec.as_slice() <= prev.as_slice() {
430 return Err(Error::Corruption { page_id: 0 });
431 }
432 }
433 prev_key = Some(key_vec.clone());
434 node.internals.push(InternalEntry { key: key_vec });
435 node.children.push(right_child);
436 }
437 Ok(())
438}
439
440fn decode_internal_slot(buf: &[u8; PAGE_SIZE], i: usize) -> Result<(Vec<u8>, u64)> {
444 let slot_off = PAYLOAD_OFFSET + INTERNAL_LEFTMOST_CHILD_BYTES + i * INTERNAL_SLOT_BYTES;
445 if slot_off + INTERNAL_SLOT_BYTES > PAYLOAD_END {
446 return Err(Error::Corruption { page_id: 0 });
447 }
448 let entry_off = u32::from_le_bytes([
449 buf[slot_off],
450 buf[slot_off + 1],
451 buf[slot_off + 2],
452 buf[slot_off + 3],
453 ]) as usize;
454 let right_child = read_u64(buf, slot_off + 4);
455 if right_child == 0 || !(PAYLOAD_OFFSET..PAYLOAD_END).contains(&entry_off) {
456 return Err(Error::Corruption { page_id: 0 });
457 }
458 let (key, _) = read_length_prefixed(buf, entry_off)?;
459 if key.len() > max_key_len() {
460 return Err(Error::Corruption { page_id: 0 });
461 }
462 Ok((key.to_vec(), right_child))
463}
464
465fn read_u64(buf: &[u8; PAGE_SIZE], offset: usize) -> u64 {
468 u64::from_le_bytes([
469 buf[offset],
470 buf[offset + 1],
471 buf[offset + 2],
472 buf[offset + 3],
473 buf[offset + 4],
474 buf[offset + 5],
475 buf[offset + 6],
476 buf[offset + 7],
477 ])
478}
479
480fn read_length_prefixed(buf: &[u8; PAGE_SIZE], offset: usize) -> Result<(&[u8], usize)> {
496 if offset >= PAYLOAD_END {
497 return Err(Error::Corruption { page_id: 0 });
498 }
499 let (len, after) = read_varint(buf, offset)?;
500 let len_usize = usize_from_u64(len)?;
501 let end = after
504 .checked_add(len_usize)
505 .ok_or(Error::Corruption { page_id: 0 })?;
506 if end > PAYLOAD_END {
507 return Err(Error::Corruption { page_id: 0 });
508 }
509 Ok((&buf[after..end], end))
510}
511
512pub fn validate_node(node: &DecodedNode) -> Result<()> {
520 validate_node_release(node)
521}
522
523fn validate_node_release(node: &DecodedNode) -> Result<()> {
524 match node.kind {
525 NodeKind::Leaf => {
526 if node.level != 0 {
527 return Err(Error::BTreeInvariantViolated {
528 reason: "leaf has non-zero level",
529 });
530 }
531 if node.leaves.len() > LEAF_SLOT_CAP {
532 return Err(Error::BTreeInvariantViolated {
533 reason: "leaf key count exceeds slot cap",
534 });
535 }
536 for w in node.leaves.windows(2) {
537 if w[0].key.as_slice() >= w[1].key.as_slice() {
538 return Err(Error::BTreeInvariantViolated {
539 reason: "leaf keys not strictly sorted",
540 });
541 }
542 }
543 }
544 NodeKind::Internal => {
545 if node.level == 0 {
546 return Err(Error::BTreeInvariantViolated {
547 reason: "internal node at level 0",
548 });
549 }
550 if node.internals.len() > INTERNAL_SLOT_CAP {
551 return Err(Error::BTreeInvariantViolated {
552 reason: "internal key count exceeds slot cap",
553 });
554 }
555 if node.children.len() != node.internals.len() + 1 {
556 return Err(Error::BTreeInvariantViolated {
557 reason: "internal children.len() != pivots+1",
558 });
559 }
560 for c in &node.children {
561 if *c == 0 {
562 return Err(Error::BTreeInvariantViolated {
563 reason: "internal node has zero child page-id",
564 });
565 }
566 }
567 for w in node.internals.windows(2) {
568 if w[0].key.as_slice() >= w[1].key.as_slice() {
569 return Err(Error::BTreeInvariantViolated {
570 reason: "internal keys not strictly sorted",
571 });
572 }
573 }
574 if node.next_sibling != 0 {
575 return Err(Error::BTreeInvariantViolated {
576 reason: "internal node has non-zero next_sibling",
577 });
578 }
579 }
580 }
581 Ok(())
582}
583
584#[must_use]
588pub fn varint_len(v: u64) -> usize {
589 let mut n: usize = 1;
590 let mut x = v >> 7;
591 while x != 0 {
592 n += 1;
593 x >>= 7;
594 }
595 n
596}
597
598fn write_varint(dst: &mut [u8], mut v: u64) -> usize {
601 let mut i = 0;
602 loop {
603 let mut byte = (v & 0x7F) as u8;
604 v >>= 7;
605 if v != 0 {
606 byte |= 0x80;
607 dst[i] = byte;
608 i += 1;
609 } else {
610 dst[i] = byte;
611 i += 1;
612 return i;
613 }
614 }
615}
616
617fn read_varint(buf: &[u8; PAGE_SIZE], offset: usize) -> Result<(u64, usize)> {
622 let mut value: u64 = 0;
623 let mut shift: u32 = 0;
624 let mut i = offset;
625 for _ in 0..10 {
626 if i >= PAYLOAD_END {
627 return Err(Error::Corruption { page_id: 0 });
628 }
629 let byte = buf[i];
630 i += 1;
631 let chunk = u64::from(byte & 0x7F);
632 value |= chunk << shift;
634 if (byte & 0x80) == 0 {
635 return Ok((value, i));
636 }
637 shift += 7;
638 if shift >= 64 {
639 return Err(Error::Corruption { page_id: 0 });
640 }
641 }
642 Err(Error::Corruption { page_id: 0 })
643}
644
645fn u32_from_usize(v: usize) -> u32 {
648 debug_assert!(u32::try_from(v).is_ok());
650 u32::try_from(v).unwrap_or(u32::MAX)
654}
655
656fn u16_from_usize(v: usize) -> u16 {
657 debug_assert!(u16::try_from(v).is_ok());
658 u16::try_from(v).unwrap_or(u16::MAX)
659}
660
661fn u64_from_usize(v: usize) -> u64 {
662 v as u64
663}
664
665fn usize_from_u64(v: u64) -> Result<usize> {
666 usize::try_from(v).map_err(|_| Error::Corruption { page_id: 0 })
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672
673 fn empty_leaf() -> DecodedNode {
674 DecodedNode {
675 kind: NodeKind::Leaf,
676 level: 0,
677 next_sibling: 0,
678 children: Vec::new(),
679 leaves: Vec::new(),
680 internals: Vec::new(),
681 }
682 }
683
684 fn leaf_with(entries: &[(&[u8], &[u8])]) -> DecodedNode {
685 let mut leaf = empty_leaf();
686 for (k, v) in entries {
687 leaf.leaves.push(LeafEntry {
688 key: k.to_vec(),
689 value: v.to_vec(),
690 });
691 }
692 leaf
693 }
694
695 #[test]
696 fn round_trip_empty_leaf() {
697 let leaf = empty_leaf();
698 let mut page = Page::zeroed();
699 encode_node(&leaf, &mut page).expect("encode");
700 let decoded = decode_node(page.as_bytes()).expect("decode");
701 assert_eq!(decoded.kind, NodeKind::Leaf);
702 assert_eq!(decoded.level, 0);
703 assert_eq!(decoded.next_sibling, 0);
704 assert!(decoded.leaves.is_empty());
705 }
706
707 #[test]
708 fn round_trip_populated_leaf() {
709 let leaf = leaf_with(&[(b"alpha", b"AAA"), (b"bravo", b"BBB"), (b"charlie", b"CCC")]);
710 let mut page = Page::zeroed();
711 encode_node(&leaf, &mut page).expect("encode");
712 let decoded = decode_node(page.as_bytes()).expect("decode");
713 assert_eq!(decoded.leaves.len(), 3);
714 assert_eq!(decoded.leaves[0].key.as_slice(), b"alpha");
715 assert_eq!(decoded.leaves[0].value.as_slice(), b"AAA");
716 assert_eq!(decoded.leaves[2].key.as_slice(), b"charlie");
717 }
718
719 #[test]
720 fn round_trip_internal() {
721 let mut internal = DecodedNode {
722 kind: NodeKind::Internal,
723 level: 1,
724 next_sibling: 0,
725 children: Vec::new(),
726 leaves: Vec::new(),
727 internals: Vec::new(),
728 };
729 for raw in [10u64, 20, 30, 40] {
731 internal.children.push(raw);
732 }
733 for k in [b"d".as_slice(), b"h", b"m"] {
734 internal.internals.push(InternalEntry { key: k.to_vec() });
735 }
736 let mut page = Page::zeroed();
737 encode_node(&internal, &mut page).expect("encode");
738 let decoded = decode_node(page.as_bytes()).expect("decode");
739 assert_eq!(decoded.kind, NodeKind::Internal);
740 assert_eq!(decoded.level, 1);
741 assert_eq!(decoded.internals.len(), 3);
742 assert_eq!(decoded.children.as_slice(), &[10, 20, 30, 40]);
743 assert_eq!(decoded.internals[0].key.as_slice(), b"d");
744 assert_eq!(decoded.internals[2].key.as_slice(), b"m");
745 }
746
747 #[test]
748 fn decode_rejects_unsorted_leaf() {
749 let mut page = Page::zeroed();
752 let buf = page.as_bytes_mut();
753 buf[OFF_PAGE_TYPE] = PAGE_TYPE_BTREE_LEAF;
754 buf[OFF_LEVEL] = 0;
755 buf[OFF_KEY_COUNT..OFF_KEY_COUNT + 2].copy_from_slice(&2u16.to_le_bytes());
756 let slot0 = u32_from_usize(PAYLOAD_END - 4);
759 let slot1 = u32_from_usize(PAYLOAD_END - 8);
760 buf[PAYLOAD_OFFSET..PAYLOAD_OFFSET + 4].copy_from_slice(&slot0.to_le_bytes());
761 buf[PAYLOAD_OFFSET + 4..PAYLOAD_OFFSET + 8].copy_from_slice(&slot1.to_le_bytes());
762 buf[PAYLOAD_END - 4] = 1;
764 buf[PAYLOAD_END - 3] = b'c';
765 buf[PAYLOAD_END - 2] = 1;
766 buf[PAYLOAD_END - 1] = b'x';
767 buf[PAYLOAD_END - 8] = 1;
769 buf[PAYLOAD_END - 7] = b'a';
770 buf[PAYLOAD_END - 6] = 1;
771 buf[PAYLOAD_END - 5] = b'y';
772 crate::pager::checksum::write_page_trailer(&mut page);
773 let err = decode_node(page.as_bytes()).expect_err("decode must reject");
774 assert!(matches!(err, Error::Corruption { .. }));
775 }
776
777 #[test]
778 fn varint_round_trip() {
779 for v in [0u64, 1, 127, 128, 0xFFFF, 0xDEAD_BEEF, u64::MAX] {
780 let mut buf = [0u8; 16];
781 let n = write_varint(&mut buf, v);
782 assert_eq!(n, varint_len(v));
783 let mut page = [0u8; PAGE_SIZE];
785 page[..n].copy_from_slice(&buf[..n]);
786 let (decoded, after) = read_varint(&page, 0).expect("decode varint");
787 assert_eq!(decoded, v);
788 assert_eq!(after, n);
789 }
790 }
791
792 #[test]
803 fn decode_node_varint_max_returns_corruption_not_panic() {
804 let mut page = Page::zeroed();
805 {
806 let buf = page.as_bytes_mut();
807 buf[OFF_PAGE_TYPE] = PAGE_TYPE_BTREE_LEAF;
808 buf[OFF_LEVEL] = 0;
809 buf[OFF_KEY_COUNT..OFF_KEY_COUNT + 2].copy_from_slice(&1u16.to_le_bytes());
813 let entry_off = PAYLOAD_END - 16;
818 let slot_off_bytes = u32_from_usize(entry_off).to_le_bytes();
819 buf[PAYLOAD_OFFSET..PAYLOAD_OFFSET + 4].copy_from_slice(&slot_off_bytes);
820 let varint = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01];
826 buf[entry_off..entry_off + varint.len()].copy_from_slice(&varint);
827 }
828 crate::pager::checksum::write_page_trailer(&mut page);
829 let result = decode_node(page.as_bytes());
830 match result {
831 Err(Error::Corruption { .. }) => {}
832 other => panic!(
833 "expected Err(Error::Corruption), got {:?} \
834 (a panic here would indicate the M13 #115 bug \
835 is still present in release builds)",
836 other.map(|_| "Ok(_)").unwrap_or("non-corruption err")
837 ),
838 }
839 }
840
841 #[test]
846 fn decode_internal_node_varint_max_returns_corruption() {
847 let mut page = Page::zeroed();
848 {
849 let buf = page.as_bytes_mut();
850 buf[OFF_PAGE_TYPE] = PAGE_TYPE_BTREE_INTERNAL;
851 buf[OFF_LEVEL] = 1;
852 buf[OFF_KEY_COUNT..OFF_KEY_COUNT + 2].copy_from_slice(&1u16.to_le_bytes());
853 buf[PAYLOAD_OFFSET..PAYLOAD_OFFSET + 8].copy_from_slice(&42u64.to_le_bytes());
855 let slot_base = PAYLOAD_OFFSET + INTERNAL_LEFTMOST_CHILD_BYTES;
857 let entry_off = PAYLOAD_END - 16;
858 buf[slot_base..slot_base + 4].copy_from_slice(&u32_from_usize(entry_off).to_le_bytes());
859 buf[slot_base + 4..slot_base + INTERNAL_SLOT_BYTES]
860 .copy_from_slice(&7u64.to_le_bytes());
861 let varint = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01];
863 buf[entry_off..entry_off + varint.len()].copy_from_slice(&varint);
864 }
865 crate::pager::checksum::write_page_trailer(&mut page);
866 let result = decode_node(page.as_bytes());
867 assert!(
868 matches!(result, Err(Error::Corruption { .. })),
869 "expected Err(Error::Corruption) on u64::MAX varint in internal slot"
870 );
871 }
872
873 #[test]
874 fn max_inline_value_is_below_payload() {
875 let k = 8;
876 let v = max_inline_value(k);
877 let entry_len = varint_len(u64_from_usize(k)) + k + varint_len(u64_from_usize(v)) + v;
880 let payload = entry_len + LEAF_SLOT_BYTES;
881 assert!(payload <= PAYLOAD_END - PAYLOAD_OFFSET);
882 }
883}