Skip to main content

btrfs_transaction/
buffer.rs

1//! # Mutable tree block wrapper
2//!
3//! An `ExtentBuffer` wraps a `nodesize`-length byte buffer representing a
4//! single btrfs tree block (node or leaf). It provides typed accessors for
5//! reading and writing header fields, item descriptors, key pointers, and raw
6//! item data regions.
7//!
8//! Unlike `btrfs_disk::tree::TreeBlock` which is a parsed, immutable snapshot,
9//! `ExtentBuffer` keeps the raw bytes and mutates them in place, which is what
10//! the write path needs for COW and item manipulation.
11
12use btrfs_disk::{
13    superblock::ChecksumType,
14    tree::{DiskKey, KeyType, TreeBlock},
15    util::{csum_tree_block, write_disk_key},
16};
17use bytes::{Buf, BufMut};
18use uuid::Uuid;
19
20/// Size of the on-disk tree block header (101 bytes).
21pub const HEADER_SIZE: usize = 101;
22
23/// Size of an item descriptor in a leaf (25 bytes): key (17) + offset (4) + size (4).
24pub const ITEM_SIZE: usize = 25;
25
26/// Size of a key pointer in an internal node (33 bytes): key (17) + blockptr (8) + generation (8).
27pub const KEY_PTR_SIZE: usize = 33;
28
29/// Size of an on-disk key (17 bytes): objectid (8) + type (1) + offset (8).
30pub const DISK_KEY_SIZE: usize = 17;
31
32/// Maximum B-tree depth (8 levels, 0-indexed: levels 0 through 7).
33pub const BTRFS_MAX_LEVEL: usize = 8;
34
35/// A mutable tree block backed by a byte buffer.
36///
37/// Provides field-level read/write access to the 101-byte header, item
38/// descriptors (for leaves), and key pointers (for nodes). The buffer is
39/// always exactly `nodesize` bytes.
40#[derive(Clone)]
41pub struct ExtentBuffer {
42    data: Vec<u8>,
43    /// Logical byte address of this block.
44    logical: u64,
45}
46
47impl ExtentBuffer {
48    /// Create an `ExtentBuffer` from raw bytes at the given logical address.
49    ///
50    /// # Panics
51    ///
52    /// Panics if `data` is empty.
53    #[must_use]
54    pub fn from_raw(data: Vec<u8>, logical: u64) -> Self {
55        assert!(!data.is_empty(), "ExtentBuffer: empty data");
56        Self { data, logical }
57    }
58
59    /// Create a zeroed `ExtentBuffer` of `nodesize` bytes at the given logical address.
60    #[must_use]
61    pub fn new_zeroed(nodesize: u32, logical: u64) -> Self {
62        Self {
63            data: vec![0u8; nodesize as usize],
64            logical,
65        }
66    }
67
68    /// Return the logical byte address of this block.
69    #[must_use]
70    pub fn logical(&self) -> u64 {
71        self.logical
72    }
73
74    /// Set the logical byte address of this block.
75    pub fn set_logical(&mut self, logical: u64) {
76        self.logical = logical;
77    }
78
79    /// Return the nodesize (length of the buffer).
80    #[must_use]
81    pub fn nodesize(&self) -> u32 {
82        self.data.len() as u32
83    }
84
85    /// Return a reference to the raw byte buffer.
86    #[must_use]
87    pub fn as_bytes(&self) -> &[u8] {
88        &self.data
89    }
90
91    /// Return a mutable reference to the raw byte buffer.
92    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
93        &mut self.data
94    }
95
96    /// Parse this buffer into a `TreeBlock` for read-only inspection.
97    #[must_use]
98    pub fn as_tree_block(&self) -> TreeBlock {
99        TreeBlock::parse(&self.data)
100    }
101
102    // --- Header field readers ---
103
104    /// Read the generation field from the header.
105    #[must_use]
106    pub fn generation(&self) -> u64 {
107        let mut b = &self.data[80..88];
108        b.get_u64_le()
109    }
110
111    /// Read the owner (tree ID) field from the header.
112    #[must_use]
113    pub fn owner(&self) -> u64 {
114        let mut b = &self.data[88..96];
115        b.get_u64_le()
116    }
117
118    /// Read the nritems field from the header.
119    #[must_use]
120    pub fn nritems(&self) -> u32 {
121        let mut b = &self.data[96..100];
122        b.get_u32_le()
123    }
124
125    /// Read the level field from the header.
126    #[must_use]
127    pub fn level(&self) -> u8 {
128        self.data[100]
129    }
130
131    /// Read the bytenr field from the header.
132    #[must_use]
133    pub fn bytenr(&self) -> u64 {
134        let mut b = &self.data[48..56];
135        b.get_u64_le()
136    }
137
138    /// Read the flags field from the header.
139    #[must_use]
140    pub fn flags(&self) -> u64 {
141        let mut b = &self.data[56..64];
142        b.get_u64_le()
143    }
144
145    /// Read the fsid from the header.
146    ///
147    /// # Panics
148    ///
149    /// Panics if the buffer is shorter than 48 bytes.
150    #[must_use]
151    pub fn fsid(&self) -> Uuid {
152        Uuid::from_bytes(self.data[32..48].try_into().unwrap())
153    }
154
155    /// Read the `chunk_tree_uuid` from the header.
156    ///
157    /// # Panics
158    ///
159    /// Panics if the buffer is shorter than 80 bytes.
160    #[must_use]
161    pub fn chunk_tree_uuid(&self) -> Uuid {
162        Uuid::from_bytes(self.data[64..80].try_into().unwrap())
163    }
164
165    // --- Header field writers ---
166
167    /// Write the generation field.
168    pub fn set_generation(&mut self, generation: u64) {
169        (&mut self.data[80..88]).put_u64_le(generation);
170    }
171
172    /// Write the owner (tree ID) field.
173    pub fn set_owner(&mut self, owner: u64) {
174        (&mut self.data[88..96]).put_u64_le(owner);
175    }
176
177    /// Write the nritems field.
178    pub fn set_nritems(&mut self, nritems: u32) {
179        (&mut self.data[96..100]).put_u32_le(nritems);
180    }
181
182    /// Write the level field.
183    pub fn set_level(&mut self, level: u8) {
184        self.data[100] = level;
185    }
186
187    /// Write the bytenr field.
188    pub fn set_bytenr(&mut self, bytenr: u64) {
189        (&mut self.data[48..56]).put_u64_le(bytenr);
190    }
191
192    /// Write the flags field.
193    pub fn set_flags(&mut self, flags: u64) {
194        (&mut self.data[56..64]).put_u64_le(flags);
195    }
196
197    /// Write the fsid.
198    pub fn set_fsid(&mut self, fsid: &Uuid) {
199        self.data[32..48].copy_from_slice(fsid.as_bytes());
200    }
201
202    /// Write the `chunk_tree_uuid`.
203    pub fn set_chunk_tree_uuid(&mut self, uuid: &Uuid) {
204        self.data[64..80].copy_from_slice(uuid.as_bytes());
205    }
206
207    // --- Leaf item accessors ---
208
209    /// Read the key of the item at the given slot index in a leaf.
210    ///
211    /// # Panics
212    ///
213    /// Panics if `slot` is out of bounds.
214    #[must_use]
215    pub fn item_key(&self, slot: usize) -> DiskKey {
216        let off = HEADER_SIZE + slot * ITEM_SIZE;
217        DiskKey::parse(&self.data, off)
218    }
219
220    /// Read the data offset field of the item at the given slot.
221    /// This offset is relative to byte 101 (immediately after the header).
222    #[must_use]
223    pub fn item_offset(&self, slot: usize) -> u32 {
224        let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE;
225        let mut b = &self.data[off..off + 4];
226        b.get_u32_le()
227    }
228
229    /// Read the data size field of the item at the given slot.
230    #[must_use]
231    pub fn item_size(&self, slot: usize) -> u32 {
232        let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE + 4;
233        let mut b = &self.data[off..off + 4];
234        b.get_u32_le()
235    }
236
237    /// Return the absolute byte offset within the block where item data starts.
238    /// The item's `offset` field is relative to byte 101 (`HEADER_SIZE`).
239    #[must_use]
240    pub fn item_data_offset(&self, slot: usize) -> usize {
241        HEADER_SIZE + self.item_offset(slot) as usize
242    }
243
244    /// Return a slice of the item's data payload.
245    #[must_use]
246    pub fn item_data(&self, slot: usize) -> &[u8] {
247        let start = self.item_data_offset(slot);
248        let size = self.item_size(slot) as usize;
249        &self.data[start..start + size]
250    }
251
252    /// Return a mutable slice of the item's data payload.
253    pub fn item_data_mut(&mut self, slot: usize) -> &mut [u8] {
254        let start = self.item_data_offset(slot);
255        let size = self.item_size(slot) as usize;
256        &mut self.data[start..start + size]
257    }
258
259    /// Write the key for an item at the given slot.
260    pub fn set_item_key(&mut self, slot: usize, key: &DiskKey) {
261        let off = HEADER_SIZE + slot * ITEM_SIZE;
262        write_disk_key(&mut self.data, off, key);
263    }
264
265    /// Write the data offset for an item at the given slot.
266    pub fn set_item_offset(&mut self, slot: usize, offset: u32) {
267        let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE;
268        (&mut self.data[off..off + 4]).put_u32_le(offset);
269    }
270
271    /// Write the data size for an item at the given slot.
272    pub fn set_item_size(&mut self, slot: usize, size: u32) {
273        let off = HEADER_SIZE + slot * ITEM_SIZE + DISK_KEY_SIZE + 4;
274        (&mut self.data[off..off + 4]).put_u32_le(size);
275    }
276
277    // --- Node key pointer accessors ---
278
279    /// Read the key of the key pointer at the given slot in a node.
280    #[must_use]
281    pub fn key_ptr_key(&self, slot: usize) -> DiskKey {
282        let off = HEADER_SIZE + slot * KEY_PTR_SIZE;
283        DiskKey::parse(&self.data, off)
284    }
285
286    /// Read the blockptr of the key pointer at the given slot.
287    #[must_use]
288    pub fn key_ptr_blockptr(&self, slot: usize) -> u64 {
289        let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE;
290        let mut b = &self.data[off..off + 8];
291        b.get_u64_le()
292    }
293
294    /// Read the generation of the key pointer at the given slot.
295    #[must_use]
296    pub fn key_ptr_generation(&self, slot: usize) -> u64 {
297        let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE + 8;
298        let mut b = &self.data[off..off + 8];
299        b.get_u64_le()
300    }
301
302    /// Write the key for a key pointer at the given slot.
303    pub fn set_key_ptr_key(&mut self, slot: usize, key: &DiskKey) {
304        let off = HEADER_SIZE + slot * KEY_PTR_SIZE;
305        write_disk_key(&mut self.data, off, key);
306    }
307
308    /// Write the blockptr for a key pointer at the given slot.
309    pub fn set_key_ptr_blockptr(&mut self, slot: usize, blockptr: u64) {
310        let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE;
311        (&mut self.data[off..off + 8]).put_u64_le(blockptr);
312    }
313
314    /// Write the generation for a key pointer at the given slot.
315    pub fn set_key_ptr_generation(&mut self, slot: usize, generation: u64) {
316        let off = HEADER_SIZE + slot * KEY_PTR_SIZE + DISK_KEY_SIZE + 8;
317        (&mut self.data[off..off + 8]).put_u64_le(generation);
318    }
319
320    /// Write a complete key pointer at the given slot.
321    pub fn set_key_ptr(
322        &mut self,
323        slot: usize,
324        key: &DiskKey,
325        blockptr: u64,
326        generation: u64,
327    ) {
328        self.set_key_ptr_key(slot, key);
329        self.set_key_ptr_blockptr(slot, blockptr);
330        self.set_key_ptr_generation(slot, generation);
331    }
332
333    // --- Leaf space management ---
334
335    /// Compute the free space in a leaf block.
336    ///
337    /// Free space is the gap between the end of the item descriptor array and
338    /// the start of the first item's data (which grows backward from the end
339    /// of the block).
340    #[must_use]
341    pub fn leaf_free_space(&self) -> u32 {
342        let nritems = self.nritems() as usize;
343        if nritems == 0 {
344            // All space after the header is free
345            return self.nodesize() - HEADER_SIZE as u32;
346        }
347        let items_end = (HEADER_SIZE + nritems * ITEM_SIZE) as u32;
348        let data_start = self.leaf_data_end();
349        data_start.saturating_sub(items_end)
350    }
351
352    /// Return the absolute byte offset of the first (lowest-offset) data byte
353    /// in the leaf. This is `HEADER_SIZE + item[nritems-1].offset` (since the
354    /// last item has the lowest offset, as data grows backward).
355    ///
356    /// For an empty leaf, returns `nodesize`.
357    #[must_use]
358    pub fn leaf_data_end(&self) -> u32 {
359        let nritems = self.nritems();
360        if nritems == 0 {
361            return self.nodesize();
362        }
363        // The last item has the smallest offset (data grows backward)
364        HEADER_SIZE as u32 + self.item_offset(nritems as usize - 1)
365    }
366
367    // --- Checksum ---
368
369    /// Recompute the checksum using `csum_type` and write it into the header.
370    pub fn update_checksum(&mut self, csum_type: ChecksumType) {
371        csum_tree_block(&mut self.data, csum_type);
372    }
373
374    // --- Bulk data operations ---
375
376    /// Copy a range of bytes within this buffer.
377    ///
378    /// Equivalent to `memmove`: handles overlapping regions correctly.
379    pub fn copy_within(&mut self, src: core::ops::Range<usize>, dest: usize) {
380        self.data.copy_within(src, dest);
381    }
382
383    /// Fill a range with zeros.
384    pub fn zero_range(&mut self, offset: usize, len: usize) {
385        self.data[offset..offset + len].fill(0);
386    }
387
388    /// Return true if this is a leaf (level == 0).
389    #[must_use]
390    pub fn is_leaf(&self) -> bool {
391        self.level() == 0
392    }
393
394    /// Return true if this is an internal node (level > 0).
395    #[must_use]
396    pub fn is_node(&self) -> bool {
397        self.level() > 0
398    }
399
400    /// Maximum number of key pointers that can fit in this node.
401    #[must_use]
402    pub fn max_key_ptrs(&self) -> u32 {
403        (self.nodesize() - HEADER_SIZE as u32) / KEY_PTR_SIZE as u32
404    }
405
406    /// Validate leaf structural invariants.
407    ///
408    /// Checks that are cheap (O(nritems), no I/O) but verify the
409    /// fundamental on-disk layout rules:
410    ///
411    /// - `level == 0`
412    /// - `bytenr == logical`
413    /// - Keys are in ascending order
414    /// - Data offsets are in descending order (item 0 highest)
415    /// - Item data regions do not overlap each other
416    /// - Item data regions fit within the block
417    /// - No overlap between item descriptors and item data
418    ///
419    /// Returns `Ok(())` if valid, or `Err(description)` on the first
420    /// violation found.
421    pub fn check_leaf(&self) -> Result<(), String> {
422        if self.level() != 0 {
423            return Err(format!(
424                "check_leaf: block at {} has level {} (expected 0)",
425                self.logical,
426                self.level()
427            ));
428        }
429        if self.bytenr() != self.logical {
430            return Err(format!(
431                "check_leaf: bytenr {} != logical {}",
432                self.bytenr(),
433                self.logical
434            ));
435        }
436        let nritems = self.nritems() as usize;
437        let nodesize = self.nodesize() as usize;
438
439        // Item descriptors must fit within the block.
440        let items_end = HEADER_SIZE + nritems * ITEM_SIZE;
441        if items_end > nodesize {
442            return Err(format!(
443                "check_leaf: {nritems} items need {items_end} bytes, \
444                 block is only {nodesize}",
445            ));
446        }
447
448        if nritems == 0 {
449            return Ok(());
450        }
451
452        // Keys must be in ascending order.
453        for i in 1..nritems {
454            let prev = self.item_key(i - 1);
455            let curr = self.item_key(i);
456            if key_cmp(&prev, &curr) != std::cmp::Ordering::Less {
457                return Err(format!(
458                    "check_leaf: key at slot {} ({:?}) >= key at slot {i} ({curr:?})",
459                    i - 1,
460                    prev,
461                ));
462            }
463        }
464
465        // Data offsets must be descending, and data must fit in the block.
466        // Item 0 has the highest data offset; item N-1 the lowest.
467        let first_data_end = HEADER_SIZE
468            + self.item_offset(0) as usize
469            + self.item_size(0) as usize;
470        if first_data_end > nodesize {
471            return Err(format!(
472                "check_leaf: item 0 data ends at {first_data_end}, \
473                 beyond block size {nodesize}",
474            ));
475        }
476
477        for i in 1..nritems {
478            let prev_off = self.item_offset(i - 1);
479            let curr_off = self.item_offset(i);
480            let curr_size = self.item_size(i);
481            // Descending offsets (equal allowed for zero-size items).
482            if curr_off > prev_off {
483                return Err(format!(
484                    "check_leaf: offset at slot {i} ({curr_off}) > \
485                     offset at slot {} ({prev_off})",
486                    i - 1,
487                ));
488            }
489            // Current item's data must not overlap the previous item's data.
490            // prev occupies [prev_off, prev_off + prev_size).
491            // curr occupies [curr_off, curr_off + curr_size).
492            // Since offsets descend, curr_off + curr_size must <= prev_off.
493            if curr_off + curr_size > prev_off {
494                return Err(format!(
495                    "check_leaf: item {i} data [{curr_off}..{}] overlaps \
496                     item {} data [{prev_off}..{}]",
497                    curr_off + curr_size,
498                    i - 1,
499                    prev_off + self.item_size(i - 1),
500                ));
501            }
502        }
503
504        // Item descriptors must not overlap the data area.
505        let data_start = HEADER_SIZE + self.item_offset(nritems - 1) as usize;
506        if items_end > data_start {
507            return Err(format!(
508                "check_leaf: item descriptors end at {items_end} but \
509                 data starts at {data_start}",
510            ));
511        }
512
513        Ok(())
514    }
515
516    /// Validate internal node structural invariants.
517    ///
518    /// Checks:
519    ///
520    /// - `level > 0`
521    /// - `bytenr == logical`
522    /// - `nritems <= max_key_ptrs`
523    /// - Key pointers are in ascending order
524    /// - All `blockptr` values are nonzero
525    ///
526    /// Returns `Ok(())` if valid, or `Err(description)` on the first
527    /// violation found.
528    pub fn check_node(&self) -> Result<(), String> {
529        if self.level() == 0 {
530            return Err(format!(
531                "check_node: block at {} has level 0 (expected > 0)",
532                self.logical,
533            ));
534        }
535        if self.bytenr() != self.logical {
536            return Err(format!(
537                "check_node: bytenr {} != logical {}",
538                self.bytenr(),
539                self.logical
540            ));
541        }
542        let nritems = self.nritems() as usize;
543        let max_ptrs = self.max_key_ptrs() as usize;
544        if nritems > max_ptrs {
545            return Err(format!(
546                "check_node: {nritems} key pointers exceeds maximum {max_ptrs}",
547            ));
548        }
549
550        for i in 0..nritems {
551            if self.key_ptr_blockptr(i) == 0 {
552                return Err(format!("check_node: slot {i} has blockptr 0"));
553            }
554        }
555
556        for i in 1..nritems {
557            let prev = self.key_ptr_key(i - 1);
558            let curr = self.key_ptr_key(i);
559            if key_cmp(&prev, &curr) != std::cmp::Ordering::Less {
560                return Err(format!(
561                    "check_node: key at slot {} ({:?}) >= key at slot {i} ({curr:?})",
562                    i - 1,
563                    prev,
564                ));
565            }
566        }
567
568        Ok(())
569    }
570
571    /// Validate this block as either a leaf or a node based on its level.
572    ///
573    /// Returns `Ok(())` if valid, or `Err(description)` on violation.
574    pub fn check(&self) -> Result<(), String> {
575        if self.level() == 0 {
576            self.check_leaf()
577        } else {
578            self.check_node()
579        }
580    }
581}
582
583/// Assert that a leaf block is structurally valid.
584///
585/// This is a `debug_assert!`-level check: compiled out in release builds.
586/// Walks all items to verify key ordering, data offset ordering, no
587/// overlaps, and descriptor/data region separation.
588#[inline]
589pub fn debug_assert_leaf_valid(eb: &ExtentBuffer) {
590    debug_assert!(
591        eb.check_leaf().is_ok(),
592        "leaf invariant violation at logical {}: {}",
593        eb.logical(),
594        eb.check_leaf().unwrap_err(),
595    );
596}
597
598/// Assert that an internal node is structurally valid.
599///
600/// This is a `debug_assert!`-level check: compiled out in release builds.
601/// Walks all key pointers to verify key ordering, nonzero blockptrs, and
602/// capacity limits.
603#[inline]
604pub fn debug_assert_node_valid(eb: &ExtentBuffer) {
605    debug_assert!(
606        eb.check_node().is_ok(),
607        "node invariant violation at logical {}: {}",
608        eb.logical(),
609        eb.check_node().unwrap_err(),
610    );
611}
612
613/// Assert that a tree block (leaf or node) is structurally valid.
614///
615/// Dispatches to [`debug_assert_leaf_valid`] or [`debug_assert_node_valid`]
616/// based on the block's level.
617#[inline]
618pub fn debug_assert_block_valid(eb: &ExtentBuffer) {
619    debug_assert!(
620        eb.check().is_ok(),
621        "block invariant violation at logical {}: {}",
622        eb.logical(),
623        eb.check().unwrap_err(),
624    );
625}
626
627#[allow(clippy::missing_fields_in_debug)]
628impl std::fmt::Debug for ExtentBuffer {
629    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630        f.debug_struct("ExtentBuffer")
631            .field("logical", &self.logical)
632            .field("level", &self.level())
633            .field("nritems", &self.nritems())
634            .field("generation", &self.generation())
635            .field("owner", &self.owner())
636            .finish()
637    }
638}
639
640/// Compare two `DiskKey` values as a `(objectid, type, offset)` tuple.
641///
642/// Returns `Ordering::Less`, `Equal`, or `Greater`.
643#[must_use]
644pub fn key_cmp(a: &DiskKey, b: &DiskKey) -> std::cmp::Ordering {
645    a.objectid
646        .cmp(&b.objectid)
647        .then_with(|| a.key_type.to_raw().cmp(&b.key_type.to_raw()))
648        .then_with(|| a.offset.cmp(&b.offset))
649}
650
651/// The minimum possible key.
652#[must_use]
653pub fn min_key() -> DiskKey {
654    DiskKey {
655        objectid: 0,
656        key_type: KeyType::from_raw(0),
657        offset: 0,
658    }
659}
660
661/// The maximum possible key.
662#[must_use]
663pub fn max_key() -> DiskKey {
664    DiskKey {
665        objectid: u64::MAX,
666        key_type: KeyType::from_raw(u8::MAX),
667        offset: u64::MAX,
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674
675    fn make_leaf(
676        nodesize: u32,
677        nritems: u32,
678        generation: u64,
679        owner: u64,
680    ) -> ExtentBuffer {
681        let mut eb = ExtentBuffer::new_zeroed(nodesize, 65536);
682        eb.set_generation(generation);
683        eb.set_owner(owner);
684        eb.set_nritems(nritems);
685        eb.set_level(0);
686        eb.set_bytenr(65536);
687        eb
688    }
689
690    fn make_node(
691        nodesize: u32,
692        nritems: u32,
693        level: u8,
694        generation: u64,
695    ) -> ExtentBuffer {
696        let mut eb = ExtentBuffer::new_zeroed(nodesize, 131072);
697        eb.set_generation(generation);
698        eb.set_owner(2);
699        eb.set_nritems(nritems);
700        eb.set_level(level);
701        eb.set_bytenr(131072);
702        eb
703    }
704
705    #[test]
706    fn header_round_trip() {
707        let mut eb = ExtentBuffer::new_zeroed(16384, 65536);
708        eb.set_generation(42);
709        eb.set_owner(5);
710        eb.set_nritems(10);
711        eb.set_level(0);
712        eb.set_bytenr(65536);
713        eb.set_flags(1);
714
715        assert_eq!(eb.generation(), 42);
716        assert_eq!(eb.owner(), 5);
717        assert_eq!(eb.nritems(), 10);
718        assert_eq!(eb.level(), 0);
719        assert_eq!(eb.bytenr(), 65536);
720        assert_eq!(eb.flags(), 1);
721        assert_eq!(eb.logical(), 65536);
722        assert_eq!(eb.nodesize(), 16384);
723        assert!(eb.is_leaf());
724        assert!(!eb.is_node());
725    }
726
727    #[test]
728    fn uuid_round_trip() {
729        let mut eb = ExtentBuffer::new_zeroed(16384, 0);
730        let fsid =
731            Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
732        let ctu =
733            Uuid::parse_str("01234567-89ab-cdef-0123-456789abcdef").unwrap();
734        eb.set_fsid(&fsid);
735        eb.set_chunk_tree_uuid(&ctu);
736        assert_eq!(eb.fsid(), fsid);
737        assert_eq!(eb.chunk_tree_uuid(), ctu);
738    }
739
740    #[test]
741    fn item_accessors() {
742        let mut eb = make_leaf(16384, 2, 7, 5);
743        let key0 = DiskKey {
744            objectid: 256,
745            key_type: KeyType::InodeItem,
746            offset: 0,
747        };
748        let key1 = DiskKey {
749            objectid: 256,
750            key_type: KeyType::DirItem,
751            offset: 100,
752        };
753
754        // Item 0: data at end of block, size 160
755        let data_off_0 = 16384 - HEADER_SIZE as u32 - 160;
756        eb.set_item_key(0, &key0);
757        eb.set_item_offset(0, data_off_0);
758        eb.set_item_size(0, 160);
759
760        // Item 1: data before item 0's data, size 50
761        let data_off_1 = data_off_0 - 50;
762        eb.set_item_key(1, &key1);
763        eb.set_item_offset(1, data_off_1);
764        eb.set_item_size(1, 50);
765
766        // Verify reads
767        let k0 = eb.item_key(0);
768        assert_eq!(k0.objectid, 256);
769        assert_eq!(k0.key_type, KeyType::InodeItem);
770        assert_eq!(k0.offset, 0);
771        assert_eq!(eb.item_offset(0), data_off_0);
772        assert_eq!(eb.item_size(0), 160);
773
774        let k1 = eb.item_key(1);
775        assert_eq!(k1.objectid, 256);
776        assert_eq!(k1.key_type, KeyType::DirItem);
777        assert_eq!(k1.offset, 100);
778        assert_eq!(eb.item_offset(1), data_off_1);
779        assert_eq!(eb.item_size(1), 50);
780
781        // Verify data slices
782        assert_eq!(eb.item_data(0).len(), 160);
783        assert_eq!(eb.item_data(1).len(), 50);
784
785        // Write and read back data
786        eb.item_data_mut(0)[0] = 0xAA;
787        eb.item_data_mut(1)[0] = 0xBB;
788        assert_eq!(eb.item_data(0)[0], 0xAA);
789        assert_eq!(eb.item_data(1)[0], 0xBB);
790    }
791
792    #[test]
793    fn key_ptr_accessors() {
794        let mut eb = make_node(16384, 3, 1, 10);
795
796        for i in 0..3u64 {
797            let key = DiskKey {
798                objectid: i + 1,
799                key_type: KeyType::RootItem,
800                offset: 0,
801            };
802            eb.set_key_ptr(i as usize, &key, (i + 1) * 65536, 10 - i);
803        }
804
805        for i in 0..3u64 {
806            let k = eb.key_ptr_key(i as usize);
807            assert_eq!(k.objectid, i + 1);
808            assert_eq!(k.key_type, KeyType::RootItem);
809            assert_eq!(eb.key_ptr_blockptr(i as usize), (i + 1) * 65536);
810            assert_eq!(eb.key_ptr_generation(i as usize), 10 - i);
811        }
812    }
813
814    #[test]
815    fn leaf_free_space_empty() {
816        let eb = make_leaf(16384, 0, 1, 5);
817        assert_eq!(eb.leaf_free_space(), 16384 - HEADER_SIZE as u32);
818    }
819
820    #[test]
821    fn leaf_free_space_with_items() {
822        let mut eb = make_leaf(4096, 1, 1, 5);
823        // One item with 100 bytes of data, placed at the end
824        let data_off = 4096 - HEADER_SIZE as u32 - 100;
825        eb.set_item_key(
826            0,
827            &DiskKey {
828                objectid: 1,
829                key_type: KeyType::InodeItem,
830                offset: 0,
831            },
832        );
833        eb.set_item_offset(0, data_off);
834        eb.set_item_size(0, 100);
835
836        // Free space = data_start - items_end
837        // items_end = 101 + 1 * 25 = 126
838        // data_start = 101 + data_off
839        let expected = (HEADER_SIZE as u32 + data_off)
840            - (HEADER_SIZE as u32 + ITEM_SIZE as u32);
841        assert_eq!(eb.leaf_free_space(), expected);
842    }
843
844    #[test]
845    fn key_comparison() {
846        let a = DiskKey {
847            objectid: 1,
848            key_type: KeyType::InodeItem,
849            offset: 0,
850        };
851        let b = DiskKey {
852            objectid: 2,
853            key_type: KeyType::InodeItem,
854            offset: 0,
855        };
856        assert_eq!(key_cmp(&a, &b), std::cmp::Ordering::Less);
857        assert_eq!(key_cmp(&b, &a), std::cmp::Ordering::Greater);
858        assert_eq!(key_cmp(&a, &a), std::cmp::Ordering::Equal);
859
860        // Same objectid, different type
861        let c = DiskKey {
862            objectid: 1,
863            key_type: KeyType::DirItem,
864            offset: 0,
865        };
866        assert_eq!(key_cmp(&a, &c), std::cmp::Ordering::Less);
867    }
868
869    #[test]
870    fn min_max_keys() {
871        assert_eq!(key_cmp(&min_key(), &max_key()), std::cmp::Ordering::Less);
872        assert_eq!(key_cmp(&min_key(), &min_key()), std::cmp::Ordering::Equal);
873    }
874
875    #[test]
876    fn checksum_round_trip() {
877        let mut eb = make_leaf(4096, 0, 1, 5);
878        eb.set_bytenr(65536);
879        eb.update_checksum(ChecksumType::Crc32);
880        // Verify that the checksum region is non-zero
881        assert_ne!(&eb.as_bytes()[0..4], &[0, 0, 0, 0]);
882        // Re-checksum should be idempotent
883        let csum1: [u8; 4] = eb.as_bytes()[0..4].try_into().unwrap();
884        eb.update_checksum(ChecksumType::Crc32);
885        let csum2: [u8; 4] = eb.as_bytes()[0..4].try_into().unwrap();
886        assert_eq!(csum1, csum2);
887    }
888
889    #[test]
890    fn checksum_dispatches_per_algorithm() {
891        // For each non-CRC32C algorithm, the checksum bytes should fill
892        // the algorithm's `size()` and the rest of the 32-byte field
893        // must be zero.
894        for csum_type in [
895            ChecksumType::Xxhash,
896            ChecksumType::Sha256,
897            ChecksumType::Blake2,
898        ] {
899            let mut eb = make_leaf(4096, 0, 1, 5);
900            eb.set_bytenr(65536);
901            eb.update_checksum(csum_type);
902            let n = csum_type.size();
903            assert!(
904                eb.as_bytes()[..n].iter().any(|&b| b != 0),
905                "{csum_type:?}: hash bytes are all zero",
906            );
907            assert!(
908                eb.as_bytes()[n..32].iter().all(|&b| b == 0),
909                "{csum_type:?}: tail of csum field is not zero",
910            );
911        }
912    }
913
914    #[test]
915    fn clone_independence() {
916        let mut eb = make_leaf(4096, 0, 1, 5);
917        let eb2 = eb.clone();
918        eb.set_generation(999);
919        assert_eq!(eb.generation(), 999);
920        assert_eq!(eb2.generation(), 1);
921    }
922
923    #[test]
924    fn as_tree_block_parse() {
925        let mut eb = make_leaf(4096, 1, 7, 5);
926        eb.set_bytenr(65536);
927        let key = DiskKey {
928            objectid: 256,
929            key_type: KeyType::InodeItem,
930            offset: 0,
931        };
932        let data_off = 4096 - HEADER_SIZE as u32 - 160;
933        eb.set_item_key(0, &key);
934        eb.set_item_offset(0, data_off);
935        eb.set_item_size(0, 160);
936
937        let tb = eb.as_tree_block();
938        match &tb {
939            TreeBlock::Leaf { header, items, .. } => {
940                assert_eq!(header.generation, 7);
941                assert_eq!(items.len(), 1);
942                assert_eq!(items[0].key.objectid, 256);
943            }
944            TreeBlock::Node { .. } => panic!("expected leaf"),
945        }
946    }
947
948    #[test]
949    fn copy_within_and_zero() {
950        let mut eb = ExtentBuffer::new_zeroed(256, 0);
951        eb.as_bytes_mut()[10] = 0xAA;
952        eb.as_bytes_mut()[11] = 0xBB;
953        eb.copy_within(10..12, 20);
954        assert_eq!(eb.as_bytes()[20], 0xAA);
955        assert_eq!(eb.as_bytes()[21], 0xBB);
956        eb.zero_range(20, 2);
957        assert_eq!(eb.as_bytes()[20], 0);
958        assert_eq!(eb.as_bytes()[21], 0);
959    }
960
961    #[test]
962    fn check_leaf_valid() {
963        let mut eb = make_leaf(4096, 0, 1, 5);
964        eb.set_bytenr(65536);
965        assert!(eb.check_leaf().is_ok());
966
967        // Add two properly-formed items.
968        let mut data_end = 4096u32;
969        let key0 = DiskKey {
970            objectid: 1,
971            key_type: KeyType::InodeItem,
972            offset: 0,
973        };
974        data_end -= 100;
975        eb.set_item_key(0, &key0);
976        eb.set_item_offset(0, data_end - HEADER_SIZE as u32);
977        eb.set_item_size(0, 100);
978
979        let key1 = DiskKey {
980            objectid: 2,
981            key_type: KeyType::InodeItem,
982            offset: 0,
983        };
984        data_end -= 50;
985        eb.set_item_key(1, &key1);
986        eb.set_item_offset(1, data_end - HEADER_SIZE as u32);
987        eb.set_item_size(1, 50);
988
989        eb.set_nritems(2);
990        assert!(eb.check_leaf().is_ok());
991    }
992
993    #[test]
994    fn check_leaf_bad_key_order() {
995        let mut eb = make_leaf(4096, 0, 1, 5);
996        eb.set_bytenr(65536);
997        eb.set_nritems(2);
998
999        // Insert keys out of order.
1000        let key0 = DiskKey {
1001            objectid: 10,
1002            key_type: KeyType::InodeItem,
1003            offset: 0,
1004        };
1005        let key1 = DiskKey {
1006            objectid: 5,
1007            key_type: KeyType::InodeItem,
1008            offset: 0,
1009        };
1010        eb.set_item_key(0, &key0);
1011        eb.set_item_offset(0, 4096 - HEADER_SIZE as u32 - 50);
1012        eb.set_item_size(0, 50);
1013        eb.set_item_key(1, &key1);
1014        eb.set_item_offset(1, 4096 - HEADER_SIZE as u32 - 100);
1015        eb.set_item_size(1, 50);
1016
1017        let err = eb.check_leaf().unwrap_err();
1018        assert!(err.contains("key at slot 0"), "{err}");
1019    }
1020
1021    #[test]
1022    fn check_leaf_bytenr_mismatch() {
1023        let mut eb = make_leaf(4096, 0, 1, 5);
1024        eb.set_bytenr(99999); // doesn't match logical (65536)
1025        let err = eb.check_leaf().unwrap_err();
1026        assert!(err.contains("bytenr"), "{err}");
1027    }
1028
1029    #[test]
1030    fn check_node_valid() {
1031        let mut eb = make_node(4096, 2, 1, 10);
1032        let key0 = DiskKey {
1033            objectid: 1,
1034            key_type: KeyType::RootItem,
1035            offset: 0,
1036        };
1037        let key1 = DiskKey {
1038            objectid: 100,
1039            key_type: KeyType::RootItem,
1040            offset: 0,
1041        };
1042        eb.set_key_ptr(0, &key0, 65536, 10);
1043        eb.set_key_ptr(1, &key1, 131072, 10);
1044        assert!(eb.check_node().is_ok());
1045    }
1046
1047    #[test]
1048    fn check_node_bad_key_order() {
1049        let mut eb = make_node(4096, 2, 1, 10);
1050        let key0 = DiskKey {
1051            objectid: 100,
1052            key_type: KeyType::RootItem,
1053            offset: 0,
1054        };
1055        let key1 = DiskKey {
1056            objectid: 1,
1057            key_type: KeyType::RootItem,
1058            offset: 0,
1059        };
1060        eb.set_key_ptr(0, &key0, 65536, 10);
1061        eb.set_key_ptr(1, &key1, 131072, 10);
1062        let err = eb.check_node().unwrap_err();
1063        assert!(err.contains("key at slot 0"), "{err}");
1064    }
1065
1066    #[test]
1067    fn check_node_zero_blockptr() {
1068        let mut eb = make_node(4096, 1, 1, 10);
1069        let key0 = DiskKey {
1070            objectid: 1,
1071            key_type: KeyType::RootItem,
1072            offset: 0,
1073        };
1074        eb.set_key_ptr_key(0, &key0);
1075        eb.set_key_ptr_blockptr(0, 0);
1076        eb.set_key_ptr_generation(0, 10);
1077        let err = eb.check_node().unwrap_err();
1078        assert!(err.contains("blockptr 0"), "{err}");
1079    }
1080
1081    #[test]
1082    fn check_dispatches_by_level() {
1083        let mut leaf = make_leaf(4096, 0, 1, 5);
1084        leaf.set_bytenr(65536);
1085        assert!(leaf.check().is_ok());
1086
1087        let mut node = make_node(4096, 1, 1, 10);
1088        let key = DiskKey {
1089            objectid: 1,
1090            key_type: KeyType::RootItem,
1091            offset: 0,
1092        };
1093        node.set_key_ptr(0, &key, 65536, 10);
1094        assert!(node.check().is_ok());
1095    }
1096}