pub(crate) struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
pub(crate) const fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
pub(crate) const fn position(&self) -> usize {
self.pos
}
pub(crate) const fn remaining(&self) -> usize {
self.data.len().saturating_sub(self.pos)
}
pub(crate) const fn is_empty(&self) -> bool {
self.remaining() == 0
}
pub(crate) fn seek(&mut self, offset: usize) -> Option<()> {
if offset > self.data.len() {
return None;
}
self.pos = offset;
Some(())
}
pub(crate) fn skip(&mut self, n: usize) -> Option<()> {
self.take(n).map(|_| ())
}
pub(crate) fn take(&mut self, n: usize) -> Option<&'a [u8]> {
let end = self.pos.checked_add(n)?;
let out = self.data.get(self.pos..end)?;
self.pos = end;
Some(out)
}
pub(crate) fn peek(&self, n: usize) -> Option<&'a [u8]> {
let end = self.pos.checked_add(n)?;
self.data.get(self.pos..end)
}
pub(crate) fn take_rest(&mut self) -> &'a [u8] {
let out = self.data.get(self.pos..).unwrap_or_default();
self.pos = self.data.len();
out
}
pub(crate) fn u8(&mut self) -> Option<u8> {
self.take(1)?.first().copied()
}
pub(crate) fn u16_be(&mut self) -> Option<u16> {
let b: [u8; 2] = self.take(2)?.try_into().ok()?;
Some(u16::from_be_bytes(b))
}
pub(crate) fn u16_le(&mut self) -> Option<u16> {
let b: [u8; 2] = self.take(2)?.try_into().ok()?;
Some(u16::from_le_bytes(b))
}
pub(crate) fn u32_be(&mut self) -> Option<u32> {
let b: [u8; 4] = self.take(4)?.try_into().ok()?;
Some(u32::from_be_bytes(b))
}
pub(crate) fn u32_le(&mut self) -> Option<u32> {
let b: [u8; 4] = self.take(4)?.try_into().ok()?;
Some(u32::from_le_bytes(b))
}
}
pub(crate) fn u32_to_usize(value: u32) -> Option<usize> {
usize::try_from(value).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn take_stops_at_the_end_instead_of_panicking() {
let mut r = Reader::new(&[1, 2, 3]);
assert_eq!(r.take(2), Some(&[1u8, 2][..]));
assert_eq!(r.take(2), None, "a truncated read yields None, not a panic");
assert_eq!(r.remaining(), 1, "a failed take must not consume anything");
}
#[test]
fn a_length_field_near_usize_max_cannot_overflow_the_cursor() {
let mut r = Reader::new(&[0xFF; 8]);
assert_eq!(r.skip(4), Some(()));
assert_eq!(r.take(usize::MAX), None);
assert_eq!(r.position(), 4, "the cursor is unmoved by a failed read");
}
#[test]
fn seek_past_the_end_is_refused_not_clamped() {
let mut r = Reader::new(&[1, 2, 3]);
assert_eq!(r.seek(4), None);
assert_eq!(r.position(), 0);
assert_eq!(r.seek(3), Some(()), "seeking exactly to the end is valid");
assert!(r.is_empty());
}
#[test]
fn integers_are_read_with_the_declared_endianness() {
let mut r = Reader::new(&[0x00, 0x01, 0x00, 0x00, 0x00, 0x02]);
assert_eq!(r.u16_be(), Some(1));
assert_eq!(r.u32_be(), Some(2));
assert_eq!(r.u8(), None);
}
#[test]
fn take_rest_is_total() {
let mut r = Reader::new(&[1, 2, 3]);
assert_eq!(r.skip(1), Some(()));
assert_eq!(r.take_rest(), &[2, 3][..]);
assert_eq!(r.take_rest(), &[][..], "calling it again is harmless");
}
}