1#![no_std]
2#![forbid(unsafe_code)]
3
4macro_rules! bounded_reader {
33 ($name:ident, $try_name:ident, $ty:ty, $width:literal, $from_bytes:ident) => {
34 #[doc = concat!("Read a `", stringify!($ty), "` at `off`; `None` if out of range. Never panics. Use when `0` must be distinguished from absent/truncated.")]
35 #[must_use]
36 pub fn $try_name(data: &[u8], off: usize) -> Option<$ty> {
37 let end = off.checked_add($width)?;
38 let slice = data.get(off..end)?;
39 let mut buf = [0u8; $width];
40 buf.copy_from_slice(slice);
41 Some(<$ty>::$from_bytes(buf))
42 }
43
44 #[doc = concat!("Read a `", stringify!($ty), "` at `off`; `0` if out of range. Never panics.")]
45 #[must_use]
46 pub fn $name(data: &[u8], off: usize) -> $ty {
47 $try_name(data, off).unwrap_or(0)
48 }
49 };
50}
51
52bounded_reader!(be_u16, try_be_u16, u16, 2, from_be_bytes);
53bounded_reader!(be_u32, try_be_u32, u32, 4, from_be_bytes);
54bounded_reader!(be_u64, try_be_u64, u64, 8, from_be_bytes);
55bounded_reader!(le_u16, try_le_u16, u16, 2, from_le_bytes);
56bounded_reader!(le_u32, try_le_u32, u32, 4, from_le_bytes);
57bounded_reader!(le_u64, try_le_u64, u64, 8, from_le_bytes);
58
59#[must_use]
61pub fn try_u8(data: &[u8], off: usize) -> Option<u8> {
62 data.get(off).copied()
63}
64
65#[must_use]
68pub fn u8(data: &[u8], off: usize) -> u8 {
69 try_u8(data, off).unwrap_or(0)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn big_endian_reads_in_range() {
78 assert_eq!(be_u16(&[0x12, 0x34], 0), 0x1234);
79 assert_eq!(be_u32(&[0, 0, 1, 0], 0), 256);
80 assert_eq!(
81 be_u64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0),
82 0x0102_0304_0506_0708
83 );
84 }
85
86 #[test]
87 fn little_endian_reads_in_range() {
88 assert_eq!(le_u16(&[0x34, 0x12], 0), 0x1234);
89 assert_eq!(le_u32(&[0, 1, 0, 0], 0), 256);
90 assert_eq!(
91 le_u64(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01], 0),
92 0x0102_0304_0506_0708
93 );
94 }
95
96 #[test]
97 fn reads_honor_offset() {
98 assert_eq!(be_u16(&[0xaa, 0x12, 0x34], 1), 0x1234);
99 assert_eq!(le_u32(&[0xff, 0xff, 0, 1, 0, 0], 2), 256);
100 }
101
102 #[test]
103 fn single_byte_reads() {
104 assert_eq!(u8(&[0xab, 0xcd], 0), 0xab);
105 assert_eq!(u8(&[0xab, 0xcd], 1), 0xcd);
106 assert_eq!(u8(&[0xab], 5), 0); assert_eq!(u8(&[], 0), 0);
108 assert_eq!(try_u8(&[0xab], 0), Some(0xab));
109 assert_eq!(try_u8(&[0xab], 1), None);
110 }
111
112 #[test]
113 fn try_variants_distinguish_zero_from_absent() {
114 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!(
118 try_be_u64(&[1, 2, 3, 4, 5, 6, 7, 8], 0),
119 Some(0x0102_0304_0506_0708)
120 );
121 assert_eq!(try_le_u16(&[], 0), None);
122 }
123
124 #[test]
125 fn out_of_range_returns_zero_never_panics() {
126 assert_eq!(be_u32(&[1, 2, 3], 0), 0);
127 assert_eq!(be_u64(&[1, 2, 3, 4, 5, 6, 7], 0), 0);
128 assert_eq!(be_u32(&[1, 2, 3, 4], 2), 0);
129 assert_eq!(le_u16(&[1, 2], 2), 0);
130 assert_eq!(be_u16(&[], 0), 0);
131 assert_eq!(le_u32(&[1, 2, 3, 4], 100), 0);
132 }
133
134 #[test]
135 fn offset_overflow_returns_zero() {
136 assert_eq!(be_u32(&[1, 2, 3, 4], usize::MAX), 0);
137 assert_eq!(try_be_u32(&[1, 2, 3, 4], usize::MAX), None);
138 }
139}