Skip to main content

automotive_wire_codec/
encode.rs

1//! The [`Encode`] trait: serialize a value into an [`embedded_io::Write`] sink.
2
3use embedded_io::Write;
4
5use crate::error::InsufficientBuffer;
6
7/// Error from [`Encode::encode_to_slice`].
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum EncodeToSliceError<E> {
10    /// The slice was smaller than [`Encode::encoded_size`]; carries both counts.
11    InsufficientBuffer(InsufficientBuffer),
12    /// The value itself failed to encode (or to size).
13    Encode(E),
14}
15
16impl<E> From<InsufficientBuffer> for EncodeToSliceError<E> {
17    fn from(e: InsufficientBuffer) -> Self {
18        EncodeToSliceError::InsufficientBuffer(e)
19    }
20}
21impl<E: core::fmt::Display> core::fmt::Display for EncodeToSliceError<E> {
22    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23        match self {
24            EncodeToSliceError::InsufficientBuffer(e) => e.fmt(f),
25            EncodeToSliceError::Encode(e) => e.fmt(f),
26        }
27    }
28}
29impl<E: core::fmt::Debug + core::fmt::Display> core::error::Error for EncodeToSliceError<E> {}
30
31/// Infallible [`embedded_io::Write`] sink that counts bytes and stores nothing.
32///
33/// Backs the default [`Encode::encoded_size`]; also useful in consumer tests to
34/// assert an `encoded_size` override agrees with `encode`.
35#[derive(Debug, Default)]
36pub struct CountingSink {
37    count: usize,
38}
39
40impl CountingSink {
41    /// New sink with a zero count.
42    #[must_use]
43    pub const fn new() -> Self {
44        Self { count: 0 }
45    }
46
47    /// Total bytes written so far.
48    #[must_use]
49    pub const fn count(&self) -> usize {
50        self.count
51    }
52}
53
54impl embedded_io::ErrorType for CountingSink {
55    type Error = core::convert::Infallible;
56}
57
58impl Write for CountingSink {
59    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
60        self.count += buf.len();
61        Ok(buf.len())
62    }
63    fn flush(&mut self) -> Result<(), Self::Error> {
64        Ok(())
65    }
66}
67
68/// TX-side: serialize `self` into an [`embedded_io::Write`] sink.
69pub trait Encode {
70    /// Per-implementation error; constructible from an I/O [`embedded_io::ErrorKind`]
71    /// so the fixed-width `write_*` leaf helpers lift through `?`.
72    ///
73    /// The variable-width helper [`write_be_uint`](crate::write_be_uint)
74    /// returns [`WriteUintError`](crate::WriteUintError) instead; to call it
75    /// inside `encode` with `?`, additionally implement
76    /// `From<WriteUintError>` for your error (match both arms — see the
77    /// error pattern in `MIGRATION.md`).
78    type Error: From<embedded_io::ErrorKind>;
79
80    /// Exact number of bytes [`encode`](Encode::encode) will write.
81    ///
82    /// The default runs `encode` against an infallible [`CountingSink`]
83    /// (one extra `encode` invocation per size query — see the purity
84    /// requirement on [`encode`](Encode::encode)) and
85    /// returns the bytes actually written — correct by construction, so
86    /// hand-maintained sizes cannot drift from `encode` (the bug class every
87    /// migrated consumer had). Override only where a closed-form size is
88    /// cheaper on a hot path; an override MUST return exactly the byte count a
89    /// successful `encode` writes — nested encoders reserve space from it with
90    /// no staging buffer. Because the default runs a full `encode` pass,
91    /// call sites that size before encoding (nested length-prefix encoders,
92    /// which compound per level) traverse the value once per size query under
93    /// the default, so hot paths should prefer closed-form overrides.
94    ///
95    /// An `encode` implementation that relies on this default must NOT call
96    /// `self.encoded_size()` (infinite recursion). Calling `encoded_size()` on
97    /// *nested fields* is fine, and is the intended pre-sizing pattern.
98    ///
99    /// # Errors
100    /// Whatever `encode` returns for a value that cannot be encoded; the
101    /// counting sink itself never fails.
102    ///
103    /// # Panics
104    /// In debug builds, if `encode` returns a byte count different from the
105    /// bytes it actually wrote — that is a bug in the `encode` impl
106    /// (`written == encoded_size()?` is a hard invariant).
107    fn encoded_size(&self) -> Result<usize, Self::Error> {
108        let mut sink = CountingSink::new();
109        let reported = self.encode(&mut sink)?;
110        debug_assert!(
111            reported == sink.count(),
112            "encode returned {reported} but wrote {} bytes",
113            sink.count()
114        );
115        Ok(sink.count())
116    }
117
118    /// Serialize into `writer`; return the number of bytes written.
119    ///
120    /// **`encode` must be a pure function of `&self`** — same bytes every
121    /// call, no observable side effects. The trait's provided methods may
122    /// invoke it more than once per logical serialization: the default
123    /// [`encoded_size`](Encode::encoded_size) counts by encoding into a
124    /// [`CountingSink`], and [`encode_to_slice`](Encode::encode_to_slice)
125    /// re-runs sizing after a failed encode to classify the error. An
126    /// implementation that mutates through interior mutability (e.g. a
127    /// rolling sequence or alive counter advanced inside `encode`) will have
128    /// that side effect applied per *invocation*, not per frame — advance
129    /// such state outside `encode`, then encode the snapshot.
130    ///
131    /// # Errors
132    /// `Self::Error` if the sink rejects a write or the value cannot be encoded.
133    fn encode(&self, writer: &mut impl Write) -> Result<usize, Self::Error>;
134
135    /// Encode into a fixed slice, reporting `needed`/`available`
136    /// ([`InsufficientBuffer`]) instead of a bare
137    /// [`embedded_io::ErrorKind::WriteZero`] when the slice is too small, and
138    /// hiding the `&mut &mut [u8]` cursor re-borrow every fixed-buffer call
139    /// site otherwise writes by hand.
140    ///
141    /// The success path is a single `encode` pass —
142    /// [`encoded_size`](Encode::encoded_size) is consulted only after a
143    /// failed encode, to classify the error (under the default
144    /// `encoded_size` that means a second `encode` invocation; see the
145    /// purity requirement on [`encode`](Encode::encode)). On error, `buf`
146    /// may hold partially written bytes; on success, bytes past the returned
147    /// count are untouched.
148    ///
149    /// # Errors
150    /// [`EncodeToSliceError::InsufficientBuffer`] if `buf` is smaller than
151    /// `encoded_size()`; [`EncodeToSliceError::Encode`] if encoding itself
152    /// fails.
153    fn encode_to_slice(&self, buf: &mut [u8]) -> Result<usize, EncodeToSliceError<Self::Error>> {
154        let available = buf.len();
155        let mut cursor: &mut [u8] = buf;
156        match self.encode(&mut cursor) {
157            Ok(n) => Ok(n),
158            Err(e) => {
159                // Distinguish "slice too small" from a value error. If sizing
160                // itself fails, the value is unencodable — report the
161                // original encode error.
162                match self.encoded_size() {
163                    Ok(needed) if available < needed => {
164                        Err(InsufficientBuffer { needed, available }.into())
165                    }
166                    _ => Err(EncodeToSliceError::Encode(e)),
167                }
168            }
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::error::InsufficientBuffer;
177    use crate::write::write_u16_be;
178
179    #[derive(Debug)]
180    enum TestErr {
181        Io(embedded_io::ErrorKind),
182    }
183    impl From<embedded_io::ErrorKind> for TestErr {
184        fn from(kind: embedded_io::ErrorKind) -> Self {
185            TestErr::Io(kind)
186        }
187    }
188
189    struct Val(u16);
190    impl Encode for Val {
191        type Error = TestErr;
192        fn encoded_size(&self) -> Result<usize, TestErr> {
193            Ok(2)
194        }
195        fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, TestErr> {
196            Ok(write_u16_be(writer, self.0)?)
197        }
198    }
199
200    #[test]
201    fn encode_reports_size_and_writes_into_slice() {
202        let v = Val(0xABCD);
203        let mut buf = [0u8; 4];
204        let mut w: &mut [u8] = &mut buf;
205        let n = v.encode(&mut w).unwrap();
206        assert_eq!(n, v.encoded_size().unwrap());
207        assert_eq!(&buf[..2], &[0xAB, 0xCD]);
208    }
209
210    #[test]
211    fn encode_into_too_small_slice_errors() {
212        let v = Val(0xABCD);
213        let mut buf = [0u8; 1];
214        let mut w: &mut [u8] = &mut buf;
215        let err = v.encode(&mut w).unwrap_err();
216        // Reads the `Io` field so it is load-bearing (irrefutable: single-variant enum).
217        let TestErr::Io(kind) = err;
218        // `embedded_io::Write for &mut [u8]` yields `SliceWriteError::Full`, whose
219        // `kind()` is `WriteZero`, when the sink is exhausted mid-write.
220        assert_eq!(kind, embedded_io::ErrorKind::WriteZero);
221    }
222
223    #[test]
224    fn counting_sink_counts_and_never_fails() {
225        let mut sink = CountingSink::new();
226        // Uses the existing test type Val(u16) which writes 2 bytes.
227        let n = Val(0xABCD).encode(&mut sink).unwrap();
228        assert_eq!(n, 2);
229        assert_eq!(sink.count(), 2);
230        // Accumulates across encodes.
231        Val(0x0102).encode(&mut sink).unwrap();
232        assert_eq!(sink.count(), 4);
233    }
234
235    // Uses the default encoded_size — no hand-written size at all.
236    struct TwoVals(u16, u16);
237    impl Encode for TwoVals {
238        type Error = TestErr;
239        fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, TestErr> {
240            let mut n = write_u16_be(writer, self.0)?;
241            n += write_u16_be(writer, self.1)?;
242            Ok(n)
243        }
244    }
245
246    // Encode fails for a VALUE reason (uds C1 shape): default encoded_size
247    // must surface it as Err, not panic.
248    struct Rejecting;
249    impl Encode for Rejecting {
250        type Error = TestErr;
251        fn encode(&self, _writer: &mut impl embedded_io::Write) -> Result<usize, TestErr> {
252            Err(TestErr::Io(embedded_io::ErrorKind::InvalidData))
253        }
254    }
255
256    #[test]
257    fn default_encoded_size_counts_actual_bytes() {
258        assert_eq!(TwoVals(1, 2).encoded_size().unwrap(), 4);
259    }
260
261    #[test]
262    fn default_encoded_size_surfaces_value_errors() {
263        assert!(Rejecting.encoded_size().is_err());
264    }
265
266    #[test]
267    fn override_still_supported() {
268        // Val overrides encoded_size with a closed form (see impl above).
269        assert_eq!(Val(0xABCD).encoded_size().unwrap(), 2);
270    }
271
272    #[test]
273    fn encode_to_slice_writes_and_counts() {
274        // SE-5: no `let mut w: &mut [u8] = &mut buf;` dance at the call site.
275        let mut buf = [0u8; 4];
276        let n = Val(0xABCD).encode_to_slice(&mut buf).unwrap();
277        assert_eq!(n, 2);
278        assert_eq!(&buf[..2], &[0xAB, 0xCD]);
279    }
280
281    #[test]
282    fn encode_to_slice_too_small_reports_both_counts() {
283        // someip F5: needed/available diagnostics, not a bare WriteZero.
284        let mut buf = [0u8; 1];
285        let err = Val(0xABCD).encode_to_slice(&mut buf).unwrap_err();
286        assert!(matches!(
287            err,
288            EncodeToSliceError::InsufficientBuffer(InsufficientBuffer {
289                needed: 2,
290                available: 1
291            })
292        ));
293    }
294
295    // Counts encode() invocations; uses the default (counting) encoded_size.
296    struct CountsEncodes<'a>(&'a core::cell::Cell<u32>);
297    impl Encode for CountsEncodes<'_> {
298        type Error = TestErr;
299        fn encode(&self, writer: &mut impl embedded_io::Write) -> Result<usize, TestErr> {
300            self.0.set(self.0.get() + 1);
301            Ok(write_u16_be(writer, 0xABCD)?)
302        }
303    }
304
305    #[test]
306    fn encode_to_slice_success_is_single_pass() {
307        // Under the default encoded_size (which encodes into a counting
308        // sink), a successful encode_to_slice must not pay a sizing pass:
309        // encode() runs exactly once per frame on the hot path.
310        let calls = core::cell::Cell::new(0);
311        let mut buf = [0u8; 4];
312        let n = CountsEncodes(&calls).encode_to_slice(&mut buf).unwrap();
313        assert_eq!(n, 2);
314        assert_eq!(&buf[..2], &[0xAB, 0xCD]);
315        assert_eq!(calls.get(), 1);
316    }
317
318    #[test]
319    fn encode_to_slice_too_small_reports_counts_with_default_size() {
320        // The needed/available diagnostics survive the single-pass rewrite
321        // even for types relying on the default encoded_size.
322        let calls = core::cell::Cell::new(0);
323        let mut buf = [0u8; 1];
324        let err = CountsEncodes(&calls).encode_to_slice(&mut buf).unwrap_err();
325        assert!(matches!(
326            err,
327            EncodeToSliceError::InsufficientBuffer(InsufficientBuffer {
328                needed: 2,
329                available: 1
330            })
331        ));
332    }
333
334    #[test]
335    fn encode_to_slice_propagates_encode_errors() {
336        let mut buf = [0u8; 8];
337        let err = Rejecting.encode_to_slice(&mut buf).unwrap_err();
338        assert!(matches!(err, EncodeToSliceError::Encode(_)));
339    }
340}