mctp_rs/buffer_encoding.rs
1//! Stateless byte-level buffer-encoding transform for MCTP media.
2//!
3//! Most media (SMBus/eSPI) ship MCTP packets verbatim — wire bytes ARE
4//! payload bytes. Some media (DSP0253 serial) need byte-stuffing: an
5//! escape character expands certain payload bytes into 2-byte sequences
6//! on the wire, and decode reverses that transform.
7//!
8//! [`BufferEncoding`] is the byte-stuffing layer ONLY. It is stateless:
9//! [`write_byte`](BufferEncoding::write_byte) and
10//! [`read_byte`](BufferEncoding::read_byte) are associated functions with
11//! no `self` and no struct state. Higher-level framing concerns
12//! (start/end delimiters, FCS / CRC) live on the medium type, not here.
13
14use core::marker::PhantomData;
15
16#[derive(Debug, Copy, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub enum EncodeError {
19 /// `wire_buf` did not have room for the encoded bytes (1 for plain,
20 /// up to 2 for an escape sequence). The caller should advance no
21 /// cursors and treat the encode as failed.
22 BufferFull,
23}
24
25#[derive(Debug, Copy, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27pub enum DecodeError {
28 /// `wire_buf` was empty or ended mid-escape-sequence. Indicates the
29 /// caller asked to decode past the end of valid wire data.
30 PrematureEnd,
31 /// An escape byte was followed by a byte not in the medium's
32 /// accept-list (strict-XOR rule per RFC1662 §4.2 / DSP0253 §6.4).
33 /// The caller should reject the entire frame. Currently unreachable;
34 /// produced only by stuffing encodings introduced in a later phase.
35 InvalidEscape,
36}
37
38/// Stateless byte-stuffing transform. Implementors define how a single
39/// logical (payload) byte maps to one or more wire bytes (encode) and
40/// how a wire-byte prefix maps back to a single payload byte (decode).
41///
42/// All methods are associated functions — there is no `self` and no
43/// struct state. Callers own the buffers and the read/write cursors.
44pub trait BufferEncoding {
45 /// Encode one logical payload byte into `wire_buf` starting at
46 /// index 0. Returns the number of wire bytes written (1 for plain,
47 /// 2 for an escape sequence). The caller advances their write
48 /// cursor by the returned count.
49 fn write_byte(wire_buf: &mut [u8], byte: u8) -> Result<usize, EncodeError>;
50
51 /// Decode the next logical payload byte from `wire_buf` starting at
52 /// index 0. Returns `(decoded_byte, wire_bytes_consumed)`. The
53 /// caller advances their read cursor by `wire_bytes_consumed`.
54 fn read_byte(wire_buf: &[u8]) -> Result<(u8, usize), DecodeError>;
55
56 /// Wire-byte footprint of `decoded` under this encoding. Must equal
57 /// the sum of `write_byte(_, b)` lengths for each `b` in `decoded`.
58 /// NO default impl: every encoding declares its sizing rule
59 /// explicitly.
60 fn wire_size_of(decoded: &[u8]) -> usize;
61}
62
63/// No-op encoding: wire bytes ARE payload bytes. Used by media that do
64/// not byte-stuff (SMBus/eSPI, test fixtures).
65#[derive(Debug, Copy, Clone, PartialEq, Eq)]
66#[cfg_attr(feature = "defmt", derive(defmt::Format))]
67pub struct PassthroughEncoding;
68
69impl BufferEncoding for PassthroughEncoding {
70 fn write_byte(wire_buf: &mut [u8], byte: u8) -> Result<usize, EncodeError> {
71 match wire_buf.first_mut() {
72 Some(slot) => {
73 *slot = byte;
74 Ok(1)
75 }
76 None => Err(EncodeError::BufferFull),
77 }
78 }
79
80 fn read_byte(wire_buf: &[u8]) -> Result<(u8, usize), DecodeError> {
81 match wire_buf.first() {
82 Some(&byte) => Ok((byte, 1)),
83 None => Err(DecodeError::PrematureEnd),
84 }
85 }
86
87 fn wire_size_of(decoded: &[u8]) -> usize {
88 decoded.len()
89 }
90}
91
92/// Stateful cursor over a `&[u8]` wire buffer that reads decoded bytes
93/// through `E: BufferEncoding`. Constructed by [`MctpMedium::deserialize`]
94/// and handed to higher layers so they cannot bypass the encoding by
95/// slicing the underlying buffer directly.
96///
97/// [`MctpMedium::deserialize`]: crate::medium::MctpMedium::deserialize
98pub struct EncodingDecoder<'buf, E: BufferEncoding> {
99 buf: &'buf [u8],
100 wire_pos: usize,
101 _phantom: PhantomData<E>,
102}
103
104impl<'buf, E: BufferEncoding> EncodingDecoder<'buf, E> {
105 /// Wrap a wire-byte buffer for stateful encoding-mediated reads.
106 pub fn new(buf: &'buf [u8]) -> Self {
107 Self {
108 buf,
109 wire_pos: 0,
110 _phantom: PhantomData,
111 }
112 }
113
114 /// Read one decoded byte. Advances the wire cursor by the encoding's
115 /// per-byte wire footprint. Returns `DecodeError::PrematureEnd` when
116 /// the wire buffer is exhausted (or ends mid-escape) and
117 /// `DecodeError::InvalidEscape` for malformed escape sequences.
118 pub fn read(&mut self) -> Result<u8, DecodeError> {
119 let (byte, n) = E::read_byte(&self.buf[self.wire_pos..])?;
120 self.wire_pos += n;
121 Ok(byte)
122 }
123}
124
125/// Stateful cursor over a `&mut [u8]` wire buffer that writes decoded
126/// bytes through `E: BufferEncoding`. Constructed by
127/// [`MctpMedium::serialize`] and handed to the caller's `message_writer`
128/// closure so the closure cannot bypass the encoding.
129///
130/// [`MctpMedium::serialize`]: crate::medium::MctpMedium::serialize
131pub struct EncodingEncoder<'buf, E: BufferEncoding> {
132 buf: &'buf mut [u8],
133 wire_pos: usize,
134 _phantom: PhantomData<E>,
135}
136
137impl<'buf, E: BufferEncoding> EncodingEncoder<'buf, E> {
138 /// Wrap a wire-byte buffer for stateful encoding-mediated writes.
139 pub fn new(buf: &'buf mut [u8]) -> Self {
140 Self {
141 buf,
142 wire_pos: 0,
143 _phantom: PhantomData,
144 }
145 }
146
147 /// Write one decoded byte. Advances the wire cursor by the encoding's
148 /// per-byte wire footprint. Returns `EncodeError::BufferFull` when
149 /// the underlying wire buffer cannot fit the encoded representation.
150 pub fn write(&mut self, byte: u8) -> Result<(), EncodeError> {
151 let n = E::write_byte(&mut self.buf[self.wire_pos..], byte)?;
152 self.wire_pos += n;
153 Ok(())
154 }
155
156 /// Write a contiguous slice of decoded bytes; aborts on the first
157 /// encode error. Equivalent to a `for &b in bytes { self.write(b)? }`
158 /// loop, but more concise at call sites that just splat a byte slice.
159 pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), EncodeError> {
160 for &b in bytes {
161 self.write(b)?;
162 }
163 Ok(())
164 }
165
166 /// Wire bytes written so far (the size of the produced wire frame).
167 pub fn wire_position(&self) -> usize {
168 self.wire_pos
169 }
170
171 /// Wire bytes remaining in the underlying buffer.
172 pub fn remaining_wire(&self) -> usize {
173 self.buf.len() - self.wire_pos
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn passthrough_write_byte_writes_one_byte() {
183 let mut buf = [0u8; 4];
184 let n = PassthroughEncoding::write_byte(&mut buf, 0xAB).unwrap();
185 assert_eq!(n, 1);
186 assert_eq!(buf, [0xAB, 0, 0, 0]);
187 }
188
189 #[test]
190 fn passthrough_write_byte_full_buffer() {
191 let mut buf = [];
192 let err = PassthroughEncoding::write_byte(&mut buf, 0xAB).unwrap_err();
193 assert_eq!(err, EncodeError::BufferFull);
194 }
195
196 #[test]
197 fn passthrough_read_byte_reads_one_byte() {
198 let buf = [0xAB, 0xCD];
199 let (b, n) = PassthroughEncoding::read_byte(&buf).unwrap();
200 assert_eq!(b, 0xAB);
201 assert_eq!(n, 1);
202 }
203
204 #[test]
205 fn passthrough_read_byte_premature_end() {
206 let buf = [];
207 let err = PassthroughEncoding::read_byte(&buf).unwrap_err();
208 assert_eq!(err, DecodeError::PrematureEnd);
209 }
210
211 #[test]
212 fn decoder_reads_all_bytes_via_passthrough() {
213 let buf = [0xAA, 0xBB, 0xCC, 0xDD];
214 let mut decoder = EncodingDecoder::<PassthroughEncoding>::new(&buf);
215 assert_eq!(decoder.read().unwrap(), 0xAA);
216 assert_eq!(decoder.read().unwrap(), 0xBB);
217 assert_eq!(decoder.read().unwrap(), 0xCC);
218 assert_eq!(decoder.read().unwrap(), 0xDD);
219 assert_eq!(decoder.read().unwrap_err(), DecodeError::PrematureEnd);
220 }
221
222 #[test]
223 fn encoder_writes_all_bytes_via_passthrough() {
224 let mut buf = [0u8; 4];
225 {
226 let mut encoder = EncodingEncoder::<PassthroughEncoding>::new(&mut buf);
227 assert_eq!(encoder.wire_position(), 0);
228 assert_eq!(encoder.remaining_wire(), 4);
229 encoder.write(0x11).unwrap();
230 encoder.write(0x22).unwrap();
231 encoder.write(0x33).unwrap();
232 encoder.write(0x44).unwrap();
233 assert_eq!(encoder.wire_position(), 4);
234 assert_eq!(encoder.remaining_wire(), 0);
235 assert_eq!(encoder.write(0x55).unwrap_err(), EncodeError::BufferFull);
236 }
237 assert_eq!(buf, [0x11, 0x22, 0x33, 0x44]);
238 }
239
240 #[test]
241 fn passthrough_wire_size_of_returns_input_len() {
242 assert_eq!(PassthroughEncoding::wire_size_of(&[]), 0);
243 assert_eq!(PassthroughEncoding::wire_size_of(&[0xAB]), 1);
244 let buf = [0u8; 64];
245 assert_eq!(PassthroughEncoding::wire_size_of(&buf), 64);
246 }
247}