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}