cobs/
enc.rs

1/// The [`CobsEncoder`] type is used to encode a stream of bytes to a given mutable output slice.
2///
3/// This is often useful when heap data structures are not available, or when not all message bytes
4/// are received at a single point in time.
5#[derive(Debug)]
6pub struct CobsEncoder<'a> {
7    dest: &'a mut [u8],
8    dest_idx: usize,
9    state: EncoderState,
10    might_be_done: bool,
11}
12
13#[derive(Debug, PartialEq, Eq, thiserror::Error)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16#[error("out of bounds error during encoding")]
17pub struct DestBufTooSmallError;
18
19/// The [`EncoderState`] is used to track the current state of a streaming encoder. This struct
20/// does not contain the output buffer (or a reference to one), and can be used when streaming the
21/// encoded output to a custom data type
22///
23/// **IMPORTANT NOTE**: When implementing a custom streaming encoder,
24/// the [`EncoderState`] state machine assumes that the output buffer
25/// **ALREADY** contains a single placeholder byte, and no other bytes.
26/// This placeholder byte will be later modified with the first distance
27/// to the next header/zero byte.
28#[derive(Clone, Debug)]
29pub struct EncoderState {
30    code_idx: usize,
31    num_bt_sent: u8,
32    offset_idx: u8,
33}
34
35/// [`PushResult`] is used to represent the changes to an (encoded)
36/// output data buffer when an unencoded byte is pushed into [`EncoderState`].
37pub enum PushResult {
38    /// The returned byte should be placed at the current end of the data buffer
39    AddSingle(u8),
40
41    /// The byte at the given index should be replaced with the given byte.
42    /// Additionally, a placeholder byte should be inserted at the current
43    /// end of the output buffer to be later modified
44    ModifyFromStartAndSkip((usize, u8)),
45
46    /// The byte at the given index should be replaced with the given byte.
47    /// Then, the last u8 in this tuple should be inserted at the end of the
48    /// current output buffer. Finally, a placeholder byte should be inserted at
49    /// the current end of the output buffer to be later modified if the encoding process is
50    /// not done yet.
51    ModifyFromStartAndPushAndSkip((usize, u8, u8)),
52}
53
54impl Default for EncoderState {
55    /// Create a default initial state representation for a COBS encoder
56    fn default() -> Self {
57        Self {
58            code_idx: 0,
59            num_bt_sent: 1,
60            offset_idx: 1,
61        }
62    }
63}
64
65impl EncoderState {
66    /// Push a single unencoded byte into the encoder state machine
67    pub fn push(&mut self, data: u8) -> PushResult {
68        if data == 0 {
69            let ret = PushResult::ModifyFromStartAndSkip((self.code_idx, self.num_bt_sent));
70            self.code_idx += usize::from(self.offset_idx);
71            self.num_bt_sent = 1;
72            self.offset_idx = 1;
73            ret
74        } else {
75            self.num_bt_sent += 1;
76            self.offset_idx += 1;
77
78            if 0xFF == self.num_bt_sent {
79                let ret = PushResult::ModifyFromStartAndPushAndSkip((
80                    self.code_idx,
81                    self.num_bt_sent,
82                    data,
83                ));
84                self.num_bt_sent = 1;
85                self.code_idx += usize::from(self.offset_idx);
86                self.offset_idx = 1;
87                ret
88            } else {
89                PushResult::AddSingle(data)
90            }
91        }
92    }
93
94    /// Finalize the encoding process for a single message.
95    /// The byte at the given index should be replaced with the given value,
96    /// and the sentinel value (typically 0u8) must be inserted at the current
97    /// end of the output buffer, serving as a framing byte.
98    pub fn finalize(self) -> (usize, u8) {
99        (self.code_idx, self.num_bt_sent)
100    }
101}
102
103impl<'a> CobsEncoder<'a> {
104    /// Create a new streaming Cobs Encoder.
105    pub fn new(out_buf: &'a mut [u8]) -> CobsEncoder<'a> {
106        CobsEncoder {
107            dest: out_buf,
108            dest_idx: 1,
109            state: EncoderState::default(),
110            might_be_done: false,
111        }
112    }
113
114    /// Push a slice of data to be encoded
115    pub fn push(&mut self, data: &[u8]) -> Result<(), DestBufTooSmallError> {
116        // TODO: could probably check if this would fit without
117        // iterating through all data
118
119        // There was the possibility that the encoding process is done, but more data is pushed
120        // instead of a `finalize` call, so the destination index needs to be incremented.
121        if self.might_be_done {
122            self.dest_idx += 1;
123            self.might_be_done = false;
124        }
125        for (slice_idx, val) in data.iter().enumerate() {
126            use PushResult::*;
127            match self.state.push(*val) {
128                AddSingle(y) => {
129                    *self
130                        .dest
131                        .get_mut(self.dest_idx)
132                        .ok_or(DestBufTooSmallError)? = y;
133                }
134                ModifyFromStartAndSkip((idx, mval)) => {
135                    *self.dest.get_mut(idx).ok_or(DestBufTooSmallError)? = mval;
136                }
137                ModifyFromStartAndPushAndSkip((idx, mval, nval1)) => {
138                    *self.dest.get_mut(idx).ok_or(DestBufTooSmallError)? = mval;
139                    *self
140                        .dest
141                        .get_mut(self.dest_idx)
142                        .ok_or(DestBufTooSmallError)? = nval1;
143                    // Do not increase index if these is the possibility that we are finished.
144                    if slice_idx == data.len() - 1 {
145                        // If push is called again, the index will be incremented. If finalize
146                        // is called, there is no need to increment the index.
147                        self.might_be_done = true;
148                    } else {
149                        self.dest_idx += 1;
150                    }
151                }
152            }
153
154            // All branches above require advancing the pointer at least once
155            self.dest_idx += 1;
156        }
157
158        Ok(())
159    }
160
161    /// Complete encoding of the output message. Does NOT terminate the message with the sentinel
162    /// value.
163    pub fn finalize(self) -> usize {
164        // Get the last index that needs to be fixed
165        let (idx, mval) = if self.dest_idx == 0 {
166            (0, 0x01)
167        } else {
168            self.state.finalize()
169        };
170
171        // If the current code index is outside of the destination slice,
172        // we do not need to write it out
173        if let Some(i) = self.dest.get_mut(idx) {
174            *i = mval;
175        }
176
177        self.dest_idx
178    }
179}
180
181/// Encodes the `source` buffer into the `dest` buffer.
182///
183/// This function assumes the typical sentinel value of 0, but does not terminate the encoded
184/// message with the sentinel value. This should be done by the caller to ensure proper framing.
185///
186/// # Returns
187///
188/// The number of bytes written to in the `dest` buffer.
189///
190/// # Panics
191///
192/// This function will panic if the `dest` buffer is not large enough for the
193/// encoded message. You can calculate the size the `dest` buffer needs to be with
194/// the [crate::max_encoding_length] function.
195pub fn encode(source: &[u8], dest: &mut [u8]) -> usize {
196    let mut enc = CobsEncoder::new(dest);
197    enc.push(source).unwrap();
198    enc.finalize()
199}
200
201/// Attempts to encode the `source` buffer into the `dest` buffer.
202///
203/// This function assumes the typical sentinel value of 0, but does not terminate the encoded
204/// message with the sentinel value. This should be done by the caller to ensure proper framing.
205///
206/// # Returns
207///
208/// The number of bytes written to in the `dest` buffer.
209///
210/// If the destination buffer does not have enough room, an error will be returned.
211pub fn try_encode(source: &[u8], dest: &mut [u8]) -> Result<usize, DestBufTooSmallError> {
212    let mut enc = CobsEncoder::new(dest);
213    enc.push(source)?;
214    Ok(enc.finalize())
215}
216
217/// Encodes the `source` buffer into the `dest` buffer using an
218/// arbitrary sentinel value.
219///
220/// This is done by first encoding the message with the typical sentinel value
221/// of 0, then XOR-ing each byte of the encoded message with the chosen sentinel
222/// value. This will ensure that the sentinel value doesn't show up in the encoded
223/// message. See the paper "Consistent Overhead Byte Stuffing" for details.
224///
225/// This function does not terminate the encoded message with the sentinel value. This should be
226/// done by the caller to ensure proper framing.
227///
228/// # Returns
229///
230/// The number of bytes written to in the `dest` buffer.
231pub fn encode_with_sentinel(source: &[u8], dest: &mut [u8], sentinel: u8) -> usize {
232    let encoded_size = encode(source, dest);
233    for x in &mut dest[..encoded_size] {
234        *x ^= sentinel;
235    }
236    encoded_size
237}
238
239#[cfg(feature = "alloc")]
240/// Encodes the `source` buffer into a vector, using the [encode] function.
241pub fn encode_vec(source: &[u8]) -> alloc::vec::Vec<u8> {
242    let mut encoded = alloc::vec![0; crate::max_encoding_length(source.len())];
243    let encoded_len = encode(source, &mut encoded[..]);
244    encoded.truncate(encoded_len);
245    encoded
246}
247
248#[cfg(feature = "alloc")]
249/// Encodes the `source` buffer into a vector with an arbitrary sentinel value, using the
250/// [encode_with_sentinel] function.
251pub fn encode_vec_with_sentinel(source: &[u8], sentinel: u8) -> alloc::vec::Vec<u8> {
252    let mut encoded = alloc::vec![0; crate::max_encoding_length(source.len())];
253    let encoded_len = encode_with_sentinel(source, &mut encoded[..], sentinel);
254    encoded.truncate(encoded_len);
255    encoded
256}
257
258#[cfg(test)]
259mod tests {
260    #[cfg(feature = "alloc")]
261    use super::*;
262
263    #[test]
264    #[cfg(feature = "alloc")]
265    fn encode_target_buf_too_small() {
266        let source = &[10, 11, 0, 12];
267        let expected = &[3, 10, 11, 2, 12];
268        for len in 0..expected.len() {
269            let mut dest = alloc::vec![0; len];
270            matches!(
271                try_encode(source, &mut dest).unwrap_err(),
272                DestBufTooSmallError
273            );
274        }
275    }
276
277    #[test]
278    #[cfg(feature = "alloc")]
279    #[should_panic]
280    fn encode_target_buf_too_small_panicking() {
281        let source = &[10, 11, 0, 12];
282        let expected = &[3, 10, 11, 2, 12];
283        encode(source, &mut alloc::vec![0; expected.len() - 1]);
284    }
285}