#![no_std]
#![forbid(unsafe_code)]
macro_rules! bounded_reader {
($name:ident, $try_name:ident, $ty:ty, $width:literal, $from_bytes:ident) => {
#[doc = concat!("Read a `", stringify!($ty), "` at `off`; `None` if out of range. Never panics. Use when `0` must be distinguished from absent/truncated.")]
#[must_use]
pub fn $try_name(data: &[u8], off: usize) -> Option<$ty> {
let end = off.checked_add($width)?;
let slice = data.get(off..end)?;
let mut buf = [0u8; $width];
buf.copy_from_slice(slice);
Some(<$ty>::$from_bytes(buf))
}
#[doc = concat!("Read a `", stringify!($ty), "` at `off`; `0` if out of range. Never panics.")]
#[must_use]
pub fn $name(data: &[u8], off: usize) -> $ty {
$try_name(data, off).unwrap_or(0)
}
};
}
bounded_reader!(be_u16, try_be_u16, u16, 2, from_be_bytes);
bounded_reader!(be_u32, try_be_u32, u32, 4, from_be_bytes);
bounded_reader!(be_u64, try_be_u64, u64, 8, from_be_bytes);
bounded_reader!(le_u16, try_le_u16, u16, 2, from_le_bytes);
bounded_reader!(le_u32, try_le_u32, u32, 4, from_le_bytes);
bounded_reader!(le_u64, try_le_u64, u64, 8, from_le_bytes);
#[must_use]
pub fn try_u8(data: &[u8], off: usize) -> Option<u8> {
data.get(off).copied()
}
#[must_use]
pub fn u8(data: &[u8], off: usize) -> u8 {
try_u8(data, off).unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn big_endian_reads_in_range() {
assert_eq!(be_u16(&[0x12, 0x34], 0), 0x1234);
assert_eq!(be_u32(&[0, 0, 1, 0], 0), 256);
assert_eq!(
be_u64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0),
0x0102_0304_0506_0708
);
}
#[test]
fn little_endian_reads_in_range() {
assert_eq!(le_u16(&[0x34, 0x12], 0), 0x1234);
assert_eq!(le_u32(&[0, 1, 0, 0], 0), 256);
assert_eq!(
le_u64(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01], 0),
0x0102_0304_0506_0708
);
}
#[test]
fn reads_honor_offset() {
assert_eq!(be_u16(&[0xaa, 0x12, 0x34], 1), 0x1234);
assert_eq!(le_u32(&[0xff, 0xff, 0, 1, 0, 0], 2), 256);
}
#[test]
fn single_byte_reads() {
assert_eq!(u8(&[0xab, 0xcd], 0), 0xab);
assert_eq!(u8(&[0xab, 0xcd], 1), 0xcd);
assert_eq!(u8(&[0xab], 5), 0); assert_eq!(u8(&[], 0), 0);
assert_eq!(try_u8(&[0xab], 0), Some(0xab));
assert_eq!(try_u8(&[0xab], 1), None);
}
#[test]
fn try_variants_distinguish_zero_from_absent() {
assert_eq!(try_le_u32(&[0, 0, 0, 0], 0), Some(0)); assert_eq!(try_le_u32(&[0, 0, 0], 0), None); assert_eq!(try_be_u16(&[1, 2], 2), None); assert_eq!(
try_be_u64(&[1, 2, 3, 4, 5, 6, 7, 8], 0),
Some(0x0102_0304_0506_0708)
);
assert_eq!(try_le_u16(&[], 0), None);
}
#[test]
fn out_of_range_returns_zero_never_panics() {
assert_eq!(be_u32(&[1, 2, 3], 0), 0);
assert_eq!(be_u64(&[1, 2, 3, 4, 5, 6, 7], 0), 0);
assert_eq!(be_u32(&[1, 2, 3, 4], 2), 0);
assert_eq!(le_u16(&[1, 2], 2), 0);
assert_eq!(be_u16(&[], 0), 0);
assert_eq!(le_u32(&[1, 2, 3, 4], 100), 0);
}
#[test]
fn offset_overflow_returns_zero() {
assert_eq!(be_u32(&[1, 2, 3, 4], usize::MAX), 0);
assert_eq!(try_be_u32(&[1, 2, 3, 4], usize::MAX), None);
}
}