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}