#[must_use]
pub fn read_u8(buf: &[u8], offset: usize) -> Option<u8> {
buf.get(offset).copied()
}
#[must_use]
pub fn read_u16_be(buf: &[u8], offset: usize) -> Option<u16> {
let chunk = buf.get(offset..)?.first_chunk::<2>()?;
Some(u16::from_be_bytes(*chunk))
}
#[must_use]
pub fn read_u24_be(buf: &[u8], offset: usize) -> Option<u32> {
let &[a, b, c] = buf.get(offset..)?.first_chunk::<3>()?;
Some(u32::from_be_bytes([0, a, b, c]))
}
#[must_use]
pub fn read_u32_be(buf: &[u8], offset: usize) -> Option<u32> {
let chunk = buf.get(offset..)?.first_chunk::<4>()?;
Some(u32::from_be_bytes(*chunk))
}
#[must_use]
pub fn read_u64_be(buf: &[u8], offset: usize) -> Option<u64> {
let chunk = buf.get(offset..)?.first_chunk::<8>()?;
Some(u64::from_be_bytes(*chunk))
}
#[must_use]
pub fn read_uint_be(buf: &[u8], offset: usize, n: usize) -> Option<u64> {
if n == 0 || n > 8 {
return None;
}
let end = offset.checked_add(n)?;
let slice = buf.get(offset..end)?;
Some(slice.iter().fold(0_u64, |acc, &b| acc.wrapping_shl(8).wrapping_add(u64::from(b))))
}
#[must_use]
pub fn read_ascii(buf: &[u8], offset: usize, count: usize) -> Option<String> {
let end = offset.checked_add(count)?;
let slice = buf.get(offset..end)?;
Some(slice.iter().map(|&b| if b.is_ascii() { char::from(b) } else { '?' }).collect())
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, prop_assert, prop_assert_eq, proptest};
use super::{
read_ascii, read_u8, read_u16_be, read_u24_be, read_u32_be, read_u64_be, read_uint_be,
};
#[test]
fn read_u8_reads_single_byte() {
let buf = [0x12_u8, 0x34];
assert_eq!(read_u8(&buf, 0), Some(0x12));
assert_eq!(read_u8(&buf, 1), Some(0x34));
}
#[test]
fn read_u8_out_of_bounds_is_none() {
let buf = [0x12_u8];
assert_eq!(read_u8(&buf, 1), None);
assert_eq!(read_u8(&buf, usize::MAX), None);
}
#[test]
fn read_u16_be_reads_big_endian() {
let buf = [0x12_u8, 0x34, 0x56];
assert_eq!(read_u16_be(&buf, 0), Some(0x1234));
assert_eq!(read_u16_be(&buf, 1), Some(0x3456));
}
#[test]
fn read_u16_be_out_of_bounds_is_none() {
let buf = [0x12_u8];
assert_eq!(read_u16_be(&buf, 0), None);
assert_eq!(read_u16_be(&buf, 5), None);
assert_eq!(read_u16_be(&buf, usize::MAX), None);
}
#[test]
fn read_u24_be_reads_big_endian() {
let buf = [0x12_u8, 0x34, 0x56, 0x78];
assert_eq!(read_u24_be(&buf, 0), Some(0x0012_3456));
assert_eq!(read_u24_be(&buf, 1), Some(0x0034_5678));
}
#[test]
fn read_u24_be_out_of_bounds_is_none() {
let buf = [0x12_u8, 0x34];
assert_eq!(read_u24_be(&buf, 0), None);
assert_eq!(read_u24_be(&buf, usize::MAX), None);
}
#[test]
fn read_u32_be_reads_big_endian() {
let buf = [0xDE_u8, 0xAD, 0xBE, 0xEF, 0x00];
assert_eq!(read_u32_be(&buf, 0), Some(0xDEAD_BEEF));
assert_eq!(read_u32_be(&buf, 1), Some(0xADBE_EF00));
}
#[test]
fn read_u32_be_out_of_bounds_is_none() {
let buf = [0x00_u8, 0x11, 0x22];
assert_eq!(read_u32_be(&buf, 0), None);
assert_eq!(read_u32_be(&buf, usize::MAX), None);
}
#[test]
fn read_u64_be_reads_big_endian() {
let buf = [0x01_u8, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x00];
assert_eq!(read_u64_be(&buf, 0), Some(0x0123_4567_89AB_CDEF));
assert_eq!(read_u64_be(&buf, 1), Some(0x2345_6789_ABCD_EF00));
}
#[test]
fn read_u64_be_out_of_bounds_is_none() {
let buf = [0x00_u8; 7];
assert_eq!(read_u64_be(&buf, 0), None);
assert_eq!(read_u64_be(&buf, usize::MAX), None);
}
#[test]
fn read_uint_be_reads_each_width() {
let buf = [0x11_u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
assert_eq!(read_uint_be(&buf, 0, 1), Some(0x11));
assert_eq!(read_uint_be(&buf, 0, 2), Some(0x1122));
assert_eq!(read_uint_be(&buf, 0, 3), Some(0x0011_2233));
assert_eq!(read_uint_be(&buf, 0, 4), Some(0x1122_3344));
assert_eq!(read_uint_be(&buf, 0, 8), Some(0x1122_3344_5566_7788));
}
#[test]
fn read_uint_be_rejects_zero_and_overwide_widths() {
let buf = [0x11_u8; 16];
assert_eq!(read_uint_be(&buf, 0, 0), None);
assert_eq!(read_uint_be(&buf, 0, 9), None);
}
#[test]
fn read_uint_be_out_of_bounds_is_none() {
let buf = [0x11_u8, 0x22];
assert_eq!(read_uint_be(&buf, 0, 3), None);
assert_eq!(read_uint_be(&buf, usize::MAX, 1), None);
}
#[test]
fn read_ascii_reads_index_magic() {
let buf = *b"INDX0300\x00\x00";
assert_eq!(read_ascii(&buf, 0, 8).as_deref(), Some("INDX0300"));
}
#[test]
fn read_ascii_reads_offset_magic() {
let buf = *b"....MPLS0100";
assert_eq!(read_ascii(&buf, 4, 8).as_deref(), Some("MPLS0100"));
}
#[test]
fn read_ascii_replaces_non_ascii_with_question_mark() {
let buf = [b'A', 0x80, 0xFF, b'Z'];
assert_eq!(read_ascii(&buf, 0, 4).as_deref(), Some("A??Z"));
}
#[test]
fn read_ascii_zero_count_is_empty() {
let buf = *b"AB";
assert_eq!(read_ascii(&buf, 0, 0).as_deref(), Some(""));
}
#[test]
fn read_ascii_out_of_bounds_is_none() {
let buf = *b"AB";
assert_eq!(read_ascii(&buf, 0, 3), None);
assert_eq!(read_ascii(&buf, usize::MAX, 1), None);
}
proptest! {
#[test]
fn read_u8_never_panics(buf in any::<Vec<u8>>(), offset in any::<usize>()) {
let got = read_u8(&buf, offset);
prop_assert_eq!(got.is_some(), offset < buf.len());
}
#[test]
fn read_u16_be_never_panics(buf in any::<Vec<u8>>(), offset in any::<usize>()) {
let got = read_u16_be(&buf, offset);
prop_assert_eq!(got.is_some(), offset.checked_add(2).is_some_and(|e| e <= buf.len()));
}
#[test]
fn read_u16_be_matches_from_be_bytes(prefix in any::<Vec<u8>>(), a: u8, b: u8) {
let offset = prefix.len();
let mut buf = prefix;
buf.push(a);
buf.push(b);
prop_assert_eq!(read_u16_be(&buf, offset), Some(u16::from_be_bytes([a, b])));
}
#[test]
fn read_u24_be_never_panics(buf in any::<Vec<u8>>(), offset in any::<usize>()) {
let got = read_u24_be(&buf, offset);
prop_assert_eq!(got.is_some(), offset.checked_add(3).is_some_and(|e| e <= buf.len()));
}
#[test]
fn read_u24_be_matches_from_be_bytes(prefix in any::<Vec<u8>>(), a: u8, b: u8, c: u8) {
let offset = prefix.len();
let mut buf = prefix;
buf.extend_from_slice(&[a, b, c]);
prop_assert_eq!(read_u24_be(&buf, offset), Some(u32::from_be_bytes([0, a, b, c])));
}
#[test]
fn read_u32_be_never_panics(buf in any::<Vec<u8>>(), offset in any::<usize>()) {
let got = read_u32_be(&buf, offset);
prop_assert_eq!(got.is_some(), offset.checked_add(4).is_some_and(|e| e <= buf.len()));
}
#[test]
fn read_u64_be_never_panics(buf in any::<Vec<u8>>(), offset in any::<usize>()) {
let got = read_u64_be(&buf, offset);
prop_assert_eq!(got.is_some(), offset.checked_add(8).is_some_and(|e| e <= buf.len()));
}
#[test]
fn read_u64_be_matches_from_be_bytes(
prefix in any::<Vec<u8>>(),
chunk in any::<[u8; 8]>(),
) {
let offset = prefix.len();
let mut buf = prefix;
buf.extend_from_slice(&chunk);
prop_assert_eq!(read_u64_be(&buf, offset), Some(u64::from_be_bytes(chunk)));
}
#[test]
fn read_uint_be_never_panics(
buf in any::<Vec<u8>>(),
offset in any::<usize>(),
n in 0_usize..12,
) {
let got = read_uint_be(&buf, offset, n);
let valid = (1..=8).contains(&n)
&& offset.checked_add(n).is_some_and(|e| e <= buf.len());
prop_assert_eq!(got.is_some(), valid);
}
#[test]
fn read_uint_be_matches_fixed_width_readers(
buf in any::<Vec<u8>>(),
offset in any::<usize>(),
) {
prop_assert_eq!(read_uint_be(&buf, offset, 1), read_u8(&buf, offset).map(u64::from));
prop_assert_eq!(read_uint_be(&buf, offset, 2), read_u16_be(&buf, offset).map(u64::from));
prop_assert_eq!(read_uint_be(&buf, offset, 3), read_u24_be(&buf, offset).map(u64::from));
prop_assert_eq!(read_uint_be(&buf, offset, 4), read_u32_be(&buf, offset).map(u64::from));
prop_assert_eq!(read_uint_be(&buf, offset, 8), read_u64_be(&buf, offset));
}
#[test]
fn read_ascii_never_panics(
buf in any::<Vec<u8>>(),
offset in any::<usize>(),
count in 0_usize..64,
) {
let got = read_ascii(&buf, offset, count);
let valid = offset.checked_add(count).is_some_and(|e| e <= buf.len());
prop_assert_eq!(got.is_some(), valid);
if let Some(s) = got {
prop_assert_eq!(s.chars().count(), count);
}
}
#[test]
fn read_ascii_roundtrips_ascii(s in "[ -~]{0,32}") {
let bytes = s.as_bytes();
let got = read_ascii(bytes, 0, bytes.len());
prop_assert_eq!(got.as_deref(), Some(s.as_str()));
}
#[test]
fn read_ascii_high_bytes_become_question_mark(byte in 0x80_u8..=0xFF) {
let buf = [byte];
let s = read_ascii(&buf, 0, 1);
prop_assert_eq!(s.as_deref(), Some("?"));
prop_assert!(s.is_some());
}
}
}