1#![no_std]
2#![forbid(unsafe_code)]
3
4macro_rules! bounded_reader {
40 ($name:ident, $try_name:ident, $ty:ty, $width:literal, $from_bytes:ident) => {
41 #[doc = concat!("Read a `", stringify!($ty), "` at `off`; `None` if out of range. Never panics. Use when `0` must be distinguished from absent/truncated.")]
42 #[must_use]
43 pub fn $try_name(data: &[u8], off: usize) -> Option<$ty> {
44 let end = off.checked_add($width)?;
45 let slice = data.get(off..end)?;
46 let mut buf = [0u8; $width];
47 buf.copy_from_slice(slice);
48 Some(<$ty>::$from_bytes(buf))
49 }
50
51 #[doc = concat!("Read a `", stringify!($ty), "` at `off`; `0` if out of range. Never panics.")]
52 #[must_use]
53 pub fn $name(data: &[u8], off: usize) -> $ty {
54 $try_name(data, off).unwrap_or(0)
55 }
56 };
57}
58
59bounded_reader!(be_u16, try_be_u16, u16, 2, from_be_bytes);
60bounded_reader!(be_u32, try_be_u32, u32, 4, from_be_bytes);
61bounded_reader!(be_u64, try_be_u64, u64, 8, from_be_bytes);
62bounded_reader!(le_u16, try_le_u16, u16, 2, from_le_bytes);
63bounded_reader!(le_u32, try_le_u32, u32, 4, from_le_bytes);
64bounded_reader!(le_u64, try_le_u64, u64, 8, from_le_bytes);
65
66bounded_reader!(be_i16, try_be_i16, i16, 2, from_be_bytes);
70bounded_reader!(be_i32, try_be_i32, i32, 4, from_be_bytes);
71bounded_reader!(be_i64, try_be_i64, i64, 8, from_be_bytes);
72bounded_reader!(le_i16, try_le_i16, i16, 2, from_le_bytes);
73bounded_reader!(le_i32, try_le_i32, i32, 4, from_le_bytes);
74bounded_reader!(le_i64, try_le_i64, i64, 8, from_le_bytes);
75
76#[must_use]
89pub fn try_bytes<const N: usize>(data: &[u8], off: usize) -> Option<[u8; N]> {
90 let end = off.checked_add(N)?;
91 let slice = data.get(off..end)?;
92 let mut buf = [0u8; N];
93 buf.copy_from_slice(slice);
94 Some(buf)
95}
96
97#[must_use]
99pub fn try_u8(data: &[u8], off: usize) -> Option<u8> {
100 data.get(off).copied()
101}
102
103#[must_use]
106pub fn u8(data: &[u8], off: usize) -> u8 {
107 try_u8(data, off).unwrap_or(0)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn big_endian_reads_in_range() {
116 assert_eq!(be_u16(&[0x12, 0x34], 0), 0x1234);
117 assert_eq!(be_u32(&[0, 0, 1, 0], 0), 256);
118 assert_eq!(
119 be_u64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0),
120 0x0102_0304_0506_0708
121 );
122 }
123
124 #[test]
125 fn little_endian_reads_in_range() {
126 assert_eq!(le_u16(&[0x34, 0x12], 0), 0x1234);
127 assert_eq!(le_u32(&[0, 1, 0, 0], 0), 256);
128 assert_eq!(
129 le_u64(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01], 0),
130 0x0102_0304_0506_0708
131 );
132 }
133
134 #[test]
135 fn reads_honor_offset() {
136 assert_eq!(be_u16(&[0xaa, 0x12, 0x34], 1), 0x1234);
137 assert_eq!(le_u32(&[0xff, 0xff, 0, 1, 0, 0], 2), 256);
138 }
139
140 #[test]
141 fn single_byte_reads() {
142 assert_eq!(u8(&[0xab, 0xcd], 0), 0xab);
143 assert_eq!(u8(&[0xab, 0xcd], 1), 0xcd);
144 assert_eq!(u8(&[0xab], 5), 0); assert_eq!(u8(&[], 0), 0);
146 assert_eq!(try_u8(&[0xab], 0), Some(0xab));
147 assert_eq!(try_u8(&[0xab], 1), None);
148 }
149
150 #[test]
151 fn try_variants_distinguish_zero_from_absent() {
152 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!(
156 try_be_u64(&[1, 2, 3, 4, 5, 6, 7, 8], 0),
157 Some(0x0102_0304_0506_0708)
158 );
159 assert_eq!(try_le_u16(&[], 0), None);
160 }
161
162 #[test]
163 fn out_of_range_returns_zero_never_panics() {
164 assert_eq!(be_u32(&[1, 2, 3], 0), 0);
165 assert_eq!(be_u64(&[1, 2, 3, 4, 5, 6, 7], 0), 0);
166 assert_eq!(be_u32(&[1, 2, 3, 4], 2), 0);
167 assert_eq!(le_u16(&[1, 2], 2), 0);
168 assert_eq!(be_u16(&[], 0), 0);
169 assert_eq!(le_u32(&[1, 2, 3, 4], 100), 0);
170 }
171
172 #[test]
173 fn offset_overflow_returns_zero() {
174 assert_eq!(be_u32(&[1, 2, 3, 4], usize::MAX), 0);
175 assert_eq!(try_be_u32(&[1, 2, 3, 4], usize::MAX), None);
176 }
177
178 #[test]
179 fn signed_reads_in_range() {
180 assert_eq!(le_i16(&[0x34, 0x12], 0), 0x1234);
181 assert_eq!(be_i16(&[0x12, 0x34], 0), 0x1234);
182 assert_eq!(le_i32(&[0, 1, 0, 0], 0), 256);
183 assert_eq!(be_i32(&[0, 0, 1, 0], 0), 256);
184 assert_eq!(
185 le_i64(&[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01], 0),
186 0x0102_0304_0506_0708
187 );
188 assert_eq!(
189 be_i64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0),
190 0x0102_0304_0506_0708
191 );
192 }
193
194 #[test]
195 fn signed_reads_honor_offset() {
196 assert_eq!(be_i16(&[0xaa, 0x12, 0x34], 1), 0x1234);
197 assert_eq!(le_i32(&[0xff, 0xff, 0, 1, 0, 0], 2), 256);
198 }
199
200 #[test]
203 fn signed_reads_round_trip_negative_values() {
204 assert_eq!(le_i16(&[0xff, 0xff], 0), -1);
205 assert_eq!(be_i16(&[0xff, 0xff], 0), -1);
206 assert_eq!(le_i32(&[0xff, 0xff, 0xff, 0xff], 0), -1);
207 assert_eq!(be_i32(&[0xff, 0xff, 0xff, 0xff], 0), -1);
208 assert_eq!(le_i64(&[0xff; 8], 0), -1);
209 assert_eq!(be_i64(&[0xff; 8], 0), -1);
210
211 assert_eq!(le_i16(&[0x00, 0x80], 0), i16::MIN);
213 assert_eq!(be_i16(&[0x80, 0x00], 0), i16::MIN);
214 assert_eq!(le_i32(&[0x00, 0x00, 0x00, 0x80], 0), i32::MIN);
215 assert_eq!(be_i32(&[0x80, 0x00, 0x00, 0x00], 0), i32::MIN);
216 assert_eq!(le_i64(&[0, 0, 0, 0, 0, 0, 0, 0x80], 0), i64::MIN);
217 assert_eq!(be_i64(&[0x80, 0, 0, 0, 0, 0, 0, 0], 0), i64::MIN);
218
219 assert_eq!(
221 le_i64(&[0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], 0),
222 -2
223 );
224 assert_eq!(le_i32(&[0x9c, 0xff, 0xff, 0xff], 0), -100);
225
226 assert_eq!(try_le_i32(&[0xff, 0xff, 0xff, 0xff], 0), Some(-1));
227 assert_eq!(try_be_i64(&[0xff; 8], 0), Some(-1));
228 }
229
230 #[test]
231 fn signed_out_of_range_returns_zero_or_none() {
232 assert_eq!(le_i16(&[1], 0), 0);
233 assert_eq!(be_i16(&[], 0), 0);
234 assert_eq!(le_i32(&[1, 2, 3], 0), 0);
235 assert_eq!(be_i32(&[1, 2, 3, 4], 2), 0);
236 assert_eq!(le_i64(&[1, 2, 3, 4, 5, 6, 7], 0), 0);
237 assert_eq!(be_i64(&[1, 2, 3, 4, 5, 6, 7, 8], 100), 0);
238
239 assert_eq!(try_le_i16(&[1], 0), None);
240 assert_eq!(try_be_i16(&[], 0), None);
241 assert_eq!(try_le_i32(&[1, 2, 3], 0), None);
242 assert_eq!(try_be_i32(&[1, 2, 3, 4], 2), None);
243 assert_eq!(try_le_i64(&[1, 2, 3, 4, 5, 6, 7], 0), None);
244 assert_eq!(try_be_i64(&[1, 2, 3, 4, 5, 6, 7, 8], 100), None);
245
246 assert_eq!(try_le_i32(&[0, 0, 0, 0], 0), Some(0));
248 }
249
250 #[test]
251 fn signed_offset_overflow_returns_zero() {
252 assert_eq!(le_i16(&[1, 2, 3, 4], usize::MAX), 0);
253 assert_eq!(be_i32(&[1, 2, 3, 4], usize::MAX), 0);
254 assert_eq!(le_i64(&[1, 2, 3, 4], usize::MAX), 0);
255 assert_eq!(try_le_i16(&[1, 2, 3, 4], usize::MAX), None);
256 assert_eq!(try_be_i32(&[1, 2, 3, 4], usize::MAX), None);
257 assert_eq!(try_le_i64(&[1, 2, 3, 4], usize::MAX), None);
258 }
259
260 #[test]
261 fn try_bytes_window_in_range() {
262 let data = [0xde, 0xad, 0xbe, 0xef, 0x01, 0x02];
263 assert_eq!(try_bytes::<4>(&data, 0), Some([0xde, 0xad, 0xbe, 0xef]));
264 assert_eq!(try_bytes::<2>(&data, 4), Some([0x01, 0x02]));
265 assert_eq!(try_bytes::<1>(&data, 5), Some([0x02]));
266 assert_eq!(try_bytes::<6>(&data, 0), Some(data));
267 assert_eq!(try_bytes::<0>(&data, 6), Some([]));
269 assert_eq!(try_bytes::<0>(&[], 0), Some([]));
270 let guid = [
272 0x77, 0x4e, 0xc1, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
273 0x00, 0x00,
274 ];
275 assert_eq!(try_bytes::<16>(&guid, 0), Some(guid));
276 }
277
278 #[test]
279 fn try_bytes_out_of_range_returns_none() {
280 let data = [1u8, 2, 3, 4];
281 assert_eq!(try_bytes::<5>(&data, 0), None); assert_eq!(try_bytes::<4>(&data, 1), None); assert_eq!(try_bytes::<1>(&data, 4), None); assert_eq!(try_bytes::<1>(&[], 0), None);
285 assert_eq!(try_bytes::<16>(&data, 0), None);
286 }
287
288 #[test]
291 fn try_bytes_offset_overflow_returns_none() {
292 let data = [1u8, 2, 3, 4];
293 assert_eq!(try_bytes::<4>(&data, usize::MAX), None);
294 assert_eq!(try_bytes::<16>(&data, usize::MAX - 8), None);
295 assert_eq!(try_bytes::<2>(&data, usize::MAX - 1), None);
296 }
297}