Skip to content

Commit

Permalink
feat(resolver): add support for raw leaves in unixfs
Browse files Browse the repository at this point in the history
  • Loading branch information
dignifiedquire committed May 24, 2022
1 parent c2e7691 commit 1d6d57d
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
world
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-
$p �3�a%G>b�Z��R5G�?����}s.���bar=3
$U X���"��m������qc�4Ђ���F�� hello.txt

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1
$U �X�H��Lcu6��IN�����k��y\���bar.txt

122 changes: 117 additions & 5 deletions iroh-resolver/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,19 +254,19 @@ impl Resolver {
bytes: Bytes,
path: Vec<String>,
) -> Result<Out> {
if let Ok(node) = UnixfsNode::decode(bytes.clone()) {
if let Ok(node) = UnixfsNode::decode(&cid, bytes.clone()) {
let mut current = node;

// TODO: handle if `path` is now empty
for part in path {
match current.typ() {
DataType::Directory => {
Some(DataType::Directory) => {
let next_link = current
.get_link_by_name(&part)
.await?
.ok_or_else(|| anyhow!("link {} not found", part))?;
let next_bytes = self.load_cid(&next_link.cid).await?;
let next_node = UnixfsNode::decode(next_bytes)?;
let next_node = UnixfsNode::decode(&next_link.cid, next_bytes)?;

current = next_node;
}
Expand Down Expand Up @@ -537,7 +537,7 @@ mod tests {
}

#[tokio::test]
async fn test_unixfs_basics() {
async fn test_unixfs_basics_cid_v0() {
// Test content
// ------------
// QmaRGe7bVmVaLmxbrMiVNXqW4pRNNp3xq7hFtyRKA3mtJL foo/bar/bar.txt
Expand All @@ -560,7 +560,119 @@ mod tests {
let root_cid_str = "QmdkGfDx42RNdAZFALHn5hjHqUq7L9o6Ef4zLnFEu3Y4Go";
let root_cid: Cid = root_cid_str.parse().unwrap();
let root_block_bytes = load_fixture(root_cid_str).await;
let root_block = UnixfsNode::decode(root_block_bytes.clone()).unwrap();
let root_block = UnixfsNode::decode(&root_cid, root_block_bytes.clone()).unwrap();

let links: Vec<_> = root_block.links().collect::<Result<_>>().unwrap();
assert_eq!(links.len(), 2);

assert_eq!(links[0].cid, bar_cid_str.parse().unwrap());
assert_eq!(links[0].name.unwrap(), "bar");

assert_eq!(links[1].cid, hello_txt_cid_str.parse().unwrap());
assert_eq!(links[1].name.unwrap(), "hello.txt");

let loader: HashMap<Cid, Bytes> = [
(root_cid, root_block_bytes.clone()),
(hello_txt_cid_str.parse().unwrap(), hello_txt_block_bytes),
(bar_cid_str.parse().unwrap(), bar_block_bytes),
(bar_txt_cid_str.parse().unwrap(), bar_txt_block_bytes),
]
.into_iter()
.collect();
let resolver = Resolver::new(loader);

{
let ipld_foo = resolver
.resolve(root_cid_str.parse().unwrap())
.await
.unwrap();

if let Out::Unixfs(node) = ipld_foo {
assert_eq!(
std::str::from_utf8(&node.pretty().unwrap()).unwrap(),
"bar\nhello.txt\n"
);
} else {
panic!("invalid result: {:?}", ipld_foo);
}
}

{
let ipld_hello_txt = resolver
.resolve(format!("{root_cid_str}/hello.txt").parse().unwrap())
.await
.unwrap();

if let Out::Unixfs(node) = ipld_hello_txt {
assert_eq!(
std::str::from_utf8(&node.pretty().unwrap()).unwrap(),
"hello\n"
);
} else {
panic!("invalid result: {:?}", ipld_hello_txt);
}
}

{
let ipld_bar = resolver
.resolve(format!("{root_cid_str}/bar").parse().unwrap())
.await
.unwrap();

if let Out::Unixfs(node) = ipld_bar {
assert_eq!(
std::str::from_utf8(&node.pretty().unwrap()).unwrap(),
"bar.txt\n"
);
} else {
panic!("invalid result: {:?}", ipld_bar);
}
}

{
let ipld_bar_txt = resolver
.resolve(format!("{root_cid_str}/bar/bar.txt").parse().unwrap())
.await
.unwrap();

if let Out::Unixfs(node) = ipld_bar_txt {
assert_eq!(
std::str::from_utf8(&node.pretty().unwrap()).unwrap(),
"world\n"
);
} else {
panic!("invalid result: {:?}", ipld_bar_txt);
}
}
}

#[tokio::test]
async fn test_unixfs_basics_cid_v1() {
// uses raw leaves

// Test content
// ------------
// bafkreihcldjer7njjrrxknqh67cestxa7s7jf4nhnp62y6k4twcbahvtc4 foo/bar/bar.txt
// contains: "world"
// bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am foo/hello.txt
// contains: "hello"
// bafybeihmgpuwcdrfi47gfxisll7kmurvi6kd7rht5hlq2ed5omxobfip3a foo/bar
// bafybeietod5kx72jgbngoontthoax6nva4edkjnieghwqfzenstg4gil5i foo

let bar_txt_cid_str = "bafkreihcldjer7njjrrxknqh67cestxa7s7jf4nhnp62y6k4twcbahvtc4";
let bar_txt_block_bytes = load_fixture(bar_txt_cid_str).await;

let bar_cid_str = "bafybeihmgpuwcdrfi47gfxisll7kmurvi6kd7rht5hlq2ed5omxobfip3a";
let bar_block_bytes = load_fixture(bar_cid_str).await;

let hello_txt_cid_str = "bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am";
let hello_txt_block_bytes = load_fixture(hello_txt_cid_str).await;

// read root
let root_cid_str = "bafybeietod5kx72jgbngoontthoax6nva4edkjnieghwqfzenstg4gil5i";
let root_cid: Cid = root_cid_str.parse().unwrap();
let root_block_bytes = load_fixture(root_cid_str).await;
let root_block = UnixfsNode::decode(&root_cid, root_block_bytes.clone()).unwrap();

let links: Vec<_> = root_block.links().collect::<Result<_>>().unwrap();
assert_eq!(links.len(), 2);
Expand Down
148 changes: 105 additions & 43 deletions iroh-resolver/src/unixfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use bytes::{Buf, Bytes};
use cid::Cid;
use prost::Message;

use crate::codecs::Codec;

mod unixfs_pb {
include!(concat!(env!("OUT_DIR"), "/unixfs_pb.rs"));
}
Expand Down Expand Up @@ -62,40 +64,50 @@ pub struct LinkRef<'a> {
}

#[derive(Debug)]
pub struct UnixfsNode {
outer: dag_pb::PbNode,
inner: unixfs_pb::Data,
pub enum UnixfsNode {
Raw {
data: Bytes,
},
Pb {
outer: dag_pb::PbNode,
inner: unixfs_pb::Data,
},
}

impl UnixfsNode {
pub fn decode<B: Buf>(buf: B) -> Result<Self> {
let outer = dag_pb::PbNode::decode(buf)?;
let inner_data = outer
.data
.as_ref()
.cloned()
.ok_or_else(|| anyhow!("missing data"))?;
let inner = unixfs_pb::Data::decode(inner_data)?;
// ensure correct unixfs type
let _typ: DataType = inner.r#type.try_into()?;

Ok(Self { outer, inner })
pub fn decode(cid: &Cid, buf: Bytes) -> Result<Self> {
match cid.codec() {
c if c == Codec::Raw as u64 => Ok(UnixfsNode::Raw { data: buf }),
_ => {
let outer = dag_pb::PbNode::decode(buf)?;
let inner_data = outer
.data
.as_ref()
.cloned()
.ok_or_else(|| anyhow!("missing data"))?;
let inner = unixfs_pb::Data::decode(inner_data)?;
// ensure correct unixfs type
let _typ: DataType = inner.r#type.try_into()?;

Ok(UnixfsNode::Pb { outer, inner })
}
}
}

pub fn typ(&self) -> DataType {
self.inner.r#type.try_into().expect("invalid data type")
pub fn typ(&self) -> Option<DataType> {
match self {
UnixfsNode::Raw { .. } => None,
UnixfsNode::Pb { inner, .. } => {
Some(inner.r#type.try_into().expect("invalid data type"))
}
}
}

pub fn links(&self) -> impl Iterator<Item = Result<LinkRef<'_>>> {
self.outer.links.iter().map(|l| {
let c = l.hash.as_ref().ok_or_else(|| anyhow!("missing link"))?;

Ok(LinkRef {
cid: Cid::read_bytes(Cursor::new(c))?,
name: l.name.as_deref(),
tsize: l.tsize,
})
})
pub fn links(&self) -> Links {
match self {
UnixfsNode::Raw { .. } => Links::Raw,
UnixfsNode::Pb { outer, .. } => Links::Pb { i: 0, outer },
}
}

pub async fn get_link_by_name<S: AsRef<str>>(
Expand All @@ -112,27 +124,77 @@ impl UnixfsNode {
}

pub fn pretty(&self) -> Result<Bytes> {
match self.typ() {
DataType::File => {
if self.outer.links.is_empty() {
// simplest case just one file
Ok(self.inner.data.as_ref().cloned().unwrap_or_default())
} else {
bail!("not implemented: files with multiple blocks")
match self {
UnixfsNode::Raw { data } => Ok(data.clone()),
UnixfsNode::Pb { outer, inner } => {
match self.typ().unwrap() {
DataType::File => {
if outer.links.is_empty() {
// simplest case just one file
Ok(inner.data.as_ref().cloned().unwrap_or_default())
} else {
bail!("not implemented: files with multiple blocks")
}
}
DataType::Directory => {
let mut res = String::new();
for link in &outer.links {
if let Some(ref name) = link.name {
res += name;
}
res += "\n";
}

Ok(Bytes::from(res))
}
_ => bail!("not implemented: {:?}", self.typ()),
}
}
DataType::Directory => {
let mut res = String::new();
for link in &self.outer.links {
if let Some(ref name) = link.name {
res += name;
}
res += "\n";
}
}
}

#[derive(Debug)]
pub enum Links<'a> {
Raw,
Pb { i: usize, outer: &'a dag_pb::PbNode },
}

impl<'a> Iterator for Links<'a> {
type Item = Result<LinkRef<'a>>;

fn next(&mut self) -> Option<Self::Item> {
match self {
Links::Raw => None,
Links::Pb { i, outer } => {
if *i == outer.links.len() {
return None;
}

Ok(Bytes::from(res))
let l = &outer.links[*i];
*i += 1;

let res = l
.hash
.as_ref()
.ok_or_else(|| anyhow!("missing link"))
.and_then(|c| {
Ok(LinkRef {
cid: Cid::read_bytes(Cursor::new(c))?,
name: l.name.as_deref(),
tsize: l.tsize,
})
});

Some(res)
}
_ => bail!("not implemented: {:?}", self.typ()),
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Links::Raw => (0, Some(0)),
Links::Pb { outer, .. } => (outer.links.len(), Some(outer.links.len())),
}
}
}

0 comments on commit 1d6d57d

Please sign in to comment.