1use std::io;
21use std::pin::Pin;
22use std::task::{Context, Poll};
23use tokio::io::{AsyncRead, ReadBuf};
24
25pub trait IntegrityCheck: Send {
28 fn update(&mut self, chunk: &[u8]);
31
32 fn finalize(self: Box<Self>, total: u64) -> Result<(), String>;
37}
38
39#[derive(Debug, Clone, Copy)]
44pub struct LengthCheck {
45 expected: u64,
46}
47
48impl LengthCheck {
49 pub fn new(expected: u64) -> Self {
51 Self { expected }
52 }
53}
54
55impl IntegrityCheck for LengthCheck {
56 fn update(&mut self, _chunk: &[u8]) {}
57
58 fn finalize(self: Box<Self>, total: u64) -> Result<(), String> {
59 if total == self.expected {
60 Ok(())
61 } else {
62 Err(format!(
63 "body length mismatch: object store advertised {} byte(s) but read {} \
64 (truncated or corrupted transfer)",
65 self.expected, total
66 ))
67 }
68 }
69}
70
71pub struct VerifyingReader<R> {
75 inner: R,
76 checks: Vec<Box<dyn IntegrityCheck>>,
77 total: u64,
78 finalized: bool,
79}
80
81impl<R> VerifyingReader<R> {
82 pub fn new(inner: R, checks: Vec<Box<dyn IntegrityCheck>>) -> Self {
85 Self {
86 inner,
87 checks,
88 total: 0,
89 finalized: false,
90 }
91 }
92}
93
94impl<R: AsyncRead + Unpin> AsyncRead for VerifyingReader<R> {
95 fn poll_read(
96 self: Pin<&mut Self>,
97 cx: &mut Context<'_>,
98 buf: &mut ReadBuf<'_>,
99 ) -> Poll<io::Result<()>> {
100 let this = self.get_mut();
101 let before = buf.filled().len();
102 match Pin::new(&mut this.inner).poll_read(cx, buf) {
103 Poll::Ready(Ok(())) => {
104 let filled = buf.filled();
105 let n = filled.len() - before;
106 if n > 0 {
107 let chunk = &filled[before..];
110 this.total += n as u64;
111 for check in &mut this.checks {
112 check.update(chunk);
113 }
114 Poll::Ready(Ok(()))
115 } else if this.finalized {
116 Poll::Ready(Ok(()))
118 } else {
119 this.finalized = true;
122 for check in std::mem::take(&mut this.checks) {
123 if let Err(reason) = check.finalize(this.total) {
124 return Poll::Ready(Err(io::Error::new(
125 io::ErrorKind::InvalidData,
126 reason,
127 )));
128 }
129 }
130 Poll::Ready(Ok(()))
131 }
132 }
133 other => other,
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use tokio::io::AsyncReadExt as _;
142
143 struct SumCheck {
147 expected: u64,
148 running: u64,
149 }
150 impl SumCheck {
151 fn new(expected: u64) -> Self {
152 Self {
153 expected,
154 running: 0,
155 }
156 }
157 }
158 impl IntegrityCheck for SumCheck {
159 fn update(&mut self, chunk: &[u8]) {
160 self.running += chunk.iter().map(|b| *b as u64).sum::<u64>();
161 }
162 fn finalize(self: Box<Self>, _total: u64) -> Result<(), String> {
163 if self.running == self.expected {
164 Ok(())
165 } else {
166 Err(format!(
167 "checksum mismatch: expected {}, computed {}",
168 self.expected, self.running
169 ))
170 }
171 }
172 }
173
174 struct OneByteAtATime {
177 data: Vec<u8>,
178 pos: usize,
179 }
180 impl AsyncRead for OneByteAtATime {
181 fn poll_read(
182 self: Pin<&mut Self>,
183 _cx: &mut Context<'_>,
184 buf: &mut ReadBuf<'_>,
185 ) -> Poll<io::Result<()>> {
186 let this = self.get_mut();
187 if this.pos < this.data.len() {
188 buf.put_slice(&this.data[this.pos..this.pos + 1]);
189 this.pos += 1;
190 }
191 Poll::Ready(Ok(()))
192 }
193 }
194
195 #[tokio::test]
196 async fn digest_mismatch_fails() {
197 let data: &[u8] = &[1, 2, 3]; let mut reader = VerifyingReader::new(data, vec![Box::new(SumCheck::new(99))]);
199 let mut out = Vec::new();
200 let err = reader
201 .read_to_end(&mut out)
202 .await
203 .expect_err("wrong checksum must fail the read");
204 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
205 assert!(err.to_string().contains("checksum mismatch"), "got: {err}");
206 }
207
208 #[tokio::test]
209 async fn digest_match_passes() {
210 let data: &[u8] = &[1, 2, 3]; let mut reader = VerifyingReader::new(data, vec![Box::new(SumCheck::new(6))]);
212 let mut out = Vec::new();
213 reader
214 .read_to_end(&mut out)
215 .await
216 .expect("matching checksum must pass");
217 assert_eq!(out, vec![1, 2, 3]);
218 }
219
220 #[tokio::test]
221 async fn accumulates_across_one_byte_reads() {
222 let body = vec![10u8, 20, 30, 40]; let reader = OneByteAtATime {
226 data: body.clone(),
227 pos: 0,
228 };
229 let mut reader = VerifyingReader::new(
230 reader,
231 vec![Box::new(LengthCheck::new(4)), Box::new(SumCheck::new(100))],
232 );
233 let mut out = Vec::new();
234 reader
235 .read_to_end(&mut out)
236 .await
237 .expect("incremental reads must verify");
238 assert_eq!(out, body);
239 }
240
241 #[tokio::test]
242 async fn first_failing_check_surfaces() {
243 let data: &[u8] = &[1, 2, 3];
246 let mut reader = VerifyingReader::new(
247 data,
248 vec![Box::new(LengthCheck::new(3)), Box::new(SumCheck::new(0))],
249 );
250 let mut out = Vec::new();
251 let err = reader.read_to_end(&mut out).await.expect_err("must fail");
252 assert!(err.to_string().contains("checksum mismatch"), "got: {err}");
253 }
254
255 #[tokio::test]
256 async fn empty_body_expecting_zero_passes() {
257 let data: &[u8] = &[];
258 let mut reader = VerifyingReader::new(data, vec![Box::new(LengthCheck::new(0))]);
259 let mut out = Vec::new();
260 let n = reader
261 .read_to_end(&mut out)
262 .await
263 .expect("empty body, len 0");
264 assert_eq!(n, 0);
265 }
266
267 #[tokio::test]
268 async fn empty_body_expecting_nonzero_fails() {
269 let data: &[u8] = &[];
270 let mut reader = VerifyingReader::new(data, vec![Box::new(LengthCheck::new(5))]);
271 let mut out = Vec::new();
272 let err = reader
273 .read_to_end(&mut out)
274 .await
275 .expect_err("an empty body advertised as 5 bytes must fail");
276 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
277 }
278
279 #[tokio::test]
280 async fn overlong_read_fails() {
281 let data: &[u8] = b"helloworld"; let mut reader = VerifyingReader::new(data, vec![Box::new(LengthCheck::new(4))]);
284 let mut out = Vec::new();
285 let err = reader
286 .read_to_end(&mut out)
287 .await
288 .expect_err("reading more than advertised must fail");
289 assert!(err.to_string().contains("length mismatch"), "got: {err}");
290 }
291
292 #[tokio::test]
293 async fn no_checks_is_transparent_passthrough() {
294 let data: &[u8] = b"passthrough";
295 let mut reader = VerifyingReader::new(data, Vec::new());
296 let mut out = Vec::new();
297 reader.read_to_end(&mut out).await.expect("no checks = ok");
298 assert_eq!(out, b"passthrough");
299 }
300
301 #[tokio::test]
302 async fn short_read_fails_length_check() {
303 let data: &[u8] = b"hello"; let mut reader = VerifyingReader::new(data, vec![Box::new(LengthCheck::new(10))]);
305 let mut out = Vec::new();
306 let err = reader
307 .read_to_end(&mut out)
308 .await
309 .expect_err("a 5-byte body against an advertised 10 must fail");
310 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
311 assert!(
312 err.to_string().contains("length mismatch"),
313 "unexpected error: {err}"
314 );
315 }
316
317 #[tokio::test]
318 async fn exact_length_passes() {
319 let data: &[u8] = b"helloworld"; let mut reader = VerifyingReader::new(data, vec![Box::new(LengthCheck::new(10))]);
321 let mut out = Vec::new();
322 let n = reader
323 .read_to_end(&mut out)
324 .await
325 .expect("exact-length body must succeed");
326 assert_eq!(n, 10);
327 assert_eq!(out, b"helloworld");
328 }
329}