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}