use alloc::boxed::Box; use core::cell::RefCell; use core::fmt::Display; use core::marker::PhantomData; use core::mem::size_of; use core::ops::Deref; use core::ptr::NonNull; use crate::structs::{Header, Item, Key, KeyPtr, KnownObjectId, ObjectType, TreeItem}; use crate::{Error, Result}; use alloc::rc::Rc; use alloc::vec::Vec; use derivative::Derivative; use scroll::Pread; use zerocopy::FromBytes; use super::volume::Volume; /// A leaf node in a btrfs tree, containing different items #[derive(Debug, Clone)] pub struct BTreeLeafNode { pub header: Header, /// actual leaf data pub items: Vec, } #[derive(Debug)] pub enum NodePtr { Unvisited(KeyPtr), Visited { key: KeyPtr, node: BoxedNode }, // TODO: this doesnt need to be an Rc, can just be a NonNull with manual memory management } impl Clone for NodePtr { fn clone(&self) -> Self { match self { Self::Unvisited(arg0) => Self::Unvisited(arg0.clone()), Self::Visited { key, node } => Self::Visited { key: key.clone(), node: Node::clone_from_nonnull(node), }, } } } impl NodePtr { pub fn key_ptr(&self) -> &KeyPtr { match self { NodePtr::Unvisited(key) => key, NodePtr::Visited { key, .. } => key, } } pub fn node(&self) -> Option<&BoxedNode> { match self { NodePtr::Unvisited(_) => None, NodePtr::Visited { node, .. } => Some(&node), } } pub fn key(&self) -> &Key { &self.key_ptr().key } pub fn destroy(self) { match self { Self::Visited { node, .. } => { _ = node; // TODO: from box drop } _ => {} } } } /// An internal node in a btrfs tree, containing `KeyPtr`s to other internal nodes or leaf nodes. #[derive(Derivative, Clone)] #[derivative(Debug)] pub struct BTreeInternalNode { pub header: Header, #[derivative(Debug = "ignore")] children: Vec>, } impl BTreeInternalNode { pub fn visit_child( &self, idx: usize, volume: &super::volume::Volume, ) -> Result { match self.children.get(idx) { Some(child) => self.visit_child_inner(child, volume), None => Err(Error::OutOfBounds { range: 0..self.children.len(), index: idx, }), } } fn visit_child_inner( &self, child: &RefCell, volume: &super::volume::Volume, ) -> Result { match unsafe { &*child.as_ptr() } { NodePtr::Unvisited(keyptr) => { let node = volume .read_keyptr(keyptr) .and_then(|bytes| Node::boxed_from_bytes(bytes))?; child.replace(NodePtr::Visited { key: *keyptr, node: node.clone(), }); Ok(node) } NodePtr::Visited { node, .. } => Ok(node.clone()), } } pub fn visit_children_keys( &self, ) -> impl Iterator + DoubleEndedIterator + '_ { self.children .iter() .enumerate() .map(|(i, child)| (i, unsafe { *(&*child.as_ptr()).key() })) } pub fn visit_children<'a, 'b, R: super::Read>( &'a self, volume: &'b super::volume::Volume, ) -> impl Iterator)> + 'a where 'b: 'a, { self.children .iter() .enumerate() .map(|(i, child)| (i, self.visit_child_inner(child, volume))) } } impl PartialEq for BTreeInternalNode { fn eq(&self, other: &Self) -> bool { self.header == other.header } } impl PartialEq for BTreeLeafNode { fn eq(&self, other: &Self) -> bool { self.header == other.header } } impl Eq for BTreeLeafNode {} impl Eq for BTreeInternalNode {} #[derive(Debug, PartialEq, Eq, Clone)] pub enum BTreeNode { Internal(BTreeInternalNode), Leaf(BTreeLeafNode), } #[derive(Derivative, Eq, Clone)] #[derivative(Debug, PartialEq)] pub struct Node { inner: BTreeNode, #[derivative(Debug = "ignore")] #[derivative(PartialEq = "ignore")] bytes: Vec, } #[repr(transparent)] #[derive(Debug, Clone, PartialEq, Eq)] pub struct BoxedNode(NonNull); impl Deref for BoxedNode { type Target = Node; fn deref(&self) -> &Self::Target { self.as_ref() } } impl From> for BoxedNode { fn from(value: NonNull) -> Self { Self(value) } } impl From for BoxedNode { fn from(value: Node) -> Self { Self(unsafe { NonNull::new_unchecked(Box::leak(Box::new(value))) }) } } impl BoxedNode { pub fn as_ref(&self) -> &Node { unsafe { self.0.as_ref() } } pub fn as_ptr(self) -> *mut Node { self.0.as_ptr() } } //type BoxedNode = NonNull; #[derive(Debug, Clone, PartialEq, Eq)] pub struct NodeHandle { parents: Vec<(BoxedNode, u32)>, node: BoxedNode, idx: u32, } impl Display for NodeHandle { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "NodeHandle {{\n\tindex: {},\n\tnode: {:#?},\n\t ..\n}}\n", self.idx, self.node.inner.header() ) } } /// range of nodes that iterates thru all leaf nodes from start to end, inclusively. #[derive(Debug)] pub struct Range<'tree, R: super::Read> { volume: Rc>, pub(crate) start: RootOrEdge, pub(crate) end: RootOrEdge, phantom: PhantomData<&'tree ()>, } pub mod entry { use super::*; #[derive(Debug, PartialEq, Eq)] pub enum Entry<'tree> { Occupied(OccupiedEntry<'tree>), Vacant(VacantEntry<'tree>), } impl<'tree> From> for Entry<'tree> { fn from(v: VacantEntry<'tree>) -> Self { Self::Vacant(v) } } impl<'tree> From> for Entry<'tree> { fn from(v: OccupiedEntry<'tree>) -> Self { Self::Occupied(v) } } #[derive(Debug, PartialEq, Eq)] pub struct OccupiedEntry<'tree> { key: Key, node: NodeHandle, phantom: PhantomData<&'tree ()>, } impl<'tree> OccupiedEntry<'tree> { pub fn new(key: Key, node: NodeHandle) -> Self { Self { key, node, phantom: PhantomData, } } pub fn key(&self) -> Key { self.key } pub fn item_and_value(&self) -> Result<(Item, TreeItem)> { self.node.parse_item() } pub fn value(&self) -> Result { Ok(self.node.parse_item()?.1) } } #[derive(Debug, PartialEq, Eq)] pub struct VacantEntry<'tree> { phantom: PhantomData<&'tree ()>, } impl<'tree> VacantEntry<'tree> { pub fn new() -> Self { Self { phantom: PhantomData, } } } } #[derive(Derivative)] #[derivative(Debug)] pub struct Tree { #[derivative(Debug = "ignore")] volume: Rc>, root: BoxedNode, } impl Clone for Tree { fn clone(&self) -> Self { Self { volume: self.volume.clone(), root: Node::clone_from_nonnull(&self.root), } } } impl Drop for Tree { fn drop(&mut self) { log::debug!("======= cleaning up tree ======="); Node::destroy(self.root.clone()); log::debug!("======= [done] ======="); } } impl Tree { pub fn from_logical_offset(volume: Rc>, logical: u64) -> Result { // TODO: this might read a very big range, far more than needed let bytes = volume .read_range_from_logical(logical)? .ok_or(Error::BadLogicalAddress)?; // TODO: make this a better error let root = Node::boxed_from_bytes(bytes)?; Ok(Self { volume, root }) } fn find_node_inner(&self, key: &K, find: F) -> Result> where K: PartialEq + PartialOrd, F: Fn(NodeHandle, &K) -> SearchResult, { let mut node = NodeHandle::start(self.root.clone()); loop { let search = find(node, key); match search { SearchResult::Leaf(a) => { return Ok(Some(a)); } SearchResult::Edge(mut edge) => match &edge.node.inner { BTreeNode::Internal(internal) => { let child = internal .visit_child(edge.idx as usize, &self.volume) .expect("child"); edge.parents.push((edge.node, edge.idx)); node = NodeHandle { parents: edge.parents, node: child, idx: 0, }; // recurse } BTreeNode::Leaf(_) => { // leaf node returning and edge means key is not present return Ok(None); } }, } } } fn find_node_rev(&self, key: &K) -> Result> where K: PartialEq + PartialOrd, { self.find_node_inner(key, NodeHandle::find_key_rev) } fn find_node(&self, key: &K) -> Result> where K: PartialEq + PartialOrd, { self.find_node_inner(key, NodeHandle::find_key) } pub fn entry(&self, key: &K) -> Result where K: PartialEq + PartialOrd, { let entry: entry::Entry = match self.find_node(key)? { Some(node) => entry::OccupiedEntry::new(node.parse_key(), node).into(), None => entry::VacantEntry::new().into(), }; Ok(entry) } pub fn entry_rev(&self, key: &K) -> Result where K: PartialEq + PartialOrd, { let entry: entry::Entry = match self.find_node_rev(key)? { Some(node) => entry::OccupiedEntry::new(node.parse_key(), node).into(), None => entry::VacantEntry::new().into(), }; Ok(entry) } pub fn find_range(&self, key: &K) -> Result> where K: PartialEq + PartialOrd, { let start = self.find_node(key)?; let end = self.find_node_rev(key)?; Ok(Range::from_handles( self.volume.clone(), start.unwrap_or(NodeHandle::start(self.root.clone())), end.unwrap_or(NodeHandle::end(self.root.clone())), )) } pub fn iter(&self) -> Range<'_, R> { Range::new(self.volume.clone(), self.root.clone(), self.root.clone()) } } impl BTreeLeafNode { pub fn parse(header: Header, bytes: &[u8]) -> Result { log::debug!("leaf:"); let offset = &mut 0; let items = core::iter::from_fn(|| { if *offset as usize + size_of::() < bytes.len() { let item = Item::read_from(&bytes[*offset..*offset + size_of::()]); *offset += size_of::(); if let Some(item) = item.as_ref() { log::debug!("\t{item:?}"); } item } else { None } }) .take(header.nritems.get() as usize) .collect::>(); Ok(Self { header, items }) } } impl BTreeInternalNode { pub fn parse(header: Header, bytes: &[u8]) -> Result { log::debug!("internal lvl: {}", header.level); let offset = &mut 0; let size = size_of::(); let children = core::iter::from_fn(|| { if *offset as usize + size < bytes.len() { let item = KeyPtr::read_from(&bytes[*offset..*offset + size]); *offset += size; if let Some(item) = item.as_ref() { log::debug!( "\tchild gen: {} offset: {}", item.generation.get(), item.key.offset.get() ); } item } else { None } }) .take(header.nritems.get() as usize) .map(|ptr| RefCell::new(NodePtr::Unvisited(ptr))) .collect::>(); Ok(Self { header, children }) } } impl BTreeNode { pub fn parse(bytes: &[u8]) -> Result { let offset = &mut 0; let header = bytes.gread::
(offset)?; if header.level == 0 { Ok(Self::Leaf(BTreeLeafNode::parse(header, &bytes[*offset..])?)) } else { Ok(Self::Internal(BTreeInternalNode::parse( header, &bytes[*offset..], )?)) } } pub fn header(&self) -> &Header { match self { BTreeNode::Internal(node) => &node.header, BTreeNode::Leaf(node) => &node.header, } } /// Returns `true` if the btree node is [`Internal`]. /// /// [`Internal`]: BTreeNode::Internal #[must_use] pub fn is_internal(&self) -> bool { matches!(self, Self::Internal(..)) } /// Returns `true` if the btree node is [`Leaf`]. /// /// [`Leaf`]: BTreeNode::Leaf #[must_use] pub fn is_leaf(&self) -> bool { matches!(self, Self::Leaf(..)) } pub fn as_internal(&self) -> Option<&BTreeInternalNode> { if let Self::Internal(v) = self { Some(v) } else { None } } pub fn as_leaf(&self) -> Option<&BTreeLeafNode> { if let Self::Leaf(v) = self { Some(v) } else { None } } } pub enum NodeHandleAdvanceResult { Decend { parent: NodeHandle, child_ptr: KeyPtr, }, Next(NodeHandle), Ascend, } pub enum SearchResult { Leaf(NodeHandle), Edge(NodeHandle), } #[derive(Debug, Clone, Eq)] pub(crate) enum RootOrEdge { Root(NodeHandle), Edge(NodeHandle), } impl RootOrEdge { pub fn into_handle(&self) -> NodeHandle { match self { RootOrEdge::Root(handle) => handle, RootOrEdge::Edge(handle) => handle, } .clone() } fn into_next_node_any( self, volume: &super::volume::Volume, f: F, ) -> core::result::Result where F: Fn( NodeHandle, &super::volume::Volume, ) -> core::result::Result, R: super::Read, { let node = match self { RootOrEdge::Root(root) => Ok(root), RootOrEdge::Edge(edge) => f(edge, volume), }; match node { Ok(node) => Ok(Self::Edge(node)), Err(node) => Ok(Self::Edge(node)), } } pub fn into_next_node( self, volume: &super::volume::Volume, ) -> core::result::Result where R: super::Read, { self.into_next_node_any(volume, NodeHandle::into_next) } pub fn into_next_back_node( self, volume: &super::volume::Volume, ) -> core::result::Result where R: super::Read, { self.into_next_node_any(volume, NodeHandle::into_next_back) } fn into_next_leaf_any( self, volume: &super::volume::Volume, f: F, ) -> core::result::Result where F: Fn( NodeHandle, &super::volume::Volume, ) -> core::result::Result, R: super::Read, { // this will either give us the next immediate node, or self if self was a Root. let mut this = self.into_next_node_any(volume, &f)?; loop { // we are only interested in leaf nodes if this.node.inner.is_leaf() { break Ok(this); } else { // this will eventually return Err(self) so will always return this = this.into_next_node_any(volume, &f)?; } } } /// returns the next leaf, or the end of the tree as Err pub fn into_next_leaf( self, volume: &super::volume::Volume, ) -> core::result::Result { self.into_next_leaf_any(volume, NodeHandle::into_next) } /// returns the next RootOrEdge, or the end of the tree as Err, in reverse direction pub fn into_next_back_leaf( self, volume: &super::volume::Volume, ) -> core::result::Result { self.into_next_leaf_any(volume, NodeHandle::into_next_back) } pub fn as_handle(&self) -> &NodeHandle { match self { RootOrEdge::Root(handle) => handle, RootOrEdge::Edge(handle) => handle, } } } impl Deref for RootOrEdge { type Target = NodeHandle; fn deref(&self) -> &Self::Target { match self { RootOrEdge::Root(node) => node, RootOrEdge::Edge(node) => node, } } } impl PartialEq for RootOrEdge { fn eq(&self, other: &Self) -> bool { match self { RootOrEdge::Root(root) => match other { RootOrEdge::Root(_) => false, RootOrEdge::Edge(other) => root.eq(other), }, RootOrEdge::Edge(edge) => match other { RootOrEdge::Edge(_) => false, RootOrEdge::Root(other) => edge.eq(other), }, } } } impl Node { pub fn clone_from_nonnull(this: &BoxedNode) -> BoxedNode { (*this.as_ref()).clone().into() } pub fn boxed_from_bytes(bytes: Vec) -> Result { Ok(Self::from_bytes(bytes)?.into()) } pub fn from_bytes(bytes: Vec) -> Result { let inner = BTreeNode::parse(&bytes)?; Ok(Self { inner, bytes }) } /// must not be called if any outstanding live references to `this` exist pub fn destroy(this: BoxedNode) { log::debug!("Node::destroy"); { log::debug!("free: {:?}", this); let inner = &this.as_ref().inner; match inner { BTreeNode::Internal(node) => { log::debug!("destroying children.."); for child in node.children.iter() { match unsafe { &*child.as_ptr() } { NodePtr::Visited { node, .. } => { Self::destroy(node.clone()); } _ => {} } } log::debug!("destroying children [end]"); } BTreeNode::Leaf(_) => {} } } log::debug!("dropping: {:?}", this); unsafe { drop(Box::from_raw(this.as_ptr())); } } /// returns Ok(None) if `i` is out of bounds fn read_nth_key(&self, i: usize) -> Option { match &self.inner { BTreeNode::Internal(internal) => { let item = internal .children .get(i) .map(|child| *unsafe { &*child.as_ptr() }.key()); item } BTreeNode::Leaf(leaf) => { let key = leaf.items.get(i).map(|item| item.key); key } } } /// returns None if pointing at an internal node or `i` is out of bounds. /// returns an Error if parsing the item failed. pub fn read_nth_item(&self, i: usize) -> Result> { match &self.inner { BTreeNode::Internal(_) => Ok(None), BTreeNode::Leaf(leaf) => { // TODO: better error to indicate that it was out of bounds let item = if let Some(item) = leaf.items.get(i) { let start = size_of::
() + item.offset.get() as usize; let size = item.size.get() as usize; let bytes = &self.bytes[start..start + size]; let value = TreeItem::parse(item, bytes)?; Some((*item, value)) } else { None }; Ok(item) } } } } impl<'tree, R> Range<'tree, R> where R: super::Read, { pub fn from_handles(volume: Rc>, start: NodeHandle, end: NodeHandle) -> Self { Self { volume, start: RootOrEdge::Root(start), end: RootOrEdge::Root(end), phantom: PhantomData, } } pub fn new(volume: Rc>, start: BoxedNode, end: BoxedNode) -> Self { Self::from_handles(volume, NodeHandle::start(start), NodeHandle::end(end)) } pub fn is_empty(&self) -> bool { return self.start == self.end; } } impl<'tree, R> Iterator for Range<'tree, R> where R: super::Read, { type Item = (Item, TreeItem); fn next(&mut self) -> Option { loop { if !self.is_empty() { replace_with::replace_with_or_abort(&mut self.start, |start| { match start.into_next_node(&self.volume) { Ok(next) => next, Err(next) => next, } }); if self.start.node.inner.is_leaf() { break Some(self.start.as_handle().parse_item().expect("range item")); } // else repeat } else { break None; } } } } impl<'tree, R> DoubleEndedIterator for Range<'tree, R> where R: super::Read, { fn next_back(&mut self) -> Option { loop { if !self.is_empty() { replace_with::replace_with_or_abort(&mut self.end, |start| { match start.into_next_back_node(&self.volume) { Ok(next) => next, Err(next) => next, } }); if self.end.node.inner.is_leaf() { break Some(self.end.as_handle().parse_item().expect("range item")); } // else repeat } else { break None; } } } } impl NodeHandle { pub fn start(node: BoxedNode) -> Self { let parents = Vec::with_capacity(node.inner.header().level as usize); Self { node, parents, idx: 0, } } /// returns None if pointing at an internal node or `i` is out of bounds. /// returns an Error if parsing the item failed. pub fn parse_item(&self) -> Result<(Item, TreeItem)> { self.node .read_nth_item(self.idx as usize) .map(|result| result.unwrap()) } /// returns an Error if the key read fails pub fn parse_key(&self) -> Key { self.node .read_nth_key(self.idx as usize) .expect("idx out of bounds") } pub fn advance_sideways(self) -> NodeHandleAdvanceResult { let header = self.node.inner.header(); if header.nritems.get() >= self.idx + 1 { NodeHandleAdvanceResult::Ascend } else { match &self.node.inner { BTreeNode::Internal(_) => NodeHandleAdvanceResult::Next(Self { idx: self.idx + 1, ..self }), _ => unreachable!(), } } } pub fn find_key_rev + PartialOrd>(self, key: &K) -> SearchResult { match &self.node.inner { BTreeNode::Internal(node) => { let idx = node .visit_children_keys() .rev() .find_map(|(i, child)| match key.partial_cmp(&child) { Some(core::cmp::Ordering::Greater) | Some(core::cmp::Ordering::Equal) | None => Some(i as u32), _ => None, }) .unwrap_or(0); SearchResult::Edge(NodeHandle { idx, ..self }) } BTreeNode::Leaf(node) => { for (i, child) in node.items.iter().enumerate().rev() { if key.eq(&child.key) { return SearchResult::Leaf(NodeHandle { idx: i as u32, ..self }); } } log::debug!("key definitely not found!"); SearchResult::Edge(NodeHandle { idx: 0, ..self }) } } } pub fn find_key + PartialOrd>(self, key: &K) -> SearchResult { match &self.node.inner { BTreeNode::Internal(node) => { let idx = node .visit_children_keys() .find_map(|(i, child)| match key.partial_cmp(&child) { Some(core::cmp::Ordering::Less) => { Some(if i == 0 { 0 } else { i as u32 - 1 }) } Some(core::cmp::Ordering::Equal) | None => Some(i as u32), _ => None, }) .unwrap_or(node.children.len() as u32 - 1); SearchResult::Edge(NodeHandle { idx, ..self }) } BTreeNode::Leaf(node) => { for (i, child) in node.items.iter().enumerate() { // if key < &child.key { // return SearchResult::Leaf(NodeHandle { // node: self.clone(), // idx: if i == 0 { 0 } else { i as u32 - 1 }, // }); // } else if key.eq(&child.key) { return SearchResult::Leaf(NodeHandle { idx: i as u32, ..self }); } } log::debug!("key definitely not found!"); SearchResult::Edge(NodeHandle { idx: node.items.len() as u32 - 1, ..self }) } } } // needs reference to volume to be able to read children key_ptrs // this is a live reference so if in the future I want to have a &mut to something in Volume // greppable: volume ref pub fn into_next_back( self, volume: &super::volume::Volume, ) -> core::result::Result { let Self { mut parents, node, idx, } = self; if idx < 1 { // go up match parents.pop() { Some((node, idx)) => Self { node, idx, parents }.into_next_back(volume), None => Err(Self { node, idx, parents }), } } else { match &node.inner { BTreeNode::Internal(internal) => { let node = match internal.visit_child(idx as usize, volume) { Ok(child) => { parents.push((node, idx)); Ok(Self { parents, idx: child.inner.header().nritems.get() - 1, node: child, }) } Err(_) => Err(Self { parents, node, idx }), }; // TODO: better error or panic here? this would return self, indicating the end of the tree, even though we simply failed to retrieve the next node node } BTreeNode::Leaf(_) => Ok(Self { idx: idx - 1, parents, node, }), } } } // needs reference to volume to be able to read children key_ptrs // this is a live reference so if in the future I want to have a &mut to something in Volume // greppable: volume ref /// returns Ok(next) or Err(self) if self is already the last node pub fn into_next( self, volume: &super::volume::Volume, ) -> core::result::Result { let Self { mut parents, node, idx, } = self; let header = node.inner.header(); if idx + 1 >= header.nritems.get() { // go up match parents.pop() { Some((node, idx)) => Self { idx: (idx + 1).min(node.inner.header().nritems.get()), node, parents, } .into_next(volume), None => Err(Self { node, idx, parents }), } } else { match &node.inner { BTreeNode::Internal(internal) => { let node = match internal.visit_child(idx as usize, volume) { Ok(child) => { parents.push((node, idx)); Ok(Self { parents, idx: 0, node: child, }) } Err(_) => { log::error!("failed to read child node!"); Err(Self { parents, node, idx }) } }; // TODO: better error or panic here? this would return self, indicating the end of the tree, even though we simply failed to retrieve the next node node } BTreeNode::Leaf(_) => Ok(Self { idx: idx + 1, parents, node, }), } } } pub fn end(node: BoxedNode) -> Self { let parents = Vec::with_capacity(node.inner.header().level as usize); Self { parents, idx: node.inner.header().nritems.get() - 1, node, } } } // PARTIAL KEY /// key lookup that will find the first key that matches the present items in this partial key pub struct PartialKey { pub id: Option, pub ty: Option, pub offset: Option, } impl PartialKey { pub fn new(id: Option, ty: Option, offset: Option) -> Self { Self { id, ty, offset } } } impl PartialEq for PartialKey { fn eq(&self, other: &Key) -> bool { self.id.map(|id| id == other.id()).unwrap_or(true) && self.ty.map(|ty| ty == other.ty()).unwrap_or(true) && self .offset .map(|offset| offset == other.offset.get()) .unwrap_or(true) } } /// compares Self to a key, by comparing each item with the element in key if present, and skipping to the next item if missing. impl PartialOrd for PartialKey { fn partial_cmp(&self, other: &Key) -> Option { let id = self.id.and_then(|id| id.partial_cmp(&other.id())); let ty = self.ty.and_then(|ty| ty.partial_cmp(&other.ty())); let offset = self .offset .and_then(|offset| offset.partial_cmp(&other.offset.get())); match id { Some(core::cmp::Ordering::Equal) | None => match ty { Some(core::cmp::Ordering::Equal) | None => offset, ord => ord, }, ord => ord, } } } #[cfg(test)] mod partial_key_tests { use test_log::test; use super::*; #[test] fn test_partial_key_ord() { let key = Key::new( KnownObjectId::ChunkTree, ObjectType::DirItem, 0x8dbfc2d2, // crc of "default" ); let pkey = PartialKey::new( Some(KnownObjectId::ChunkTree), Some(ObjectType::DirItem), None, ); assert_eq!(pkey.partial_cmp(&key), None); let pkey = PartialKey::new( Some(KnownObjectId::ChunkTree), Some(ObjectType::DirItem), Some(0xdeadbeef), ); assert_ne!(pkey.partial_cmp(&key), Some(core::cmp::Ordering::Equal)); let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef)); assert_ne!(pkey.partial_cmp(&key), None); let pkey = PartialKey::new( Some(KnownObjectId::ChunkTree), Some(ObjectType::DirItem), Some(0x8dbfc2d2), ); assert_eq!(pkey.partial_cmp(&key), Some(core::cmp::Ordering::Equal)); } #[test] fn test_partial_eq_partial_key() { let key = Key::new( KnownObjectId::ChunkTree, ObjectType::DirItem, 0x8dbfc2d2, // crc of "default" ); let pkey = PartialKey::new( Some(KnownObjectId::ChunkTree), Some(ObjectType::DirItem), None, ); assert!(pkey.eq(&key)); let pkey = PartialKey::new( Some(KnownObjectId::ChunkTree), Some(ObjectType::DirItem), Some(0xdeadbeef), ); assert!(!pkey.eq(&key)); let pkey = PartialKey::new(None, Some(ObjectType::DirItem), Some(0xdeadbeef)); assert!(!pkey.eq(&key)); let pkey = PartialKey::new( Some(KnownObjectId::ChunkTree), Some(ObjectType::DirItem), Some(0x8dbfc2d2), ); assert!(pkey.eq(&key)); } }