Skip to main content

oxideav_source/
sub.rs

1//! Windowed view over an existing [`BytesSource`].
2//!
3//! [`SubSource`] re-projects a slice `[base, base + len)` of an inner
4//! source onto the virtual address space `[0, len)`. Containers commonly
5//! hand a windowed view of the payload to a codec: e.g. an MP4 `mdat`
6//! sample at file offset `4_321_000` with length `34_112` should look
7//! like an independent `Read + Seek` stream `[0, 34_112)` to the codec
8//! that decodes it.
9//!
10//! This is the seekable analogue of `std::io::Read::take`: `take` only
11//! caps forward reads, but a codec that needs to seek backwards within
12//! its sample (e.g. to re-read a header after probing it) needs a real
13//! windowed seek too. `SubSource` provides both.
14//!
15//! ## Semantics
16//!
17//! * `Read`: returns up to `len - pos` bytes per call, then `Ok(0)` (EOF).
18//! * `Seek::Start(n)`: maps to inner offset `base + n`. `n > len` is
19//!   permitted (mirrors `std::io::Cursor`), but a subsequent `read`
20//!   returns 0 until the position is reduced.
21//! * `Seek::End(d)`: anchors at `base + len`; `d > 0` permitted, `d`
22//!   such that the result is negative errors `InvalidInput`.
23//! * `Seek::Current(d)`: relative to the current position; underflow
24//!   errors `InvalidInput`.
25//!
26//! ## Sharing the inner source
27//!
28//! A `SubSource` takes ownership of the inner reader. To share one
29//! underlying file across multiple windows, open the source once per
30//! window — that is the cheap path (file descriptors are tiny; the
31//! kernel page cache shares the actual bytes between readers). A
32//! shared-`Arc`-with-locking design was rejected because it forces
33//! every read to serialise on a mutex, which defeats the parallel-read
34//! pattern a multi-stream demuxer needs.
35//!
36//! ## Bound checks
37//!
38//! [`SubSource::new`] requires `base + len <= inner.stream_len()`. The
39//! inner length is captured at construction via `seek(SeekFrom::End(0))`
40//! and the source is left positioned at `base`. If the inner source's
41//! length changes under the window after construction, reads near the
42//! tail may surface short reads from the underlying source like any
43//! other reader.
44
45use std::io::{self, Read, Seek, SeekFrom};
46
47use oxideav_core::{BytesSource, Error, Result};
48
49/// Windowed view over an inner [`BytesSource`].
50///
51/// See the [module docs](self) for full semantics.
52pub struct SubSource {
53    inner: Box<dyn BytesSource>,
54    base: u64,
55    len: u64,
56    /// Current position in the *window* coordinate space (`0..len`).
57    /// May exceed `len` after a seek-past-end (mirrors `Cursor`).
58    pos: u64,
59}
60
61impl SubSource {
62    /// Build a `SubSource` exposing `[base, base + len)` of `inner` as
63    /// `[0, len)`.
64    ///
65    /// Errors when `base + len` overflows or exceeds the inner source's
66    /// length, or when the underlying seek to `base` fails. The inner
67    /// source is consumed; recover it via [`SubSource::into_inner`].
68    pub fn new(mut inner: Box<dyn BytesSource>, base: u64, len: u64) -> Result<Self> {
69        let end = base
70            .checked_add(len)
71            .ok_or_else(|| Error::invalid("SubSource: base + len overflows u64"))?;
72        let inner_len = stream_len(&mut inner)
73            .map_err(|e| Error::invalid(format!("SubSource: cannot probe inner length: {e}")))?;
74        if end > inner_len {
75            return Err(Error::invalid(format!(
76                "SubSource: window [{base}, {end}) extends past inner length {inner_len}"
77            )));
78        }
79        inner
80            .seek(SeekFrom::Start(base))
81            .map_err(|e| Error::invalid(format!("SubSource: cannot seek inner to {base}: {e}")))?;
82        Ok(Self {
83            inner,
84            base,
85            len,
86            pos: 0,
87        })
88    }
89
90    /// Window length (bytes accessible via this view).
91    pub fn len(&self) -> u64 {
92        self.len
93    }
94
95    /// `true` iff the window is zero-length.
96    pub fn is_empty(&self) -> bool {
97        self.len == 0
98    }
99
100    /// Inner source's offset at which this window starts.
101    pub fn base(&self) -> u64 {
102        self.base
103    }
104
105    /// Consume the window and return the inner source. Its position is
106    /// wherever the last `Read`/`Seek` left it; callers that want a
107    /// known position should `seek` it themselves.
108    pub fn into_inner(self) -> Box<dyn BytesSource> {
109        self.inner
110    }
111}
112
113/// Probe the total length of a seekable source non-destructively:
114/// remembers the current position, seeks to `End(0)`, and restores the
115/// position before returning. Useful in any code path that wants the
116/// inner length once and doesn't care about reading the bytes.
117pub fn stream_len(src: &mut dyn BytesSource) -> io::Result<u64> {
118    let saved = src.stream_position()?;
119    let end = src.seek(SeekFrom::End(0))?;
120    // Only seek back if we actually moved; minor optimisation for the
121    // case where the caller just opened the source.
122    if saved != end {
123        src.seek(SeekFrom::Start(saved))?;
124    }
125    Ok(end)
126}
127
128impl Read for SubSource {
129    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
130        if buf.is_empty() || self.pos >= self.len {
131            return Ok(0);
132        }
133        // Bytes remaining in the window from current position.
134        let remaining = (self.len - self.pos) as usize;
135        let want = buf.len().min(remaining);
136
137        // Inner-source offset for our current window position. The inner
138        // source's position may not match (if the caller mixed reads
139        // and seeks on a different window over the same FD — but we
140        // own the inner exclusively, so this is just defensive), so we
141        // explicitly seek before each read. Two syscalls per read is
142        // cheap relative to the actual IO and avoids a fragile "we know
143        // where the inner is" invariant.
144        let inner_off = self.base + self.pos;
145        self.inner.seek(SeekFrom::Start(inner_off))?;
146        let n = self.inner.read(&mut buf[..want])?;
147        self.pos += n as u64;
148        Ok(n)
149    }
150}
151
152impl Seek for SubSource {
153    fn seek(&mut self, from: SeekFrom) -> io::Result<u64> {
154        let new_pos = match from {
155            SeekFrom::Start(n) => n,
156            SeekFrom::End(d) => add_signed(self.len, d)?,
157            SeekFrom::Current(d) => add_signed(self.pos, d)?,
158        };
159        // Update *window* position; defer the inner-source seek to the
160        // next read. A pure seek call with no follow-up read should not
161        // pay for an inner syscall.
162        self.pos = new_pos;
163        Ok(self.pos)
164    }
165}
166
167fn add_signed(base: u64, delta: i64) -> io::Result<u64> {
168    let result = if delta >= 0 {
169        base.checked_add(delta as u64)
170    } else {
171        base.checked_sub(delta.unsigned_abs())
172    };
173    result.ok_or_else(|| {
174        io::Error::new(
175            io::ErrorKind::InvalidInput,
176            "SubSource: seek resolves to a negative or overflowing position",
177        )
178    })
179}
180
181#[cfg(test)]
182mod tests {
183    use std::io::Cursor;
184
185    use super::*;
186
187    fn ramp(n: usize) -> Vec<u8> {
188        (0..n).map(|i| (i & 0xff) as u8).collect()
189    }
190
191    #[test]
192    fn window_reads_the_correct_slice() {
193        let data = ramp(256);
194        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data.clone()));
195        let mut sub = SubSource::new(inner, 50, 40).unwrap();
196        assert_eq!(sub.len(), 40);
197        assert_eq!(sub.base(), 50);
198        let mut out = vec![0u8; 40];
199        sub.read_exact(&mut out).unwrap();
200        assert_eq!(out, &data[50..90]);
201    }
202
203    #[test]
204    fn read_past_window_returns_eof() {
205        let data = ramp(128);
206        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
207        let mut sub = SubSource::new(inner, 10, 20).unwrap();
208        let mut out = vec![0u8; 50];
209        let n = sub.read(&mut out).unwrap();
210        assert_eq!(n, 20); // capped at window length, not buffer length
211        let n2 = sub.read(&mut out).unwrap();
212        assert_eq!(n2, 0); // window exhausted
213    }
214
215    #[test]
216    fn seek_within_window_then_read() {
217        let data = ramp(256);
218        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data.clone()));
219        let mut sub = SubSource::new(inner, 100, 100).unwrap();
220        // Seek to window-relative 50 == inner offset 150.
221        sub.seek(SeekFrom::Start(50)).unwrap();
222        let mut byte = [0u8; 1];
223        sub.read_exact(&mut byte).unwrap();
224        assert_eq!(byte[0], data[150]);
225    }
226
227    #[test]
228    fn seek_end_anchors_at_window_end() {
229        let data = ramp(64);
230        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
231        let mut sub = SubSource::new(inner, 8, 16).unwrap();
232        let pos = sub.seek(SeekFrom::End(0)).unwrap();
233        assert_eq!(pos, 16);
234        let mut byte = [0u8; 1];
235        assert_eq!(sub.read(&mut byte).unwrap(), 0);
236    }
237
238    #[test]
239    fn seek_current_relative() {
240        let data = ramp(128);
241        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
242        let mut sub = SubSource::new(inner, 0, 64).unwrap();
243        sub.seek(SeekFrom::Start(20)).unwrap();
244        let p = sub.seek(SeekFrom::Current(5)).unwrap();
245        assert_eq!(p, 25);
246        let p = sub.seek(SeekFrom::Current(-10)).unwrap();
247        assert_eq!(p, 15);
248    }
249
250    #[test]
251    fn seek_before_zero_errors() {
252        let data = ramp(32);
253        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
254        let mut sub = SubSource::new(inner, 0, 16).unwrap();
255        let r = sub.seek(SeekFrom::Current(-1));
256        assert!(r.is_err());
257        let r = sub.seek(SeekFrom::End(-100));
258        assert!(r.is_err());
259    }
260
261    #[test]
262    fn seek_past_window_then_read_returns_zero() {
263        // Cursor semantics: seeking past the end is OK, but reads return 0.
264        let data = ramp(64);
265        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
266        let mut sub = SubSource::new(inner, 0, 32).unwrap();
267        sub.seek(SeekFrom::Start(1000)).unwrap();
268        let mut out = [0u8; 8];
269        assert_eq!(sub.read(&mut out).unwrap(), 0);
270        // Step back inside the window, the bytes should still be there.
271        sub.seek(SeekFrom::Start(4)).unwrap();
272        let mut byte = [0u8; 1];
273        sub.read_exact(&mut byte).unwrap();
274        assert_eq!(byte[0], 4);
275    }
276
277    #[test]
278    fn window_extending_past_inner_rejected() {
279        let data = ramp(64);
280        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
281        let r = SubSource::new(inner, 50, 50); // 50 + 50 = 100 > 64
282        assert!(r.is_err());
283    }
284
285    #[test]
286    fn window_at_exact_end_accepted() {
287        let data = ramp(64);
288        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
289        // 30 + 34 = 64 == inner length, exact tail.
290        let mut sub = SubSource::new(inner, 30, 34).unwrap();
291        let mut out = Vec::new();
292        sub.read_to_end(&mut out).unwrap();
293        assert_eq!(out.len(), 34);
294        assert_eq!(out[0], 30);
295        assert_eq!(out[33], 63);
296    }
297
298    #[test]
299    fn zero_length_window() {
300        let data = ramp(64);
301        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
302        let mut sub = SubSource::new(inner, 16, 0).unwrap();
303        assert!(sub.is_empty());
304        let mut out = [0u8; 4];
305        assert_eq!(sub.read(&mut out).unwrap(), 0);
306    }
307
308    #[test]
309    fn overflowing_window_rejected() {
310        let data = ramp(64);
311        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data));
312        let r = SubSource::new(inner, u64::MAX, 1);
313        assert!(r.is_err());
314    }
315
316    #[test]
317    fn into_inner_returns_inner_source() {
318        let data = ramp(64);
319        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data.clone()));
320        let mut sub = SubSource::new(inner, 8, 16).unwrap();
321        let mut byte = [0u8; 1];
322        sub.read_exact(&mut byte).unwrap();
323        let mut recovered = sub.into_inner();
324        // Seek the recovered handle to a known offset and read.
325        recovered.seek(SeekFrom::Start(0)).unwrap();
326        let mut head = [0u8; 4];
327        recovered.read_exact(&mut head).unwrap();
328        assert_eq!(head, [0, 1, 2, 3]);
329    }
330
331    #[test]
332    fn nested_windows_compose() {
333        let data = ramp(256);
334        let inner: Box<dyn BytesSource> = Box::new(Cursor::new(data.clone()));
335        let outer = SubSource::new(inner, 64, 128).unwrap();
336        // Window the outer into its own [16, 16+32) = inner [80, 112).
337        let mut nested = SubSource::new(Box::new(outer), 16, 32).unwrap();
338        let mut out = vec![0u8; 32];
339        nested.read_exact(&mut out).unwrap();
340        assert_eq!(out, &data[80..112]);
341    }
342
343    #[test]
344    fn stream_len_helper_preserves_position() {
345        let data = ramp(128);
346        let mut src: Box<dyn BytesSource> = Box::new(Cursor::new(data));
347        src.seek(SeekFrom::Start(42)).unwrap();
348        let len = stream_len(&mut *src).unwrap();
349        assert_eq!(len, 128);
350        assert_eq!(src.stream_position().unwrap(), 42);
351    }
352}