cobs/
enc.rs

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