cobs_acc/
lib.rs

1#![cfg_attr(not(any(test, feature = "std")), no_std)]
2
3use core::ops::DerefMut;
4
5/// The call to `push_reset` failed due to overflow
6struct Overflow;
7
8/// Basically postcard's cobs accumulator, but without the deser part
9pub struct CobsAccumulator<B: DerefMut<Target = [u8]>> {
10    buf: B,
11    idx: usize,
12    in_overflow: bool,
13}
14
15/// The result of feeding the accumulator.
16#[derive(Debug)]
17pub enum FeedResult<'input, 'buf> {
18    /// Consumed all data, still pending.
19    Consumed,
20
21    /// Buffer was filled. Contains remaining section of input, if any.
22    OverFull(&'input mut [u8]),
23
24    /// Reached end of chunk, but cobs decode failed. Contains remaining
25    /// section of input, if any.
26    DecodeError(&'input mut [u8]),
27
28    /// We decoded a message successfully. The data is currently
29    /// stored in our storage buffer.
30    Success {
31        /// Decoded data.
32        data: &'buf [u8],
33
34        /// Remaining data left in the buffer after deserializing.
35        remaining: &'input mut [u8],
36    },
37
38    /// We decoded a message successfully. The data is currently
39    /// stored in the passed-in input buffer
40    SuccessInput {
41        /// Decoded data.
42        data: &'input [u8],
43
44        /// Remaining data left in the buffer after deserializing.
45        remaining: &'input mut [u8],
46    },
47}
48
49#[cfg(any(feature = "std", test))]
50impl CobsAccumulator<Box<[u8]>> {
51    pub fn new_boxslice(len: usize) -> Self {
52        Self::new(vec![0u8; len].into_boxed_slice())
53    }
54}
55
56impl<B: DerefMut<Target = [u8]>> CobsAccumulator<B> {
57    /// Create a new accumulator.
58    pub fn new(b: B) -> Self {
59        CobsAccumulator {
60            buf: b,
61            idx: 0,
62            in_overflow: false,
63        }
64    }
65
66    /// Appends data to the internal buffer and attempts to deserialize the accumulated data into
67    /// `T`.
68    ///
69    /// This differs from feed, as it allows the `T` to reference data within the internal buffer, but
70    /// mutably borrows the accumulator for the lifetime of the deserialization.
71    /// If `T` does not require the reference, the borrow of `self` ends at the end of the function.
72    pub fn feed_raw<'me, 'input>(
73        &'me mut self,
74        input: &'input mut [u8],
75    ) -> FeedResult<'input, 'me> {
76        // No input? No work!
77        if input.is_empty() {
78            return FeedResult::Consumed;
79        }
80
81        // Can we find any zeroes in the whole input?
82        let zero_pos = input.iter().position(|&i| i == 0);
83        let Some(n) = zero_pos else {
84            // No zero in this entire input.
85            //
86            // Are we currently overflowing?
87            if self.in_overflow {
88                // Yes: overflowing, and no zero to rescue us. Consume the whole
89                // input, remain in overflow.
90                return FeedResult::OverFull(&mut []);
91            }
92
93            // Not overflowing, Does the input fit?
94            return match self.push(input) {
95                // We ate the whole input, and no zero, so we're done here.
96                Ok(()) => FeedResult::Consumed,
97                // If there's NO zero in this input, and we JUST entered the overflow
98                // state, then we're going to consume the entire input, no point in
99                // giving partial data back to the caller.
100                Err(Overflow) => {
101                    self.in_overflow = true;
102                    FeedResult::OverFull(&mut [])
103                }
104            };
105        };
106
107        // Yes! We have an end of message here.
108        // Add one to include the zero in the "take" portion
109        // of the buffer, rather than in "release".
110        let (take, release) = input.split_at_mut(n + 1);
111
112        // If we got a zero, this frees us from the overflow condition,
113        // don't attempt to decode, we've already lost some part of this
114        // message.
115        if self.in_overflow {
116            self.in_overflow = false;
117            return FeedResult::OverFull(release);
118        }
119
120        // If there's no data in the buffer, then we don't need to copy it in,
121        // just decode directly in the input buffer without doing an extra
122        // memcpy
123        if self.idx == 0 {
124            return match cobs::decode_in_place(take) {
125                Ok(ct) => FeedResult::SuccessInput {
126                    data: &take[..ct],
127                    remaining: release,
128                },
129                Err(_) => FeedResult::DecodeError(release),
130            };
131        }
132
133        // Does it fit? This will give us a view of the buffer, but reset the
134        // count, so the next call will see an empty buffer.
135        let Ok(used) = self.push_reset(take) else {
136            // If we overflowed, tell the caller. DON'T mark ourselves as
137            // in-overflow, because we DID get a zero, which clears the
138            // state, we just lost the current message. We are ready to
139            // start again with `release` on the next call.
140            return FeedResult::OverFull(release);
141        };
142
143        // Finally: attempt to de-cobs the contents of our storage buffer.
144        match cobs::decode_in_place(used) {
145            // It worked! Tell the caller it went great
146            Ok(ct) => FeedResult::Success {
147                data: &used[..ct],
148                remaining: release,
149            },
150            // It did NOT work, tell the caller
151            Err(_) => FeedResult::DecodeError(release),
152        }
153    }
154
155    #[inline]
156    fn push(&mut self, data: &[u8]) -> Result<(), Overflow> {
157        let old_idx = self.idx;
158        let new_end = old_idx + data.len();
159        if let Some(sli) = self.buf.get_mut(old_idx..new_end) {
160            sli.copy_from_slice(data);
161            self.idx = self.buf.len().min(new_end);
162            Ok(())
163        } else {
164            self.idx = 0;
165            Err(Overflow)
166        }
167    }
168
169    #[inline]
170    fn push_reset(&'_ mut self, data: &[u8]) -> Result<&'_ mut [u8], Overflow> {
171        let old_idx = self.idx;
172        let new_end = old_idx + data.len();
173
174        let res = if let Some(sli) = self.buf.get_mut(..new_end) {
175            sli[old_idx..].copy_from_slice(data);
176            Ok(sli)
177        } else {
178            Err(Overflow)
179        };
180        self.idx = 0;
181        res
182    }
183
184    #[doc(hidden)]
185    #[cfg(test)]
186    pub fn contents(&self) -> Option<&[u8]> {
187        if self.in_overflow {
188            None
189        } else {
190            Some(&self.buf[..self.idx])
191        }
192    }
193}
194
195#[cfg(all(test, feature = "std"))]
196mod test {
197    use crate::{CobsAccumulator, FeedResult};
198
199    #[test]
200    fn smoke() {
201        let mut acc = CobsAccumulator::new_boxslice(16);
202        let mut input = vec![];
203        for i in 0..6 {
204            input.push(0);
205            input.push(i);
206        }
207        let mut inenc = cobs::encode_vec(&input);
208        inenc.push(0);
209        assert_eq!(inenc.len(), 14);
210        let inenc = inenc;
211
212        // No matter the stride of the input, we get the expected data out
213        for chsz in 1..inenc.len() {
214            let mut inenc = inenc.clone();
215            let mut got_data = None;
216            let mut fed = 0;
217            for ch in inenc.chunks_mut(chsz) {
218                fed += ch.len();
219                match acc.feed_raw(ch) {
220                    FeedResult::Consumed => {}
221                    FeedResult::Success { data, remaining } => {
222                        assert!(remaining.is_empty());
223                        got_data = Some(data.to_vec());
224                        break;
225                    }
226                    _ => panic!(),
227                }
228            }
229            assert_eq!(fed, 14);
230            let got = got_data.unwrap();
231            assert_eq!(got, input);
232        }
233
234        // Do it again, but we might have two messages
235        let mut twoenc = vec![];
236        twoenc.extend_from_slice(&inenc);
237        twoenc.extend_from_slice(&inenc);
238        let twoenc = twoenc;
239
240        for chsz in 1..twoenc.len() {
241            let mut twoenc = twoenc.clone();
242            let mut got_data = 0;
243            let mut fed = 0;
244            for mut ch in twoenc.chunks_mut(chsz) {
245                fed += ch.len();
246                'feed: loop {
247                    match acc.feed_raw(ch) {
248                        FeedResult::Consumed => break 'feed,
249                        FeedResult::Success { data, remaining } => {
250                            assert_eq!(data, &input);
251                            got_data += 1;
252                            ch = remaining;
253                        }
254                        FeedResult::SuccessInput { data, remaining } => {
255                            assert_eq!(data, &input);
256                            got_data += 1;
257                            ch = remaining;
258                        }
259                        e => panic!("{e:?}"),
260                    }
261                }
262            }
263            assert_eq!(fed, 28);
264            assert_eq!(got_data, 2);
265        }
266    }
267
268    #[test]
269    fn decode_err() {
270        let mut acc = CobsAccumulator::new_boxslice(16);
271        let mut input = vec![];
272        for i in 0..6 {
273            input.push(0);
274            input.push(i);
275        }
276        let mut inenc = cobs::encode_vec(&input);
277        let mut badenc = inenc.clone();
278        inenc.push(0);
279        badenc.push(4);
280        badenc.push(0);
281        assert_eq!(inenc.len(), 14);
282        assert_eq!(badenc.len(), 15);
283        let inenc = inenc;
284        let badenc = badenc;
285
286        // Set up good bad good as the pattern, ensure we get both
287        // goods and don't get the bad.
288
289        let mut sandwich = vec![];
290        sandwich.extend_from_slice(&inenc);
291        sandwich.extend_from_slice(&badenc);
292        sandwich.extend_from_slice(&inenc);
293        let sandwich = sandwich;
294
295        for chsz in 1..sandwich.len() {
296            let mut sandwich = sandwich.clone();
297            let mut got_data = 0;
298            let mut bad_data = 0;
299            let mut fed = 0;
300            for mut ch in sandwich.chunks_mut(chsz) {
301                fed += ch.len();
302                'feed: loop {
303                    match acc.feed_raw(ch) {
304                        FeedResult::Consumed => break 'feed,
305                        FeedResult::Success { data, remaining } => {
306                            assert_eq!(data, &input);
307                            got_data += 1;
308                            ch = remaining;
309                        }
310                        FeedResult::SuccessInput { data, remaining } => {
311                            assert_eq!(data, &input);
312                            got_data += 1;
313                            ch = remaining;
314                        }
315                        FeedResult::DecodeError(remaining) => {
316                            bad_data += 1;
317                            ch = remaining;
318                        }
319                        e => panic!("{e:?}"),
320                    }
321                }
322            }
323            assert_eq!(fed, inenc.len() * 2 + badenc.len());
324            assert_eq!(got_data, 2);
325            assert_eq!(bad_data, 1);
326        }
327    }
328
329    #[test]
330    fn overflow_err() {
331        let mut acc = CobsAccumulator::new_boxslice(16);
332        let mut input = vec![];
333        for i in 0..6 {
334            input.push(0);
335            input.push(i);
336        }
337        let mut inenc = cobs::encode_vec(&input);
338        inenc.push(0);
339
340        let mut biginput = vec![];
341        for i in 0..25 {
342            biginput.push(0);
343            biginput.push(i);
344        }
345        let mut bigenc = cobs::encode_vec(&biginput);
346        bigenc.push(0);
347
348        assert_eq!(inenc.len(), 14);
349        assert_eq!(bigenc.len(), 52);
350        let inenc = inenc;
351        let bigenc = bigenc;
352
353        // Set up good bad good as the pattern, ensure we get both
354        // goods and don't get the bad.
355
356        let mut sandwich = vec![];
357        sandwich.extend_from_slice(&inenc);
358        sandwich.extend_from_slice(&bigenc);
359        sandwich.extend_from_slice(&inenc);
360        let sandwich = sandwich;
361
362        // NOTE: we cap the max chunk size here to the size of the
363        // storage buffer, because in cases where we get the ENTIRE message
364        // in input, we don't actually overflow!
365        for chsz in 1..16 {
366            let mut sandwich = sandwich.clone();
367            let mut got_data = 0;
368            let mut fed = 0;
369            for mut ch in sandwich.chunks_mut(chsz) {
370                fed += ch.len();
371                println!("CH: {ch:?}");
372                'feed: loop {
373                    println!("{:?} <- {ch:?}", acc.contents());
374                    match acc.feed_raw(ch) {
375                        FeedResult::Consumed => break 'feed,
376                        FeedResult::Success { data, remaining } => {
377                            assert_eq!(data, &input);
378                            got_data += 1;
379                            ch = remaining;
380                        }
381                        FeedResult::SuccessInput { data, remaining } => {
382                            assert_eq!(data, &input);
383                            got_data += 1;
384                            ch = remaining;
385                        }
386                        FeedResult::OverFull(remaining) => {
387                            ch = remaining;
388                        }
389                        e => panic!("{e:?}"),
390                    }
391                }
392            }
393            assert_eq!(fed, inenc.len() * 2 + bigenc.len());
394            assert_eq!(got_data, 2);
395        }
396    }
397
398    #[test]
399    fn permute_256() {
400        let mut acc = CobsAccumulator::new_boxslice(16);
401
402        // 00: good message
403        let mut input = vec![];
404        for i in 0..6 {
405            input.push(0);
406            input.push(i);
407        }
408        let mut inenc = cobs::encode_vec(&input);
409        inenc.push(0);
410        let inenc = inenc;
411
412        // 01: bad decode
413        let mut binput = vec![];
414        for i in 0..6 {
415            binput.push(0);
416            binput.push(i);
417        }
418        let mut badenc = cobs::encode_vec(&binput);
419        badenc.push(4);
420        badenc.push(0);
421        let badenc = badenc;
422
423        // 10: empty good
424        let empenc = vec![0u8];
425
426        // 11: overflow
427        let mut biginput = vec![];
428        for i in 0..25 {
429            biginput.push(0);
430            biginput.push(i);
431        }
432        let mut bigenc = cobs::encode_vec(&biginput);
433        bigenc.push(0);
434        let bigenc = bigenc;
435
436        // Use an 8 bit integer to come up with 256 test cases,
437        // each with a stream made up of 4 of the 4 above scenarios
438        for mut scenario_byte in 0u8..=255u8 {
439            let mut input_stream = vec![];
440            let mut good_emptys = 0;
441            let mut good_data = 0;
442            let mut bad_dec = 0;
443            for _ in 0..4 {
444                let scen = scenario_byte & 0b11;
445                scenario_byte >>= 2;
446                match scen {
447                    0b00 => {
448                        input_stream.extend_from_slice(&inenc);
449                        good_data += 1;
450                    }
451                    0b01 => {
452                        input_stream.extend_from_slice(&badenc);
453                        bad_dec += 1;
454                    }
455                    0b10 => {
456                        input_stream.extend_from_slice(&empenc);
457                        good_emptys += 1;
458                    }
459                    _ => {
460                        input_stream.extend_from_slice(&bigenc);
461                    }
462                }
463            }
464
465            // NOTE: we cap the max chunk size here to one less than the
466            // overflow case, because in cases where we get the ENTIRE message
467            // in input, we don't actually overflow!
468            for chsz in 1..bigenc.len() {
469                let mut input_stream = input_stream.clone();
470                let mut got_data = 0;
471                let mut got_empty = 0;
472                let mut got_bads = 0;
473                let mut fed = 0;
474                for mut ch in input_stream.chunks_mut(chsz) {
475                    fed += ch.len();
476                    println!("CH: {ch:?}");
477                    'feed: loop {
478                        println!("{:?} <- {ch:?}", acc.contents());
479                        match acc.feed_raw(ch) {
480                            FeedResult::Consumed => break 'feed,
481                            FeedResult::Success { data, remaining } => {
482                                if data.is_empty() {
483                                    got_empty += 1;
484                                } else {
485                                    assert_eq!(data, &input);
486                                    got_data += 1;
487                                }
488                                ch = remaining;
489                            }
490                            FeedResult::SuccessInput { data, remaining } => {
491                                if data.is_empty() {
492                                    got_empty += 1;
493                                } else {
494                                    assert_eq!(data, &input);
495                                    got_data += 1;
496                                }
497                                ch = remaining;
498                            }
499                            FeedResult::DecodeError(remaining) => {
500                                got_bads += 1;
501                                ch = remaining;
502                            }
503                            FeedResult::OverFull(remaining) => {
504                                ch = remaining;
505                            }
506                        }
507                    }
508                }
509                assert_eq!(fed, input_stream.len());
510                assert_eq!(got_data, good_data);
511                assert_eq!(got_bads, bad_dec);
512                assert_eq!(got_empty, good_emptys);
513            }
514        }
515    }
516}