Skip to content

Commit df0ffcb

Browse files
authored
feat: optional serialization support for core types (#64)
2 parents 82100a9 + bc36028 commit df0ffcb

File tree

8 files changed

+288
-163
lines changed

8 files changed

+288
-163
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ iroh-io = { version = "0.6.0", default-features = false, optional = true }
2424
positioned-io = { version = "0.3.1", default-features = false }
2525
genawaiter = { version = "0.99.1", features = ["futures03"], optional = true }
2626
tokio = { version = "1", features = ["sync"], default-features = false, optional = true }
27+
serde = { version = "1", features = ["derive"], optional = true }
2728

2829
[features]
30+
serde = ["dep:serde", "bytes/serde"]
2931
tokio_fsm = ["dep:futures-lite", "dep:iroh-io"]
3032
validate = ["dep:genawaiter"]
3133
experimental-mixed = ["dep:tokio"]
32-
default = ["tokio_fsm", "validate"]
34+
default = ["tokio_fsm", "validate", "serde"]
3335

3436
[dev-dependencies]
3537
hex = "0.4.3"

src/io/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl DecodeError {
8787
/// or a size mismatch. If the remote end stops listening while we are writing,
8888
/// the error will indicate which parent or chunk we were writing at the time.
8989
#[derive(Debug)]
90+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9091
pub enum EncodeError {
9192
/// The hash of a parent did not match the expected hash
9293
ParentHashMismatch(TreeNode),
@@ -99,6 +100,7 @@ pub enum EncodeError {
99100
/// File size does not match size in outboard
100101
SizeMismatch,
101102
/// There was an error reading from the underlying io
103+
#[cfg_attr(feature = "serde", serde(with = "crate::io_error_serde"))]
102104
Io(io::Error),
103105
}
104106

src/io/mixed.rs

+64-31
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Read from sync, send to tokio sender
2-
use std::result;
2+
use std::{future::Future, result};
33

44
use bytes::Bytes;
55
use iroh_blake3 as blake3;
66
use iroh_blake3::guts::parent_cv;
7+
use serde::{Deserialize, Serialize};
78
use smallvec::SmallVec;
89

910
use super::{sync::Outboard, EncodeError, Leaf, Parent};
@@ -13,7 +14,7 @@ use crate::{
1314
};
1415

1516
/// A content item for the bao streaming protocol.
16-
#[derive(Debug)]
17+
#[derive(Debug, Serialize, Deserialize)]
1718
pub enum EncodedItem {
1819
/// total data size, will be the first item
1920
Size(u64),
@@ -45,28 +46,52 @@ impl From<EncodeError> for EncodedItem {
4546
}
4647
}
4748

49+
/// Abstract sender trait for sending encoded items
50+
pub trait Sender {
51+
/// Error type
52+
type Error;
53+
/// Send an item
54+
fn send(
55+
&mut self,
56+
item: EncodedItem,
57+
) -> impl Future<Output = std::result::Result<(), Self::Error>> + '_;
58+
}
59+
60+
impl Sender for tokio::sync::mpsc::Sender<EncodedItem> {
61+
type Error = tokio::sync::mpsc::error::SendError<EncodedItem>;
62+
fn send(
63+
&mut self,
64+
item: EncodedItem,
65+
) -> impl Future<Output = std::result::Result<(), Self::Error>> + '_ {
66+
tokio::sync::mpsc::Sender::send(self, item)
67+
}
68+
}
69+
4870
/// Traverse ranges relevant to a query from a reader and outboard to a stream
4971
///
5072
/// This function validates the data before writing.
5173
///
5274
/// It is possible to encode ranges from a partial file and outboard.
5375
/// This will either succeed if the requested ranges are all present, or fail
5476
/// as soon as a range is missing.
55-
pub async fn traverse_ranges_validated<D: ReadBytesAt, O: Outboard>(
77+
pub async fn traverse_ranges_validated<D, O, F>(
5678
data: D,
5779
outboard: O,
5880
ranges: &ChunkRangesRef,
59-
encoded: &tokio::sync::mpsc::Sender<EncodedItem>,
60-
) {
61-
encoded
62-
.send(EncodedItem::Size(outboard.tree().size()))
63-
.await
64-
.ok();
65-
let res = match traverse_ranges_validated_impl(data, outboard, ranges, encoded).await {
66-
Ok(()) => EncodedItem::Done,
81+
send: &mut F,
82+
) -> std::result::Result<(), F::Error>
83+
where
84+
D: ReadBytesAt,
85+
O: Outboard,
86+
F: Sender,
87+
{
88+
send.send(EncodedItem::Size(outboard.tree().size())).await?;
89+
let res = match traverse_ranges_validated_impl(data, outboard, ranges, send).await {
90+
Ok(Ok(())) => EncodedItem::Done,
6791
Err(cause) => EncodedItem::Error(cause),
92+
Ok(Err(err)) => return Err(err),
6893
};
69-
encoded.send(res).await.ok();
94+
send.send(res).await
7095
}
7196

7297
/// Encode ranges relevant to a query from a reader and outboard to a writer
@@ -76,14 +101,19 @@ pub async fn traverse_ranges_validated<D: ReadBytesAt, O: Outboard>(
76101
/// It is possible to encode ranges from a partial file and outboard.
77102
/// This will either succeed if the requested ranges are all present, or fail
78103
/// as soon as a range is missing.
79-
async fn traverse_ranges_validated_impl<D: ReadBytesAt, O: Outboard>(
104+
async fn traverse_ranges_validated_impl<D, O, F>(
80105
data: D,
81106
outboard: O,
82107
ranges: &ChunkRangesRef,
83-
encoded: &tokio::sync::mpsc::Sender<EncodedItem>,
84-
) -> result::Result<(), EncodeError> {
108+
send: &mut F,
109+
) -> result::Result<std::result::Result<(), F::Error>, EncodeError>
110+
where
111+
D: ReadBytesAt,
112+
O: Outboard,
113+
F: Sender,
114+
{
85115
if ranges.is_empty() {
86-
return Ok(());
116+
return Ok(Ok(()));
87117
}
88118
let mut stack: SmallVec<[_; 10]> = SmallVec::<[blake3::Hash; 10]>::new();
89119
stack.push(outboard.root());
@@ -112,16 +142,13 @@ async fn traverse_ranges_validated_impl<D: ReadBytesAt, O: Outboard>(
112142
if left {
113143
stack.push(l_hash);
114144
}
115-
encoded
116-
.send(
117-
Parent {
118-
node,
119-
pair: (l_hash, r_hash),
120-
}
121-
.into(),
122-
)
123-
.await
124-
.ok();
145+
let item = Parent {
146+
node,
147+
pair: (l_hash, r_hash),
148+
};
149+
if let Err(e) = send.send(item.into()).await {
150+
return Ok(Err(e));
151+
}
125152
}
126153
BaoChunk::Leaf {
127154
start_chunk,
@@ -152,7 +179,9 @@ async fn traverse_ranges_validated_impl<D: ReadBytesAt, O: Outboard>(
152179
return Err(EncodeError::LeafHashMismatch(start_chunk));
153180
}
154181
for item in out_buf.into_iter() {
155-
encoded.send(item).await.ok();
182+
if let Err(e) = send.send(item).await {
183+
return Ok(Err(e));
184+
}
156185
}
157186
} else {
158187
let actual = hash_subtree(start_chunk.0, &buffer, is_root);
@@ -164,12 +193,14 @@ async fn traverse_ranges_validated_impl<D: ReadBytesAt, O: Outboard>(
164193
data: buffer,
165194
offset: start_chunk.to_bytes(),
166195
};
167-
encoded.send(item.into()).await.ok();
196+
if let Err(e) = send.send(item.into()).await {
197+
return Ok(Err(e));
198+
}
168199
};
169200
}
170201
}
171202
}
172-
Ok(())
203+
Ok(Ok(()))
173204
}
174205

175206
/// Encode ranges relevant to a query from a slice and outboard to a buffer.
@@ -299,11 +330,13 @@ mod tests {
299330
async fn smoke() {
300331
let data = [0u8; 100000];
301332
let outboard = PreOrderMemOutboard::create(data, BlockSize::from_chunk_log(4));
302-
let (tx, mut rx) = tokio::sync::mpsc::channel(10);
333+
let (mut tx, mut rx) = tokio::sync::mpsc::channel(10);
303334
let mut encoded = Vec::new();
304335
encode_ranges_validated(&data[..], &outboard, &ChunkRanges::empty(), &mut encoded).unwrap();
305336
tokio::spawn(async move {
306-
traverse_ranges_validated(&data[..], &outboard, &ChunkRanges::empty(), &tx).await;
337+
traverse_ranges_validated(&data[..], &outboard, &ChunkRanges::empty(), &mut tx)
338+
.await
339+
.unwrap();
307340
});
308341
let mut res = Vec::new();
309342
while let Some(item) = rx.recv().await {

src/io/mod.rs

+55-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
//! Implementation of bao streaming for std io and tokio io
2-
use std::pin::Pin;
3-
42
use bytes::Bytes;
53

64
use crate::{blake3, BlockSize, ChunkNum, ChunkRanges, TreeNode};
75

86
mod error;
9-
use std::future::Future;
107

118
pub use error::*;
129
use range_collections::{range_set::RangeSetRange, RangeSetRef};
@@ -27,7 +24,58 @@ pub struct Parent {
2724
pub pair: (blake3::Hash, blake3::Hash),
2825
}
2926

27+
#[cfg(feature = "serde")]
28+
mod serde_support {
29+
use serde::{ser::SerializeSeq, Deserialize, Serialize};
30+
31+
use super::{blake3, Parent, TreeNode};
32+
impl Serialize for Parent {
33+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
34+
let (l, r) = self.pair;
35+
let mut seq = serializer.serialize_seq(Some(2))?;
36+
seq.serialize_element(&self.node)?;
37+
seq.serialize_element(l.as_bytes())?;
38+
seq.serialize_element(r.as_bytes())?;
39+
seq.end()
40+
}
41+
}
42+
43+
impl<'a> Deserialize<'a> for Parent {
44+
fn deserialize<D: serde::Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
45+
struct ParentVisitor;
46+
impl<'de> serde::de::Visitor<'de> for ParentVisitor {
47+
type Value = Parent;
48+
49+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
50+
formatter.write_str("a parent node")
51+
}
52+
53+
fn visit_seq<A: serde::de::SeqAccess<'de>>(
54+
self,
55+
mut seq: A,
56+
) -> Result<Self::Value, A::Error> {
57+
let node = seq.next_element::<TreeNode>()?.ok_or_else(|| {
58+
serde::de::Error::invalid_length(0, &"a parent node with 3 elements")
59+
})?;
60+
let l = seq.next_element::<[u8; 32]>()?.ok_or_else(|| {
61+
serde::de::Error::invalid_length(1, &"a parent node with 3 elements")
62+
})?;
63+
let r = seq.next_element::<[u8; 32]>()?.ok_or_else(|| {
64+
serde::de::Error::invalid_length(2, &"a parent node with 3 elements")
65+
})?;
66+
Ok(Parent {
67+
node,
68+
pair: (blake3::Hash::from(l), blake3::Hash::from(r)),
69+
})
70+
}
71+
}
72+
deserializer.deserialize_seq(ParentVisitor)
73+
}
74+
}
75+
}
76+
3077
/// A leaf node.
78+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3179
pub struct Leaf {
3280
/// The byte offset of the leaf in the file.
3381
pub offset: u64,
@@ -49,6 +97,7 @@ impl std::fmt::Debug for Leaf {
4997
/// After reading the initial header, the only possible items are `Parent` and
5098
/// `Leaf`.
5199
#[derive(Debug)]
100+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52101
pub enum BaoContentItem {
53102
/// a parent node, to update the outboard
54103
Parent(Parent),
@@ -161,7 +210,9 @@ pub(crate) fn combine_hash_pair(l: &blake3::Hash, r: &blake3::Hash) -> [u8; 64]
161210
res
162211
}
163212

164-
pub(crate) type LocalBoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
213+
#[cfg(feature = "validate")]
214+
pub(crate) type LocalBoxFuture<'a, T> =
215+
std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a>>;
165216

166217
#[cfg(test)]
167218
mod tests {

0 commit comments

Comments
 (0)