use std::io::{Read, Seek, SeekFrom};
use crate::error::Result;
#[derive(Debug)]
pub struct OffsetReader<R> {
inner: R,
base: u64,
len: u64,
pos: u64,
}
impl<R: Read + Seek> OffsetReader<R> {
pub fn new(mut inner: R, base: u64, len: u64) -> Result<Self> {
inner.seek(SeekFrom::Start(base))?;
Ok(Self {
inner,
base,
len,
pos: 0,
})
}
#[must_use]
pub fn len(&self) -> u64 {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<R: Read + Seek> Read for OffsetReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let remaining = self.len.saturating_sub(self.pos);
if remaining == 0 {
return Ok(0);
}
let cap = remaining.min(buf.len() as u64) as usize;
let abs = self.base.checked_add(self.pos).ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "offset overflow")
})?;
self.inner.seek(SeekFrom::Start(abs))?;
let n = self.inner.read(&mut buf[..cap])?;
self.pos += n as u64;
Ok(n)
}
}
impl<R: Read + Seek> Seek for OffsetReader<R> {
fn seek(&mut self, from: SeekFrom) -> std::io::Result<u64> {
let target: i128 = match from {
SeekFrom::Start(n) => n as i128,
SeekFrom::Current(d) => self.pos as i128 + d as i128,
SeekFrom::End(d) => self.len as i128 + d as i128,
};
if target < 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek before partition start",
));
}
self.pos = u64::try_from(target).unwrap_or(u64::MAX);
Ok(self.pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn disk() -> Cursor<Vec<u8>> {
Cursor::new((0u8..64).collect())
}
#[test]
fn reads_are_relative_to_base() {
let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
let mut buf = [0u8; 4];
r.read_exact(&mut buf).unwrap();
assert_eq!(buf, [16, 17, 18, 19]); }
#[test]
fn seek_is_relative_to_base() {
let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
r.seek(SeekFrom::Start(8)).unwrap();
let mut buf = [0u8; 2];
r.read_exact(&mut buf).unwrap();
assert_eq!(buf, [24, 25]); }
#[test]
fn seek_end_is_partition_length() {
let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
let end = r.seek(SeekFrom::End(0)).unwrap();
assert_eq!(end, 32); }
#[test]
fn read_is_clamped_at_partition_end() {
let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
r.seek(SeekFrom::Start(30)).unwrap();
let mut buf = [0u8; 8];
let n = r.read(&mut buf).unwrap();
assert_eq!(n, 2); assert_eq!(&buf[..2], &[46, 47]); assert_eq!(r.read(&mut buf).unwrap(), 0);
}
#[test]
fn rejects_seek_before_start() {
let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
assert!(r.seek(SeekFrom::Current(-1)).is_err());
}
#[test]
fn len_reports_window_size() {
let r = OffsetReader::new(disk(), 16, 32).unwrap();
assert_eq!(r.len(), 32);
assert!(!r.is_empty());
}
#[test]
fn read_rejects_base_plus_position_overflow() {
let mut r = OffsetReader::new(disk(), u64::MAX, u64::MAX).unwrap();
r.seek(SeekFrom::Start(1)).unwrap();
let mut buf = [0u8; 4];
let err = r.read(&mut buf).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
}