1use core::ops::ControlFlow;
11
12use bytes::Bytes;
13use futures::stream::StreamExt;
14
15use super::error::Result;
16use super::joiner::GenericJoiner;
17use super::mode::JoinMode;
18use crate::store::{ChunkGet, MaybeSend};
19
20impl<G, M, const BODY_SIZE: usize> GenericJoiner<G, M, BODY_SIZE>
21where
22 G: ChunkGet<BODY_SIZE> + 'static,
23 M: JoinMode + MaybeSend + Sync,
24{
25 pub async fn for_each_chunk<F>(self, mut f: F) -> Result<()>
29 where
30 F: FnMut(u64, &Bytes) -> ControlFlow<()>,
31 {
32 let s = self.into_offset_stream_chunked();
33 futures::pin_mut!(s);
34 while let Some(p) = s.next().await {
35 let (off, body) = p?;
36 if f(off, &body).is_break() {
37 break;
38 }
39 }
40 Ok(())
41 }
42
43 pub async fn try_for_each_window<B, F>(self, window: usize, init: B, mut f: F) -> Result<B>
49 where
50 F: FnMut(B, &[u8]) -> ControlFlow<B, B>,
51 {
52 let mut reader = self.into_windowed_reader(window);
53 let s = reader.stream();
54 futures::pin_mut!(s);
55 let mut acc = init;
56 while let Some(run) = s.next().await {
57 let bytes = run?;
58 match f(acc, &bytes) {
59 ControlFlow::Continue(b) => acc = b,
60 ControlFlow::Break(b) => return Ok(b),
61 }
62 }
63 Ok(acc)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::bmt::DEFAULT_BODY_SIZE;
71 use crate::chunk::{AnyChunk, ChunkAddress};
72 use crate::file::Joiner;
73 use crate::file::split;
74 use futures::executor::block_on;
75 use std::collections::HashMap;
76
77 fn split_and_store(data: &[u8]) -> (ChunkAddress, HashMap<ChunkAddress, AnyChunk>) {
78 let (root, store) = split::<DEFAULT_BODY_SIZE>(data).unwrap();
79 (root, store.into_chunks())
80 }
81
82 fn assert_for_each_chunk_matches(data: &[u8], width: usize) {
85 let (root, store) = split_and_store(data);
86 block_on(async {
87 let expected = Joiner::new(store.clone(), root)
88 .await
89 .unwrap()
90 .read_all()
91 .await
92 .unwrap();
93
94 let joiner = Joiner::new(store, root)
95 .await
96 .unwrap()
97 .with_concurrency(width);
98 let total = joiner.size();
99
100 let mut reassembled = vec![0u8; total as usize];
101 let mut covered = 0u64;
102 let mut seen = std::collections::HashSet::new();
103 joiner
104 .for_each_chunk(|off, body| {
105 assert!(seen.insert(off), "offset {off} visited more than once");
106 let start = off as usize;
107 reassembled[start..start + body.len()].copy_from_slice(body);
108 covered += body.len() as u64;
109 ControlFlow::Continue(())
110 })
111 .await
112 .unwrap();
113
114 assert_eq!(covered, total, "every byte covered exactly once");
115 assert_eq!(reassembled, expected, "reassembly equals read_all");
116 assert_eq!(reassembled, data, "reassembly equals input");
117 });
118 }
119
120 #[test]
121 fn for_each_chunk_reassembles() {
122 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3 + 123)
123 .map(|i| (i % 256) as u8)
124 .collect();
125 assert_for_each_chunk_matches(b"hello world", 8);
126 assert_for_each_chunk_matches(&data, 8);
127 assert_for_each_chunk_matches(&data, 1);
129 }
130
131 #[test]
132 fn for_each_chunk_break_stops_early() {
133 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5)
134 .map(|i| (i % 256) as u8)
135 .collect();
136 let (root, store) = split_and_store(&data);
137 block_on(async {
138 let joiner = Joiner::new(store, root).await.unwrap();
139 let mut visited = 0usize;
140 let res = joiner
141 .for_each_chunk(|_off, _body| {
142 visited += 1;
143 if visited == 2 {
144 ControlFlow::Break(())
145 } else {
146 ControlFlow::Continue(())
147 }
148 })
149 .await;
150 assert!(res.is_ok(), "early break returns Ok");
151 assert_eq!(visited, 2, "stops on the breaking leaf");
152 });
153 }
154
155 fn assert_window_concat_matches(data: &[u8], window: usize) {
157 let (root, store) = split_and_store(data);
158 block_on(async {
159 let joiner = Joiner::new(store, root).await.unwrap();
160 let out = joiner
161 .try_for_each_window(window, Vec::new(), |mut acc: Vec<u8>, run| {
162 acc.extend_from_slice(run);
163 ControlFlow::Continue(acc)
164 })
165 .await
166 .unwrap();
167 assert_eq!(out, data, "window concat equals file (window {window})");
168 });
169 }
170
171 #[test]
172 fn try_for_each_window_concat_equals_file() {
173 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 321)
174 .map(|i| (i % 256) as u8)
175 .collect();
176 assert_window_concat_matches(&data, 16);
177 assert_window_concat_matches(&data, 1);
179 }
180
181 #[test]
182 fn try_for_each_window_byte_count_equals_size() {
183 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 4 + 7)
184 .map(|i| (i % 256) as u8)
185 .collect();
186 let (root, store) = split_and_store(&data);
187 block_on(async {
188 let joiner = Joiner::new(store, root).await.unwrap();
189 let size = joiner.size();
190 let count = joiner
191 .try_for_each_window(8, 0u64, |acc, run| {
192 ControlFlow::Continue(acc + run.len() as u64)
193 })
194 .await
195 .unwrap();
196 assert_eq!(count, size, "folded byte count equals size()");
197 });
198 }
199
200 #[test]
201 fn try_for_each_window_break_returns_accumulator() {
202 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5)
203 .map(|i| (i % 256) as u8)
204 .collect();
205 let (root, store) = split_and_store(&data);
206 block_on(async {
207 let joiner = Joiner::new(store, root).await.unwrap();
208 let count = joiner
211 .try_for_each_window(8, 0u64, |acc, _run| {
212 let acc = acc + 1;
213 ControlFlow::Break(acc)
214 })
215 .await
216 .unwrap();
217 assert_eq!(count, 1, "break carries the accumulator and stops");
218 });
219 }
220
221 #[cfg(feature = "encryption")]
222 mod encrypted {
223 use super::*;
224 use crate::file::EncryptedJoiner;
225 use crate::file::split_encrypted;
226
227 fn enc_split_and_store(
228 data: &[u8],
229 ) -> (
230 crate::chunk::encryption::EncryptedChunkRef,
231 HashMap<ChunkAddress, AnyChunk>,
232 ) {
233 let (root_ref, store) = split_encrypted::<DEFAULT_BODY_SIZE>(data).unwrap();
234 (root_ref, store.into_chunks())
235 }
236
237 #[test]
238 fn for_each_chunk_reassembles_encrypted() {
239 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 3 + 123)
240 .map(|i| (i % 256) as u8)
241 .collect();
242 let (root_ref, store) = enc_split_and_store(&data);
243 block_on(async {
244 let expected = EncryptedJoiner::new(store.clone(), root_ref.clone())
245 .await
246 .unwrap()
247 .read_all()
248 .await
249 .unwrap();
250
251 let joiner = EncryptedJoiner::new(store, root_ref).await.unwrap();
252 let total = joiner.size();
253 let mut reassembled = vec![0u8; total as usize];
254 let mut seen = std::collections::HashSet::new();
255 joiner
256 .for_each_chunk(|off, body| {
257 assert!(seen.insert(off), "offset {off} visited more than once");
258 let start = off as usize;
259 reassembled[start..start + body.len()].copy_from_slice(body);
260 ControlFlow::Continue(())
261 })
262 .await
263 .unwrap();
264 assert_eq!(
265 reassembled, expected,
266 "encrypted reassembly equals read_all"
267 );
268 });
269 }
270
271 #[test]
272 fn try_for_each_window_concat_equals_file_encrypted() {
273 let data: Vec<u8> = (0..DEFAULT_BODY_SIZE * 5 + 321)
274 .map(|i| (i % 256) as u8)
275 .collect();
276 let (root_ref, store) = enc_split_and_store(&data);
277 block_on(async {
278 let joiner = EncryptedJoiner::new(store, root_ref).await.unwrap();
279 let out = joiner
280 .try_for_each_window(16, Vec::new(), |mut acc: Vec<u8>, run| {
281 acc.extend_from_slice(run);
282 ControlFlow::Continue(acc)
283 })
284 .await
285 .unwrap();
286 assert_eq!(out, data, "encrypted window concat equals file");
287 });
288 }
289 }
290}