mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 06:22:24 +00:00
271 lines
9.3 KiB
Rust
271 lines
9.3 KiB
Rust
//! Random-access byte sources: what the demuxers read from. One trait for every container, so a
|
|
//! file, buffer or custom source implements it once.
|
|
|
|
#![warn(missing_docs)]
|
|
|
|
use std::io::{self, Read, Seek, SeekFrom};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
/// Random-access, read-only byte source (file, in-memory buffer, web Blob…).
|
|
///
|
|
/// Implementations serve reads at arbitrary offsets through `&self`. `isobmff::ByteSource`,
|
|
/// `matroska::ByteSource` and `ogg::ByteSource` are this trait.
|
|
pub trait ByteSource {
|
|
/// Total length in bytes.
|
|
fn len(&self) -> u64;
|
|
/// Fill `buf` entirely with bytes starting at `offset`; fail with `UnexpectedEof` if past the end.
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()>;
|
|
/// True if the source holds no bytes.
|
|
fn is_empty(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
}
|
|
|
|
fn eof() -> io::Error {
|
|
io::Error::new(io::ErrorKind::UnexpectedEof, "read past end of byte source")
|
|
}
|
|
|
|
impl ByteSource for [u8] {
|
|
fn len(&self) -> u64 {
|
|
<[u8]>::len(self) as u64
|
|
}
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
let start = usize::try_from(offset).map_err(|_| eof())?;
|
|
let end = start.checked_add(buf.len()).ok_or_else(eof)?;
|
|
let src = self.get(start..end).ok_or_else(eof)?;
|
|
buf.copy_from_slice(src);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ByteSource for Vec<u8> {
|
|
fn len(&self) -> u64 {
|
|
self.as_slice().len() as u64
|
|
}
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
self.as_slice().read_at(offset, buf)
|
|
}
|
|
}
|
|
|
|
impl<T: ByteSource + ?Sized> ByteSource for &T {
|
|
fn len(&self) -> u64 {
|
|
(**self).len()
|
|
}
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
(**self).read_at(offset, buf)
|
|
}
|
|
}
|
|
|
|
impl<T: ByteSource + ?Sized> ByteSource for Arc<T> {
|
|
fn len(&self) -> u64 {
|
|
(**self).len()
|
|
}
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
(**self).read_at(offset, buf)
|
|
}
|
|
}
|
|
|
|
impl<T: ByteSource + ?Sized> ByteSource for Box<T> {
|
|
fn len(&self) -> u64 {
|
|
(**self).len()
|
|
}
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
(**self).read_at(offset, buf)
|
|
}
|
|
}
|
|
|
|
/// Past the largest offset a file can have (`i64::MAX`), where the OS would fail with
|
|
/// `InvalidInput` instead of reporting the end of the file.
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
fn past_file_offsets(offset: u64, len: usize) -> bool {
|
|
offset.checked_add(len as u64).is_none_or(|e| e > i64::MAX as u64)
|
|
}
|
|
|
|
/// A file read in place. `len` asks the file system each time, and a failure there reads as an
|
|
/// empty file; [`ReadSeekSource`] takes the length once and reports a failure when it is made.
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
impl ByteSource for std::fs::File {
|
|
fn len(&self) -> u64 {
|
|
self.metadata().map(|m| m.len()).unwrap_or(0)
|
|
}
|
|
#[cfg(unix)]
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
if past_file_offsets(offset, buf.len()) {
|
|
return Err(eof());
|
|
}
|
|
std::os::unix::fs::FileExt::read_exact_at(self, buf, offset)
|
|
}
|
|
#[cfg(windows)]
|
|
fn read_at(&self, mut offset: u64, mut buf: &mut [u8]) -> io::Result<()> {
|
|
use std::os::windows::fs::FileExt;
|
|
if past_file_offsets(offset, buf.len()) {
|
|
return Err(eof());
|
|
}
|
|
while !buf.is_empty() {
|
|
let n = self.seek_read(buf, offset)?;
|
|
if n == 0 {
|
|
return Err(eof());
|
|
}
|
|
offset += n as u64;
|
|
buf = &mut buf[n..];
|
|
}
|
|
Ok(())
|
|
}
|
|
#[cfg(not(any(unix, windows)))]
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
if past_file_offsets(offset, buf.len()) {
|
|
return Err(eof());
|
|
}
|
|
let mut f = self;
|
|
f.seek(SeekFrom::Start(offset))?;
|
|
f.read_exact(buf)
|
|
}
|
|
}
|
|
|
|
/// Adapts any `Read + Seek` (e.g. `BufReader<File>`, `Cursor<Vec<u8>>`) into a [`ByteSource`].
|
|
///
|
|
/// The length is taken once at construction; the stream position is tracked so sequential reads
|
|
/// do not issue redundant seeks. The reader sits behind a mutex, so the source is `Send + Sync`
|
|
/// whenever `R` is `Send` and can be shared across threads.
|
|
pub struct ReadSeekSource<R> {
|
|
inner: Mutex<(R, u64)>,
|
|
len: u64,
|
|
}
|
|
|
|
impl<R: Read + Seek> ReadSeekSource<R> {
|
|
/// Wrap a reader; seeks to the end once to learn the length.
|
|
pub fn new(mut r: R) -> io::Result<Self> {
|
|
let len = r.seek(SeekFrom::End(0))?;
|
|
Ok(Self { inner: Mutex::new((r, len)), len })
|
|
}
|
|
/// Unwrap the reader.
|
|
pub fn into_inner(self) -> R {
|
|
// A poisoned lock only means a read panicked in another thread; the reader is still usable.
|
|
self.inner.into_inner().unwrap_or_else(|e| e.into_inner()).0
|
|
}
|
|
}
|
|
|
|
impl<R: Read + Seek> ByteSource for ReadSeekSource<R> {
|
|
fn len(&self) -> u64 {
|
|
self.len
|
|
}
|
|
fn read_at(&self, offset: u64, buf: &mut [u8]) -> io::Result<()> {
|
|
if offset.checked_add(buf.len() as u64).is_none_or(|e| e > self.len) {
|
|
return Err(eof());
|
|
}
|
|
let mut g = self.inner.lock().unwrap_or_else(|e| {
|
|
// a read panicked in another thread and may have moved the reader; force a seek
|
|
let mut g = e.into_inner();
|
|
g.1 = u64::MAX;
|
|
self.inner.clear_poison();
|
|
g
|
|
});
|
|
let (r, pos) = &mut *g;
|
|
if *pos != offset {
|
|
*pos = u64::MAX; // unknown until the seek succeeds
|
|
r.seek(SeekFrom::Start(offset))?;
|
|
*pos = offset;
|
|
}
|
|
let res = r.read_exact(buf);
|
|
match res {
|
|
Ok(()) => {
|
|
*pos = offset + buf.len() as u64;
|
|
Ok(())
|
|
}
|
|
Err(e) => {
|
|
*pos = u64::MAX; // unknown; force a seek next time
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn slice_and_reader_sources() {
|
|
let v = vec![1u8, 2, 3, 4];
|
|
let mut b = [0u8; 2];
|
|
v.read_at(1, &mut b).unwrap();
|
|
assert_eq!(b, [2, 3]);
|
|
assert!(v.read_at(3, &mut b).is_err());
|
|
assert!(v.read_at(u64::MAX, &mut b).is_err());
|
|
let r = ReadSeekSource::new(io::Cursor::new(v.clone())).unwrap();
|
|
assert_eq!(r.len(), 4);
|
|
r.read_at(2, &mut b).unwrap();
|
|
assert_eq!(b, [3, 4]);
|
|
r.read_at(0, &mut b).unwrap();
|
|
assert_eq!(b, [1, 2]);
|
|
assert!(r.read_at(3, &mut b).is_err());
|
|
let a: Arc<[u8]> = Arc::from(v.clone());
|
|
a.read_at(2, &mut b).unwrap();
|
|
assert_eq!(b, [3, 4]);
|
|
assert_eq!(ByteSource::len(&a), 4);
|
|
let s: &[u8] = &v;
|
|
assert_eq!(ByteSource::len(&s), 4);
|
|
}
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
#[test]
|
|
fn file_sources_read_like_a_buffer() {
|
|
let v: Vec<u8> = (0..=255).collect();
|
|
let dir = craft_media_testkit::private_dir("source");
|
|
let path = dir.join("source.bin");
|
|
std::fs::write(&path, &v).unwrap();
|
|
let f = std::fs::File::open(&path).unwrap();
|
|
let r = ReadSeekSource::new(std::fs::File::open(&path).unwrap()).unwrap();
|
|
let sources: [&dyn ByteSource; 3] = [&v, &f, &r];
|
|
for s in sources {
|
|
assert_eq!(s.len(), 256);
|
|
// out of order, so ReadSeekSource has to seek back
|
|
for (offset, n) in [(0, 0), (10, 16), (0, 256), (200, 56), (255, 1), (3, 7)] {
|
|
let mut b = vec![0u8; n];
|
|
s.read_at(offset as u64, &mut b).unwrap();
|
|
assert_eq!(b, v[offset..offset + n], "offset {offset}, {n} bytes");
|
|
}
|
|
for (offset, n) in [(200, 57), (256, 1), (300, 1), (u64::MAX, 1), (u64::MAX - 1, 4)] {
|
|
let err = s.read_at(offset, &mut vec![0u8; n]).unwrap_err();
|
|
assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof, "offset {offset}, {n} bytes");
|
|
}
|
|
}
|
|
drop((f, r)); // Windows deletes no open file
|
|
std::fs::remove_dir_all(&dir).ok();
|
|
}
|
|
|
|
#[test]
|
|
fn read_seek_source_seeks_after_a_panicked_read() {
|
|
struct PanicOnce(io::Cursor<Vec<u8>>, bool);
|
|
impl Read for PanicOnce {
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
let n = self.0.read(&mut buf[..1])?;
|
|
if std::mem::take(&mut self.1) {
|
|
panic!("read failed halfway");
|
|
}
|
|
Ok(n)
|
|
}
|
|
}
|
|
impl Seek for PanicOnce {
|
|
fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
|
|
self.0.seek(to)
|
|
}
|
|
}
|
|
let r = ReadSeekSource::new(PanicOnce(io::Cursor::new(vec![1, 2, 3, 4]), false)).unwrap();
|
|
let mut b = [0u8; 2];
|
|
r.read_at(0, &mut b).unwrap();
|
|
r.inner.lock().unwrap().0.1 = true;
|
|
// the reader moves one byte, then panics with the tracked position at 2
|
|
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| r.read_at(2, &mut b))).is_err());
|
|
r.read_at(2, &mut b).unwrap();
|
|
assert_eq!(b, [3, 4]);
|
|
assert!(!r.inner.is_poisoned(), "recovered once, not on every read");
|
|
}
|
|
|
|
#[test]
|
|
fn read_seek_source_is_send_and_sync() {
|
|
fn shareable<T: ByteSource + Send + Sync + 'static>(_: &T) {}
|
|
shareable(&ReadSeekSource::new(io::Cursor::new(vec![0u8; 4])).unwrap());
|
|
}
|
|
}
|