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}