1use std::io::{Read, Seek, SeekFrom};
11
12use crate::error::Result;
13
14#[derive(Debug)]
17pub struct OffsetReader<R> {
18 inner: R,
19 base: u64,
20 len: u64,
21 pos: u64,
22}
23
24impl<R: Read + Seek> OffsetReader<R> {
25 pub fn new(mut inner: R, base: u64, len: u64) -> Result<Self> {
31 inner.seek(SeekFrom::Start(base))?;
32 Ok(Self {
33 inner,
34 base,
35 len,
36 pos: 0,
37 })
38 }
39
40 #[must_use]
42 pub fn len(&self) -> u64 {
43 self.len
44 }
45
46 #[must_use]
48 pub fn is_empty(&self) -> bool {
49 self.len == 0
50 }
51}
52
53impl<R: Read + Seek> Read for OffsetReader<R> {
54 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
55 let remaining = self.len.saturating_sub(self.pos);
56 if remaining == 0 {
57 return Ok(0);
58 }
59 let cap = remaining.min(buf.len() as u64) as usize;
61 let abs = self.base.checked_add(self.pos).ok_or_else(|| {
63 std::io::Error::new(std::io::ErrorKind::InvalidInput, "offset overflow")
64 })?;
65 self.inner.seek(SeekFrom::Start(abs))?;
66 let n = self.inner.read(&mut buf[..cap])?;
67 self.pos += n as u64;
68 Ok(n)
69 }
70}
71
72impl<R: Read + Seek> Seek for OffsetReader<R> {
73 fn seek(&mut self, from: SeekFrom) -> std::io::Result<u64> {
74 let target: i128 = match from {
77 SeekFrom::Start(n) => i128::from(n),
78 SeekFrom::Current(d) => i128::from(self.pos) + i128::from(d),
79 SeekFrom::End(d) => i128::from(self.len) + i128::from(d),
80 };
81 if target < 0 {
82 return Err(std::io::Error::new(
83 std::io::ErrorKind::InvalidInput,
84 "seek before partition start",
85 ));
86 }
87 self.pos = u64::try_from(target).unwrap_or(u64::MAX);
90 Ok(self.pos)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use std::io::Cursor;
98
99 fn disk() -> Cursor<Vec<u8>> {
101 Cursor::new((0u8..64).collect())
102 }
103
104 #[test]
105 fn reads_are_relative_to_base() {
106 let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
107 let mut buf = [0u8; 4];
108 r.read_exact(&mut buf).unwrap();
109 assert_eq!(buf, [16, 17, 18, 19]); }
111
112 #[test]
113 fn seek_is_relative_to_base() {
114 let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
115 r.seek(SeekFrom::Start(8)).unwrap();
116 let mut buf = [0u8; 2];
117 r.read_exact(&mut buf).unwrap();
118 assert_eq!(buf, [24, 25]); }
120
121 #[test]
122 fn seek_end_is_partition_length() {
123 let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
124 let end = r.seek(SeekFrom::End(0)).unwrap();
125 assert_eq!(end, 32); }
127
128 #[test]
129 fn read_is_clamped_at_partition_end() {
130 let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
131 r.seek(SeekFrom::Start(30)).unwrap();
132 let mut buf = [0u8; 8];
133 let n = r.read(&mut buf).unwrap();
134 assert_eq!(n, 2); assert_eq!(&buf[..2], &[46, 47]); assert_eq!(r.read(&mut buf).unwrap(), 0);
138 }
139
140 #[test]
141 fn rejects_seek_before_start() {
142 let mut r = OffsetReader::new(disk(), 16, 32).unwrap();
143 assert!(r.seek(SeekFrom::Current(-1)).is_err());
144 }
145
146 #[test]
147 fn len_reports_window_size() {
148 let r = OffsetReader::new(disk(), 16, 32).unwrap();
149 assert_eq!(r.len(), 32);
150 assert!(!r.is_empty());
151 }
152
153 #[test]
154 fn read_rejects_base_plus_position_overflow() {
155 let mut r = OffsetReader::new(disk(), u64::MAX, u64::MAX).unwrap();
157 r.seek(SeekFrom::Start(1)).unwrap();
158 let mut buf = [0u8; 4];
159 let err = r.read(&mut buf).unwrap_err();
160 assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
161 }
162}