1use std::io::SeekFrom;
4use std::marker::PhantomData;
5use std::sync::Arc;
6
7const DEFAULT_ASYNC_CONCURRENCY: usize = 8;
9
10const DEFAULT_LEAF_RETRIES: u32 = 4;
13
14#[cfg(feature = "tokio")]
15use bytes::Buf;
16use bytes::Bytes;
17use futures::stream::{self, FuturesUnordered, Stream, StreamExt};
18
19use crate::bmt::DEFAULT_BODY_SIZE;
20use crate::chunk::ChunkAddress;
21
22use super::error::{FileError, Result};
23use super::frontier::{SubtreeNode, expand_frontier, overlapping_children, read_subtree_bodies};
24use super::mode::{JoinMode, PlainMode};
25use super::tree::{ChunkRange, TreeParams};
26use crate::store::{ChunkGet, MaybeSend};
27
28#[cfg(feature = "encryption")]
29use super::mode::EncryptedMode;
30
31pub struct GenericJoiner<G, M: JoinMode, const BODY_SIZE: usize = DEFAULT_BODY_SIZE>
33where
34 G: ChunkGet<BODY_SIZE>,
35{
36 getter: Arc<G>,
37 root: ChunkAddress,
38 context: M::JoinerContext,
39 span: u64,
40 tree: TreeParams<BODY_SIZE>,
41 subtrees: Vec<SubtreeNode<M>>,
43 position: u64,
44 concurrency: usize,
45 _mode: PhantomData<M>,
46}
47
48pub type Joiner<G, const BODY_SIZE: usize = DEFAULT_BODY_SIZE> =
50 GenericJoiner<G, PlainMode, BODY_SIZE>;
51
52#[cfg(feature = "encryption")]
54pub type EncryptedJoiner<G, const BODY_SIZE: usize = DEFAULT_BODY_SIZE> =
55 GenericJoiner<G, EncryptedMode, BODY_SIZE>;
56
57impl<G, M, const BODY_SIZE: usize> std::fmt::Debug for GenericJoiner<G, M, BODY_SIZE>
58where
59 G: ChunkGet<BODY_SIZE>,
60 M: JoinMode,
61{
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("GenericJoiner")
64 .field("root", &self.root)
65 .field("span", &self.span)
66 .field("position", &self.position)
67 .field("concurrency", &self.concurrency)
68 .finish_non_exhaustive()
69 }
70}
71
72async fn collect_subtree_bodies<G, M, const BODY_SIZE: usize>(
74 getter: &Arc<G>,
75 subtrees: Vec<SubtreeNode<M>>,
76 chunk_range: ChunkRange,
77 concurrency: usize,
78) -> Result<Vec<Bytes>>
79where
80 G: ChunkGet<BODY_SIZE>,
81 M: JoinMode + MaybeSend + Sync,
82{
83 let bodies: Vec<Bytes> = stream::iter(subtrees)
84 .map(|st| {
85 let getter = Arc::clone(getter);
86 async move { read_subtree_bodies::<G, M, BODY_SIZE>(&*getter, &st, &chunk_range).await }
87 })
88 .buffered(concurrency)
89 .collect::<Vec<_>>()
90 .await
91 .into_iter()
92 .collect::<Result<Vec<Vec<Bytes>>>>()?
93 .into_iter()
94 .flatten()
95 .collect();
96 Ok(bodies)
97}
98
99impl<G, M, const BODY_SIZE: usize> GenericJoiner<G, M, BODY_SIZE>
100where
101 G: ChunkGet<BODY_SIZE>,
102 M: JoinMode + MaybeSend + Sync,
103{
104 pub async fn new(getter: G, input: M::RootRef) -> Result<Self> {
106 const { super::constants::assert_valid_body_size::<BODY_SIZE>() };
107
108 let (root, span, context) =
109 super::mode::joiner_init::<M, G, BODY_SIZE>(&getter, input).await?;
110 let tree = TreeParams::<BODY_SIZE>::new(span);
111
112 let target = DEFAULT_ASYNC_CONCURRENCY * 2;
113 let full_range = tree.chunks_for_range(0, span);
114 let subtrees =
115 expand_frontier::<G, M, BODY_SIZE>(&getter, &root, &context, span, &full_range, target)
116 .await?;
117
118 Ok(Self {
119 getter: Arc::new(getter),
120 root,
121 context,
122 span,
123 tree,
124 subtrees,
125 position: 0,
126 concurrency: DEFAULT_ASYNC_CONCURRENCY,
127 _mode: PhantomData,
128 })
129 }
130
131 pub fn with_concurrency(mut self, concurrency: usize) -> Self {
133 self.concurrency = concurrency.max(1);
134 self
135 }
136
137 #[inline]
139 pub const fn size(&self) -> u64 {
140 self.span
141 }
142
143 #[inline]
145 pub const fn position(&self) -> u64 {
146 self.position
147 }
148
149 #[inline]
151 pub const fn root(&self) -> &ChunkAddress {
152 &self.root
153 }
154
155 #[allow(dead_code, reason = "consumed by sibling-module joiner extensions")]
157 pub(crate) const fn getter(&self) -> &Arc<G> {
158 &self.getter
159 }
160 #[allow(dead_code, reason = "consumed by sibling-module joiner extensions")]
161 pub(crate) fn subtrees(&self) -> &[SubtreeNode<M>] {
162 &self.subtrees
163 }
164 #[allow(dead_code, reason = "consumed by sibling-module joiner extensions")]
165 pub(crate) const fn tree(&self) -> TreeParams<BODY_SIZE> {
166 self.tree
167 }
168 #[allow(dead_code, reason = "consumed by sibling-module joiner extensions")]
169 pub(crate) const fn concurrency(&self) -> usize {
170 self.concurrency
171 }
172 #[allow(dead_code, reason = "consumed by sibling-module joiner extensions")]
173 pub(crate) const fn context(&self) -> &M::JoinerContext {
174 &self.context
175 }
176
177 pub async fn read_range(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
179 Self::read_range_with(
180 &self.getter,
181 &self.subtrees,
182 &self.root,
183 &self.context,
184 self.span,
185 self.tree,
186 self.concurrency,
187 offset,
188 len,
189 )
190 .await
191 }
192
193 pub async fn read_all(&self) -> Result<Vec<u8>> {
195 self.read_range(0, self.span as usize).await
196 }
197
198 #[allow(
200 clippy::too_many_arguments,
201 reason = "internal helper threading already-decomposed reader state from two call sites"
202 )]
203 async fn read_range_with(
204 getter: &Arc<G>,
205 subtrees: &[SubtreeNode<M>],
206 root: &ChunkAddress,
207 context: &M::JoinerContext,
208 span: u64,
209 tree: TreeParams<BODY_SIZE>,
210 concurrency: usize,
211 offset: u64,
212 len: usize,
213 ) -> Result<Vec<u8>> {
214 use super::helpers::{ReadRangeCheck, validate_read_range};
215
216 let (offset, actual_len) = match validate_read_range::<BODY_SIZE>(offset, len, span) {
217 ReadRangeCheck::Empty => return Ok(Vec::new()),
218 ReadRangeCheck::SingleChunk { offset, actual_len } => {
219 let chunk = getter.get(root).await.map_err(FileError::getter)?;
220 let chunk = chunk.into_content().ok_or(FileError::InvalidChunkType {
221 type_name: "non-content",
222 })?;
223 let body = M::decode_body::<BODY_SIZE>(chunk, context, span)?;
224 let start = offset as usize;
225 let end = start + actual_len;
226 return Ok(body[start..end].to_vec());
227 }
228 ReadRangeCheck::MultiChunk { offset, actual_len } => (offset, actual_len),
229 };
230
231 let chunk_range = tree.chunks_for_range(offset, actual_len as u64);
232 let range_start_byte = chunk_range.start * BODY_SIZE as u64;
233 let range_end_byte = chunk_range.end * BODY_SIZE as u64;
234
235 let relevant: Vec<_> = subtrees
236 .iter()
237 .filter(|st| {
238 st.byte_offset < range_end_byte && st.byte_offset + st.span > range_start_byte
239 })
240 .cloned()
241 .collect();
242
243 let bodies =
244 collect_subtree_bodies::<G, M, BODY_SIZE>(getter, relevant, chunk_range, concurrency)
245 .await?;
246
247 Ok(super::tree::assemble_range(
248 &tree,
249 offset,
250 actual_len,
251 &chunk_range,
252 &bodies,
253 ))
254 }
255
256 pub fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
258 self.position = super::resolve_seek_position(pos, self.position, self.span)?;
259 Ok(self.position)
260 }
261
262 pub fn into_stream(self) -> impl Stream<Item = Result<Bytes>> {
264 let getter = self.getter;
265 let chunk_range = self.tree.chunks_for_range(0, self.span);
266
267 struct State<M: JoinMode> {
268 subtrees: std::vec::IntoIter<SubtreeNode<M>>,
269 pending: std::vec::IntoIter<Bytes>,
270 }
271
272 let state = State {
273 subtrees: self.subtrees.into_iter(),
274 pending: Vec::new().into_iter(),
275 };
276
277 stream::unfold(state, move |mut state| {
278 let getter = Arc::clone(&getter);
279 async move {
280 if let Some(body) = state.pending.next() {
282 return Some((Ok(body), state));
283 }
284
285 let st = state.subtrees.next()?;
287 match read_subtree_bodies::<G, M, BODY_SIZE>(&*getter, &st, &chunk_range).await {
288 Ok(bodies) => {
289 let mut iter = bodies.into_iter();
290 match iter.next() {
291 Some(first) => {
292 state.pending = iter;
293 Some((Ok(first), state))
294 }
295 None => Some((Ok(Bytes::new()), state)),
296 }
297 }
298 Err(e) => Some((Err(e), state)),
299 }
300 }
301 })
302 }
303
304 pub fn into_offset_stream(self) -> impl Stream<Item = Result<(u64, Bytes)>> {
316 let getter = self.getter;
317 let concurrency = self.concurrency;
318 let chunk_range = self.tree.chunks_for_range(0, self.span);
319
320 let subtrees = stream::iter(self.subtrees)
323 .map(move |st| {
324 let getter = Arc::clone(&getter);
325 async move {
326 let base = st.byte_offset;
327 let bodies =
328 read_subtree_bodies::<G, M, BODY_SIZE>(&*getter, &st, &chunk_range).await?;
329 Ok::<(u64, Vec<Bytes>), FileError>((base, bodies))
330 }
331 })
332 .buffer_unordered(concurrency.max(1));
333
334 struct State<S> {
337 subtrees: S,
338 base: u64,
339 leaf_index: usize,
340 pending: std::vec::IntoIter<Bytes>,
341 }
342
343 let state = State {
344 subtrees: Box::pin(subtrees),
345 base: 0,
346 leaf_index: 0,
347 pending: Vec::new().into_iter(),
348 };
349
350 stream::unfold(state, move |mut state| async move {
351 loop {
352 if let Some(body) = state.pending.next() {
355 let offset = state.base + state.leaf_index as u64 * BODY_SIZE as u64;
356 state.leaf_index += 1;
357 return Some((Ok((offset, body)), state));
358 }
359
360 match state.subtrees.next().await? {
362 Ok((base, bodies)) => {
363 state.base = base;
364 state.leaf_index = 0;
365 state.pending = bodies.into_iter();
366 }
368 Err(e) => return Some((Err(e), state)),
369 }
370 }
371 })
372 }
373
374 pub fn into_offset_stream_chunked(self) -> impl Stream<Item = Result<(u64, Bytes)>>
399 where
400 G: 'static,
401 {
402 let getter = self.getter;
403 let width = self.concurrency.max(1);
404 let chunk_range = self.tree.chunks_for_range(0, self.span);
405
406 struct Pending<M: JoinMode> {
409 node: SubtreeNode<M>,
410 retries: u32,
411 }
412
413 enum Resolved<M: JoinMode> {
415 Leaf(u64, Bytes),
417 Children(Vec<SubtreeNode<M>>),
419 Retry(Pending<M>),
421 Failed(FileError),
423 }
424
425 #[cfg(not(target_arch = "wasm32"))]
426 type BoxResolvedFuture<M> =
427 std::pin::Pin<Box<dyn std::future::Future<Output = Resolved<M>> + Send>>;
428 #[cfg(target_arch = "wasm32")]
429 type BoxResolvedFuture<M> =
430 std::pin::Pin<Box<dyn std::future::Future<Output = Resolved<M>>>>;
431
432 async fn fetch_one<G, M, const BS: usize>(
435 getter: &G,
436 chunk_range: &ChunkRange,
437 pending: Pending<M>,
438 ) -> Resolved<M>
439 where
440 G: ChunkGet<BS>,
441 M: JoinMode + MaybeSend + Sync,
442 {
443 let node = &pending.node;
444 let body = match super::mode::read_chunk_body::<M, G, BS>(
445 getter,
446 &node.addr,
447 &node.context,
448 node.span,
449 )
450 .await
451 {
452 Ok(body) => body,
453 Err(e) => {
454 if node.span <= BS as u64 && pending.retries > 0 {
455 return Resolved::Retry(Pending {
456 node: pending.node,
457 retries: pending.retries - 1,
458 });
459 }
460 return Resolved::Failed(e);
461 }
462 };
463
464 if node.span <= BS as u64 {
465 return Resolved::Leaf(node.byte_offset, body);
466 }
467 match overlapping_children::<M, BS>(&body, node, chunk_range) {
468 Ok(children) => Resolved::Children(children),
469 Err(e) => Resolved::Failed(e),
470 }
471 }
472
473 struct State<G, M: JoinMode, const BS: usize> {
474 getter: Arc<G>,
475 chunk_range: ChunkRange,
476 width: usize,
477 queue: std::collections::VecDeque<Pending<M>>,
478 in_flight: FuturesUnordered<BoxResolvedFuture<M>>,
479 }
480
481 let mut queue = std::collections::VecDeque::new();
482 for st in self.subtrees {
483 queue.push_back(Pending {
484 node: st,
485 retries: DEFAULT_LEAF_RETRIES,
486 });
487 }
488
489 let state = State::<G, M, BODY_SIZE> {
490 getter,
491 chunk_range,
492 width,
493 queue,
494 in_flight: FuturesUnordered::new(),
495 };
496
497 stream::unfold(state, move |mut state| async move {
498 loop {
499 while state.in_flight.len() < state.width {
502 let Some(pending) = state.queue.pop_front() else {
503 break;
504 };
505 let getter = Arc::clone(&state.getter);
506 let range = state.chunk_range;
507 state.in_flight.push(Box::pin(async move {
508 fetch_one::<G, M, BODY_SIZE>(&*getter, &range, pending).await
509 }) as BoxResolvedFuture<M>);
510 }
511
512 let resolved = state.in_flight.next().await?;
514 match resolved {
515 Resolved::Leaf(offset, body) => {
516 return Some((Ok((offset, body)), state));
517 }
518 Resolved::Children(children) => {
519 for child in children {
520 state.queue.push_back(Pending {
521 node: child,
522 retries: DEFAULT_LEAF_RETRIES,
523 });
524 }
525 }
526 Resolved::Retry(pending) => {
527 state.queue.push_back(pending);
530 }
531 Resolved::Failed(e) => return Some((Err(e), state)),
532 }
533 }
534 })
535 }
536
537 pub fn into_offset_stream_chunked_range(
548 self,
549 start: u64,
550 len: u64,
551 ) -> impl Stream<Item = Result<(u64, Bytes)>>
552 where
553 G: 'static,
554 {
555 chunked_range_stream_from::<G, M, BODY_SIZE>(
556 self.getter,
557 self.subtrees,
558 self.tree,
559 self.span,
560 self.concurrency,
561 start,
562 len,
563 )
564 }
565
566 #[cfg(feature = "tokio")]
568 pub fn into_reader(self) -> JoinerReader<G, M, BODY_SIZE> {
569 JoinerReader {
570 joiner: self,
571 buffer: Bytes::new(),
572 future: None,
573 }
574 }
575}
576
577pub(crate) fn chunked_range_stream_from<G, M, const BODY_SIZE: usize>(
587 getter: Arc<G>,
588 subtrees: Vec<SubtreeNode<M>>,
589 tree: TreeParams<BODY_SIZE>,
590 span: u64,
591 concurrency: usize,
592 start: u64,
593 len: u64,
594) -> impl Stream<Item = Result<(u64, Bytes)>> + 'static
595where
596 G: ChunkGet<BODY_SIZE> + 'static,
597 M: JoinMode + MaybeSend + Sync,
598{
599 let width = concurrency.max(1);
600
601 let range_start = start.min(span);
605 let range_end = (start.saturating_add(len)).min(span);
606 let chunk_range = tree.chunks_for_range(range_start, range_end - range_start);
607
608 struct Pending<M: JoinMode> {
611 node: SubtreeNode<M>,
612 retries: u32,
613 }
614
615 enum Resolved<M: JoinMode> {
617 Leaf(u64, Bytes),
619 Children(Vec<SubtreeNode<M>>),
621 Retry(Pending<M>),
623 Failed(FileError),
625 }
626
627 #[cfg(not(target_arch = "wasm32"))]
628 type BoxResolvedFuture<M> =
629 std::pin::Pin<Box<dyn std::future::Future<Output = Resolved<M>> + Send>>;
630 #[cfg(target_arch = "wasm32")]
631 type BoxResolvedFuture<M> = std::pin::Pin<Box<dyn std::future::Future<Output = Resolved<M>>>>;
632
633 async fn fetch_one<G, M, const BS: usize>(
637 getter: &G,
638 chunk_range: &ChunkRange,
639 pending: Pending<M>,
640 ) -> Resolved<M>
641 where
642 G: ChunkGet<BS>,
643 M: JoinMode + MaybeSend + Sync,
644 {
645 let node = &pending.node;
646 let body = match super::mode::read_chunk_body::<M, G, BS>(
647 getter,
648 &node.addr,
649 &node.context,
650 node.span,
651 )
652 .await
653 {
654 Ok(body) => body,
655 Err(e) => {
656 if node.span <= BS as u64 && pending.retries > 0 {
657 return Resolved::Retry(Pending {
658 node: pending.node,
659 retries: pending.retries - 1,
660 });
661 }
662 return Resolved::Failed(e);
663 }
664 };
665
666 if node.span <= BS as u64 {
667 return Resolved::Leaf(node.byte_offset, body);
668 }
669 match overlapping_children::<M, BS>(&body, node, chunk_range) {
670 Ok(children) => Resolved::Children(children),
671 Err(e) => Resolved::Failed(e),
672 }
673 }
674
675 struct State<G, M: JoinMode> {
676 getter: Arc<G>,
677 chunk_range: ChunkRange,
678 range_start: u64,
679 range_end: u64,
680 width: usize,
681 queue: std::collections::VecDeque<Pending<M>>,
682 in_flight: FuturesUnordered<BoxResolvedFuture<M>>,
683 }
684
685 let mut queue = std::collections::VecDeque::new();
688 if range_end > range_start {
689 let range_start_byte = chunk_range.start * BODY_SIZE as u64;
690 let range_end_byte = chunk_range.end * BODY_SIZE as u64;
691 for st in subtrees {
692 if st.byte_offset < range_end_byte && st.byte_offset + st.span > range_start_byte {
693 queue.push_back(Pending {
694 node: st,
695 retries: DEFAULT_LEAF_RETRIES,
696 });
697 }
698 }
699 }
700
701 let state = State::<G, M> {
702 getter,
703 chunk_range,
704 range_start,
705 range_end,
706 width,
707 queue,
708 in_flight: FuturesUnordered::new(),
709 };
710
711 stream::unfold(state, move |mut state| async move {
712 loop {
713 while state.in_flight.len() < state.width {
716 let Some(pending) = state.queue.pop_front() else {
717 break;
718 };
719 let getter = Arc::clone(&state.getter);
720 let range = state.chunk_range;
721 state.in_flight.push(Box::pin(async move {
722 fetch_one::<G, M, BODY_SIZE>(&*getter, &range, pending).await
723 }) as BoxResolvedFuture<M>);
724 }
725
726 let resolved = state.in_flight.next().await?;
728 match resolved {
729 Resolved::Leaf(leaf_start, body) => {
730 let leaf_end = leaf_start + body.len() as u64;
735 if leaf_end <= state.range_start || leaf_start >= state.range_end {
736 continue;
737 }
738 let clip_lo = state.range_start.saturating_sub(leaf_start) as usize;
739 let clip_hi = (state.range_end - leaf_start).min(body.len() as u64) as usize;
740 let offset = leaf_start.max(state.range_start);
741 return Some((Ok((offset, body.slice(clip_lo..clip_hi))), state));
742 }
743 Resolved::Children(children) => {
744 for child in children {
745 state.queue.push_back(Pending {
746 node: child,
747 retries: DEFAULT_LEAF_RETRIES,
748 });
749 }
750 }
751 Resolved::Retry(pending) => {
752 state.queue.push_back(pending);
755 }
756 Resolved::Failed(e) => return Some((Err(e), state)),
757 }
758 }
759 })
760}
761
762#[cfg(feature = "tokio")]
766pub struct JoinerReader<G, M: JoinMode, const BODY_SIZE: usize = DEFAULT_BODY_SIZE>
767where
768 G: ChunkGet<BODY_SIZE>,
769{
770 joiner: GenericJoiner<G, M, BODY_SIZE>,
771 buffer: Bytes,
772 #[allow(clippy::type_complexity)]
773 future: Option<std::pin::Pin<Box<dyn std::future::Future<Output = Result<Vec<u8>>> + Send>>>,
774}
775
776#[cfg(feature = "tokio")]
777impl<G, M, const BODY_SIZE: usize> std::fmt::Debug for JoinerReader<G, M, BODY_SIZE>
778where
779 G: ChunkGet<BODY_SIZE>,
780 M: JoinMode,
781{
782 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
783 f.debug_struct("JoinerReader")
784 .field("joiner", &self.joiner)
785 .field("buffer_len", &self.buffer.len())
786 .field("has_pending_future", &self.future.is_some())
787 .finish()
788 }
789}
790
791#[cfg(feature = "tokio")]
794impl<G: ChunkGet<BODY_SIZE>, M: JoinMode, const BODY_SIZE: usize> Unpin
795 for JoinerReader<G, M, BODY_SIZE>
796{
797}
798
799#[cfg(feature = "tokio")]
800impl<G, M, const BODY_SIZE: usize> tokio::io::AsyncRead for JoinerReader<G, M, BODY_SIZE>
801where
802 G: ChunkGet<BODY_SIZE> + 'static,
803 M: JoinMode + Send + Sync + 'static,
804{
805 fn poll_read(
806 self: std::pin::Pin<&mut Self>,
807 cx: &mut std::task::Context<'_>,
808 buf: &mut tokio::io::ReadBuf<'_>,
809 ) -> std::task::Poll<std::io::Result<()>> {
810 use std::task::Poll;
811
812 let this = self.get_mut();
813
814 if !this.buffer.is_empty() {
816 let to_copy = this.buffer.len().min(buf.remaining());
817 buf.put_slice(&this.buffer[..to_copy]);
818 this.buffer.advance(to_copy);
819 return Poll::Ready(Ok(()));
820 }
821
822 if this.joiner.position >= this.joiner.span {
824 return Poll::Ready(Ok(()));
825 }
826
827 if this.future.is_none() {
829 let position = this.joiner.position;
830 let remaining = (this.joiner.span - position) as usize;
831 let read_len = remaining.min(BODY_SIZE);
832 let getter = Arc::clone(&this.joiner.getter);
833 let root = this.joiner.root;
834 let context = this.joiner.context.clone();
835 let span = this.joiner.span;
836 let tree = this.joiner.tree;
837 let concurrency = this.joiner.concurrency;
838 let subtrees: Vec<SubtreeNode<M>> = this.joiner.subtrees.clone();
839
840 let fut = async move {
841 GenericJoiner::<G, M, BODY_SIZE>::read_range_with(
842 &getter,
843 &subtrees,
844 &root,
845 &context,
846 span,
847 tree,
848 concurrency,
849 position,
850 read_len,
851 )
852 .await
853 };
854 this.future = Some(Box::pin(fut));
855 }
856
857 let fut = this.future.as_mut().unwrap();
859 match fut.as_mut().poll(cx) {
860 Poll::Ready(Ok(data)) => {
861 this.future = None;
862 let bytes = Bytes::from(data);
863 this.joiner.position += bytes.len() as u64;
864 let to_copy = bytes.len().min(buf.remaining());
865 buf.put_slice(&bytes[..to_copy]);
866 if to_copy < bytes.len() {
867 this.buffer = bytes.slice(to_copy..);
868 }
869 Poll::Ready(Ok(()))
870 }
871 Poll::Ready(Err(e)) => {
872 this.future = None;
873 Poll::Ready(Err(std::io::Error::other(e)))
874 }
875 Poll::Pending => Poll::Pending,
876 }
877 }
878}
879
880#[cfg(feature = "tokio")]
881impl<G, M, const BODY_SIZE: usize> tokio::io::AsyncSeek for JoinerReader<G, M, BODY_SIZE>
882where
883 G: ChunkGet<BODY_SIZE> + 'static,
884 M: JoinMode + Send + Sync + 'static,
885{
886 fn start_seek(self: std::pin::Pin<&mut Self>, pos: SeekFrom) -> std::io::Result<()> {
887 let this = self.get_mut();
888 this.joiner.position =
889 super::resolve_seek_position(pos, this.joiner.position, this.joiner.span)?;
890 this.buffer = Bytes::new();
891 this.future = None;
892 Ok(())
893 }
894
895 fn poll_complete(
896 self: std::pin::Pin<&mut Self>,
897 _cx: &mut std::task::Context<'_>,
898 ) -> std::task::Poll<std::io::Result<u64>> {
899 std::task::Poll::Ready(Ok(self.get_mut().joiner.position))
900 }
901}
902
903#[cfg(all(test, feature = "tokio"))]
904mod tests {
905 use super::*;
906 use crate::chunk::AnyChunk;
907 use crate::file::split;
908 use std::collections::HashMap;
909
910 fn split_and_store(data: &[u8]) -> (ChunkAddress, HashMap<ChunkAddress, AnyChunk>) {
911 let (root, store) = split::<DEFAULT_BODY_SIZE>(data).unwrap();
912 (root, store.into_chunks())
913 }
914
915 generate_plain_joiner_tests!(tokio::test, Joiner, [async], [await]);
917
918 #[tokio::test]
921 async fn test_joiner_stream() {
922 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3)
923 .map(|i| (i % 256) as u8)
924 .collect();
925 let (root, store) = split_and_store(&data);
926
927 let joiner = Joiner::new(store, root).await.unwrap();
928 let chunks: Vec<Result<Bytes>> = joiner.into_stream().collect().await;
929
930 let mut recovered = Vec::new();
931 for chunk in chunks {
932 recovered.extend_from_slice(&chunk.unwrap());
933 }
934 assert_eq!(recovered, data);
935 }
936
937 async fn assert_offset_stream_matches(data: &[u8]) {
940 let (root, store) = split_and_store(data);
941
942 let expected = Joiner::new(store.clone(), root)
943 .await
944 .unwrap()
945 .read_all()
946 .await
947 .unwrap();
948
949 let joiner = Joiner::new(store, root).await.unwrap();
950 let total = joiner.size();
951 let pairs: Vec<Result<(u64, Bytes)>> = joiner.into_offset_stream().collect().await;
952
953 let mut reassembled = vec![0u8; total as usize];
954 let mut covered = 0u64;
955 let mut seen_offsets = std::collections::HashSet::new();
956 for pair in pairs {
957 let (offset, body) = pair.unwrap();
958 assert!(
959 seen_offsets.insert(offset),
960 "offset {offset} yielded more than once"
961 );
962 let start = offset as usize;
963 let end = start + body.len();
964 reassembled[start..end].copy_from_slice(&body);
965 covered += body.len() as u64;
966 }
967
968 assert_eq!(covered, total, "every byte covered exactly once");
969 assert_eq!(reassembled, expected, "offset reassembly equals read_all");
970 assert_eq!(reassembled, data, "offset reassembly equals input");
971 }
972
973 #[tokio::test]
974 async fn test_offset_stream_small() {
975 assert_offset_stream_matches(b"hello world").await;
976 }
977
978 #[tokio::test]
979 async fn test_offset_stream_exact_chunk() {
980 assert_offset_stream_matches(&vec![0xAB; DEFAULT_BODY_SIZE]).await;
981 }
982
983 #[tokio::test]
984 async fn test_offset_stream_multi_chunk() {
985 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3 + 123)
986 .map(|i| (i % 256) as u8)
987 .collect();
988 assert_offset_stream_matches(&data).await;
989 }
990
991 #[tokio::test]
992 async fn test_offset_stream_129_chunks() {
993 let refs_per_chunk = DEFAULT_BODY_SIZE / super::super::constants::REF_SIZE;
994 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * (refs_per_chunk + 1))
995 .map(|i| (i % 256) as u8)
996 .collect();
997 assert_offset_stream_matches(&data).await;
998 }
999
1000 #[tokio::test]
1001 async fn test_offset_stream_concurrency_one() {
1002 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 7)
1005 .map(|i| (i % 256) as u8)
1006 .collect();
1007 let (root, store) = split_and_store(&data);
1008 let joiner = Joiner::new(store, root).await.unwrap().with_concurrency(1);
1009 let total = joiner.size();
1010 let mut reassembled = vec![0u8; total as usize];
1011 let stream = joiner.into_offset_stream();
1012 futures::pin_mut!(stream);
1013 while let Some(pair) = stream.next().await {
1014 let (offset, body) = pair.unwrap();
1015 let start = offset as usize;
1016 reassembled[start..start + body.len()].copy_from_slice(&body);
1017 }
1018 assert_eq!(reassembled, data);
1019 }
1020
1021 async fn assert_offset_stream_chunked_matches(data: &[u8]) {
1024 let (root, store) = split_and_store(data);
1025
1026 let expected = Joiner::new(store.clone(), root)
1027 .await
1028 .unwrap()
1029 .read_all()
1030 .await
1031 .unwrap();
1032
1033 let joiner = Joiner::new(store, root).await.unwrap();
1034 let total = joiner.size();
1035 let pairs: Vec<Result<(u64, Bytes)>> = joiner.into_offset_stream_chunked().collect().await;
1036
1037 let mut reassembled = vec![0u8; total as usize];
1038 let mut covered = 0u64;
1039 let mut seen_offsets = std::collections::HashSet::new();
1040 for pair in pairs {
1041 let (offset, body) = pair.unwrap();
1042 assert!(
1043 seen_offsets.insert(offset),
1044 "offset {offset} yielded more than once"
1045 );
1046 let start = offset as usize;
1047 let end = start + body.len();
1048 reassembled[start..end].copy_from_slice(&body);
1049 covered += body.len() as u64;
1050 }
1051
1052 assert_eq!(covered, total, "every byte covered exactly once");
1053 assert_eq!(reassembled, expected, "chunked reassembly equals read_all");
1054 assert_eq!(reassembled, data, "chunked reassembly equals input");
1055 }
1056
1057 #[tokio::test]
1058 async fn test_offset_stream_chunked_small() {
1059 assert_offset_stream_chunked_matches(b"hello world").await;
1060 }
1061
1062 #[tokio::test]
1063 async fn test_offset_stream_chunked_exact_chunk() {
1064 assert_offset_stream_chunked_matches(&vec![0xAB; DEFAULT_BODY_SIZE]).await;
1065 }
1066
1067 #[tokio::test]
1068 async fn test_offset_stream_chunked_multi_chunk() {
1069 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3 + 123)
1070 .map(|i| (i % 256) as u8)
1071 .collect();
1072 assert_offset_stream_chunked_matches(&data).await;
1073 }
1074
1075 #[tokio::test]
1076 async fn test_offset_stream_chunked_three_level_tree() {
1077 let refs_per_chunk = DEFAULT_BODY_SIZE / super::super::constants::REF_SIZE;
1080 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * (refs_per_chunk + 1))
1081 .map(|i| (i % 256) as u8)
1082 .collect();
1083 assert_offset_stream_chunked_matches(&data).await;
1084 }
1085
1086 #[tokio::test]
1087 async fn test_offset_stream_chunked_concurrency_one() {
1088 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 7)
1090 .map(|i| (i % 256) as u8)
1091 .collect();
1092 let (root, store) = split_and_store(&data);
1093 let joiner = Joiner::new(store, root).await.unwrap().with_concurrency(1);
1094 let total = joiner.size();
1095 let mut reassembled = vec![0u8; total as usize];
1096 let stream = joiner.into_offset_stream_chunked();
1097 futures::pin_mut!(stream);
1098 while let Some(pair) = stream.next().await {
1099 let (offset, body) = pair.unwrap();
1100 let start = offset as usize;
1101 reassembled[start..start + body.len()].copy_from_slice(&body);
1102 }
1103 assert_eq!(reassembled, data);
1104 }
1105
1106 #[derive(Clone)]
1110 struct ConcurrencyProbe {
1111 chunks: Arc<HashMap<ChunkAddress, AnyChunk>>,
1112 in_flight: Arc<std::sync::atomic::AtomicUsize>,
1113 max_in_flight: Arc<std::sync::atomic::AtomicUsize>,
1114 }
1115
1116 impl crate::store::ChunkGet<DEFAULT_BODY_SIZE> for ConcurrencyProbe {
1117 type Error = crate::store::ChunkStoreError;
1118
1119 async fn get(&self, address: &ChunkAddress) -> std::result::Result<AnyChunk, Self::Error> {
1120 use std::sync::atomic::Ordering;
1121 let now = self.in_flight.fetch_add(1, Ordering::SeqCst) + 1;
1122 self.max_in_flight.fetch_max(now, Ordering::SeqCst);
1123 for _ in 0..8 {
1126 tokio::task::yield_now().await;
1127 }
1128 self.in_flight.fetch_sub(1, Ordering::SeqCst);
1129 self.chunks
1130 .get(address)
1131 .cloned()
1132 .ok_or_else(|| crate::store::ChunkStoreError::not_found(address))
1133 }
1134 }
1135
1136 #[tokio::test(flavor = "current_thread")]
1137 async fn test_offset_stream_chunked_per_chunk_concurrency() {
1138 let leaves = 40usize;
1143 let width = 16usize;
1144 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * leaves)
1145 .map(|i| (i % 256) as u8)
1146 .collect();
1147 let (root, store) = split_and_store(&data);
1148
1149 let probe = ConcurrencyProbe {
1150 chunks: Arc::new(store),
1151 in_flight: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
1152 max_in_flight: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
1153 };
1154 let max_seen = Arc::clone(&probe.max_in_flight);
1155
1156 let joiner = Joiner::new(probe, root)
1157 .await
1158 .unwrap()
1159 .with_concurrency(width);
1160
1161 let total = joiner.size();
1162 let mut reassembled = vec![0u8; total as usize];
1163 let stream = joiner.into_offset_stream_chunked();
1164 futures::pin_mut!(stream);
1165 while let Some(pair) = stream.next().await {
1166 let (offset, body) = pair.unwrap();
1167 let start = offset as usize;
1168 reassembled[start..start + body.len()].copy_from_slice(&body);
1169 }
1170 assert_eq!(reassembled, data, "concurrency probe still reassembles");
1171
1172 let peak = max_seen.load(std::sync::atomic::Ordering::SeqCst);
1173 assert!(
1174 peak >= width,
1175 "chunk-granular stream should reach width {width} in-flight, saw {peak}"
1176 );
1177 }
1178
1179 async fn assert_offset_stream_chunked_range_matches(data: &[u8], start: u64, len: u64) {
1184 for width in [DEFAULT_ASYNC_CONCURRENCY, 1] {
1185 let (root, store) = split_and_store(data);
1186
1187 let expected = Joiner::new(store.clone(), root)
1188 .await
1189 .unwrap()
1190 .read_range(start, len as usize)
1191 .await
1192 .unwrap();
1193
1194 let joiner = Joiner::new(store, root)
1195 .await
1196 .unwrap()
1197 .with_concurrency(width);
1198 let pairs: Vec<Result<(u64, Bytes)>> = joiner
1199 .into_offset_stream_chunked_range(start, len)
1200 .collect()
1201 .await;
1202
1203 let mut reassembled = vec![0u8; expected.len()];
1204 let mut seen_offsets = std::collections::HashSet::new();
1205 for pair in pairs {
1206 let (offset, body) = pair.unwrap();
1207 assert!(
1208 offset >= start && offset + body.len() as u64 <= start + len,
1209 "offset {offset} (+{}) outside [{start}, {})",
1210 body.len(),
1211 start + len
1212 );
1213 assert!(
1214 seen_offsets.insert(offset),
1215 "offset {offset} yielded more than once (width {width})"
1216 );
1217 let rel = (offset - start) as usize;
1218 reassembled[rel..rel + body.len()].copy_from_slice(&body);
1219 }
1220
1221 assert_eq!(
1222 reassembled, expected,
1223 "range reassembly equals read_range (width {width}, start {start}, len {len})"
1224 );
1225 }
1226 }
1227
1228 #[tokio::test]
1229 async fn test_offset_stream_chunked_range_windows() {
1230 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 321)
1231 .map(|i| (i % 256) as u8)
1232 .collect();
1233 let bs = DEFAULT_BODY_SIZE as u64;
1234 let total = data.len() as u64;
1235
1236 assert_offset_stream_chunked_range_matches(&data, bs + 10, 50).await;
1238 assert_offset_stream_chunked_range_matches(&data, bs, bs).await;
1240 assert_offset_stream_chunked_range_matches(&data, bs / 2, bs * 3 + 7).await;
1242 assert_offset_stream_chunked_range_matches(&data, bs * 5, total - bs * 5).await;
1244 assert_offset_stream_chunked_range_matches(&data, 0, total).await;
1246 assert_offset_stream_chunked_range_matches(&data, bs, 0).await;
1248 }
1249
1250 #[tokio::test]
1251 async fn test_offset_stream_chunked_range_whole_equals_chunked() {
1252 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3 + 99)
1255 .map(|i| (i % 256) as u8)
1256 .collect();
1257 let (root, store) = split_and_store(&data);
1258
1259 let full = Joiner::new(store.clone(), root).await.unwrap();
1260 let total = full.size();
1261 let mut from_full: Vec<(u64, Vec<u8>)> = full
1262 .into_offset_stream_chunked()
1263 .collect::<Vec<_>>()
1264 .await
1265 .into_iter()
1266 .map(|p| {
1267 let (o, b) = p.unwrap();
1268 (o, b.to_vec())
1269 })
1270 .collect();
1271 from_full.sort_by_key(|(o, _)| *o);
1272
1273 let ranged = Joiner::new(store, root).await.unwrap();
1274 let mut from_range: Vec<(u64, Vec<u8>)> = ranged
1275 .into_offset_stream_chunked_range(0, total)
1276 .collect::<Vec<_>>()
1277 .await
1278 .into_iter()
1279 .map(|p| {
1280 let (o, b) = p.unwrap();
1281 (o, b.to_vec())
1282 })
1283 .collect();
1284 from_range.sort_by_key(|(o, _)| *o);
1285
1286 assert_eq!(
1287 from_range, from_full,
1288 "whole-file range equals chunked walk"
1289 );
1290
1291 let expected = Joiner::new(split_and_store(&data).1, root)
1293 .await
1294 .unwrap()
1295 .read_all()
1296 .await
1297 .unwrap();
1298 let mut reassembled = vec![0u8; total as usize];
1299 for (o, b) in &from_range {
1300 reassembled[*o as usize..*o as usize + b.len()].copy_from_slice(b);
1301 }
1302 assert_eq!(reassembled, expected);
1303 }
1304
1305 #[cfg(feature = "tokio")]
1306 #[tokio::test]
1307 async fn test_reader_small() {
1308 use tokio::io::AsyncReadExt;
1309
1310 let data = b"hello world";
1311 let (root, store) = split_and_store(data);
1312
1313 let joiner = Joiner::new(store, root).await.unwrap();
1314 let mut reader = joiner.into_reader();
1315 let mut result = Vec::new();
1316 reader.read_to_end(&mut result).await.unwrap();
1317 assert_eq!(result, data);
1318 }
1319
1320 #[cfg(feature = "tokio")]
1321 #[tokio::test]
1322 async fn test_reader_multi_chunk() {
1323 use tokio::io::AsyncReadExt;
1324
1325 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3 + 123)
1326 .map(|i| (i % 256) as u8)
1327 .collect();
1328 let (root, store) = split_and_store(&data);
1329
1330 let joiner = Joiner::new(store, root).await.unwrap();
1331 let mut reader = joiner.into_reader();
1332 let mut result = Vec::new();
1333 reader.read_to_end(&mut result).await.unwrap();
1334 assert_eq!(result, data);
1335 }
1336
1337 #[cfg(feature = "tokio")]
1338 #[tokio::test]
1339 async fn test_reader_seek() {
1340 use tokio::io::{AsyncReadExt, AsyncSeekExt};
1341
1342 let data = b"hello world";
1343 let (root, store) = split_and_store(data);
1344
1345 let joiner = Joiner::new(store, root).await.unwrap();
1346 let mut reader = joiner.into_reader();
1347
1348 reader.seek(SeekFrom::Start(6)).await.unwrap();
1349 let mut buf = vec![0u8; 5];
1350 reader.read_exact(&mut buf).await.unwrap();
1351 assert_eq!(&buf, b"world");
1352 }
1353
1354 #[cfg(feature = "tokio")]
1355 #[tokio::test]
1356 async fn test_reader_seek_back_and_forth() {
1357 use tokio::io::{AsyncReadExt, AsyncSeekExt};
1358
1359 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3)
1360 .map(|i| (i % 256) as u8)
1361 .collect();
1362 let (root, store) = split_and_store(&data);
1363
1364 let joiner = Joiner::new(store, root).await.unwrap();
1365 let mut reader = joiner.into_reader();
1366
1367 reader
1369 .seek(SeekFrom::Start(DEFAULT_BODY_SIZE as u64))
1370 .await
1371 .unwrap();
1372 let mut buf1 = vec![0u8; 100];
1373 reader.read_exact(&mut buf1).await.unwrap();
1374 assert_eq!(&buf1, &data[DEFAULT_BODY_SIZE..DEFAULT_BODY_SIZE + 100]);
1375
1376 reader.seek(SeekFrom::Start(0)).await.unwrap();
1378 let mut buf2 = vec![0u8; 100];
1379 reader.read_exact(&mut buf2).await.unwrap();
1380 assert_eq!(&buf2, &data[..100]);
1381
1382 reader.seek(SeekFrom::End(-50)).await.unwrap();
1384 let mut buf3 = vec![0u8; 50];
1385 reader.read_exact(&mut buf3).await.unwrap();
1386 assert_eq!(&buf3, &data[data.len() - 50..]);
1387 }
1388
1389 #[cfg(feature = "encryption")]
1390 mod encrypted {
1391 use super::*;
1392 use crate::file::split_encrypted;
1393
1394 fn encrypted_split_and_store(
1395 data: &[u8],
1396 ) -> (
1397 crate::chunk::encryption::EncryptedChunkRef,
1398 HashMap<ChunkAddress, AnyChunk>,
1399 ) {
1400 let (root_ref, store) = split_encrypted::<DEFAULT_BODY_SIZE>(data).unwrap();
1401 (root_ref, store.into_chunks())
1402 }
1403
1404 generate_encrypted_joiner_tests!(tokio::test, EncryptedJoiner, [async], [await]);
1406
1407 #[tokio::test]
1410 async fn test_encrypted_joiner_stream() {
1411 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3)
1412 .map(|i| (i % 256) as u8)
1413 .collect();
1414 let (root_ref, store) = encrypted_split_and_store(&data);
1415
1416 let joiner = EncryptedJoiner::new(store, root_ref).await.unwrap();
1417 let chunks: Vec<Result<Bytes>> = joiner.into_stream().collect().await;
1418
1419 let mut recovered = Vec::new();
1420 for chunk in chunks {
1421 recovered.extend_from_slice(&chunk.unwrap());
1422 }
1423 assert_eq!(recovered, data);
1424 }
1425
1426 async fn assert_encrypted_range_matches(data: &[u8], start: u64, len: u64) {
1430 for width in [DEFAULT_ASYNC_CONCURRENCY, 1] {
1431 let (root_ref, store) = encrypted_split_and_store(data);
1432
1433 let expected = EncryptedJoiner::new(store.clone(), root_ref.clone())
1434 .await
1435 .unwrap()
1436 .read_range(start, len as usize)
1437 .await
1438 .unwrap();
1439
1440 let joiner = EncryptedJoiner::new(store, root_ref)
1441 .await
1442 .unwrap()
1443 .with_concurrency(width);
1444 let pairs: Vec<Result<(u64, Bytes)>> = joiner
1445 .into_offset_stream_chunked_range(start, len)
1446 .collect()
1447 .await;
1448
1449 let mut reassembled = vec![0u8; expected.len()];
1450 let mut seen_offsets = std::collections::HashSet::new();
1451 for pair in pairs {
1452 let (offset, body) = pair.unwrap();
1453 assert!(
1454 offset >= start && offset + body.len() as u64 <= start + len,
1455 "offset {offset} (+{}) outside [{start}, {})",
1456 body.len(),
1457 start + len
1458 );
1459 assert!(
1460 seen_offsets.insert(offset),
1461 "offset {offset} yielded more than once (width {width})"
1462 );
1463 let rel = (offset - start) as usize;
1464 reassembled[rel..rel + body.len()].copy_from_slice(&body);
1465 }
1466
1467 assert_eq!(
1468 reassembled, expected,
1469 "encrypted range equals read_range (width {width}, start {start}, len {len})"
1470 );
1471 }
1472 }
1473
1474 #[tokio::test]
1475 async fn test_encrypted_offset_stream_chunked_range_windows() {
1476 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 321)
1477 .map(|i| (i % 256) as u8)
1478 .collect();
1479 let bs = DEFAULT_BODY_SIZE as u64;
1480 let total = data.len() as u64;
1481
1482 assert_encrypted_range_matches(&data, bs + 10, 50).await;
1484 assert_encrypted_range_matches(&data, bs, bs).await;
1486 assert_encrypted_range_matches(&data, bs / 2, bs * 3 + 7).await;
1488 assert_encrypted_range_matches(&data, bs * 5, total - bs * 5).await;
1490 assert_encrypted_range_matches(&data, 0, total).await;
1492 assert_encrypted_range_matches(&data, bs, 0).await;
1494 }
1495 }
1496}