Skip to main content

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}