use std::ops::Range;
#[inline]
#[must_use]
pub const fn align8(n: u64) -> u64 {
(n + 7) & !7
}
#[inline]
#[must_use]
pub const fn padding_to_align8(len: usize) -> usize {
(8 - (len % 8)) % 8
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpanError {
AddOverflow,
OutOfBounds {
end: u64,
},
}
pub fn checked_u64_byte_span(offset: u64, len: u64, container_len: u64) -> Result<(), SpanError> {
let end = offset.checked_add(len).ok_or(SpanError::AddOverflow)?;
if end > container_len {
return Err(SpanError::OutOfBounds { end });
}
Ok(())
}
pub fn checked_subslice(
offset: usize,
len: usize,
container_len: usize,
) -> Result<Range<usize>, SpanError> {
let end = offset.checked_add(len).ok_or(SpanError::AddOverflow)?;
if end > container_len {
return Err(SpanError::OutOfBounds { end: end as u64 });
}
Ok(offset..end)
}
#[derive(Debug)]
pub struct LeWriter<'a> {
buf: &'a mut [u8],
pos: usize,
}
impl<'a> LeWriter<'a> {
#[inline]
pub fn new(buf: &'a mut [u8]) -> Self {
Self { buf, pos: 0 }
}
#[inline]
#[must_use]
pub fn position(&self) -> usize {
self.pos
}
#[inline]
pub fn put_bytes(&mut self, bytes: &[u8]) {
self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
self.pos += bytes.len();
}
#[inline]
pub fn put_u16(&mut self, v: u16) {
self.put_bytes(&v.to_le_bytes());
}
#[inline]
pub fn put_u32(&mut self, v: u32) {
self.put_bytes(&v.to_le_bytes());
}
#[inline]
pub fn put_u64(&mut self, v: u64) {
self.put_bytes(&v.to_le_bytes());
}
#[inline]
pub fn put_zeros(&mut self, count: usize) {
self.buf[self.pos..self.pos + count].fill(0);
self.pos += count;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("{structure}: need {need} bytes, got {got}")]
pub struct BufferTooShort {
pub structure: &'static str,
pub need: usize,
pub got: usize,
}
#[derive(Debug)]
pub struct LeReader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> LeReader<'a> {
#[inline]
#[must_use]
pub fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
#[inline]
pub fn require(
buf: &'a [u8],
structure: &'static str,
need: usize,
) -> Result<Self, BufferTooShort> {
if buf.len() < need {
return Err(BufferTooShort {
structure,
need,
got: buf.len(),
});
}
Ok(Self::new(buf))
}
#[inline]
#[must_use]
pub fn position(&self) -> usize {
self.pos
}
#[inline]
#[must_use]
pub fn remaining(&self) -> &'a [u8] {
&self.buf[self.pos..]
}
#[inline]
pub fn take_4(&mut self) -> [u8; 4] {
let mut out = [0u8; 4];
out.copy_from_slice(&self.buf[self.pos..self.pos + 4]);
self.pos += 4;
out
}
#[inline]
pub fn take_16(&mut self) -> [u8; 16] {
let mut out = [0u8; 16];
out.copy_from_slice(&self.buf[self.pos..self.pos + 16]);
self.pos += 16;
out
}
#[inline]
pub fn take_u16(&mut self) -> u16 {
let mut b = [0u8; 2];
b.copy_from_slice(&self.buf[self.pos..self.pos + 2]);
self.pos += 2;
u16::from_le_bytes(b)
}
#[inline]
pub fn take_u32(&mut self) -> u32 {
let mut b = [0u8; 4];
b.copy_from_slice(&self.buf[self.pos..self.pos + 4]);
self.pos += 4;
u32::from_le_bytes(b)
}
#[inline]
pub fn take_u64(&mut self) -> u64 {
let mut b = [0u8; 8];
b.copy_from_slice(&self.buf[self.pos..self.pos + 8]);
self.pos += 8;
u64::from_le_bytes(b)
}
#[inline]
pub fn skip(&mut self, count: usize) {
self.pos += count;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn align8_rounds_up() {
assert_eq!(align8(0), 0);
assert_eq!(align8(1), 8);
assert_eq!(align8(8), 8);
assert_eq!(align8(9), 16);
assert_eq!(align8(63), 64);
assert_eq!(padding_to_align8(0), 0);
assert_eq!(padding_to_align8(1), 7);
assert_eq!(padding_to_align8(8), 0);
}
#[test]
fn writer_reader_round_trip() {
let mut buf = [0u8; 26];
let mut w = LeWriter::new(&mut buf);
w.put_bytes(b"TESS");
w.put_u16(0xBEEF);
w.put_u32(0xDEAD_BEEF);
w.put_u64(0x0102_0304_0506_0708);
w.put_zeros(8);
assert_eq!(w.position(), 26);
let mut r = LeReader::new(&buf);
assert_eq!(&r.take_4(), b"TESS");
assert_eq!(r.take_u16(), 0xBEEF);
assert_eq!(r.take_u32(), 0xDEAD_BEEF);
assert_eq!(r.take_u64(), 0x0102_0304_0506_0708);
r.skip(8);
assert_eq!(r.position(), 26);
}
#[test]
fn require_rejects_short_buffer() {
let buf = [0u8; 4];
let result = LeReader::require(&buf, "thing", 8);
assert_eq!(
result.unwrap_err(),
BufferTooShort {
structure: "thing",
need: 8,
got: 4,
}
);
}
#[test]
fn span_checks() {
assert!(checked_u64_byte_span(10, 5, 15).is_ok());
assert!(matches!(
checked_u64_byte_span(10, 6, 15),
Err(SpanError::OutOfBounds { end: 16 })
));
assert!(matches!(
checked_u64_byte_span(u64::MAX, 1, u64::MAX),
Err(SpanError::AddOverflow)
));
assert_eq!(checked_subslice(2, 3, 10).unwrap(), 2..5);
assert!(checked_subslice(8, 3, 10).is_err());
}
}