bao_tree/
lib.rs

1//! # Efficient BLAKE3 based verified streaming
2//!
3//! This crate is similar to the [bao crate](https://crates.io/crates/bao), but
4//! takes a slightly different approach.
5//!
6//! The core struct is [BaoTree], which describes the geometry of the tree and
7//! various ways to traverse it. An individual tree node is identified by
8//! [TreeNode], which is just a newtype wrapper for an u64.
9//!
10//! [TreeNode] provides various helpers to e.g. get the offset of a node in
11//! different traversal orders.
12//!
13//! There are newtypes for the different kinds of integers used in the
14//! tree:
15//! [ChunkNum] is an u64 number of chunks,
16//! [TreeNode] is an u64 tree node identifier,
17//! and [BlockSize] is the log base 2 of the chunk group size.
18//!
19//! All this is then used in the [io] module to implement the actual io, both
20//! [synchronous](io::sync) and [asynchronous](io::tokio).
21//!
22//! # Basic usage
23//!
24//! The basic workflow is like this: you have some existing data, for which
25//! you want to enable verified streaming. This data can either be in memory,
26//! in a file, or even a remote resource such as an HTTP server.
27//!
28//! ## Outboard creation
29//!
30//! You create an outboard using the [CreateOutboard](io::sync::CreateOutboard)
31//! trait. It has functions to [create](io::sync::CreateOutboard::create) an
32//! outboard from scratch or to [initialize](io::sync::CreateOutboard::init_from)
33//! data and root hash from existing data.
34//!
35//! ## Serving requests
36//!
37//! You serve streaming requests by using the
38//! [encode_ranges](io::sync::encode_ranges) or
39//! [encode_ranges_validated](io::sync::encode_ranges_validated) functions
40//! in the sync or async io module. For this you need data and a matching
41//! outboard.
42//!
43//! The difference between the two functions is that the validated version
44//! will check the hash of each chunk against the bao tree encoded in the
45//! outboard, so you will detect data corruption before sending out data
46//! to the requester. When using the unvalidated version, you might send out
47//! corrupted data without ever noticing and earn a bad reputation.
48//!
49//! Due to the speed of the blake3 hash function, validation is not a
50//! significant performance overhead compared to network operations and
51//! encryption.
52//!
53//! The requester will send a set of chunk ranges they are interested in.
54//! To compute chunk ranges from byte ranges, there is a helper function
55//! [round_up_to_chunks](io::round_up_to_chunks) that takes a byte range and
56//! rounds up to chunk ranges.
57//!
58//! If you just want to stream the entire blob, you can use [ChunkRanges::all]
59//! as the range.
60//!
61//! ## Processing requests
62//!
63//! You process requests by using the [decode_ranges](io::sync::decode_ranges)
64//! function in the sync or async io module. This function requires prior
65//! knowledge of the tree geometry (total data size and block size). A common
66//! way to get this information is to have the block size as a common parameter
67//! of both sides, and send the total data size as a prefix of the encoded data.
68//! E.g. the original bao crate uses a little endian u64 as the prefix.
69//!
70//! This function will perform validation in any case, there is no variant
71//! that skips validation since that would defeat the purpose of verified
72//! streaming.
73//!
74//! ## Simple end to end example
75//!
76//! ```no_run
77//! use bao_tree::{
78//!     io::{
79//!         outboard::PreOrderOutboard,
80//!         round_up_to_chunks,
81//!         sync::{decode_ranges, encode_ranges_validated, valid_ranges, CreateOutboard},
82//!     },
83//!     BlockSize, ByteRanges, ChunkRanges,
84//! };
85//! use std::io;
86//!
87//! /// Use a block size of 16 KiB, a good default for most cases
88//! const BLOCK_SIZE: BlockSize = BlockSize::from_chunk_log(4);
89//!
90//! # fn main() -> io::Result<()> {
91//! // The file we want to serve
92//! let file = std::fs::File::open("video.mp4")?;
93//! // Create an outboard for the file, using the current size
94//! let ob = PreOrderOutboard::<Vec<u8>>::create(&file, BLOCK_SIZE)?;
95//! // Encode the first 100000 bytes of the file
96//! let ranges = ByteRanges::from(0..100000);
97//! let ranges = round_up_to_chunks(&ranges);
98//! // Stream of data to client. Needs to implement `io::Write`. We just use a vec here.
99//! let mut to_client = vec![];
100//! encode_ranges_validated(&file, &ob, &ranges, &mut to_client)?;
101//!
102//! // Stream of data from client. Needs to implement `io::Read`. We just wrap the vec in a cursor.
103//! let from_server = io::Cursor::new(to_client);
104//! let root = ob.root;
105//! let tree = ob.tree;
106//!
107//! // Decode the encoded data into a file
108//! let mut decoded = std::fs::File::create("copy.mp4")?;
109//! let mut ob = PreOrderOutboard {
110//!     tree,
111//!     root,
112//!     data: vec![],
113//! };
114//! decode_ranges(from_server, &ranges, &mut decoded, &mut ob)?;
115//!
116//! // the first 100000 bytes of the file should now be in `decoded`
117//! // in addition, the required part of the tree to validate that the data is
118//! // correct are in `ob.data`
119//!
120//! // Print the valid ranges of the file
121//! for range in valid_ranges(&ob, &decoded, &ChunkRanges::all()) {
122//!     println!("{:?}", range);
123//! }
124//! # Ok(())
125//! # }
126//! ```
127//!
128//! # Async end to end example
129//!
130//! The async version is very similar to the sync version, except that it needs
131//! an async context. All functions that do IO are async. The file has to be
132//! an [iroh_io::File], which is just a wrapper for [std::fs::File] that implements
133//! async random access via the [AsyncSliceReader](iroh_io::AsyncSliceReader) trait.
134//!
135//! We use [futures_lite] crate, but using the normal futures crate will also work.
136//!
137//! ```no_run
138//! use bao_tree::{
139//!     io::{
140//!         outboard::PreOrderOutboard,
141//!         round_up_to_chunks,
142//!         fsm::{decode_ranges, encode_ranges_validated, valid_ranges, CreateOutboard},
143//!     },
144//!     BlockSize, ByteRanges, ChunkRanges,
145//! };
146//! use bytes::BytesMut;
147//! use futures_lite::StreamExt;
148//! use std::io;
149//!
150//! /// Use a block size of 16 KiB, a good default for most cases
151//! const BLOCK_SIZE: BlockSize = BlockSize::from_chunk_log(4);
152//!
153//!# #[tokio::main]
154//!# async fn main() -> io::Result<()> {
155//! // The file we want to serve
156//! let mut file = iroh_io::File::open("video.mp4".into()).await?;
157//! // Create an outboard for the file, using the current size
158//! let mut ob = PreOrderOutboard::<BytesMut>::create(&mut file, BLOCK_SIZE).await?;
159//! // Encode the first 100000 bytes of the file
160//! let ranges = ByteRanges::from(0..100000);
161//! let ranges = round_up_to_chunks(&ranges);
162//! // Stream of data to client. Needs to implement `io::Write`. We just use a vec here.
163//! let mut to_client = Vec::new();
164//! encode_ranges_validated(file, &mut ob, &ranges, &mut to_client).await?;
165//!
166//! // Stream of data from client. Needs to implement `io::Read`. We just wrap the vec in a cursor.
167//! let from_server = io::Cursor::new(to_client.as_slice());
168//! let root = ob.root;
169//! let tree = ob.tree;
170//!
171//! // Decode the encoded data into a file
172//! let mut decoded = iroh_io::File::open("copy.mp4".into()).await?;
173//! let mut ob = PreOrderOutboard {
174//!     tree,
175//!     root,
176//!     data: BytesMut::new(),
177//! };
178//! decode_ranges(from_server, ranges, &mut decoded, &mut ob).await?;
179//!
180//! // the first 100000 bytes of the file should now be in `decoded`
181//! // in addition, the required part of the tree to validate that the data is
182//! // correct are in `ob.data`
183//!
184//! // Print the valid ranges of the file
185//! let ranges = ChunkRanges::all();
186//! let mut stream = valid_ranges(&mut ob, &mut decoded, &ranges);
187//! while let Some(range) = stream.next().await {
188//!     println!("{:?}", range);
189//! }
190//!# Ok(())
191//!# }
192//! ```
193//!
194//! # Compatibility with the [bao crate](https://crates.io/crates/bao)
195//!
196//! This crate will be compatible with the bao crate, provided you do the
197//! following:
198//!
199//! - use a block size of 1024, so no chunk groups
200//! - use a little endian u64 as the prefix for the encoded data
201//! - use only a single range
202#![deny(missing_docs)]
203use range_collections::RangeSetRef;
204use std::{
205    fmt::{self, Debug},
206    ops::Range,
207};
208#[macro_use]
209mod macros;
210pub mod iter;
211mod rec;
212mod tree;
213use iter::*;
214pub use tree::{BlockSize, ChunkNum};
215pub mod io;
216pub use iroh_blake3 as blake3;
217
218#[cfg(all(test, feature = "tokio_fsm"))]
219mod tests;
220#[cfg(all(test, feature = "tokio_fsm"))]
221mod tests2;
222
223/// A set of chunk ranges
224pub type ChunkRanges = range_collections::RangeSet2<ChunkNum>;
225
226/// A set of byte ranges
227pub type ByteRanges = range_collections::RangeSet2<u64>;
228
229/// A referenceable set of chunk ranges
230///
231/// [ChunkRanges] implements [`AsRef<ChunkRangesRef>`].
232pub type ChunkRangesRef = range_collections::RangeSetRef<ChunkNum>;
233
234fn hash_subtree(start_chunk: u64, data: &[u8], is_root: bool) -> blake3::Hash {
235    if data.len().is_power_of_two() {
236        blake3::guts::hash_subtree(start_chunk, data, is_root)
237    } else {
238        recursive_hash_subtree(start_chunk, data, is_root)
239    }
240}
241
242/// This is a recursive version of [`hash_subtree`], for testing.
243fn recursive_hash_subtree(start_chunk: u64, data: &[u8], is_root: bool) -> blake3::Hash {
244    use blake3::guts::{ChunkState, CHUNK_LEN};
245    if data.len() <= CHUNK_LEN {
246        let mut hasher = ChunkState::new(start_chunk);
247        hasher.update(data);
248        hasher.finalize(is_root)
249    } else {
250        let chunks = data.len() / CHUNK_LEN + (data.len() % CHUNK_LEN != 0) as usize;
251        let chunks = chunks.next_power_of_two();
252        let mid = chunks / 2;
253        let mid_bytes = mid * CHUNK_LEN;
254        let left = recursive_hash_subtree(start_chunk, &data[..mid_bytes], false);
255        let right = recursive_hash_subtree(start_chunk + mid as u64, &data[mid_bytes..], false);
256        blake3::guts::parent_cv(&left, &right, is_root)
257    }
258}
259
260/// Defines a Bao tree.
261///
262/// This is just the specification of the tree, it does not contain any actual data.
263///
264/// Usually trees are self-contained. This means that the tree starts at chunk 0,
265/// and the hash of the root node is computed with the is_root flag set to true.
266///
267/// For some internal use, it is also possible to create trees that are just subtrees
268/// of a larger tree. In this case, the start_chunk is the chunk number of the first
269/// chunk in the tree, and the is_root flag can be false.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub struct BaoTree {
272    /// Total number of bytes in the file
273    size: u64,
274    /// Log base 2 of the chunk group size
275    block_size: BlockSize,
276}
277
278/// An offset of a node in a post-order outboard
279#[derive(Debug, Clone, Copy)]
280pub enum PostOrderOffset {
281    /// the node is stable and won't change when appending data
282    Stable(u64),
283    /// the node is unstable and will change when appending data
284    Unstable(u64),
285}
286
287impl PostOrderOffset {
288    /// Just get the offset value, ignoring whether it's stable or unstable
289    pub fn value(self) -> u64 {
290        match self {
291            Self::Stable(n) => n,
292            Self::Unstable(n) => n,
293        }
294    }
295}
296
297impl BaoTree {
298    /// Create a new self contained BaoTree
299    pub fn new(size: u64, block_size: BlockSize) -> Self {
300        Self { size, block_size }
301    }
302
303    /// The size of the blob from which this tree was constructed, in bytes
304    pub fn size(&self) -> u64 {
305        self.size
306    }
307
308    /// The block size of the tree
309    pub fn block_size(&self) -> BlockSize {
310        self.block_size
311    }
312
313    /// Given a tree of size `size` and block size `block_size`,
314    /// compute the root node and the number of nodes for a shifted tree.
315    pub(crate) fn shifted(&self) -> (TreeNode, TreeNode) {
316        let level = self.block_size.0;
317        let size = self.size;
318        let shift = 10 + level;
319        let mask = (1 << shift) - 1;
320        // number of full blocks of size 1024 << level
321        let full_blocks = size >> shift;
322        // 1 if the last block is non zero, 0 otherwise
323        let open_block = ((size & mask) != 0) as u64;
324        // total number of blocks, rounding up to 1 if there are no blocks
325        let blocks = (full_blocks + open_block).max(1);
326        let n = (blocks + 1) / 2;
327        // root node
328        let root = n.next_power_of_two() - 1;
329        // number of nodes in the tree
330        let filled_size = n + n.saturating_sub(1);
331        (TreeNode(root), TreeNode(filled_size))
332    }
333
334    fn byte_range(&self, node: TreeNode) -> Range<u64> {
335        let start = node.chunk_range().start.to_bytes();
336        let end = node.chunk_range().end.to_bytes();
337        start..end.min(self.size)
338    }
339
340    /// Compute the byte ranges for a leaf node
341    ///
342    /// Returns two ranges, the first is the left range, the second is the right range
343    /// If the leaf is partially contained in the tree, the right range will be empty
344    fn leaf_byte_ranges3(&self, leaf: TreeNode) -> (u64, u64, u64) {
345        let Range { start, end } = leaf.byte_range();
346        let mid = leaf.mid().to_bytes();
347        if !(start < self.size || (start == 0 && self.size == 0)) {
348            debug_assert!(start < self.size || (start == 0 && self.size == 0));
349        }
350        (start, mid.min(self.size), end.min(self.size))
351    }
352
353    /// Traverse the entire tree in post order as [BaoChunk]s
354    ///
355    /// This iterator is used by both the sync and async io code for computing
356    /// an outboard from existing data
357    pub fn post_order_chunks_iter(&self) -> PostOrderChunkIter {
358        PostOrderChunkIter::new(*self)
359    }
360
361    /// Traverse the part of the tree that is relevant for a ranges query
362    /// in pre order as [BaoChunk]s
363    ///
364    /// This iterator is used by both the sync and async io code for encoding
365    /// from an outboard and ranges as well as decoding an encoded stream.
366    pub fn ranges_pre_order_chunks_iter_ref<'a>(
367        &self,
368        ranges: &'a RangeSetRef<ChunkNum>,
369        min_level: u8,
370    ) -> PreOrderPartialChunkIterRef<'a> {
371        PreOrderPartialChunkIterRef::new(*self, ranges, min_level)
372    }
373
374    /// Traverse the entire tree in post order as [TreeNode]s,
375    /// down to the level given by the block size.
376    pub fn post_order_nodes_iter(&self) -> impl Iterator<Item = TreeNode> {
377        let (root, len) = self.shifted();
378        let shift = self.block_size.0;
379        PostOrderNodeIter::new(root, len).map(move |x| x.subtract_block_size(shift))
380    }
381
382    /// Traverse the entire tree in pre order as [TreeNode]s,
383    /// down to the level given by the block size.
384    pub fn pre_order_nodes_iter(&self) -> impl Iterator<Item = TreeNode> {
385        let (root, len) = self.shifted();
386        let shift = self.block_size.0;
387        PreOrderNodeIter::new(root, len).map(move |x| x.subtract_block_size(shift))
388    }
389
390    /// Traverse the part of the tree that is relevant for a ranges querys
391    /// in pre order as [NodeInfo]s
392    ///
393    /// This is mostly used internally.
394    ///
395    /// When `min_level` is set to a value greater than 0, the iterator will
396    /// skip all branch nodes that are at a level < min_level if they are fully
397    /// covered by the ranges.
398    #[cfg(test)]
399    pub fn ranges_pre_order_nodes_iter<'a>(
400        &self,
401        ranges: &'a RangeSetRef<ChunkNum>,
402        min_level: u8,
403    ) -> PreOrderPartialIterRef<'a> {
404        PreOrderPartialIterRef::new(*self, ranges, min_level)
405    }
406
407    /// Root of the tree
408    ///
409    /// Does not consider block size
410    pub fn root(&self) -> TreeNode {
411        let shift = 10;
412        let mask = (1 << shift) - 1;
413        let full_blocks = self.size >> shift;
414        let open_block = ((self.size & mask) != 0) as u64;
415        let blocks = (full_blocks + open_block).max(1);
416        let chunks = ChunkNum(blocks);
417        TreeNode::root(chunks)
418    }
419
420    /// Number of blocks in the tree
421    ///
422    /// At chunk group size 1, this is the same as the number of chunks
423    /// Even a tree with 0 bytes size has a single block
424    pub fn blocks(&self) -> u64 {
425        // handle the case of an empty tree having 1 block
426        blocks(self.size, self.block_size).max(1)
427    }
428
429    /// Number of chunks in the tree
430    pub fn chunks(&self) -> ChunkNum {
431        ChunkNum::chunks(self.size)
432    }
433
434    /// Number of hash pairs in the outboard
435    fn outboard_hash_pairs(&self) -> u64 {
436        self.blocks() - 1
437    }
438
439    /// The outboard size for this tree.
440    ///
441    /// This is the outboard size *without* the size prefix.
442    pub fn outboard_size(&self) -> u64 {
443        self.outboard_hash_pairs() * 64
444    }
445
446    #[allow(dead_code)]
447    fn filled_size(&self) -> TreeNode {
448        let blocks = self.chunks();
449        let n = (blocks.0 + 1) / 2;
450        TreeNode(n + n.saturating_sub(1))
451    }
452
453    /// true if the node is a leaf for this tree
454    ///
455    /// If a tree has a non-zero block size, this is different than the node
456    /// being a leaf (level=0).
457    #[cfg(test)]
458    const fn is_leaf(&self, node: TreeNode) -> bool {
459        node.level() == self.block_size.to_u32()
460    }
461
462    /// true if the given node is persisted
463    ///
464    /// the only node that is not persisted is the last leaf node, if it is
465    /// less than half full
466    #[inline]
467    #[cfg(test)]
468    const fn is_persisted(&self, node: TreeNode) -> bool {
469        !self.is_leaf(node) || node.mid().to_bytes() < self.size
470    }
471
472    /// true if this is a node that is relevant for the outboard
473    #[inline]
474    const fn is_relevant_for_outboard(&self, node: TreeNode) -> bool {
475        let level = node.level();
476        if level < self.block_size.to_u32() {
477            // too small, this outboard does not track it
478            false
479        } else if level > self.block_size.to_u32() {
480            // a parent node, always relevant
481            true
482        } else {
483            node.mid().to_bytes() < self.size
484        }
485    }
486
487    /// The offset of the given node in the pre order traversal
488    pub fn pre_order_offset(&self, node: TreeNode) -> Option<u64> {
489        // if the node has a level less than block_size, this will return None
490        let shifted = node.add_block_size(self.block_size.0)?;
491        let is_half_leaf = shifted.is_leaf() && node.mid().to_bytes() >= self.size;
492        if !is_half_leaf {
493            let (_, tree_filled_size) = self.shifted();
494            Some(pre_order_offset_loop(shifted.0, tree_filled_size.0))
495        } else {
496            None
497        }
498    }
499
500    /// The offset of the given node in the post order traversal
501    pub fn post_order_offset(&self, node: TreeNode) -> Option<PostOrderOffset> {
502        // if the node has a level less than block_size, this will return None
503        let shifted = node.add_block_size(self.block_size.0)?;
504        if node.byte_range().end <= self.size {
505            // stable node, use post_order_offset
506            Some(PostOrderOffset::Stable(shifted.post_order_offset()))
507        } else {
508            // unstable node
509            if shifted.is_leaf() && node.mid().to_bytes() >= self.size {
510                // half full leaf node, not considered
511                None
512            } else {
513                // compute the offset based on the total size and the height of the node
514                self.outboard_hash_pairs()
515                    .checked_sub(u64::from(node.right_count()) + 1)
516                    .map(PostOrderOffset::Unstable)
517            }
518        }
519    }
520
521    const fn chunk_group_chunks(&self) -> ChunkNum {
522        ChunkNum(1 << self.block_size.0)
523    }
524
525    fn chunk_group_bytes(&self) -> usize {
526        self.chunk_group_chunks().to_bytes().try_into().unwrap()
527    }
528}
529
530/// number of blocks that this number of bytes covers,
531/// given a block size
532pub(crate) const fn blocks(size: u64, block_size: BlockSize) -> u64 {
533    let chunk_group_log = block_size.0;
534    let block_bits = chunk_group_log + 10;
535    let block_mask = (1 << block_bits) - 1;
536    let full_blocks = size >> block_bits;
537    let open_block = ((size & block_mask) != 0) as u64;
538    full_blocks + open_block
539}
540
541/// An u64 that defines a node in a bao tree.
542///
543/// You typically don't have to use this, but it can be useful for debugging
544/// and error handling. Hash validation errors contain a `TreeNode` that allows
545/// you to find the position where validation failed.
546#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
547pub struct TreeNode(u64);
548
549impl fmt::Display for TreeNode {
550    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
551        write!(f, "{}", self.0)
552    }
553}
554
555impl fmt::Debug for TreeNode {
556    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557        if !f.alternate() {
558            write!(f, "TreeNode({})", self.0)
559        } else if self.is_leaf() {
560            write!(f, "TreeNode::Leaf({})", self.0)
561        } else {
562            write!(f, "TreeNode::Branch({}, level={})", self.0, self.level())
563        }
564    }
565}
566
567impl TreeNode {
568    /// Create a new tree node from a start chunk and a level
569    ///
570    /// The start chunk must be the start of a subtree with the given level.
571    /// So for level 0, the start chunk must even. For level 1, the start chunk
572    /// must be divisible by 4, etc.
573    ///
574    /// This is a bridge from the recursive reference implementation to the node
575    /// based implementations, and is therefore only used in tests.
576    #[cfg(all(test, feature = "tokio_fsm"))]
577    fn from_start_chunk_and_level(start_chunk: ChunkNum, level: BlockSize) -> Self {
578        let start_chunk = start_chunk.0;
579        let level = level.0;
580        // check that the start chunk a start of a subtree with level `level`
581        // this ensures that there is a 0 at bit `level`.
582        let check_mask = (1 << (level + 1)) - 1;
583        debug_assert_eq!(start_chunk & check_mask, 0);
584        let level_mask = (1 << level) - 1;
585        // set the trailing `level` bits to 1.
586        // The level is the number of trailing ones.
587        Self(start_chunk | level_mask)
588    }
589
590    /// Given a number of blocks, gives root node
591    fn root(chunks: ChunkNum) -> TreeNode {
592        Self(((chunks.0 + 1) / 2).next_power_of_two() - 1)
593    }
594
595    /// the middle of the tree node, in blocks
596    pub const fn mid(&self) -> ChunkNum {
597        ChunkNum(self.0 + 1)
598    }
599
600    #[inline]
601    const fn half_span(&self) -> u64 {
602        1 << self.level()
603    }
604
605    /// The level of the node in the tree, 0 for leafs.
606    #[inline]
607    pub const fn level(&self) -> u32 {
608        self.0.trailing_ones()
609    }
610
611    /// True if this is a leaf node.
612    #[inline]
613    pub const fn is_leaf(&self) -> bool {
614        (self.0 & 1) == 0
615    }
616
617    /// Convert a node to a node in a tree with a smaller block size
618    ///
619    /// E.g. a leaf node in a tree with block size 4 will become a node
620    /// with level 4 in a tree with block size 0.
621    ///
622    /// This works by just adding n trailing 1 bits to the node by shifting
623    /// to the left.
624    #[inline]
625    pub const fn subtract_block_size(&self, n: u8) -> Self {
626        let shifted = !(!self.0 << n);
627        Self(shifted)
628    }
629
630    /// Convert a node to a node in a tree with a larger block size
631    ///
632    /// If the nodes has n trailing 1 bits, they are removed by shifting
633    /// the node to the right by n bits.
634    ///
635    /// If the node has less than n trailing 1 bits, the node is too small
636    /// to be represented in the target tree.
637    #[inline]
638    pub const fn add_block_size(&self, n: u8) -> Option<Self> {
639        let mask = (1 << n) - 1;
640        // check if the node has a high enough level
641        if self.0 & mask == mask {
642            Some(Self(self.0 >> n))
643        } else {
644            None
645        }
646    }
647
648    /// Range of blocks that this node covers, given a block size
649    ///
650    /// Note that this will give the untruncated range, which may be larger than
651    /// the actual tree. To get the exact byte range for a tree, use
652    /// [BaoTree::byte_range];
653    fn byte_range(&self) -> Range<u64> {
654        let range = self.chunk_range();
655        range.start.to_bytes()..range.end.to_bytes()
656    }
657
658    /// Number of nodes below this node, excluding this node.
659    #[inline]
660    pub const fn count_below(&self) -> u64 {
661        // go to representation where trailing zeros are the level
662        let x = self.0 + 1;
663        // isolate the lowest bit
664        let lowest_bit = x & (-(x as i64) as u64);
665        // number of nodes is n * 2 - 1, subtract 1 for the node itself
666        lowest_bit * 2 - 2
667    }
668
669    /// Get the next left ancestor of this node, or None if there is none.
670    pub fn next_left_ancestor(&self) -> Option<Self> {
671        self.next_left_ancestor0().map(Self)
672    }
673
674    /// Get the left child of this node, or None if it is a child node.
675    pub fn left_child(&self) -> Option<Self> {
676        let offset = 1 << self.level().checked_sub(1)?;
677        Some(Self(self.0 - offset))
678    }
679
680    /// Get the right child of this node, or None if it is a child node.
681    pub fn right_child(&self) -> Option<Self> {
682        let offset = 1 << self.level().checked_sub(1)?;
683        Some(Self(self.0 + offset))
684    }
685
686    /// Unrestricted parent, can only be None if we are at the top
687    pub fn parent(&self) -> Option<Self> {
688        let level = self.level();
689        if level == 63 {
690            return None;
691        }
692        let span = 1u64 << level;
693        let offset = self.0;
694        Some(Self(if (offset & (span * 2)) == 0 {
695            offset + span
696        } else {
697            offset - span
698        }))
699    }
700
701    /// Restricted parent, will be None if we call parent on the root
702    pub fn restricted_parent(&self, len: Self) -> Option<Self> {
703        let mut curr = *self;
704        while let Some(parent) = curr.parent() {
705            if parent.0 < len.0 {
706                return Some(parent);
707            }
708            curr = parent;
709        }
710        // we hit the top
711        None
712    }
713
714    /// Get a valid right descendant for an offset
715    pub(crate) fn right_descendant(&self, len: Self) -> Option<Self> {
716        let mut node = self.right_child()?;
717        while node >= len {
718            node = node.left_child()?;
719        }
720        Some(node)
721    }
722
723    /// Get the range of nodes this node covers
724    pub const fn node_range(&self) -> Range<Self> {
725        let half_span = self.half_span();
726        let nn = self.0;
727        let r = nn + half_span;
728        let l = nn + 1 - half_span;
729        Self(l)..Self(r)
730    }
731
732    /// Get the range of blocks this node covers
733    pub fn chunk_range(&self) -> Range<ChunkNum> {
734        let level = self.level();
735        let span = 1 << level;
736        let mid = self.0 + 1;
737        // at level 0 (leaf), range will be nn..nn+2
738        // at level >0 (branch), range will be centered on nn+1
739        ChunkNum(mid - span)..ChunkNum(mid + span)
740    }
741
742    /// the number of times you have to go right from the root to get to this node
743    ///
744    /// 0 for a root node
745    pub fn right_count(&self) -> u32 {
746        (self.0 + 1).count_ones() - 1
747    }
748
749    /// Get the post order offset of this node
750    #[inline]
751    pub const fn post_order_offset(&self) -> u64 {
752        // compute number of nodes below me
753        let below_me = self.count_below();
754        // compute next ancestor that is to the left
755        let next_left_ancestor = self.next_left_ancestor0();
756        // compute offset
757        match next_left_ancestor {
758            Some(nla) => below_me + nla + 1 - ((nla + 1).count_ones() as u64),
759            None => below_me,
760        }
761    }
762
763    /// Get the range of post order offsets this node covers
764    pub const fn post_order_range(&self) -> Range<u64> {
765        let offset = self.post_order_offset();
766        let end = offset + 1;
767        let start = offset - self.count_below();
768        start..end
769    }
770
771    /// Get the next left ancestor, or None if we don't have one
772    ///
773    /// this is a separate fn so it can be const.
774    #[inline]
775    const fn next_left_ancestor0(&self) -> Option<u64> {
776        // add 1 to go to the representation where trailing zeroes = level
777        let x = self.0 + 1;
778        // clear the lowest bit
779        let without_lowest_bit = x & (x - 1);
780        // go back to the normal representation,
781        // producing None if without_lowest_bit is 0, which means that there is no next left ancestor
782        without_lowest_bit.checked_sub(1)
783    }
784}
785
786/// Iterative way to find the offset of a node in a pre-order traversal.
787///
788/// I am sure there is a way that does not require a loop, but this will do for now.
789/// It is slower than the direct formula, but it is still in the nanosecond range,
790/// so at a block size of 16 KiB it should not be the limiting factor for anything.
791fn pre_order_offset_loop(node: u64, len: u64) -> u64 {
792    // node level, 0 for leaf nodes
793    let level = (!node).trailing_zeros();
794    // span of the node, 1 for leaf nodes
795    let span = 1u64 << level;
796    // nodes to the left of the tree of this node
797    let left = node + 1 - span;
798    // count the parents with a loop
799    let mut parent_count = 0;
800    let mut offset = node;
801    let mut span = span;
802    // loop until we reach the root, adding valid parents
803    loop {
804        let pspan = span * 2;
805        // find parent
806        offset = if (offset & pspan) == 0 {
807            offset + span
808        } else {
809            offset - span
810        };
811        // if parent is inside the tree, increase parent count
812        if offset < len {
813            parent_count += 1;
814        }
815        if pspan >= len {
816            // we are at the root
817            break;
818        }
819        span = pspan;
820    }
821    left - (left.count_ones() as u64) + parent_count
822}
823
824/// Split a range set into range sets for the left and right half of a node
825///
826/// Requires that the range set is minimal, it should not contain any redundant
827/// boundaries outside of the range of the node. The values outside of the node
828/// range don't matter, so any change outside the range must be omitted.
829///
830/// Produces two range sets that are also minimal. A range set for left or right
831/// that covers the entire range of the node will be replaced with the set of
832/// all chunks, so an is_all() check can be used to check if further recursion
833/// is necessary.
834pub(crate) fn split(
835    ranges: &RangeSetRef<ChunkNum>,
836    node: TreeNode,
837) -> (&RangeSetRef<ChunkNum>, &RangeSetRef<ChunkNum>) {
838    let mid = node.mid();
839    let start = node.chunk_range().start;
840    split_inner(ranges, start, mid)
841}
842
843/// The actual implementation of split. This is used from split and from the
844/// recursive reference implementation.
845pub(crate) fn split_inner(
846    ranges: &RangeSetRef<ChunkNum>,
847    start: ChunkNum,
848    mid: ChunkNum,
849) -> (&RangeSetRef<ChunkNum>, &RangeSetRef<ChunkNum>) {
850    let (mut a, mut b) = ranges.split(mid);
851    // check that a does not contain a redundant boundary at or after mid
852    debug_assert!(a.boundaries().last() < Some(&mid));
853    // Replace a with the canonicalized version if it is a single interval that
854    // starts at or before start. This is necessary to be able to check it with
855    // RangeSetRef::is_all()
856    if a.boundaries().len() == 1 && a.boundaries()[0] <= start {
857        a = RangeSetRef::new(&[ChunkNum(0)]).unwrap();
858    }
859    // Replace b with the canonicalized version if it is a single interval that
860    // starts at or before mid. This is necessary to be able to check it with
861    // RangeSetRef::is_all()
862    if b.boundaries().len() == 1 && b.boundaries()[0] <= mid {
863        b = RangeSetRef::new(&[ChunkNum(0)]).unwrap();
864    }
865    (a, b)
866}