pub(crate) struct Reader<'a> {
data: &'a [u8],
consumed: usize,
}
impl<'a> Reader<'a> {
pub(crate) const fn new(data: &'a [u8]) -> Self {
Self { data, consumed: 0 }
}
#[cfg(test)]
pub(crate) const fn consumed(&self) -> usize {
self.consumed
}
pub(crate) const fn remaining(&self) -> usize {
self.data.len()
}
pub(crate) const fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub(crate) fn take(&mut self, len: usize) -> Option<&'a [u8]> {
let head = self.data.get(..len)?;
self.data = self.data.get(len..)?;
self.consumed += len;
Some(head)
}
pub(crate) fn u8(&mut self) -> Option<u8> {
Some(*self.take(1)?.first()?)
}
pub(crate) fn u32(&mut self) -> Option<u32> {
let bytes = self.take(4)?;
Some(u32::from_be_bytes(bytes.try_into().ok()?))
}
pub(crate) fn entry_id(&mut self) -> Option<super::entry::EntryId> {
let bytes = self.take(super::entry::EntryId::LEN)?;
Some(super::entry::EntryId::from_bytes(bytes.try_into().ok()?))
}
pub(crate) fn nonce(&mut self) -> Option<super::crypto::Nonce> {
let bytes = self.take(super::crypto::NONCE_LEN)?;
bytes.try_into().ok()
}
}
pub(crate) fn put_u32(out: &mut Vec<u8>, value: u32) {
out.extend_from_slice(&value.to_be_bytes());
}
pub(crate) fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
#[expect(
clippy::cast_possible_truncation,
reason = "the caller guarantees the length fits in u32; an assertion below backs it up"
)]
let len = bytes.len() as u32;
assert!(
bytes.len() <= u32::MAX as usize,
"field length exceeds the u32 range"
);
put_u32(out, len);
out.extend_from_slice(bytes);
}
#[cfg(test)]
mod tests {
use super::*;
fn put_u64(out: &mut Vec<u8>, value: u64) {
out.extend_from_slice(&value.to_be_bytes());
}
#[test]
fn reader_reads_sequential_values() {
let mut buf = Vec::new();
put_u32(&mut buf, 0xDEAD_BEEF);
put_u64(&mut buf, 0x0123_4567_89AB_CDEF);
buf.push(0x7F);
put_bytes(&mut buf, b"hi");
let mut r = Reader::new(&buf);
assert_eq!(r.u32(), Some(0xDEAD_BEEF));
assert_eq!(
r.take(8),
Some(&0x0123_4567_89AB_CDEF_u64.to_be_bytes()[..])
);
assert_eq!(r.u8(), Some(0x7F));
assert_eq!(r.u32(), Some(2));
assert_eq!(r.take(2), Some(&b"hi"[..]));
assert!(r.is_empty());
assert_eq!(r.consumed(), buf.len());
}
#[test]
fn reader_never_panics_on_short_input() {
for len in 0..40_usize {
let data = vec![0xAB_u8; len];
let mut r = Reader::new(&data);
let _ = r.u32();
let _ = r.take(8);
let _ = r.entry_id();
let _ = r.nonce();
let _ = r.take(usize::MAX);
let _ = r.take(1000);
let _ = r.u8();
}
}
#[test]
fn huge_length_prefix_does_not_allocate() {
let mut buf = Vec::new();
put_u32(&mut buf, u32::MAX);
buf.extend_from_slice(b"short");
let mut r = Reader::new(&buf);
let claimed = r.u32().expect("length prefix readable");
assert_eq!(claimed, u32::MAX);
assert_eq!(r.take(claimed as usize), None);
}
#[test]
fn put_bytes_roundtrips_via_reader() {
for payload in [&b""[..], b"a", b"hello world"] {
let mut buf = Vec::new();
put_bytes(&mut buf, payload);
let mut r = Reader::new(&buf);
let len = r.u32().expect("length readable");
assert_eq!(len as usize, payload.len());
assert_eq!(r.take(len as usize), Some(payload));
assert!(r.is_empty());
}
}
#[test]
fn entry_id_and_nonce_roundtrip() {
let id = super::super::entry::EntryId::from_bytes([0x5A; 16]);
let nonce = [0x3C; super::super::crypto::NONCE_LEN];
let mut buf = Vec::new();
buf.extend_from_slice(id.as_bytes());
buf.extend_from_slice(&nonce);
let mut r = Reader::new(&buf);
assert_eq!(r.entry_id(), Some(id));
assert_eq!(r.nonce(), Some(nonce));
assert!(r.is_empty());
}
}