///! dag-pb support operations. Placing this module inside unixfs module is a bit unfortunate but ///! follows from the inseparability of dag-pb and UnixFS. use crate::pb::PBNode; use alloc::borrow::Cow; use core::convert::TryFrom; use core::fmt; use core::ops::Range; /// Extracts the PBNode::Data field from the block as it appears on the block. pub fn node_data(block: &[u8]) -> Result, quick_protobuf::Error> { let doc = PBNode::try_from(block)?; Ok(match doc.Data { Some(Cow::Borrowed(slice)) => Some(slice), Some(Cow::Owned(_)) => unreachable!("never converted to owned"), None => None, }) } /// Creates a wrapper around the given block representation which does not consume the block /// representation but allows accessing the dag-pb node Data. pub fn wrap_node_data(block: T) -> Result, quick_protobuf::Error> where T: AsRef<[u8]>, { let full = block.as_ref(); let range = node_data(full)? .map(|data| subslice_to_range(full, data).expect("this has to be in range")); Ok(NodeData { inner: block, range, }) } fn subslice_to_range(full: &[u8], sub: &[u8]) -> Option> { // note this doesn't work for all types, for example () or similar ZSTs. let max = full.len(); let amt = sub.len(); if max < amt { // if the latter slice is larger than the first one, surely it isn't a subslice. return None; } let full = full.as_ptr() as usize; let sub = sub.as_ptr() as usize; sub.checked_sub(full) // not needed as it would divide by one: .map(|diff| diff / mem::size_of::()) // // if there are two slices of a continious chunk, [A|B] we need to make sure B will not be // calculated as subslice of A .and_then(|start| if start >= max { None } else { Some(start) }) .map(|start| start..(start + amt)) } /// The wrapper returned from [`wrap_node_data`], allows accessing dag-pb nodes Data. #[derive(PartialEq)] pub struct NodeData { inner: T, range: Option>, } impl> fmt::Debug for NodeData { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!( fmt, "NodeData<{}> {{ inner.len: {}, range: {:?} }}", std::any::type_name::(), self.inner.as_ref().len(), self.range ) } } impl> NodeData { /// Returns the dag-pb nodes Data field as slice pub fn node_data(&self) -> &[u8] { if let Some(range) = self.range.as_ref() { &self.inner.as_ref()[range.clone()] } else { &[][..] } } /// Returns access to the wrapped block representation pub fn get_ref(&self) -> &T { &self.inner } /// Consumes self and returns the block representation pub fn into_inner(self) -> T { self.inner } } impl, B: AsRef<[u8]>> PartialEq for NodeData { fn eq(&self, other: &B) -> bool { self.node_data() == other.as_ref() } } #[cfg(test)] mod tests { use super::subslice_to_range; #[test] fn subslice_ranges() { let full = &b"01234"[..]; for start in 0..(full.len() - 1) { for end in start..(full.len() - 1) { let sub = &full[start..end]; assert_eq!(subslice_to_range(full, sub), Some(start..end)); } } } #[test] fn not_in_following_subslice() { // this could be done with two distinct/disjoint 'static slices but there might not be any // guarantees it working in all rust released and unreleased versions, and with different // linkers. let full = &b"0123456789"[..]; let a = &full[0..4]; let b = &full[4..]; let a_sub = &a[1..3]; let b_sub = &b[0..2]; assert_eq!(subslice_to_range(a, a_sub), Some(1..3)); assert_eq!(subslice_to_range(b, b_sub), Some(0..2)); assert_eq!(subslice_to_range(a, b_sub), None); assert_eq!(subslice_to_range(b, a_sub), None); } }