use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct ByteReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> ByteReader<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
pub fn position(&self) -> usize {
self.pos
}
pub fn remaining(&self) -> usize {
self.data.len() - self.pos
}
pub fn is_at_end(&self) -> bool {
self.pos >= self.data.len()
}
pub fn peek_u8(&self) -> Option<u8> {
self.data.get(self.pos).copied()
}
pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
if self.remaining() < n {
return Err(Error::UnexpectedEof {
offset: self.pos,
needed: n - self.remaining(),
});
}
let slice = &self.data[self.pos..self.pos + n];
self.pos += n;
Ok(slice)
}
pub fn skip(&mut self, n: usize) -> Result<()> {
self.take(n).map(|_| ())
}
pub fn u8(&mut self) -> Result<u8> {
Ok(self.take(1)?[0])
}
pub fn u32(&mut self) -> Result<u32> {
Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
pub fn i32(&mut self) -> Result<i32> {
Ok(i32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
pub fn f32(&mut self) -> Result<f32> {
Ok(f32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
pub fn bytes<const N: usize>(&mut self) -> Result<[u8; N]> {
Ok(self.take(N)?.try_into().unwrap())
}
pub fn u32s<const N: usize>(&mut self) -> Result<[u32; N]> {
let mut out = [0u32; N];
for v in &mut out {
*v = self.u32()?;
}
Ok(out)
}
pub fn i32s<const N: usize>(&mut self) -> Result<[i32; N]> {
let mut out = [0i32; N];
for v in &mut out {
*v = self.i32()?;
}
Ok(out)
}
pub fn f32s<const N: usize>(&mut self) -> Result<[f32; N]> {
let mut out = [0f32; N];
for v in &mut out {
*v = self.f32()?;
}
Ok(out)
}
pub fn cstr_lossy(&mut self) -> Result<String> {
let rest = &self.data[self.pos..];
match rest.iter().position(|&b| b == 0) {
Some(nul) => {
let s = String::from_utf8_lossy(&rest[..nul]).into_owned();
self.pos += nul + 1;
Ok(s)
}
None => {
let s = String::from_utf8_lossy(rest).into_owned();
self.pos = self.data.len();
Ok(s)
}
}
}
}
pub fn parse_counted<T>(
r: &mut ByteReader<'_>,
mut parse_one: impl FnMut(&mut ByteReader<'_>) -> Result<T>,
) -> Result<Vec<T>> {
if r.remaining() < 4 {
return Ok(Vec::new());
}
let count = r.u32()? as usize;
let mut items = Vec::with_capacity(count.min(4096));
for _ in 0..count {
items.push(parse_one(r)?);
}
Ok(items)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn primitives_and_position() {
let data = [0x01, 0x02, 0x00, 0x00, 0x00, 0xFF];
let mut r = ByteReader::new(&data);
assert_eq!(r.u8().unwrap(), 1);
assert_eq!(r.u32().unwrap(), 2);
assert_eq!(r.remaining(), 1);
assert_eq!(r.peek_u8(), Some(0xFF));
assert_eq!(r.u8().unwrap(), 0xFF);
assert!(r.is_at_end());
}
#[test]
fn eof_is_an_error_not_a_panic() {
let mut r = ByteReader::new(&[0x01, 0x02]);
let err = r.u32().unwrap_err();
assert!(matches!(
err,
Error::UnexpectedEof {
offset: 0,
needed: 2
}
));
}
#[test]
fn cstr_reads_terminator_and_tolerates_missing_nul() {
let mut r = ByteReader::new(b"abc\0def");
assert_eq!(r.cstr_lossy().unwrap(), "abc");
assert_eq!(r.cstr_lossy().unwrap(), "def");
assert!(r.is_at_end());
}
#[test]
fn counted_returns_empty_on_truncation() {
let mut r = ByteReader::new(&[0x01]);
let items = parse_counted(&mut r, |r| r.u8()).unwrap();
assert!(items.is_empty());
}
}