1use bytes::{Bytes, BytesMut};
9use futures::stream::{BoxStream, StreamExt};
10use std::io;
11use std::path::Path;
12use tokio::io::{AsyncRead, AsyncReadExt};
13
14const SOURCE_CHUNK_BYTES: usize = 64 * 1024;
20
21pub type PayloadStream = BoxStream<'static, io::Result<Bytes>>;
26
27pub struct PayloadSource {
34 stream: PayloadStream,
35 size_bytes: Option<u64>,
36}
37
38impl std::fmt::Debug for PayloadSource {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("PayloadSource")
41 .field("size_bytes", &self.size_bytes)
42 .finish_non_exhaustive()
43 }
44}
45
46impl PayloadSource {
47 pub fn stream(stream: PayloadStream) -> Self {
49 Self {
50 stream,
51 size_bytes: None,
52 }
53 }
54
55 pub fn sized_stream(stream: PayloadStream, size_bytes: u64) -> Self {
61 Self {
62 stream,
63 size_bytes: Some(size_bytes),
64 }
65 }
66
67 pub fn reader<R>(reader: R) -> Self
70 where
71 R: AsyncRead + Send + Unpin + 'static,
72 {
73 Self::stream(read_in_chunks(reader))
74 }
75
76 pub async fn open_file(path: impl AsRef<Path>) -> io::Result<Self> {
79 let file = tokio::fs::File::open(path.as_ref()).await?;
80 let size_bytes = file.metadata().await?.len();
81 Ok(Self::sized_stream(read_in_chunks(file), size_bytes))
82 }
83
84 pub fn size_bytes(&self) -> Option<u64> {
86 self.size_bytes
87 }
88
89 pub fn into_stream(self) -> (PayloadStream, Option<u64>) {
93 (self.stream, self.size_bytes)
94 }
95}
96
97fn read_in_chunks<R>(reader: R) -> PayloadStream
99where
100 R: AsyncRead + Send + Unpin + 'static,
101{
102 futures::stream::unfold(Some(reader), |reader| async move {
103 let mut reader = reader?;
104 let mut buffer = BytesMut::with_capacity(SOURCE_CHUNK_BYTES);
105 match reader.read_buf(&mut buffer).await {
106 Ok(0) => None,
109 Ok(_) => Some((Ok(buffer.freeze()), Some(reader))),
110 Err(error) => Some((Err(error), None)),
111 }
112 })
113 .boxed()
114}
115
116pub(crate) struct PartReader {
123 stream: PayloadStream,
124 carry: Option<Bytes>,
126 part_bytes: usize,
127 exhausted: bool,
128}
129
130impl PartReader {
131 pub(crate) fn new(stream: PayloadStream, part_bytes: usize) -> Self {
132 Self {
133 stream,
134 carry: None,
135 part_bytes: part_bytes.max(1),
136 exhausted: false,
137 }
138 }
139
140 pub(crate) async fn next_part(&mut self) -> io::Result<Option<Bytes>> {
147 let mut buffer: Option<BytesMut> = None;
148 loop {
149 let filled = buffer.as_ref().map_or(0, BytesMut::len);
150 if filled >= self.part_bytes {
151 break;
152 }
153 let mut chunk = match self.carry.take() {
154 Some(chunk) => chunk,
155 None if self.exhausted => break,
156 None => match self.stream.next().await {
157 Some(chunk) => chunk?,
158 None => {
159 self.exhausted = true;
160 break;
161 }
162 },
163 };
164 let take = (self.part_bytes - filled).min(chunk.len());
165 let taken = chunk.split_to(take);
166 if !chunk.is_empty() {
167 self.carry = Some(chunk);
168 }
169 match &mut buffer {
170 Some(buffer) => buffer.extend_from_slice(&taken),
171 None if taken.len() == self.part_bytes => return Ok(Some(taken)),
175 None => {
176 let mut fresh = BytesMut::with_capacity(self.part_bytes);
177 fresh.extend_from_slice(&taken);
178 buffer = Some(fresh);
179 }
180 }
181 }
182 Ok(buffer
183 .filter(|buffer| !buffer.is_empty())
184 .map(BytesMut::freeze))
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 fn source(chunks: Vec<&'static [u8]>) -> PayloadStream {
193 futures::stream::iter(chunks.into_iter().map(|chunk| Ok(Bytes::from(chunk)))).boxed()
194 }
195
196 async fn cut(reader: &mut PartReader) -> Vec<Vec<u8>> {
197 let mut parts = Vec::new();
198 while let Some(part) = reader.next_part().await.expect("cut a part") {
199 parts.push(part.to_vec());
200 }
201 parts
202 }
203
204 #[tokio::test]
207 async fn parts_are_cut_regardless_of_how_the_source_chunked_them() {
208 let mut reader = PartReader::new(source(vec![b"abcde", b"fg", b"hijkl"]), 4);
209 assert_eq!(
210 cut(&mut reader).await,
211 vec![b"abcd".to_vec(), b"efgh".to_vec(), b"ijkl".to_vec()]
212 );
213 }
214
215 #[tokio::test]
218 async fn the_last_part_is_whatever_is_left() {
219 let mut reader = PartReader::new(source(vec![b"abcdef"]), 4);
220 assert_eq!(
221 cut(&mut reader).await,
222 vec![b"abcd".to_vec(), b"ef".to_vec()]
223 );
224
225 let mut reader = PartReader::new(source(vec![b"abcd"]), 4);
226 assert_eq!(cut(&mut reader).await, vec![b"abcd".to_vec()]);
227 }
228
229 #[tokio::test]
232 async fn an_empty_source_produces_no_parts() {
233 let mut reader = PartReader::new(source(vec![]), 4);
234 assert!(reader.next_part().await.expect("cut a part").is_none());
235 }
236
237 #[tokio::test]
238 async fn a_file_source_knows_its_length() {
239 let directory = tempfile::tempdir().expect("tempdir");
240 let path = directory.path().join("payload.bin");
241 std::fs::write(&path, vec![7u8; 5_000]).expect("write payload");
242
243 let source = PayloadSource::open_file(&path).await.expect("open payload");
244 assert_eq!(source.size_bytes(), Some(5_000));
245
246 let (stream, _) = source.into_stream();
247 let mut reader = PartReader::new(stream, 4_096);
248 let first = reader.next_part().await.expect("cut").expect("first part");
249 let second = reader.next_part().await.expect("cut").expect("second part");
250 assert_eq!(first.len(), 4_096);
251 assert_eq!(second.len(), 904);
252 assert!(reader.next_part().await.expect("cut").is_none());
253 }
254
255 #[tokio::test]
258 async fn a_reader_source_declares_no_length() {
259 let source = PayloadSource::reader(std::io::Cursor::new(vec![1u8; 100]));
260 assert_eq!(source.size_bytes(), None);
261 let (stream, _) = source.into_stream();
262 let mut reader = PartReader::new(stream, 64);
263 assert_eq!(
264 reader.next_part().await.expect("cut").expect("part").len(),
265 64
266 );
267 assert_eq!(
268 reader.next_part().await.expect("cut").expect("part").len(),
269 36
270 );
271 assert!(reader.next_part().await.expect("cut").is_none());
272 }
273}