Skip to main content

otf_pixels_core/
io.rs

1//! Streaming byte input and output.
2//!
3//! Per ADR-0005, streaming is the **only** external I/O contract: a [`Source`]
4//! is forward-only and a [`Sink`] is write-only. No seek is ever required of a
5//! caller, so a socket, a pipe and a file are all equally valid inputs. Where a
6//! format cannot decode incrementally, its codec buffers internally — the
7//! external contract stays streaming.
8//!
9//! Both traits have blanket implementations over [`std::io::Read`] and
10//! [`std::io::Write`], so `File`, `&[u8]`, `Vec<u8>`, `TcpStream` and
11//! `Stdin`/`Stdout` work as sources and sinks with no adapter.
12
13use crate::{PixelsError, Result};
14
15/// A forward-only source of bytes.
16///
17/// Implementors need not support seeking, and the engine never asks them to.
18/// Sources perform no decoding and allocate nothing proportional to image size.
19pub trait Source: Send {
20    /// Read into `buf`, returning the number of bytes read.
21    ///
22    /// Returning `Ok(0)` means end of input. A short read is not an error: the
23    /// engine calls again until it has what it needs or the source ends.
24    ///
25    /// # Errors
26    ///
27    /// Returns [`PixelsError::Io`] if the underlying reader fails.
28    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
29
30    /// Fill `buf` completely.
31    ///
32    /// # Errors
33    ///
34    /// Returns [`PixelsError::Io`] on reader failure, or
35    /// [`PixelsError::Malformed`] if the source ends before `buf` is full —
36    /// a truncated stream is malformed input, not an I/O fault.
37    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
38        let wanted = buf.len();
39        let mut filled = 0;
40        while filled < wanted {
41            let Some(rest) = buf.get_mut(filled..) else {
42                break;
43            };
44            match self.read(rest)? {
45                0 => {
46                    return Err(PixelsError::malformed(
47                        "stream",
48                        format!("stream ended after {filled} of {wanted} expected bytes"),
49                    ));
50                }
51                n => filled += n,
52            }
53        }
54        Ok(())
55    }
56}
57
58/// A sink for encoded bytes.
59pub trait Sink: Send {
60    /// Write all of `buf`.
61    ///
62    /// # Errors
63    ///
64    /// Returns [`PixelsError::Io`] if the underlying writer fails.
65    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
66
67    /// Flush any buffered bytes to the destination.
68    ///
69    /// # Errors
70    ///
71    /// Returns [`PixelsError::Io`] if the underlying writer fails.
72    fn flush(&mut self) -> Result<()>;
73}
74
75impl<R: std::io::Read + Send> Source for R {
76    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
77        loop {
78            return match std::io::Read::read(self, buf) {
79                Ok(n) => Ok(n),
80                Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
81                Err(e) => Err(PixelsError::io("reading from source", e)),
82            };
83        }
84    }
85}
86
87impl<W: std::io::Write + Send> Sink for W {
88    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
89        std::io::Write::write_all(self, buf).map_err(|e| PixelsError::io("writing to sink", e))
90    }
91
92    fn flush(&mut self) -> Result<()> {
93        std::io::Write::flush(self).map_err(|e| PixelsError::io("flushing sink", e))
94    }
95}
96
97/// A source that replays a buffered prefix before delegating to the rest.
98///
99/// Format sniffing needs to look at the first bytes and then hand the *whole*
100/// stream to a decoder, but [`Source`] is forward-only (ADR-0005) and cannot
101/// rewind. Reading the magic bytes into a prefix and putting them back in
102/// front is how sniffing stays compatible with that: nothing is re-read, and
103/// no source is required to seek.
104#[derive(Debug)]
105pub struct Prefixed<S: Source> {
106    prefix: Vec<u8>,
107    /// How much of `prefix` has already been handed out.
108    consumed: usize,
109    rest: S,
110}
111
112impl<S: Source> Prefixed<S> {
113    /// Replay `prefix`, then read from `rest`.
114    #[must_use]
115    pub const fn new(prefix: Vec<u8>, rest: S) -> Self {
116        Self {
117            prefix,
118            consumed: 0,
119            rest,
120        }
121    }
122}
123
124impl<S: Source> Source for Prefixed<S> {
125    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
126        let Some(remaining) = self.prefix.get(self.consumed..) else {
127            return self.rest.read(buf);
128        };
129        if remaining.is_empty() {
130            return self.rest.read(buf);
131        }
132        // A short read is contractually fine, so the prefix and the rest are
133        // never mixed in one call — which keeps the boundary easy to reason
134        // about and impossible to get half-right.
135        let take = remaining.len().min(buf.len());
136        let (Some(from), Some(into)) = (remaining.get(..take), buf.get_mut(..take)) else {
137            return Ok(0);
138        };
139        into.copy_from_slice(from);
140        self.consumed += take;
141        Ok(take)
142    }
143}
144
145#[cfg(test)]
146#[allow(
147    clippy::unwrap_used,
148    clippy::indexing_slicing,
149    reason = "tests operate on known-good values and assert shapes directly"
150)]
151mod tests {
152    use super::*;
153    use crate::ErrorCode;
154
155    /// A source that yields at most `chunk` bytes per call, to exercise the
156    /// short-read path that real sockets and pipes produce.
157    struct Trickle {
158        data: Vec<u8>,
159        pos: usize,
160        chunk: usize,
161    }
162
163    impl Source for Trickle {
164        fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
165            let remaining = self.data.len() - self.pos;
166            let n = remaining.min(buf.len()).min(self.chunk);
167            buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
168            self.pos += n;
169            Ok(n)
170        }
171    }
172
173    #[test]
174    fn read_exact_reassembles_short_reads() {
175        let mut src = Trickle {
176            data: (0..10).collect(),
177            pos: 0,
178            chunk: 3,
179        };
180        let mut buf = [0_u8; 10];
181        src.read_exact(&mut buf).unwrap();
182        assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
183    }
184
185    #[test]
186    fn a_truncated_stream_is_malformed_not_an_io_error() {
187        let mut src = Trickle {
188            data: vec![1, 2, 3],
189            pos: 0,
190            chunk: 2,
191        };
192        let mut buf = [0_u8; 8];
193        let err = src.read_exact(&mut buf).unwrap_err();
194        assert_eq!(err.code(), ErrorCode::Malformed);
195        assert!(err.to_string().contains("3 of 8"), "{err}");
196    }
197
198    #[test]
199    fn slices_and_vecs_work_without_adapters() {
200        let mut src: &[u8] = b"hello";
201        let mut buf = [0_u8; 5];
202        src.read_exact(&mut buf).unwrap();
203        assert_eq!(&buf, b"hello");
204
205        let mut sink: Vec<u8> = Vec::new();
206        sink.write_all(b"out").unwrap();
207        sink.flush().unwrap();
208        assert_eq!(sink, b"out");
209    }
210
211    #[test]
212    fn read_exact_of_nothing_succeeds() {
213        let mut src: &[u8] = b"";
214        src.read_exact(&mut []).unwrap();
215    }
216
217    #[test]
218    fn sink_errors_are_reported_as_io() {
219        struct Broken;
220        impl std::io::Write for Broken {
221            fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
222                Err(std::io::Error::other("disk full"))
223            }
224            fn flush(&mut self) -> std::io::Result<()> {
225                Err(std::io::Error::other("disk full"))
226            }
227        }
228        let err = Sink::write_all(&mut Broken, b"x").unwrap_err();
229        assert_eq!(err.code(), ErrorCode::Io);
230        assert_eq!(Sink::flush(&mut Broken).unwrap_err().code(), ErrorCode::Io);
231    }
232
233    #[test]
234    fn a_prefixed_source_replays_then_delegates() {
235        let mut source = Prefixed::new(vec![1, 2, 3], &b"456"[..]);
236        let mut all = Vec::new();
237        let mut buf = [0_u8; 2];
238        loop {
239            match source.read(&mut buf).unwrap() {
240                0 => break,
241                n => all.extend_from_slice(&buf[..n]),
242            }
243        }
244        assert_eq!(all, vec![1, 2, 3, b'4', b'5', b'6']);
245    }
246
247    #[test]
248    fn a_prefixed_source_never_mixes_the_prefix_into_one_read() {
249        // The boundary is the only place this can go wrong, so pin it: a
250        // buffer larger than the prefix still stops at the prefix's end.
251        let mut source = Prefixed::new(vec![9, 9], &b"xyz"[..]);
252        let mut buf = [0_u8; 16];
253        assert_eq!(source.read(&mut buf).unwrap(), 2);
254        assert_eq!(&buf[..2], &[9, 9]);
255        assert_eq!(source.read(&mut buf).unwrap(), 3);
256        assert_eq!(&buf[..3], b"xyz");
257        assert_eq!(source.read(&mut buf).unwrap(), 0);
258    }
259
260    #[test]
261    fn an_empty_prefix_is_a_pass_through() {
262        let mut source = Prefixed::new(Vec::new(), &b"data"[..]);
263        let mut buf = [0_u8; 8];
264        assert_eq!(source.read(&mut buf).unwrap(), 4);
265        assert_eq!(&buf[..4], b"data");
266    }
267
268    #[test]
269    fn read_exact_spans_the_prefix_boundary() {
270        // `read_exact` loops over short reads, so the split must be invisible
271        // to it — this is how a decoder will actually consume the stream.
272        let mut source = Prefixed::new(vec![0xDE, 0xAD], &b"\xBE\xEF"[..]);
273        let mut buf = [0_u8; 4];
274        source.read_exact(&mut buf).unwrap();
275        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
276    }
277}