Thank you, now I can keep going. This is my current draft of a BufferReader, done for learning purposes as a first project.
I think it is quite horrible still, but am happy of how Rust works. The docs use an ElfReader as example. Need to update that.
Draft
//! Tool for reading data from a `Vec<u8>`.
use std::{fmt::Display, string::FromUtf8Error};
use num_traits::FromBytes;
/// Some index-update functions can start with `_`
/// These do not return the new index.
pub trait BufferReader {
/// Get the current index in the buffer.
fn index(&self) -> usize;
/// Reference to the underlying data.
fn data(&self) -> &[u8];
#[allow(dead_code)]
/// Mutable reference to the underlying data.
fn data_mut(&mut self) -> &mut [u8];
/// Set our current index to `new_index` value.
fn _set_index(&mut self, new_index: usize);
/// Length of underlying data vector.
fn len(&self) -> usize {
self.data().len()
}
/// Update index value by `n`.
/// Panics moving beyond `usize::MAX`.
fn _ahead(&mut self, n: usize) {
self._set_index(self.index() + n);
}
/// Update index value by `-n`.
/// Panics moving below `0`.
fn _back(&mut self, n: usize) {
self._set_index(self.index() - n);
}
/// Set the index and return the value.
fn set_index(&mut self, new_index: usize) -> usize {
self._set_index(new_index);
self.index()
}
/// Move `n` items ahead.
/// Panics moving beyond `usize::MAX`.
fn ahead(&mut self, n: usize) -> usize {
self._ahead(n);
self.index()
}
/// Move `n` items back.
/// Panics moving below 0.
#[allow(dead_code)]
fn back(&mut self, n: usize) -> usize {
self._back(n);
self.index()
}
/// Read a single byte. Updates `index`.
fn read_byte(&mut self) -> u8 {
match self.data().get(self.index()) {
Some(&b) => {
self._ahead(1);
b
}
None => panic!("Out of bounds access to data."),
}
}
/// Read out a single item of type `T`. Updates `index`.
/// Example:
/// ```rust
/// let elf = ElfReader::new(data:vec![1,1,0,0]);
/// let result = elf.read_t(1);
/// assert_eq!(result.len(),4)
/// assert_eq!(elf.index(),4)
/// ```
fn read_t<T, const N: usize>(&mut self) -> T
where
T: FromBytes<Bytes = [u8; N]> + Display,
{
self.slice(size_of::<T>())
.try_into()
.map(T::from_le_bytes)
.unwrap()
}
/// Slice `n` items. Updates index.
/// Mostly an implementation detail.
/// Example:
/// ```rust
/// let elf = ElfReader::new(data:vec![1,1,0,0]);
/// let result = elf.slice(4);
/// assert_eq!(result.len(),4);
/// assert_eq!(elf.index(),4)
/// ```
fn slice(&mut self, n: usize) -> &[u8] {
if n == 0 {
panic!("n must be larger than 0.")
}
let old_index = self.index();
let new_index = self.ahead(n);
// -1 since `end_range` is non inclusive
// No need to check `old_size`.
self.assert_within_bounds(new_index - 1);
&self.data()[old_index..new_index]
}
/// Read `n` ascii chars into a utf-8 String.
/// Updates the index.
fn read_ascii(&mut self, n: usize) -> Result<String, FromUtf8Error> {
String::from_utf8(self.slice(n).to_vec())
}
/// Read bytes into a `Vec<T>`.
/// `n` is the number of items of type `T` to read.
/// And the length of the vector.
/// Example:
/// ```rust
/// let data = vec![0,0,0,0, 1,0,0,0, 2,0,0,0];
/// let elf = ElfReader::new(data);
/// let result:Vec<u32> = elf.read_vec(3);
/// assert_eq!(result, vec![0,1,2]);
/// assert_eq!(elf.index(), 12);
/// ```
#[allow(dead_code)]
fn read_vec<T, const N: usize>(&mut self, n: usize) -> Vec<T>
where
T: FromBytes<Bytes = [u8; N]> + Display,
{
if n == 0 {
panic!("n must be larger than 0.")
}
let (items, _) = self.slice(size_of::<T>() * n).as_chunks();
items.iter().take(n).map(T::from_le_bytes).collect()
}
/// Ensure `new_index` is within the data span.
fn assert_within_bounds(&self, new_index: usize) {
let l = self.len();
if new_index >= l {
panic!("Index {new_index} >= length {l}.",)
}
}
}
I'm happy to take any advice from anyone, on incremental improvements (just not something completely advanced since that is not something I will understand.)
Some errors I am aware of are that I do a + b which can fail on a usize if we are above MAX or below MIN, maybe I should use saturation or something. Also, I am using the code in a binary parser as a first test (before writing unit tests, oops) and it panics eventually, so I got some bugs. Fixed.