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 any.
80    DeserError(&'a [u8]),
81
82    /// Deserialization complete. Contains deserialized data and remaining section of input, if any.
83    Success {
84        /// Deserialize data.
85        data: T,
86
87        /// Remaining data left in the buffer after deserializing.
88        remaining: &'a [u8],
89    },
90}
91
92impl<const N: usize> Default for CobsAccumulator<N> {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98impl<const N: usize> CobsAccumulator<N> {
99    /// Create a new accumulator.
100    pub const fn new() -> Self {
101        CobsAccumulator {
102            buf: [0; N],
103            idx: 0,
104        }
105    }
106
107    /// Appends data to the internal buffer and attempts to deserialize the accumulated data into
108    /// `T`.
109    #[inline]
110    pub fn feed<'a, T>(&mut self, input: &'a [u8]) -> FeedResult<'a, T>
111    where
112        T: for<'de> Deserialize<'de>,
113    {
114        self.feed_ref(input)
115    }
116
117    /// Appends data to the internal buffer and attempts to deserialize the accumulated data into
118    /// `T`.
119    ///
120    /// This differs from feed, as it allows the `T` to reference data within the internal buffer, but
121    /// mutably borrows the accumulator for the lifetime of the deserialization.
122    /// If `T` does not require the reference, the borrow of `self` ends at the end of the function.
123    pub fn feed_ref<'de, 'a, T>(&'de mut self, input: &'a [u8]) -> FeedResult<'a, T>
124    where
125        T: Deserialize<'de>,
126    {
127        if input.is_empty() {
128            return FeedResult::Consumed;
129        }
130
131        let zero_pos = input.iter().position(|&i| i == 0);
132
133        if let Some(n) = zero_pos {
134            // Yes! We have an end of message here.
135            // Add one to include the zero in the "take" portion
136            // of the buffer, rather than in "release".
137            let (take, release) = input.split_at(n + 1);
138
139            // Does it fit?
140            if (self.idx + take.len()) <= N {
141                // Aw yiss - add to array
142                self.extend_unchecked(take);
143
144                let retval = match crate::from_bytes_cobs::<T>(&mut self.buf[..self.idx]) {
145                    Ok(t) => FeedResult::Success {
146                        data: t,
147                        remaining: release,
148                    },
149                    Err(_) => FeedResult::DeserError(release),
150                };
151                self.idx = 0;
152                retval
153            } else {
154                self.idx = 0;
155                FeedResult::OverFull(release)
156            }
157        } else {
158            // Does it fit?
159            if (self.idx + input.len()) > N {
160                // nope
161                let new_start = N - self.idx;
162                self.idx = 0;
163                FeedResult::OverFull(&input[new_start..])
164            } else {
165                // yup!
166                self.extend_unchecked(input);
167                FeedResult::Consumed
168            }
169        }
170    }
171
172    /// Extend the internal buffer with the given input.
173    ///
174    /// # Panics
175    ///
176    /// Will panic if the input does not fit in the internal buffer.
177    fn extend_unchecked(&mut self, input: &[u8]) {
178        let new_end = self.idx + input.len();
179        self.buf[self.idx..new_end].copy_from_slice(input);
180        self.idx = new_end;
181    }
182}
183
184#[cfg(test)]
185mod test {
186    use super::*;
187
188    #[test]
189    fn loop_test() {
190        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
191        struct Demo {
192            a: u32,
193            b: u8,
194        }
195
196        let mut raw_buf = [0u8; 64];
197        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
198
199        let ser = crate::to_slice_cobs(&Demo { a: 10, b: 20 }, &mut raw_buf).unwrap();
200
201        if let FeedResult::Success { data, remaining } = cobs_buf.feed(ser) {
202            assert_eq!(Demo { a: 10, b: 20 }, data);
203            assert_eq!(remaining.len(), 0);
204        } else {
205            panic!()
206        }
207    }
208
209    #[test]
210    #[cfg(feature = "heapless")]
211    fn double_loop_test() {
212        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
213        struct Demo {
214            a: u32,
215            b: u8,
216        }
217
218        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
219
220        let mut ser = crate::to_vec_cobs::<_, 128>(&Demo { a: 10, b: 20 }).unwrap();
221        let ser2 = crate::to_vec_cobs::<_, 128>(&Demo {
222            a: 256854231,
223            b: 115,
224        })
225        .unwrap();
226        ser.extend(ser2);
227
228        let (demo1, ser) = if let FeedResult::Success { data, remaining } = cobs_buf.feed(&ser[..])
229        {
230            (data, remaining)
231        } else {
232            panic!()
233        };
234
235        assert_eq!(Demo { a: 10, b: 20 }, demo1);
236
237        let demo2 = if let FeedResult::Success { data, remaining } = cobs_buf.feed(ser) {
238            assert_eq!(remaining.len(), 0);
239            data
240        } else {
241            panic!()
242        };
243
244        assert_eq!(Demo { a: 10, b: 20 }, demo1);
245        assert_eq!(
246            Demo {
247                a: 256854231,
248                b: 115
249            },
250            demo2
251        );
252    }
253
254    #[test]
255    #[cfg(feature = "heapless")]
256    fn loop_test_ref() {
257        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
258        struct Demo<'a> {
259            a: u32,
260            b: u8,
261            c: &'a str,
262        }
263
264        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
265
266        let ser = crate::to_vec_cobs::<_, 128>(&Demo {
267            a: 10,
268            b: 20,
269            c: "test",
270        })
271        .unwrap();
272
273        if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(&ser[..]) {
274            assert_eq!(
275                Demo {
276                    a: 10,
277                    b: 20,
278                    c: "test"
279                },
280                data
281            );
282            assert_eq!(remaining.len(), 0);
283        } else {
284            panic!()
285        }
286    }
287
288    #[test]
289    #[cfg(feature = "heapless")]
290    fn double_loop_test_ref() {
291        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
292        struct Demo<'a> {
293            a: u32,
294            b: u8,
295            c: &'a str,
296        }
297
298        let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
299
300        let mut ser = crate::to_vec_cobs::<_, 128>(&Demo {
301            a: 10,
302            b: 20,
303            c: "test",
304        })
305        .unwrap();
306        let ser2 = crate::to_vec_cobs::<_, 128>(&Demo {
307            a: 256854231,
308            b: 115,
309            c: "different test",
310        })
311        .unwrap();
312        ser.extend(ser2);
313
314        let (data, ser) =
315            if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(&ser[..]) {
316                (data, remaining)
317            } else {
318                panic!()
319            };
320
321        assert!(
322            Demo {
323                a: 10,
324                b: 20,
325                c: "test"
326            } == data
327        );
328
329        let demo2 = if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(ser) {
330            assert!(remaining.is_empty());
331            data
332        } else {
333            panic!()
334        };
335
336        // Uncommenting the below line causes the test to no-longer compile, as cobs_buf would then be mutably borrowed twice
337        //assert!(Demo { a: 10, b: 20, c : "test" } == data);
338
339        assert!(
340            Demo {
341                a: 256854231,
342                b: 115,
343                c: "different test"
344            } == demo2
345        );
346    }
347
348    #[test]
349    #[cfg(feature = "heapless")]
350    fn extend_unchecked_in_bounds_test() {
351        // Test bug present in revision abcb407:
352        // extend_unchecked may be passed slice with size 1 greater than accumulator buffer causing panic
353
354        #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
355        struct Demo {
356            data: [u8; 10],
357        }
358
359        let data = crate::to_vec_cobs::<_, 128>(&Demo { data: [0xcc; 10] }).unwrap();
360        assert_eq!(data.len(), 12); // 1 byte for offset + 1 sentinel byte appended
361
362        // Accumulator has 1 byte less space than encoded message
363        let mut acc: CobsAccumulator<11> = CobsAccumulator::new();
364        assert!(matches!(
365            acc.feed::<Demo>(&data[..]),
366            FeedResult::OverFull(_)
367        ));
368
369        // Accumulator is juuuuust right
370        let mut acc: CobsAccumulator<12> = CobsAccumulator::new();
371        assert!(matches!(
372            acc.feed::<Demo>(&data[..]),
373            FeedResult::Success { .. }
374        ));
375    }
376}