Skip to main content

automotive_wire_codec/
write.rs

1//! Big-endian, `core`-only write helpers over [`embedded_io::Write`]. Each returns the
2//! number of bytes written. `embedded_io::Write for &mut [u8]` advances the slice and
3//! errors (never panics) when the slice is exhausted, so encoding into a too-small
4//! stack buffer surfaces as a recoverable `Err` — specifically
5//! [`embedded_io::ErrorKind::WriteZero`], which carries no needed/available counts
6//! (a generic sink cannot know its capacity). For counted diagnostics encode via
7//! [`Encode::encode_to_slice`](crate::Encode::encode_to_slice), which classifies
8//! a failed encode against [`Encode::encoded_size`](crate::Encode::encoded_size)
9//! and returns [`InsufficientBuffer`](crate::InsufficientBuffer).
10
11use crate::error::InvalidWidth;
12use embedded_io::{Error, Write};
13
14/// Write a single byte. Returns `1`.
15///
16/// # Errors
17/// The sink's [`embedded_io::ErrorKind`] if the write fails.
18pub fn write_u8(w: &mut impl Write, v: u8) -> Result<usize, embedded_io::ErrorKind> {
19    w.write_all(&[v]).map_err(|e| e.kind())?;
20    Ok(1)
21}
22
23/// Write a big-endian `u16`. Returns `2`.
24///
25/// # Errors
26/// The sink's [`embedded_io::ErrorKind`] if the write fails.
27pub fn write_u16_be(w: &mut impl Write, v: u16) -> Result<usize, embedded_io::ErrorKind> {
28    w.write_all(&v.to_be_bytes()).map_err(|e| e.kind())?;
29    Ok(2)
30}
31
32/// Write a big-endian `u32`. Returns `4`.
33///
34/// # Errors
35/// The sink's [`embedded_io::ErrorKind`] if the write fails.
36pub fn write_u32_be(w: &mut impl Write, v: u32) -> Result<usize, embedded_io::ErrorKind> {
37    w.write_all(&v.to_be_bytes()).map_err(|e| e.kind())?;
38    Ok(4)
39}
40
41/// Write a big-endian `u64`. Returns `8`.
42///
43/// # Errors
44/// The sink's [`embedded_io::ErrorKind`] if the write fails.
45pub fn write_u64_be(w: &mut impl Write, v: u64) -> Result<usize, embedded_io::ErrorKind> {
46    w.write_all(&v.to_be_bytes()).map_err(|e| e.kind())?;
47    Ok(8)
48}
49
50/// Write a big-endian `u128`. Returns `16`.
51///
52/// # Errors
53/// The sink's [`embedded_io::ErrorKind`] if the write fails.
54pub fn write_u128_be(w: &mut impl Write, v: u128) -> Result<usize, embedded_io::ErrorKind> {
55    w.write_all(&v.to_be_bytes()).map_err(|e| e.kind())?;
56    Ok(16)
57}
58
59/// Error from the variable-width write helper ([`write_be_uint`]).
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
61pub enum WriteUintError {
62    /// The sink rejected a write.
63    Io(embedded_io::ErrorKind),
64    /// Requested width out of range for the operation.
65    InvalidWidth(InvalidWidth),
66}
67
68impl From<embedded_io::ErrorKind> for WriteUintError {
69    fn from(e: embedded_io::ErrorKind) -> Self {
70        WriteUintError::Io(e)
71    }
72}
73impl From<InvalidWidth> for WriteUintError {
74    fn from(e: InvalidWidth) -> Self {
75        WriteUintError::InvalidWidth(e)
76    }
77}
78impl core::fmt::Display for WriteUintError {
79    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80        match self {
81            WriteUintError::Io(kind) => write!(f, "write failed: {kind:?}"),
82            WriteUintError::InvalidWidth(e) => e.fmt(f),
83        }
84    }
85}
86impl core::error::Error for WriteUintError {}
87
88/// Minimal number of big-endian bytes needed to represent `value` — computes
89/// the width to pass to [`write_be_uint`] for protocols that emit
90/// minimal-width length/size/address fields.
91///
92/// `minimal_be_len(0) == 0`; protocols that require at least one byte apply
93/// `.max(1)`. The result is always `<= 16`, so it is a valid width for
94/// [`write_be_uint`], and it is `usize` so
95/// `write_be_uint(w, v, minimal_be_len(v))` needs no cast at the call site.
96#[must_use]
97#[allow(clippy::cast_possible_truncation)] // result is 0..=16
98pub const fn minimal_be_len(value: u128) -> usize {
99    (u128::BITS - value.leading_zeros()).div_ceil(8) as usize
100}
101
102/// Write the low `n` bytes (`0..=16`) of `value`, big-endian. Returns `n`.
103///
104/// The width may come straight off the wire: an out-of-range `n` is a *data*
105/// error ([`InvalidWidth`]), not a panic, in every build profile. `n == 0` is
106/// legal and writes nothing.
107///
108/// **This helper does not check that `value` fits in `n` bytes.** Bytes of
109/// `value` above the low `n` are silently dropped: if
110/// `minimal_be_len(value) > n`, the emitted field decodes to a *different*
111/// value (e.g. `write_be_uint(w, 0x1_0000, 2)` writes `[0x00, 0x00]`, which
112/// reads back as `0`). Callers emitting a field whose value can grow — length
113/// prefixes especially — must validate `minimal_be_len(value) <= n` before
114/// writing, or compute the width with [`minimal_be_len`] so nothing is lost.
115///
116/// # Errors
117/// [`WriteUintError::InvalidWidth`] if `n > 16`; [`WriteUintError::Io`] if the
118/// sink rejects the write.
119pub fn write_be_uint(w: &mut impl Write, value: u128, n: usize) -> Result<usize, WriteUintError> {
120    if n > 16 {
121        return Err(InvalidWidth { max: 16, got: n }.into());
122    }
123    let bytes = value.to_be_bytes(); // 16 bytes, big-endian
124    w.write_all(&bytes[16 - n..])
125        .map_err(|e| WriteUintError::from(e.kind()))?;
126    Ok(n)
127}
128
129/// Write a raw byte slice verbatim (e.g. an opaque payload). Returns `bytes.len()`.
130///
131/// # Errors
132/// The sink's [`embedded_io::ErrorKind`] if the write fails.
133pub fn write_all(w: &mut impl Write, bytes: &[u8]) -> Result<usize, embedded_io::ErrorKind> {
134    w.write_all(bytes).map_err(|e| e.kind())?;
135    Ok(bytes.len())
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::error::InvalidWidth;
142
143    #[test]
144    fn write_u16_be_writes_big_endian_and_counts() {
145        let mut buf = [0u8; 4];
146        let mut w: &mut [u8] = &mut buf;
147        let n = write_u16_be(&mut w, 0x1234).unwrap();
148        assert_eq!(n, 2);
149        assert_eq!(&buf[..2], &[0x12, 0x34]);
150    }
151
152    #[test]
153    fn write_be_uint_writes_only_low_n_bytes() {
154        let mut buf = [0u8; 3];
155        let mut w: &mut [u8] = &mut buf;
156        // value has high bytes set; only the low 3 must be written, big-endian.
157        let n = write_be_uint(&mut w, 0xAABB_CCDD_u128, 3).unwrap();
158        assert_eq!(n, 3);
159        assert_eq!(buf, [0xBB, 0xCC, 0xDD]);
160    }
161
162    #[test]
163    fn write_all_writes_verbatim() {
164        let mut buf = [0u8; 4];
165        let mut w: &mut [u8] = &mut buf;
166        let n = write_all(&mut w, &[9, 8, 7]).unwrap();
167        assert_eq!(n, 3);
168        assert_eq!(&buf[..3], &[9, 8, 7]);
169    }
170
171    #[test]
172    fn write_into_too_small_slice_errors_not_panics() {
173        let mut buf = [0u8; 1];
174        let mut w: &mut [u8] = &mut buf;
175        assert!(write_u16_be(&mut w, 0x1234).is_err());
176    }
177
178    #[test]
179    fn write_be_uint_hostile_width_is_data_error_not_panic() {
180        // SE-1 write half: previously `16 - n` underflowed and PANICKED in
181        // release builds. Must now be a recoverable error in all profiles.
182        let mut buf = [0u8; 300];
183        let mut w: &mut [u8] = &mut buf;
184        assert_eq!(
185            write_be_uint(&mut w, 0xABCD, 255),
186            Err(WriteUintError::InvalidWidth(InvalidWidth {
187                max: 16,
188                got: 255
189            }))
190        );
191    }
192
193    #[test]
194    fn write_be_uint_zero_width_writes_nothing() {
195        let mut buf = [0xFFu8; 2];
196        let mut w: &mut [u8] = &mut buf;
197        assert_eq!(write_be_uint(&mut w, 0xABCD, 0).unwrap(), 0);
198        assert_eq!(buf, [0xFF, 0xFF]);
199    }
200
201    #[test]
202    fn write_u128_be_writes_big_endian_and_counts() {
203        let v = 0x0102_0304_0506_0708_090A_0B0C_0D0E_0F10_u128;
204        let mut buf = [0u8; 16];
205        let mut w: &mut [u8] = &mut buf;
206        assert_eq!(write_u128_be(&mut w, v).unwrap(), 16);
207        assert_eq!(buf, v.to_be_bytes());
208    }
209
210    #[test]
211    fn minimal_be_len_boundaries() {
212        // uds P3/SE-3 contract: 0 needs 0 bytes; callers wanting >=1 use .max(1).
213        assert_eq!(minimal_be_len(0), 0);
214        assert_eq!(minimal_be_len(1), 1);
215        assert_eq!(minimal_be_len(0xFF), 1);
216        assert_eq!(minimal_be_len(0x100), 2);
217        assert_eq!(minimal_be_len(0xFFFF), 2);
218        assert_eq!(minimal_be_len(0x1_0000), 3);
219        assert_eq!(minimal_be_len(u128::from(u64::MAX)), 8);
220        assert_eq!(minimal_be_len(u128::MAX), 16);
221    }
222
223    #[test]
224    fn write_be_uint_truncates_value_wider_than_n() {
225        // Characterization of the documented contract: the helper does NOT
226        // check minimality — an over-wide value is silently truncated to its
227        // low n bytes and round-trips to a different value. Callers guard
228        // with minimal_be_len(value) <= n.
229        let mut buf = [0xEEu8; 4];
230        let mut w: &mut [u8] = &mut buf;
231        assert_eq!(write_be_uint(&mut w, 0x1_0000, 2).unwrap(), 2);
232        assert_eq!(&buf[..2], &[0x00, 0x00]); // high byte dropped, reads back as 0
233    }
234
235    #[test]
236    fn minimal_be_len_pairs_with_write_be_uint() {
237        // The minimal width loses nothing on a write/read round trip, and the
238        // advertised idiom needs no cast at the call site.
239        let v = 0x00AB_CDEF_u128;
240        let n = minimal_be_len(v);
241        assert_eq!(n, 3);
242        let mut buf = [0u8; 16];
243        let mut w: &mut [u8] = &mut buf;
244        assert_eq!(write_be_uint(&mut w, v, minimal_be_len(v)).unwrap(), n);
245        assert_eq!(&buf[..n], &[0xAB, 0xCD, 0xEF]);
246    }
247}