Skip to main content

stuffr_core/source/
reader.rs

1use std::io::Read;
2
3use super::{SeekRead, Source, SourceCaps};
4use crate::error::Result;
5
6/// A forward-only source. Models "arrived on a pipe" — it erases any seek
7/// ability the underlying reader might happen to have, so tests can exercise
8/// the non-seekable path deterministically.
9pub struct ReaderSource {
10    inner: Box<dyn Read + Send>,
11}
12
13impl ReaderSource {
14    pub fn new<R: Read + Send + 'static>(inner: R) -> Self {
15        Self {
16            inner: Box::new(inner),
17        }
18    }
19}
20
21impl Read for ReaderSource {
22    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
23        self.inner.read(buf)
24    }
25}
26
27impl Source for ReaderSource {
28    fn caps(&self) -> SourceCaps {
29        SourceCaps {
30            seekable: false,
31            len: None,
32        }
33    }
34
35    fn as_seek(&mut self) -> Option<&mut dyn SeekRead> {
36        None
37    }
38}
39
40/// Wraps a source so a bounded prefix can be inspected and then replayed.
41///
42/// This is what makes magic-byte detection non-destructive on a pipe: the probe
43/// reads the prefix, matches against it, and the consumer still sees byte zero.
44pub struct PeekSource {
45    inner: Box<dyn Source>,
46    prefix: Vec<u8>,
47    pos: usize,
48}
49
50impl PeekSource {
51    /// Reads up to `n` bytes into the replay buffer. A short read is fine — the
52    /// input may legitimately be smaller than the probe window.
53    pub fn fill(mut inner: Box<dyn Source>, n: usize) -> Result<Self> {
54        let mut prefix = vec![0u8; n];
55        let mut filled = 0;
56        while filled < n {
57            match inner.read(&mut prefix[filled..])? {
58                0 => break,
59                k => filled += k,
60            }
61        }
62        prefix.truncate(filled);
63        Ok(Self {
64            inner,
65            prefix,
66            pos: 0,
67        })
68    }
69
70    pub fn prefix(&self) -> &[u8] {
71        &self.prefix
72    }
73}
74
75impl Read for PeekSource {
76    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
77        if self.pos < self.prefix.len() {
78            let n = (self.prefix.len() - self.pos).min(buf.len());
79            buf[..n].copy_from_slice(&self.prefix[self.pos..self.pos + n]);
80            self.pos += n;
81            return Ok(n);
82        }
83        self.inner.read(buf)
84    }
85}
86
87impl Source for PeekSource {
88    fn caps(&self) -> SourceCaps {
89        // Buffering a prefix does not create seek ability.
90        SourceCaps {
91            seekable: false,
92            len: None,
93        }
94    }
95
96    fn as_seek(&mut self) -> Option<&mut dyn SeekRead> {
97        None
98    }
99}