mod reader;
use std::borrow::Cow;
use std::rc::Rc;
use std::sync::Arc;
pub use reader::{Reader, ascii};
use crate::{Error, Result};
pub trait ByteSource {
fn size(&self) -> u64;
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()>;
fn as_slice(&self) -> Option<&[u8]> {
None
}
fn read(&self, offset: u64, len: u64) -> Result<Cow<'_, [u8]>> {
let end = checked_end(offset, len, self.size())?;
if let Some(bytes) = self.as_slice() {
return Ok(Cow::Borrowed(&bytes[offset as usize..end as usize]));
}
let len = usize::try_from(len)
.map_err(|_| Error::malformed("byte source", offset, "read does not fit in memory"))?;
let mut buf = vec![0; len];
self.read_exact_at(offset, &mut buf)?;
Ok(Cow::Owned(buf))
}
fn read_up_to(&self, offset: u64, len: u64) -> Result<Cow<'_, [u8]>> {
let offset = offset.min(self.size());
self.read(offset, len.min(self.size() - offset))
}
fn read_all(&self) -> Result<Cow<'_, [u8]>> {
self.read(0, self.size())
}
}
fn checked_end(offset: u64, len: u64, size: u64) -> Result<u64> {
offset
.checked_add(len)
.filter(|&end| end <= size)
.ok_or_else(|| Error::malformed("byte source", offset, "read past the end"))
}
impl ByteSource for [u8] {
fn size(&self) -> u64 {
self.len() as u64
}
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let end = checked_end(offset, buf.len() as u64, self.size())?;
buf.copy_from_slice(&self[offset as usize..end as usize]);
Ok(())
}
fn as_slice(&self) -> Option<&[u8]> {
Some(self)
}
}
macro_rules! forward {
($($wrapper:ty),+) => {$(
impl<T: ByteSource + ?Sized> ByteSource for $wrapper {
fn size(&self) -> u64 {
(**self).size()
}
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
(**self).read_exact_at(offset, buf)
}
fn as_slice(&self) -> Option<&[u8]> {
(**self).as_slice()
}
}
)+};
}
forward!(&T, Box<T>, Rc<T>, Arc<T>);
impl ByteSource for Vec<u8> {
fn size(&self) -> u64 {
self.as_slice().size()
}
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
self.as_slice().read_exact_at(offset, buf)
}
fn as_slice(&self) -> Option<&[u8]> {
Some(self)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Window<S> {
source: S,
offset: u64,
len: u64,
}
impl<S: ByteSource> Window<S> {
pub fn new(source: S, offset: u64, len: u64) -> Result<Self> {
checked_end(offset, len, source.size())
.map_err(|_| Error::malformed("byte source", offset, "window exceeds its source"))?;
Ok(Self {
source,
offset,
len,
})
}
pub fn offset(&self) -> u64 {
self.offset
}
}
impl<S: ByteSource> ByteSource for Window<S> {
fn size(&self) -> u64 {
self.len
}
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
checked_end(offset, buf.len() as u64, self.len)?;
self.source.read_exact_at(self.offset + offset, buf)
}
fn as_slice(&self) -> Option<&[u8]> {
let bytes = self.source.as_slice()?;
Some(&bytes[self.offset as usize..(self.offset + self.len) as usize])
}
}
#[cfg(any(unix, windows))]
#[derive(Debug)]
pub struct FileSource {
file: std::fs::File,
size: u64,
}
#[cfg(any(unix, windows))]
impl FileSource {
pub fn open(path: impl AsRef<std::path::Path>) -> Result<Self> {
let file = std::fs::File::open(path)?;
let size = file.metadata()?.len();
Ok(Self { file, size })
}
}
#[cfg(any(unix, windows))]
impl ByteSource for FileSource {
fn size(&self) -> u64 {
self.size
}
fn read_exact_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
checked_end(offset, buf.len() as u64, self.size)?;
#[cfg(unix)]
std::os::unix::fs::FileExt::read_exact_at(&self.file, buf, offset)?;
#[cfg(windows)]
{
let (mut done, mut offset) = (0, offset);
while done < buf.len() {
let read =
std::os::windows::fs::FileExt::seek_read(&self.file, &mut buf[done..], offset)?;
if read == 0 {
return Err(Error::Host("file ended early".into()));
}
done += read;
offset += read as u64;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::Counting;
#[test]
fn slices_borrow_and_other_sources_read() {
let bytes = b"0123456789".as_slice();
assert!(matches!(bytes.read(2, 3).unwrap(), Cow::Borrowed(b"234")));
let counting = Counting::new(bytes);
let window = Window::new(&counting, 4, 4).unwrap();
assert!(matches!(window.read(1, 2).unwrap(), Cow::Owned(ref v) if v == b"56"));
assert_eq!(counting.bytes_read(), 2);
assert_eq!(&*window.read_up_to(2, 100).unwrap(), b"67");
}
#[test]
fn out_of_range_reads_are_errors() {
let bytes = b"0123456789".as_slice();
let mut buf = [0; 4];
assert!(bytes.read_exact_at(8, &mut buf).is_err());
assert!(bytes.read_exact_at(u64::MAX, &mut buf).is_err());
assert!(Window::new(bytes, 8, 4).is_err());
let counting = Counting::new(bytes);
let window = Window::new(&counting, 0, 4).unwrap();
assert!(window.read_exact_at(2, &mut buf).is_err());
assert_eq!(counting.bytes_read(), 0);
}
#[test]
fn files_are_read_in_place() {
let path = std::env::temp_dir().join(format!("romkit-io-{}", std::process::id()));
std::fs::write(&path, b"0123456789").unwrap();
let file = FileSource::open(&path).unwrap();
assert_eq!(file.size(), 10);
assert_eq!(&*file.read(3, 4).unwrap(), b"3456");
assert!(file.read(8, 4).is_err());
std::fs::remove_file(path).unwrap();
}
}