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}