use crate::error::FitError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Endian {
Little,
Big,
}
#[derive(Debug, Clone)]
pub struct ByteStream<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> ByteStream<'a> {
#[inline]
pub fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
#[inline]
pub fn as_slice(&self) -> &'a [u8] {
self.bytes
}
#[inline]
pub fn position(&self) -> usize {
self.pos
}
pub fn seek(&mut self, offset: usize) -> Result<(), FitError> {
if offset > self.bytes.len() {
return Err(FitError::UnexpectedEof { offset });
}
self.pos = offset;
Ok(())
}
#[inline]
pub fn remaining(&self) -> usize {
self.bytes.len().saturating_sub(self.pos)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.pos >= self.bytes.len()
}
pub fn peek_u8(&self) -> Result<u8, FitError> {
self.bytes
.get(self.pos)
.copied()
.ok_or(FitError::UnexpectedEof { offset: self.pos })
}
pub fn read_u8(&mut self) -> Result<u8, FitError> {
let v = self.peek_u8()?;
self.pos += 1;
Ok(v)
}
pub fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], FitError> {
if self.remaining() < n {
return Err(FitError::UnexpectedEof { offset: self.pos });
}
let slice = &self.bytes[self.pos..self.pos + n];
self.pos += n;
Ok(slice)
}
pub fn read_array<const N: usize>(&mut self) -> Result<[u8; N], FitError> {
let slice = self.read_bytes(N)?;
let mut arr = [0u8; N];
arr.copy_from_slice(slice);
Ok(arr)
}
pub fn read_u16(&mut self, endian: Endian) -> Result<u16, FitError> {
let bytes = self.read_array::<2>()?;
Ok(match endian {
Endian::Little => u16::from_le_bytes(bytes),
Endian::Big => u16::from_be_bytes(bytes),
})
}
pub fn read_u32(&mut self, endian: Endian) -> Result<u32, FitError> {
let bytes = self.read_array::<4>()?;
Ok(match endian {
Endian::Little => u32::from_le_bytes(bytes),
Endian::Big => u32::from_be_bytes(bytes),
})
}
#[inline]
pub fn read_u16_le(&mut self) -> Result<u16, FitError> {
self.read_u16(Endian::Little)
}
#[inline]
pub fn read_u32_le(&mut self) -> Result<u32, FitError> {
self.read_u32(Endian::Little)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn position_and_remaining() {
let mut s = ByteStream::new(&[1, 2, 3, 4, 5]);
assert_eq!(s.position(), 0);
assert_eq!(s.remaining(), 5);
let _ = s.read_u8().unwrap();
assert_eq!(s.position(), 1);
assert_eq!(s.remaining(), 4);
}
#[test]
fn peek_does_not_advance() {
let mut s = ByteStream::new(&[42]);
assert_eq!(s.peek_u8().unwrap(), 42);
assert_eq!(s.position(), 0);
assert_eq!(s.read_u8().unwrap(), 42);
assert_eq!(s.position(), 1);
}
#[test]
fn read_past_end_errors_with_correct_offset() {
let mut s = ByteStream::new(&[1, 2]);
let _ = s.read_u8().unwrap();
let _ = s.read_u8().unwrap();
assert_eq!(s.read_u8(), Err(FitError::UnexpectedEof { offset: 2 }),);
}
#[test]
fn endianness_is_correct() {
let mut s = ByteStream::new(&[0x12, 0x34, 0x56, 0x78]);
assert_eq!(s.read_u16(Endian::Little).unwrap(), 0x3412);
s.seek(0).unwrap();
assert_eq!(s.read_u16(Endian::Big).unwrap(), 0x1234);
s.seek(0).unwrap();
assert_eq!(s.read_u32(Endian::Little).unwrap(), 0x7856_3412);
s.seek(0).unwrap();
assert_eq!(s.read_u32(Endian::Big).unwrap(), 0x1234_5678);
}
#[test]
fn read_bytes_borrows_correct_range() {
let data = [10, 20, 30, 40, 50];
let mut s = ByteStream::new(&data);
assert_eq!(s.read_bytes(2).unwrap(), &[10, 20]);
assert_eq!(s.read_bytes(3).unwrap(), &[30, 40, 50]);
assert!(s.is_empty());
}
#[test]
fn seek_within_and_at_end_ok() {
let mut s = ByteStream::new(&[1, 2, 3]);
s.seek(3).unwrap(); assert!(s.is_empty());
assert_eq!(s.seek(4), Err(FitError::UnexpectedEof { offset: 4 }));
}
}