Skip to main content

automotive_wire_codec/
read.rs

1//! Big-endian, `core`-only slice read helpers. Each returns `(value, remainder)` so
2//! callers thread the remainder through sequential/nested decodes.
3
4use core::mem::size_of;
5
6use crate::error::{Incomplete, InvalidWidth};
7
8/// Split `n` bytes off the front of `buf`, returning `(head, tail)`.
9///
10/// # Errors
11/// [`Incomplete`] if `buf` has fewer than `n` bytes.
12pub fn take(buf: &[u8], n: usize) -> Result<(&[u8], &[u8]), Incomplete> {
13    if buf.len() < n {
14        Err(Incomplete {
15            needed: n,
16            available: buf.len(),
17        })
18    } else {
19        Ok(buf.split_at(n))
20    }
21}
22
23/// Read a single byte.
24///
25/// # Errors
26/// [`Incomplete`] if `buf` is empty.
27pub fn read_u8(buf: &[u8]) -> Result<(u8, &[u8]), Incomplete> {
28    let (b, rest) = take(buf, 1)?;
29    Ok((b[0], rest))
30}
31
32/// Read a big-endian `u16`.
33///
34/// # Errors
35/// [`Incomplete`] if fewer than 2 bytes remain.
36pub fn read_u16_be(buf: &[u8]) -> Result<(u16, &[u8]), Incomplete> {
37    let (b, rest) = take(buf, 2)?;
38    Ok((u16::from_be_bytes([b[0], b[1]]), rest))
39}
40
41/// Read a big-endian `u32`.
42///
43/// # Errors
44/// [`Incomplete`] if fewer than 4 bytes remain.
45pub fn read_u32_be(buf: &[u8]) -> Result<(u32, &[u8]), Incomplete> {
46    let (b, rest) = take(buf, 4)?;
47    Ok((u32::from_be_bytes([b[0], b[1], b[2], b[3]]), rest))
48}
49
50/// Read a big-endian `u64`.
51///
52/// # Errors
53/// [`Incomplete`] if fewer than 8 bytes remain.
54pub fn read_u64_be(buf: &[u8]) -> Result<(u64, &[u8]), Incomplete> {
55    let (b, rest) = take(buf, 8)?;
56    Ok((
57        u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]),
58        rest,
59    ))
60}
61
62/// Read a big-endian `u128`.
63///
64/// # Errors
65/// [`Incomplete`] if fewer than 16 bytes remain.
66pub fn read_u128_be(buf: &[u8]) -> Result<(u128, &[u8]), Incomplete> {
67    let (arr, rest) = read_array::<16>(buf)?;
68    Ok((u128::from_be_bytes(arr), rest))
69}
70
71/// Read a fixed-size `N`-byte array (e.g. a 17-byte VIN, a 6-byte EID).
72///
73/// # Errors
74/// [`Incomplete`] if fewer than `N` bytes remain.
75pub fn read_array<const N: usize>(buf: &[u8]) -> Result<([u8; N], &[u8]), Incomplete> {
76    let (b, rest) = take(buf, N)?;
77    let mut arr = [0u8; N];
78    arr.copy_from_slice(b);
79    Ok((arr, rest))
80}
81
82/// Error from the variable-width read helpers ([`read_be_uint`],
83/// `read_be_uint_into`).
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85pub enum ReadUintError {
86    /// Not enough input bytes.
87    Incomplete(Incomplete),
88    /// Requested width out of range for the operation.
89    InvalidWidth(InvalidWidth),
90}
91
92impl From<Incomplete> for ReadUintError {
93    fn from(e: Incomplete) -> Self {
94        ReadUintError::Incomplete(e)
95    }
96}
97impl From<InvalidWidth> for ReadUintError {
98    fn from(e: InvalidWidth) -> Self {
99        ReadUintError::InvalidWidth(e)
100    }
101}
102impl core::fmt::Display for ReadUintError {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        match self {
105            ReadUintError::Incomplete(e) => e.fmt(f),
106            ReadUintError::InvalidWidth(e) => e.fmt(f),
107        }
108    }
109}
110impl core::error::Error for ReadUintError {}
111
112/// Read a variable-width (`0..=16` byte) big-endian unsigned integer into a `u128`.
113///
114/// The width may come straight off the wire: an out-of-range `n` is a *data*
115/// error ([`InvalidWidth`]), not a panic, in every build profile. `n == 0` is
116/// legal: it reads nothing and returns `0` (protocols that require a minimum
117/// width of 1 must validate that upstream).
118///
119/// # Errors
120/// [`ReadUintError::InvalidWidth`] if `n > 16`;
121/// [`ReadUintError::Incomplete`] if fewer than `n` bytes remain.
122pub fn read_be_uint(buf: &[u8], n: usize) -> Result<(u128, &[u8]), ReadUintError> {
123    if n > 16 {
124        return Err(InvalidWidth { max: 16, got: n }.into());
125    }
126    let (b, rest) = take(buf, n)?;
127    let mut acc: u128 = 0;
128    for &byte in b {
129        acc = (acc << 8) | u128::from(byte);
130    }
131    Ok((acc, rest))
132}
133
134mod sealed {
135    pub trait Sealed {}
136}
137
138/// Unsigned-integer targets for [`read_be_uint_into`]. Sealed: implemented for
139/// `u8`, `u16`, `u32`, `u64`, `u128` only.
140pub trait BeUint: sealed::Sealed + Sized {
141    /// Byte width of the target type.
142    const BYTES: usize;
143    #[doc(hidden)]
144    fn from_u128(v: u128) -> Self;
145}
146
147macro_rules! impl_be_uint {
148    ($($t:ty),*) => {$(
149        impl sealed::Sealed for $t {}
150        impl BeUint for $t {
151            const BYTES: usize = size_of::<$t>();
152            #[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
153            fn from_u128(v: u128) -> Self {
154                // Callers guarantee v < 2^(8 * BYTES); see read_be_uint_into.
155                v as $t
156            }
157        }
158    )*};
159}
160impl_be_uint!(u8, u16, u32, u64, u128);
161
162/// Read a variable-width big-endian unsigned integer directly into `T`,
163/// validating the width against `T`'s size — no `as` cast at the call site,
164/// and a width the target cannot hold is a typed data error.
165///
166/// # Errors
167/// [`ReadUintError::InvalidWidth`] if `n > size_of::<T>()`;
168/// [`ReadUintError::Incomplete`] if fewer than `n` bytes remain.
169pub fn read_be_uint_into<T: BeUint>(buf: &[u8], n: usize) -> Result<(T, &[u8]), ReadUintError> {
170    if n > T::BYTES {
171        return Err(InvalidWidth {
172            max: T::BYTES,
173            got: n,
174        }
175        .into());
176    }
177    let (v, rest) = read_be_uint(buf, n)?;
178    Ok((T::from_u128(v), rest))
179}
180
181/// `Ok(())` if `buf` has at least `needed` bytes, else [`Incomplete`].
182///
183/// The check-only form of [`take`]: use it before an aggregate read whose
184/// parts you slice manually.
185///
186/// # Errors
187/// [`Incomplete`] if `buf` has fewer than `needed` bytes.
188#[inline]
189pub fn ensure_len(buf: &[u8], needed: usize) -> Result<(), Incomplete> {
190    if buf.len() < needed {
191        Err(Incomplete {
192            needed,
193            available: buf.len(),
194        })
195    } else {
196        Ok(())
197    }
198}
199
200/// Read an optional fixed-size trailing array: `Some` + remainder when `N`
201/// bytes are present, `(None, buf)` (buffer untouched) when they are not.
202///
203/// "Not enough bytes" is the `None` case, not an error — this helper is for
204/// fields a protocol defines as legitimately absent at the tail (e.g. `DoIP`'s
205/// optional 4-byte OEM fields). A *partial* tail (`1..N` bytes) also returns
206/// `None` and leaves the bytes in place, so a subsequent
207/// [`Decode::decode_exact`](crate::Decode::decode_exact) surfaces them as
208/// [`TrailingBytes`](crate::TrailingBytes).
209#[must_use]
210pub fn read_optional_array<const N: usize>(buf: &[u8]) -> (Option<[u8; N]>, &[u8]) {
211    match read_array::<N>(buf) {
212        Ok((arr, rest)) => (Some(arr), rest),
213        Err(_) => (None, buf),
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn take_splits_and_returns_remainder() {
223        let (head, tail) = take(&[1, 2, 3, 4], 2).unwrap();
224        assert_eq!(head, &[1, 2]);
225        assert_eq!(tail, &[3, 4]);
226    }
227
228    #[test]
229    fn take_past_end_is_incomplete() {
230        let buf = [1, 2, 3];
231        assert_eq!(
232            take(&buf, 4),
233            Err(Incomplete {
234                needed: 4,
235                available: 3
236            })
237        );
238    }
239
240    #[test]
241    fn read_u16_be_reads_big_endian_and_remainder() {
242        let (v, rest) = read_u16_be(&[0x12, 0x34, 0x56]).unwrap();
243        assert_eq!(v, 0x1234);
244        assert_eq!(rest, &[0x56]);
245    }
246
247    #[test]
248    fn read_u32_be_exact_leaves_empty_remainder() {
249        let (v, rest) = read_u32_be(&[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
250        assert_eq!(v, 0xDEAD_BEEF);
251        assert!(rest.is_empty());
252    }
253
254    #[test]
255    fn read_helpers_one_byte_short_are_incomplete() {
256        assert_eq!(
257            read_u8(&[]),
258            Err(Incomplete {
259                needed: 1,
260                available: 0
261            })
262        );
263        assert_eq!(
264            read_u16_be(&[0]),
265            Err(Incomplete {
266                needed: 2,
267                available: 1
268            })
269        );
270        assert_eq!(
271            read_u32_be(&[0; 3]),
272            Err(Incomplete {
273                needed: 4,
274                available: 3
275            })
276        );
277        assert_eq!(
278            read_u64_be(&[0; 7]),
279            Err(Incomplete {
280                needed: 8,
281                available: 7
282            })
283        );
284    }
285
286    #[test]
287    fn read_array_reads_fixed_width() {
288        let (arr, rest) = read_array::<3>(&[1, 2, 3, 4]).unwrap();
289        assert_eq!(arr, [1, 2, 3]);
290        assert_eq!(rest, &[4]);
291        assert_eq!(
292            read_array::<3>(&[1, 2]),
293            Err(Incomplete {
294                needed: 3,
295                available: 2
296            })
297        );
298    }
299
300    #[test]
301    fn read_be_uint_reads_low_n_bytes() {
302        let (v, rest) = read_be_uint(&[0x01, 0x02, 0x03], 2).unwrap();
303        assert_eq!(v, 0x0102);
304        assert_eq!(rest, &[0x03]);
305        assert_eq!(
306            read_be_uint(&[0x00], 2),
307            Err(ReadUintError::Incomplete(Incomplete {
308                needed: 2,
309                available: 1
310            }))
311        );
312    }
313
314    #[test]
315    fn read_be_uint_hostile_width_is_data_error_not_panic() {
316        // SE-1: n > 16 must be a recoverable error in ALL build profiles.
317        let buf = [0u8; 300];
318        assert_eq!(
319            read_be_uint(&buf, 255),
320            Err(ReadUintError::InvalidWidth(InvalidWidth {
321                max: 16,
322                got: 255
323            }))
324        );
325        assert_eq!(
326            read_be_uint(&buf, 17),
327            Err(ReadUintError::InvalidWidth(InvalidWidth {
328                max: 16,
329                got: 17
330            }))
331        );
332    }
333
334    #[test]
335    fn read_be_uint_zero_width_reads_nothing() {
336        // Documented contract: n == 0 is legal, reads nothing, returns 0.
337        let buf = [0xAA, 0xBB];
338        let (v, rest) = read_be_uint(&buf, 0).unwrap();
339        assert_eq!(v, 0);
340        assert_eq!(rest, &buf);
341    }
342
343    #[test]
344    fn read_be_uint_into_returns_target_type() {
345        // SE-4: no `as` cast, no clippy allow at the call site.
346        let buf = [0x01, 0x02, 0x03, 0xFF];
347        let (v, rest) = read_be_uint_into::<u32>(&buf, 3).unwrap();
348        assert_eq!(v, 0x0001_0203_u32);
349        assert_eq!(rest, &[0xFF]);
350    }
351
352    #[test]
353    fn read_be_uint_into_rejects_width_wider_than_target() {
354        // n = 5 fits in a u128 but NOT in the u32 target: typed data error.
355        let buf = [0u8; 8];
356        assert_eq!(
357            read_be_uint_into::<u32>(&buf, 5),
358            Err(ReadUintError::InvalidWidth(InvalidWidth { max: 4, got: 5 }))
359        );
360    }
361
362    #[test]
363    fn read_be_uint_into_full_width_roundtrips() {
364        let buf = 0xDEAD_BEEF_u32.to_be_bytes();
365        let (v, rest) = read_be_uint_into::<u32>(&buf, 4).unwrap();
366        assert_eq!(v, 0xDEAD_BEEF);
367        assert!(rest.is_empty());
368        let (b, _) = read_be_uint_into::<u8>(&buf, 1).unwrap();
369        assert_eq!(b, 0xDE);
370    }
371
372    #[test]
373    fn read_u128_be_reads_full_width() {
374        let v = 0x0102_0304_0506_0708_090A_0B0C_0D0E_0F10_u128;
375        let bytes = v.to_be_bytes();
376        let mut buf = [0u8; 17];
377        buf[..16].copy_from_slice(&bytes);
378        buf[16] = 0xFF;
379        let (got, rest) = read_u128_be(&buf).unwrap();
380        assert_eq!(got, v);
381        assert_eq!(rest, &[0xFF]);
382        assert_eq!(
383            read_u128_be(&[0u8; 15]),
384            Err(Incomplete {
385                needed: 16,
386                available: 15
387            })
388        );
389    }
390
391    #[test]
392    fn ensure_len_passes_and_fails_with_counts() {
393        assert_eq!(ensure_len(&[1, 2, 3], 3), Ok(()));
394        assert_eq!(ensure_len(&[1, 2, 3], 2), Ok(()));
395        assert_eq!(
396            ensure_len(&[1, 2, 3], 4),
397            Err(Incomplete {
398                needed: 4,
399                available: 3
400            })
401        );
402    }
403
404    #[test]
405    fn read_optional_array_present_and_absent() {
406        // doip P2: "not enough bytes" IS the None case, not an error.
407        let (arr, rest) = read_optional_array::<4>(&[1, 2, 3, 4, 5]);
408        assert_eq!(arr, Some([1, 2, 3, 4]));
409        assert_eq!(rest, &[5]);
410
411        let (arr, rest) = read_optional_array::<4>(&[]);
412        assert_eq!(arr, None);
413        assert!(rest.is_empty());
414
415        // Partial tail: None, buffer untouched — a following decode_exact
416        // will surface the stragglers as TrailingBytes.
417        let (arr, rest) = read_optional_array::<4>(&[1, 2]);
418        assert_eq!(arr, None);
419        assert_eq!(rest, &[1, 2]);
420    }
421}