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}