postcard/
accumulator.rs

1//! An accumulator used to collect chunked COBS data and deserialize it.
2
3use serde::Deserialize;
4
5/// An accumulator used to collect chunked COBS data and deserialize it.
6///
7/// This is often useful when you receive "parts" of the message at a time, for example when draining
8/// a serial port buffer that may not contain an entire uninterrupted message.
9///
10/// # Examples
11///
12/// Deserialize a struct by reading chunks from a [`Read`]er.
13///
14/// ```rust
15/// use postcard::accumulator::{CobsAccumulator, FeedResult};
16/// use serde::Deserialize;
17/// use std::io::Read;
18///
19/// # let mut input_buf = [0u8; 256];
20/// # #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
21/// # struct MyData {
22/// #     a: u32,
23/// #     b: bool,
24/// #     c: [u8; 16],
25/// # }
26/// let input = /* Anything that implements the `Read` trait */
27/// # postcard::to_slice_cobs(&MyData {
28/// #     a: 0xabcdef00,
29/// #     b: true,
30/// #     c: [0xab; 16],
31/// # }, &mut input_buf).unwrap();
32/// # let mut input = &input[..];
33///
34/// let mut raw_buf = [0u8; 32];
35/// let mut cobs_buf: CobsAccumulator<256> = CobsAccumulator::new();
36///
37/// while let Ok(ct) = input.read(&mut raw_buf) {
38///     // Finished reading input
39///     if ct == 0 {
40///         break;
41///     }
42///
43///     let buf = &raw_buf[..ct];
44///     let mut window = &buf[..];
45///
46///     'cobs: while !window.is_empty() {
47///         window = match cobs_buf.feed::<MyData>(&window) {
48///             FeedResult::Consumed => break 'cobs,
49///             FeedResult::OverFull(new_wind) => new_wind,
50///             FeedResult::DeserError(new_wind) => new_wind,
51///             FeedResult::Success { data, remaining } => {
52///                 // Do something with `data: MyData` here.
53///
54///                 dbg!(data);
55///
56///                 remaining
57///             }
58///         };
59///     }
60/// }
61/// ```
62///
63/// [`Read`]: std::io::Read
64#[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
65pub struct CobsAccumulator<const N: usize> {
66    buf: [u8; N],
67    idx: usize,
68}
69
70/// The result of feeding the accumulator.
71#[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
72pub enum FeedResult<'a, T> {
73    /// Consumed all data, still pending.
74    Consumed,
75
76    /// Buffer was filled. Contains remaining section of input, if any.
77    OverFull(&'a [u8]),
78
79    /// Reached end of chunk, but deserialization failed. Contains remaining section of input, if.
80    /// any
81    DeserError(&'a [u8]),
82
83    /// Deserialization complete. Contains deserialized data and remaining section of input, if any.
84    Success {
85        /// Deserialize data.
86        data: T,
87
88        /// Remaining data left in the buffer after deserializing.
89        remaining: &'a [u8],
90    },
91}
92
93impl<const N: usize> Default for CobsAccumulator<N> {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl<const N: usize> CobsAccumulator<N> {
100    /// Create a new accumulator.
101    pub const fn new() -> Self {
102        CobsAccumulator {
103            buf: [0; N],
104            idx: 0,
105        }
106    }
107
108    /// Appends data to the internal buffer and attempts to deserialize the accumulated data into
109    /// `T`.
110    #[inline]
111    pub fn feed<'a, T>(&mut self, input: &'a [u8]) -> FeedResult<'a, T>
112    where
113        T: for<'de> Deserialize<'de>,
114    {
115        self.feed_ref(input)
116    }
117
118    /// Appends data to the internal buffer and attempts to deserialize the accumulated data into
119    /// `T`.
120    ///
121    /// This differs from feed, as it allows the `T` to reference data within the internal buffer, but
122    /// mutably borrows the accumulator for the lifetime of the deserialization.
123    /// If `T` does not require the reference, the borrow of `self` ends at the end of the function.
124    pub fn feed_ref<'de, 'a, T>(&'de mut self, input: &'a [u8]) -> FeedResult<'a, T>
125    where
126        T: Deserialize<'de>,
127    {
128        if input.is_empty() {
129            return FeedResult::Consumed;
130        }
131
132        let zero_pos = input.iter().position(|&i| i == 0);
133
134        if let Some(n) = zero_pos {
135            // Yes! We have an end of message here.
136            // Add one to include the zero in the "take" portion
137            // of the buffer, rather than in "release".
138            let (take, release) = input.split_at(n + 1);
139
140            // Does it fit?
141            if (self.idx + take.len()) <= N {
142                // Aw yiss - add to array
143                self.extend_unchecked(take);
144
145                let retval = match crate::from_bytes_cobs::<T>(&mut self.buf[..self.idx]) {
146                    Ok(t) => FeedResult::Success {
147                        data: t,
148                        remaining: release,
149                    },
150                    Err(_) => FeedResult::DeserError(release),
151                };
152                self.idx = 0;
153                retval
154            } else {
155                self.idx = 0;
156                FeedResult::OverFull(release)
157            }
158        } else {
159            // Does it fit?
160            if (self.idx + input.len()) > N {
161                // nope
162                let new_start = N - self.idx;
163                self.idx = 0;
164                FeedResult::OverFull(&input[new_start..])
165            } else {
166                // yup!
167                self.extend_unchecked(input);
168                FeedResult::Consumed
169            }
170        }
171    }
172
173    /// Extend the internal buffer with the given input.
174    ///
175    /// # Panics
176    ///
177    /// Will panic if the input does not fit in the internal buffer.
178    fn extend_unchecked(&mut self, input: &[u8]) {
179        let new_end = self.idx + input.len();
180        self.buf[self.idx..new_end].copy_from_slice(input);
181        self.idx = new_end;
182    }
183}
184
185#[cfg(test)]
186mod test {
187    use super::*;
188
189    #[test]
190    fn loop_test() {
191        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
192        struct Demo {
193            a: u32,
194            b: u8,
195        }
196
197        let mut raw_buf = [0u8; 64];
198        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
199
200        let ser = crate::to_slice_cobs(&Demo { a: 10, b: 20 }, &mut raw_buf).unwrap();
201
202        if let FeedResult::Success { data, remaining } = cobs_buf.feed(ser) {
203            assert_eq!(Demo { a: 10, b: 20 }, data);
204            assert_eq!(remaining.len(), 0);
205        } else {
206            panic!()
207        }
208    }
209
210    #[test]
211    #[cfg(feature = "heapless")]
212    fn double_loop_test() {
213        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
214        struct Demo {
215            a: u32,
216            b: u8,
217        }
218
219        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
220
221        let mut ser = crate::to_vec_cobs::<_, 128>(&Demo { a: 10, b: 20 }).unwrap();
222        let ser2 = crate::to_vec_cobs::<_, 128>(&Demo {
223            a: 256854231,
224            b: 115,
225        })
226        .unwrap();
227        ser.extend(ser2);
228
229        let (demo1, ser) = if let FeedResult::Success { data, remaining } = cobs_buf.feed(&ser[..])
230        {
231            (data, remaining)
232        } else {
233            panic!()
234        };
235
236        assert_eq!(Demo { a: 10, b: 20 }, demo1);
237
238        let demo2 = if let FeedResult::Success { data, remaining } = cobs_buf.feed(ser) {
239            assert_eq!(remaining.len(), 0);
240            data
241        } else {
242            panic!()
243        };
244
245        assert_eq!(Demo { a: 10, b: 20 }, demo1);
246        assert_eq!(
247            Demo {
248                a: 256854231,
249                b: 115
250            },
251            demo2
252        );
253    }
254
255    #[test]
256    #[cfg(feature = "heapless")]
257    fn loop_test_ref() {
258        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
259        struct Demo<'a> {
260            a: u32,
261            b: u8,
262            c: &'a str,
263        }
264
265        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
266
267        let ser = crate::to_vec_cobs::<_, 128>(&Demo {
268            a: 10,
269            b: 20,
270            c: "test",
271        })
272        .unwrap();
273
274        if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(&ser[..]) {
275            assert_eq!(
276                Demo {
277                    a: 10,
278                    b: 20,
279                    c: "test"
280                },
281                data
282            );
283            assert_eq!(remaining.len(), 0);
284        } else {
285            panic!()
286        }
287    }
288
289    #[test]
290    #[cfg(feature = "heapless")]
291    fn double_loop_test_ref() {
292        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
293        struct Demo<'a> {
294            a: u32,
295            b: u8,
296            c: &'a str,
297        }
298
299        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
300
301        let mut ser = crate::to_vec_cobs::<_, 128>(&Demo {
302            a: 10,
303            b: 20,
304            c: "test",
305        })
306        .unwrap();
307        let ser2 = crate::to_vec_cobs::<_, 128>(&Demo {
308            a: 256854231,
309            b: 115,
310            c: "different test",
311        })
312        .unwrap();
313        ser.extend(ser2);
314
315        let (data, ser) =
316            if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(&ser[..]) {
317                (data, remaining)
318            } else {
319                panic!()
320            };
321
322        assert!(
323            Demo {
324                a: 10,
325                b: 20,
326                c: "test"
327            } == data
328        );
329
330        let demo2 = if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(ser) {
331            assert!(remaining.is_empty());
332            data
333        } else {
334            panic!()
335        };
336
337        // Uncommenting the below line causes the test to no-longer compile, as cobs_buf would then be mutably borrowed twice
338        //assert!(Demo { a: 10, b: 20, c : "test" } == data);
339
340        assert!(
341            Demo {
342                a: 256854231,
343                b: 115,
344                c: "different test"
345            } == demo2
346        );
347    }
348
349    #[test]
350    #[cfg(feature = "heapless")]
351    fn extend_unchecked_in_bounds_test() {
352        // Test bug present in revision abcb407:
353        // extend_unchecked may be passed slice with size 1 greater than accumulator buffer causing panic
354
355        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
356        struct Demo {
357            data: [u8; 10],
358        }
359
360        let data = crate::to_vec_cobs::<_, 128>(&Demo { data: [0xcc; 10] }).unwrap();
361        assert_eq!(data.len(), 12); // 1 byte for offset + 1 sentinel byte appended
362
363        // Accumulator has 1 byte less space than encoded message
364        let mut acc: CobsAccumulator<11> = CobsAccumulator::new();
365        assert!(matches!(
366            acc.feed::<Demo>(&data[..]),
367            FeedResult::OverFull(_)
368        ));
369
370        // Accumulator is juuuuust right
371        let mut acc: CobsAccumulator<12> = CobsAccumulator::new();
372        assert!(matches!(
373            acc.feed::<Demo>(&data[..]),
374            FeedResult::Success { .. }
375        ));
376    }
377}