use std::fs::File;
use std::io::Read;
use std::path::Path;
pub(crate) trait ByteCeilings {
fn source_bytes(&self) -> u64;
fn total_bytes(&self) -> u64;
fn bounds(&self, consumed: u64) -> ReadBounds {
ReadBounds {
source_bytes: self.source_bytes(),
total_bytes: self.total_bytes(),
consumed,
}
}
fn retention(&self, consumed: u64, length: u64) -> Option<ReadFault> {
exceeded(self.bounds(consumed), length)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ReadBounds {
pub(crate) source_bytes: u64,
pub(crate) total_bytes: u64,
pub(crate) consumed: u64,
}
pub(crate) enum ReadFault {
Unreadable(std::io::Error),
SourceBytes,
TotalBytes,
}
pub(crate) fn exceeded(bounds: ReadBounds, length: u64) -> Option<ReadFault> {
match (
length > bounds.source_bytes,
bounds.consumed.saturating_add(length) > bounds.total_bytes,
) {
(true, _) => Some(ReadFault::SourceBytes),
(false, true) => Some(ReadFault::TotalBytes),
(false, false) => None,
}
}
pub(crate) fn bounded(path: &Path, bounds: ReadBounds) -> Result<Vec<u8>, ReadFault> {
let mut bytes = Vec::new();
File::open(path)
.and_then(|file| {
file.take(bounds.source_bytes.saturating_add(1))
.read_to_end(&mut bytes)
})
.map_err(ReadFault::Unreadable)?;
match exceeded(bounds, byte_count(&bytes)) {
Some(fault) => Err(fault),
None => Ok(bytes),
}
}
pub(crate) fn byte_count(bytes: &[u8]) -> u64 {
u64::try_from(bytes.len()).unwrap_or(u64::MAX)
}