cobs/enc.rs
1#[derive(Debug, PartialEq, Eq, thiserror::Error)]
2#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4#[error("out of bounds error during encoding")]
5pub struct DestBufTooSmallError;
6
7/// The [`EncoderState`] is used to track the current state of a streaming encoder. This struct
8/// does not contain the output buffer (or a reference to one), and can be used when streaming the
9/// encoded output to a custom data type
10///
11/// **IMPORTANT NOTE**: When implementing a custom streaming encoder,
12/// the [`EncoderState`] state machine assumes that the output buffer
13/// **ALREADY** contains a single placeholder byte, and no other bytes.
14/// This placeholder byte will be later modified with the first distance
15/// to the next header/zero byte.
16#[derive(Clone, Debug)]
17pub struct EncoderState {
18 code_idx: usize,
19 num_bt_sent: u8,
20 offset_idx: u8,
21}
22
23/// [`PushResult`] is used to represent the changes to an (encoded)
24/// output data buffer when an unencoded byte is pushed into [`EncoderState`].
25pub enum PushResult {
26 /// The returned byte should be placed at the current end of the data buffer
27 AddSingle(u8),
28
29 /// The byte at the given index should be replaced with the given byte.
30 /// Additionally, a placeholder byte should be inserted at the current
31 /// end of the output buffer to be later modified
32 ModifyFromStartAndSkip((usize, u8)),
33
34 /// The byte at the given index should be replaced with the given byte.
35 /// Then, the last u8 in this tuple should be inserted at the end of the
36 /// current output buffer. Finally, a placeholder byte should be inserted at
37 /// the current end of the output buffer to be later modified if the encoding process is
38 /// not done yet.
39 ModifyFromStartAndPushAndSkip((usize, u8, u8)),
40}
41
42impl Default for EncoderState {
43 /// Create a default initial state representation for a COBS encoder
44 fn default() -> Self {
45 Self {
46 code_idx: 0,
47 num_bt_sent: 1,
48 offset_idx: 1,
49 }
50 }
51}
52
53impl EncoderState {
54 /// Push a single unencoded byte into the encoder state machine
55 pub fn push(&mut self, data: u8) -> PushResult {
56 if data == 0 {
57 let ret = PushResult::ModifyFromStartAndSkip((self.code_idx, self.num_bt_sent));
58 self.code_idx += usize::from(self.offset_idx);
59 self.num_bt_sent = 1;
60 self.offset_idx = 1;
61 ret
62 } else {
63 self.num_bt_sent += 1;
64 self.offset_idx += 1;
65
66 if 0xFF == self.num_bt_sent {
67 let ret = PushResult::ModifyFromStartAndPushAndSkip((
68 self.code_idx,
69 self.num_bt_sent,
70 data,
71 ));
72 self.num_bt_sent = 1;
73 self.code_idx += usize::from(self.offset_idx);
74 self.offset_idx = 1;
75 ret
76 } else {
77 PushResult::AddSingle(data)
78 }
79 }
80 }
81
82 /// Finalize the encoding process for a single message.
83 /// The byte at the given index should be replaced with the given value,
84 /// and the sentinel value (typically 0u8) must be inserted at the current
85 /// end of the output buffer, serving as a framing byte.
86 pub fn finalize(self) -> (usize, u8) {
87 (self.code_idx, self.num_bt_sent)
88 }
89}
90
91/// The [`CobsEncoder`] type is used to encode a stream of bytes to a given mutable output slice.
92///
93/// This is often useful when heap data structures are not available, or when not all message bytes
94/// are received at a single point in time.
95#[derive(Debug)]
96pub struct CobsEncoder<'a> {
97 dest: &'a mut [u8],
98 dest_idx: usize,
99 state: EncoderState,
100 might_be_done: bool,
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/// Encodes the `source` buffer into the `dest` buffer, including the default sentinel values 0
202/// around the encoded frame.
203///
204/// # Returns
205///
206/// The number of bytes written to in the `dest` buffer.
207///
208/// # Panics
209///
210/// This function will panic if the `dest` buffer is not large enough for the
211/// encoded message. You can calculate the size the `dest` buffer needs to be with
212/// the [crate::max_encoding_length] function.
213pub fn encode_including_sentinels(source: &[u8], dest: &mut [u8]) -> usize {
214 if dest.len() < 2 {
215 panic!("destination buffer too small");
216 }
217
218 dest[0] = 0;
219 let mut enc = CobsEncoder::new(&mut dest[1..]);
220 enc.push(source).unwrap();
221 let encoded_len = enc.finalize();
222 dest[encoded_len + 1] = 0;
223 encoded_len + 2
224}
225
226/// Attempts to encode the `source` buffer into the `dest` buffer.
227///
228/// This function assumes the typical sentinel value of 0, but does not terminate the encoded
229/// message with the sentinel value. This should be done by the caller to ensure proper framing.
230///
231/// # Returns
232///
233/// The number of bytes written to in the `dest` buffer.
234///
235/// If the destination buffer does not have enough room, an error will be returned.
236pub fn try_encode(source: &[u8], dest: &mut [u8]) -> Result<usize, DestBufTooSmallError> {
237 let mut enc = CobsEncoder::new(dest);
238 enc.push(source)?;
239 Ok(enc.finalize())
240}
241
242/// Encodes the `source` buffer into the `dest` buffer, including the default sentinel values 0
243/// around the encoded frame.
244///
245/// # Returns
246///
247/// The number of bytes written to in the `dest` buffer.
248///
249/// If the destination buffer does not have enough room, an error will be returned.
250pub fn try_encode_including_sentinels(
251 source: &[u8],
252 dest: &mut [u8],
253) -> Result<usize, DestBufTooSmallError> {
254 if dest.len() < 2 {
255 return Err(DestBufTooSmallError);
256 }
257 dest[0] = 0;
258 let mut enc = CobsEncoder::new(&mut dest[1..]);
259 enc.push(source)?;
260 let encoded_len = enc.finalize();
261 dest[encoded_len + 1] = 0;
262 Ok(encoded_len + 2)
263}
264
265/// Encodes the `source` buffer into the `dest` buffer using an
266/// arbitrary sentinel value.
267///
268/// This is done by first encoding the message with the typical sentinel value
269/// of 0, then XOR-ing each byte of the encoded message with the chosen sentinel
270/// value. This will ensure that the sentinel value doesn't show up in the encoded
271/// message. See the paper "Consistent Overhead Byte Stuffing" for details.
272///
273/// This function does not terminate the encoded message with the sentinel value. This should be
274/// done by the caller to ensure proper framing.
275///
276/// # Returns
277///
278/// The number of bytes written to in the `dest` buffer.
279pub fn encode_with_sentinel(source: &[u8], dest: &mut [u8], sentinel: u8) -> usize {
280 let encoded_size = encode(source, dest);
281 for x in &mut dest[..encoded_size] {
282 *x ^= sentinel;
283 }
284 encoded_size
285}
286
287#[cfg(feature = "alloc")]
288/// Encodes the `source` buffer into a vector, using the [encode] function.
289pub fn encode_vec(source: &[u8]) -> alloc::vec::Vec<u8> {
290 let mut encoded = alloc::vec![0; crate::max_encoding_length(source.len())];
291 let encoded_len = encode(source, &mut encoded[..]);
292 encoded.truncate(encoded_len);
293 encoded
294}
295
296#[cfg(feature = "alloc")]
297/// Encodes the `source` buffer into a vector, using the [encode] function, while also adding
298/// the sentinels around the encoded frame.
299pub fn encode_vec_including_sentinels(source: &[u8]) -> alloc::vec::Vec<u8> {
300 let mut encoded = alloc::vec![0; crate::max_encoding_length(source.len()) + 2];
301 let encoded_len = encode_including_sentinels(source, &mut encoded);
302 encoded.truncate(encoded_len + 2);
303 encoded
304}
305
306#[cfg(feature = "alloc")]
307/// Encodes the `source` buffer into a vector with an arbitrary sentinel value, using the
308/// [encode_with_sentinel] function.
309pub fn encode_vec_with_sentinel(source: &[u8], sentinel: u8) -> alloc::vec::Vec<u8> {
310 let mut encoded = alloc::vec![0; crate::max_encoding_length(source.len())];
311 let encoded_len = encode_with_sentinel(source, &mut encoded[..], sentinel);
312 encoded.truncate(encoded_len);
313 encoded
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::{
320 decode_vec,
321 tests::{test_decode_in_place, test_encode_decode_free_functions},
322 };
323
324 #[test]
325 fn test_encode_0() {
326 // An empty input is encoded as no characters.
327 let mut output = [0xFFu8; 16];
328 let used = encode(&[], &mut output);
329 assert_eq!(used, 1);
330 assert_eq!(output[0], 0x01);
331 }
332
333 fn test_pair(source: &[u8], encoded: &[u8]) {
334 test_encode_decode_free_functions(source, encoded);
335 test_decode_in_place(source, encoded);
336 }
337
338 #[test]
339 fn test_encode_1() {
340 test_pair(&[10, 11, 0, 12], &[3, 10, 11, 2, 12])
341 }
342
343 #[test]
344 fn test_encode_empty() {
345 test_pair(&[], &[1])
346 }
347
348 #[test]
349 fn test_encode_2() {
350 test_pair(&[0, 0, 1, 0], &[1, 1, 2, 1, 1])
351 }
352
353 #[test]
354 fn test_encode_3() {
355 test_pair(&[255, 0], &[2, 255, 1])
356 }
357
358 #[test]
359 fn test_encode_4() {
360 test_pair(&[1], &[2, 1])
361 }
362
363 #[test]
364 fn encode_target_buf_too_small() {
365 let source = &[10, 11, 0, 12];
366 let expected = &[3, 10, 11, 2, 12];
367 for len in 0..expected.len() {
368 let mut dest = alloc::vec![0; len];
369 matches!(
370 try_encode(source, &mut dest).unwrap_err(),
371 DestBufTooSmallError
372 );
373 }
374 }
375
376 #[test]
377 fn try_encode_with_sentinels() {
378 let source = &[10, 11, 0, 12];
379 let expected = &[0, 3, 10, 11, 2, 12, 0];
380 let mut dest = alloc::vec![0; expected.len()];
381 let encoded_len = try_encode_including_sentinels(source, &mut dest).unwrap();
382 assert_eq!(encoded_len, expected.len());
383 assert_eq!(dest[0], 0);
384 assert_eq!(dest[expected.len() - 1], 0);
385 assert_eq!(decode_vec(&dest).unwrap(), source);
386 }
387
388 #[test]
389 fn test_encoding_including_sentinels() {
390 let data = [1, 2, 3];
391 let encoded = encode_vec_including_sentinels(&data);
392 assert_eq!(*encoded.first().unwrap(), 0);
393 assert_eq!(*encoded.last().unwrap(), 0);
394 let data_decoded = decode_vec(&encoded).unwrap();
395 assert_eq!(data_decoded, data);
396 let data_decoded = decode_vec(&encoded[1..]).unwrap();
397 assert_eq!(data_decoded, data);
398 let data_decoded = decode_vec(&encoded[1..encoded.len() - 1]).unwrap();
399 assert_eq!(data_decoded, data);
400 }
401
402 #[test]
403 #[should_panic]
404 fn encode_target_buf_too_small_panicking() {
405 let source = &[10, 11, 0, 12];
406 let expected = &[3, 10, 11, 2, 12];
407 encode(source, &mut alloc::vec![0; expected.len() - 1]);
408 }
409}