Skip to main content

zvidlib_core/
io.rs

1use crate::{Error, ErrorKind, Result};
2use std::future::Future;
3use std::pin::Pin;
4
5/// A non-`Send` future suitable for both native and single-threaded WASM use.
6pub type IoFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + 'a>>;
7
8/// An asynchronous random-access byte source.
9pub trait ByteSource {
10    fn len(&self) -> Option<u64>;
11
12    fn is_empty(&self) -> Option<bool> {
13        self.len().map(|length| length == 0)
14    }
15
16    fn read_at<'a>(&'a self, offset: u64, destination: &'a mut [u8]) -> IoFuture<'a, usize>;
17}
18
19/// An asynchronous sequential byte sink with optional seek support.
20///
21/// `is_seekable()` defaults to `true`, so implementers that cannot seek
22/// (e.g. a streaming HTTP upload or a pipe) must override it to return
23/// `false` *and* make `seek()` reject every call with
24/// [`ErrorKind::Unsupported`]. Callers that require a seekable sink, such as
25/// `crate::mp4::Mp4Muxer`, check `is_seekable()` up front and never call
26/// `seek()` on a sink that reports `false`; a non-seekable sink's `seek()`
27/// implementation exists only to fail safely if that contract is ever
28/// violated.
29pub trait ByteSink {
30    fn position(&self) -> u64;
31    fn is_seekable(&self) -> bool {
32        true
33    }
34    fn write<'a>(&'a mut self, bytes: &'a [u8]) -> IoFuture<'a, ()>;
35    fn seek<'a>(&'a mut self, position: u64) -> IoFuture<'a, ()>;
36    fn flush<'a>(&'a mut self) -> IoFuture<'a, ()> {
37        Box::pin(async { Ok(()) })
38    }
39}
40
41/// An owned in-memory source useful for portable callers and deterministic tests.
42#[derive(Clone, Debug, Default, Eq, PartialEq)]
43pub struct MemorySource {
44    bytes: Vec<u8>,
45}
46
47impl MemorySource {
48    pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
49        Self {
50            bytes: bytes.into(),
51        }
52    }
53
54    pub fn as_slice(&self) -> &[u8] {
55        &self.bytes
56    }
57}
58
59impl ByteSource for MemorySource {
60    fn len(&self) -> Option<u64> {
61        u64::try_from(self.bytes.len()).ok()
62    }
63
64    fn read_at<'a>(&'a self, offset: u64, destination: &'a mut [u8]) -> IoFuture<'a, usize> {
65        Box::pin(async move {
66            let offset = usize::try_from(offset)
67                .map_err(|_| Error::new(ErrorKind::InvalidInput, "read offset is too large"))?;
68            if offset > self.bytes.len() {
69                return Err(Error::new(
70                    ErrorKind::InvalidInput,
71                    "read offset is beyond the source",
72                ));
73            }
74            let length = destination.len().min(self.bytes.len() - offset);
75            destination[..length].copy_from_slice(&self.bytes[offset..offset + length]);
76            Ok(length)
77        })
78    }
79}
80
81/// A seekable in-memory sink useful for portable callers and deterministic tests.
82#[derive(Clone, Debug, Default, Eq, PartialEq)]
83pub struct MemorySink {
84    bytes: Vec<u8>,
85    position: u64,
86}
87
88impl MemorySink {
89    pub fn new() -> Self {
90        Self::default()
91    }
92
93    pub fn into_inner(self) -> Vec<u8> {
94        self.bytes
95    }
96
97    pub fn as_slice(&self) -> &[u8] {
98        &self.bytes
99    }
100}
101
102impl ByteSink for MemorySink {
103    fn position(&self) -> u64 {
104        self.position
105    }
106
107    fn write<'a>(&'a mut self, bytes: &'a [u8]) -> IoFuture<'a, ()> {
108        Box::pin(async move {
109            let position = usize::try_from(self.position)
110                .map_err(|_| Error::new(ErrorKind::ResourceLimit, "sink position is too large"))?;
111            let end = position
112                .checked_add(bytes.len())
113                .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "sink allocation overflow"))?;
114            if end > self.bytes.len() {
115                self.bytes.resize(end, 0);
116            }
117            self.bytes[position..end].copy_from_slice(bytes);
118            self.position = u64::try_from(end).map_err(|_| {
119                Error::new(
120                    ErrorKind::ResourceLimit,
121                    "sink position cannot be represented",
122                )
123            })?;
124            Ok(())
125        })
126    }
127
128    fn seek<'a>(&'a mut self, position: u64) -> IoFuture<'a, ()> {
129        Box::pin(async move {
130            usize::try_from(position)
131                .map_err(|_| Error::new(ErrorKind::InvalidInput, "seek position is too large"))?;
132            self.position = position;
133            Ok(())
134        })
135    }
136}
137
138/// A sequential-only in-memory sink used to test the non-seekable `ByteSink`
139/// contract: it reports `is_seekable() == false` and rejects every `seek()`
140/// call with [`ErrorKind::Unsupported`].
141#[doc(hidden)]
142#[derive(Clone, Debug, Default, Eq, PartialEq)]
143pub struct NonSeekableSink {
144    bytes: Vec<u8>,
145}
146
147impl ByteSink for NonSeekableSink {
148    fn position(&self) -> u64 {
149        self.bytes.len() as u64
150    }
151
152    fn is_seekable(&self) -> bool {
153        false
154    }
155
156    fn write<'a>(&'a mut self, bytes: &'a [u8]) -> IoFuture<'a, ()> {
157        Box::pin(async move {
158            self.bytes.extend_from_slice(bytes);
159            Ok(())
160        })
161    }
162
163    fn seek<'a>(&'a mut self, _position: u64) -> IoFuture<'a, ()> {
164        Box::pin(async move {
165            Err(Error::new(
166                ErrorKind::Unsupported,
167                "this sink does not support seeking",
168            ))
169        })
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use std::task::{Context, Poll, Waker};
177
178    fn ready<T>(mut future: IoFuture<'_, T>) -> Result<T> {
179        let waker = Waker::noop();
180        let mut context = Context::from_waker(waker);
181        match future.as_mut().poll(&mut context) {
182            Poll::Ready(result) => result,
183            Poll::Pending => panic!("memory I/O unexpectedly returned a pending future"),
184        }
185    }
186
187    #[test]
188    fn memory_source_reads_partially_at_end_of_source() {
189        let source = MemorySource::new(b"abcdef".to_vec());
190        let mut destination = [0_u8; 4];
191        assert_eq!(ready(source.read_at(4, &mut destination)).unwrap(), 2);
192        assert_eq!(&destination[..2], b"ef");
193    }
194
195    #[test]
196    fn memory_source_rejects_offsets_beyond_end() {
197        let source = MemorySource::new(b"abc".to_vec());
198        let mut destination = [0_u8; 1];
199        let error = ready(source.read_at(4, &mut destination)).unwrap_err();
200        assert_eq!(error.kind(), ErrorKind::InvalidInput);
201    }
202
203    #[test]
204    fn memory_sink_seeks_overwrites_and_zero_fills() {
205        let mut sink = MemorySink::new();
206        ready(sink.write(b"abc")).unwrap();
207        ready(sink.seek(1)).unwrap();
208        ready(sink.write(b"Z")).unwrap();
209        ready(sink.seek(5)).unwrap();
210        ready(sink.write(b"!")).unwrap();
211        assert_eq!(sink.as_slice(), b"aZc\0\0!");
212    }
213
214    #[test]
215    fn non_seekable_sink_writes_but_rejects_seek() {
216        let mut sink = NonSeekableSink::default();
217        assert!(!sink.is_seekable());
218        ready(sink.write(b"abc")).unwrap();
219        assert_eq!(sink.bytes, b"abc");
220        let error = ready(sink.seek(0)).unwrap_err();
221        assert_eq!(error.kind(), ErrorKind::Unsupported);
222    }
223}