1use crate::{
20 FRAME_HEADER_SIZE, FRAME_TRAILER_SIZE, FrameDecodeError, FrameEncodeError, MAX_PAYLOAD_FIELD,
21 MAX_PRE_COBS_FRAME, MAX_WIRE_FRAME, crc::crc16,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27pub enum FrameResult<'a> {
28 Ok {
29 type_id: u8,
30 tag: u16,
31 payload: &'a [u8],
32 },
33 Err(FrameDecodeError),
34}
35
36pub const MAX_COBS_ENCODED: usize = MAX_WIRE_FRAME - 1;
41
42pub fn encode_frame(
49 type_id: u8,
50 tag: u16,
51 payload: &[u8],
52 out: &mut [u8],
53) -> Result<usize, FrameEncodeError> {
54 if payload.len() > MAX_PAYLOAD_FIELD {
55 return Err(FrameEncodeError::PayloadTooLarge);
56 }
57 let pre_cobs_len = FRAME_HEADER_SIZE + payload.len() + FRAME_TRAILER_SIZE;
58
59 let mut scratch = [0u8; MAX_PRE_COBS_FRAME];
60 scratch[0] = type_id;
61 scratch[1..3].copy_from_slice(&tag.to_le_bytes());
62 scratch[3..3 + payload.len()].copy_from_slice(payload);
63 let crc = crc16(&scratch[..FRAME_HEADER_SIZE + payload.len()]);
64 scratch[FRAME_HEADER_SIZE + payload.len()..pre_cobs_len].copy_from_slice(&crc.to_le_bytes());
65
66 let mut cobs_scratch = [0u8; MAX_COBS_ENCODED];
67 let encoded_len = ucobs::encode(&scratch[..pre_cobs_len], &mut cobs_scratch)
68 .ok_or(FrameEncodeError::CobsEncode)?;
69 let total = encoded_len + 1;
70 if out.len() < total {
71 return Err(FrameEncodeError::BufferTooSmall);
72 }
73 out[..encoded_len].copy_from_slice(&cobs_scratch[..encoded_len]);
74 out[encoded_len] = 0x00;
75 Ok(total)
76}
77
78pub struct FrameDecoder {
88 buf: [u8; MAX_COBS_ENCODED],
89 len: usize,
90 overflowed: bool,
91}
92
93impl FrameDecoder {
94 pub const fn new() -> Self {
95 Self {
96 buf: [0u8; MAX_COBS_ENCODED],
97 len: 0,
98 overflowed: false,
99 }
100 }
101
102 pub fn reset(&mut self) {
104 self.len = 0;
105 self.overflowed = false;
106 }
107
108 pub fn feed<F: FnMut(FrameResult<'_>)>(&mut self, data: &[u8], mut on_frame: F) {
111 for &byte in data {
112 if byte == 0x00 {
113 if self.overflowed || self.len == 0 {
114 self.len = 0;
116 self.overflowed = false;
117 continue;
118 }
119 let mut decoded = [0u8; MAX_PRE_COBS_FRAME];
120 match ucobs::decode(&self.buf[..self.len], &mut decoded) {
121 Some(n) => {
122 emit_decoded(&decoded[..n], &mut on_frame);
123 }
124 None => on_frame(FrameResult::Err(FrameDecodeError::Cobs)),
125 }
126 self.len = 0;
127 continue;
128 }
129 if self.overflowed {
130 continue;
132 }
133 if self.len < self.buf.len() {
134 self.buf[self.len] = byte;
135 self.len += 1;
136 } else {
137 self.overflowed = true;
138 self.len = 0;
139 }
140 }
141 }
142}
143
144impl Default for FrameDecoder {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150fn emit_decoded<F: FnMut(FrameResult<'_>)>(decoded: &[u8], on_frame: &mut F) {
151 if decoded.len() < FRAME_HEADER_SIZE + FRAME_TRAILER_SIZE {
152 on_frame(FrameResult::Err(FrameDecodeError::TooShort));
153 return;
154 }
155 let crc_start = decoded.len() - FRAME_TRAILER_SIZE;
156 let body = &decoded[..crc_start];
157 let expected = crc16(body);
158 let got = u16::from_le_bytes([decoded[crc_start], decoded[crc_start + 1]]);
159 if expected != got {
160 on_frame(FrameResult::Err(FrameDecodeError::Crc));
161 return;
162 }
163 let type_id = body[0];
164 let tag = u16::from_le_bytes([body[1], body[2]]);
165 let payload = &body[FRAME_HEADER_SIZE..];
166 on_frame(FrameResult::Ok {
167 type_id,
168 tag,
169 payload,
170 });
171}
172
173#[cfg(test)]
174#[allow(clippy::panic, clippy::unwrap_used)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn max_cobs_encoded_is_wire_frame_minus_one() {
180 assert_eq!(MAX_COBS_ENCODED, 283);
183 assert_eq!(MAX_COBS_ENCODED, MAX_WIRE_FRAME - 1);
184 }
185
186 fn roundtrip(type_id: u8, tag: u16, payload: &[u8]) {
187 let mut wire = [0u8; MAX_WIRE_FRAME];
188 let n = encode_frame(type_id, tag, payload, &mut wire).unwrap();
189
190 let mut decoder = FrameDecoder::new();
191 let mut emitted: heapless::Vec<(u8, u16, heapless::Vec<u8, 300>), 4> = heapless::Vec::new();
192 decoder.feed(&wire[..n], |res| match res {
193 FrameResult::Ok {
194 type_id,
195 tag,
196 payload,
197 } => {
198 let mut p = heapless::Vec::new();
199 p.extend_from_slice(payload).unwrap();
200 emitted.push((type_id, tag, p)).unwrap();
201 }
202 FrameResult::Err(e) => panic!("decode error: {:?}", e),
203 });
204 assert_eq!(emitted.len(), 1);
205 assert_eq!(emitted[0].0, type_id);
206 assert_eq!(emitted[0].1, tag);
207 assert_eq!(emitted[0].2.as_slice(), payload);
208 }
209
210 #[test]
211 fn empty_payload() {
212 roundtrip(0x01, 0x0001, &[]);
213 }
214
215 #[test]
216 fn one_byte_payload() {
217 roundtrip(0x04, 0x1234, &[0xAA]);
218 }
219
220 #[test]
221 fn max_payload() {
222 let big = [0x42u8; MAX_PAYLOAD_FIELD];
223 roundtrip(0xC0, 0x0000, &big);
224 }
225
226 #[test]
227 fn rejects_too_large_payload() {
228 let big = [0u8; MAX_PAYLOAD_FIELD + 1];
229 let mut wire = [0u8; MAX_WIRE_FRAME];
230 assert!(matches!(
231 encode_frame(0x01, 1, &big, &mut wire),
232 Err(FrameEncodeError::PayloadTooLarge)
233 ));
234 }
235
236 #[test]
237 fn rejects_small_output_buffer() {
238 let mut tiny = [0u8; 4];
239 assert!(matches!(
240 encode_frame(0x01, 1, b"hello", &mut tiny),
241 Err(FrameEncodeError::BufferTooSmall)
242 ));
243 }
244
245 #[test]
246 fn detects_crc_corruption() {
247 let mut wire = [0u8; MAX_WIRE_FRAME];
248 let n = encode_frame(0x04, 0x0042, b"hello", &mut wire).unwrap();
249 wire[4] ^= 0xFF;
251
252 let mut decoder = FrameDecoder::new();
253 let mut got_err = None;
254 decoder.feed(&wire[..n], |res| {
255 if let FrameResult::Err(e) = res {
256 got_err = Some(e);
257 }
258 });
259 assert!(matches!(
260 got_err,
261 Some(FrameDecodeError::Crc | FrameDecodeError::Cobs)
262 ));
263 }
264
265 #[test]
266 fn resync_after_garbage() {
267 let mut decoder = FrameDecoder::new();
268
269 decoder.feed(&[0xAA, 0xBB, 0xCC], |res| {
271 panic!("unexpected frame: {:?}", res);
272 });
273
274 let mut errs = 0;
276 let mut oks = 0;
277 decoder.feed(&[0x00], |res| match res {
278 FrameResult::Err(_) => errs += 1,
279 FrameResult::Ok { .. } => oks += 1,
280 });
281 assert!(errs >= 1);
282 assert_eq!(oks, 0);
283
284 let mut wire = [0u8; MAX_WIRE_FRAME];
286 let n = encode_frame(0x80, 0x0001, b"", &mut wire).unwrap();
287 let mut got_type = None;
288 decoder.feed(&wire[..n], |res| {
289 if let FrameResult::Ok { type_id, .. } = res {
290 got_type = Some(type_id);
291 }
292 });
293 assert_eq!(got_type, Some(0x80));
294 }
295
296 #[test]
297 fn overflow_resets_and_continues() {
298 let mut decoder = FrameDecoder::new();
299 let bomb = [0x55u8; MAX_COBS_ENCODED + 20];
301 decoder.feed(&bomb, |res| panic!("unexpected frame: {:?}", res));
302
303 decoder.feed(&[0x00], |res| panic!("unexpected frame: {:?}", res));
306
307 let mut wire = [0u8; MAX_WIRE_FRAME];
309 let n = encode_frame(0x01, 0x0007, &[], &mut wire).unwrap();
310 let mut got_type_tag = None;
311 decoder.feed(&wire[..n], |res| {
312 if let FrameResult::Ok { type_id, tag, .. } = res {
313 got_type_tag = Some((type_id, tag));
314 }
315 });
316 assert_eq!(got_type_tag, Some((0x01, 0x0007)));
317 }
318
319 #[test]
320 fn multiple_frames_in_one_feed() {
321 let mut wire = [0u8; MAX_WIRE_FRAME * 3];
322 let a = encode_frame(0x01, 1, &[], &mut wire).unwrap();
323 let b = encode_frame(0x02, 2, &[], &mut wire[a..]).unwrap();
324 let c = encode_frame(0x03, 3, &[], &mut wire[a + b..]).unwrap();
325 let total = a + b + c;
326
327 let mut decoder = FrameDecoder::new();
328 let mut tags: heapless::Vec<u16, 4> = heapless::Vec::new();
329 decoder.feed(&wire[..total], |res| {
330 if let FrameResult::Ok { tag, .. } = res {
331 tags.push(tag).unwrap();
332 }
333 });
334 assert_eq!(tags.as_slice(), &[1, 2, 3]);
335 }
336
337 #[test]
338 fn detects_short_decoded_frame() {
339 let wire = [0x02, 0x42, 0x00];
342 let mut decoder = FrameDecoder::new();
343 let mut got_err = None;
344 decoder.feed(&wire, |res| {
345 if let FrameResult::Err(e) = res {
346 got_err = Some(e);
347 }
348 });
349 assert_eq!(got_err, Some(FrameDecodeError::TooShort));
350 }
351
352 #[test]
353 fn spec_ping_example() {
354 let mut wire = [0u8; MAX_WIRE_FRAME];
357 let n = encode_frame(0x01, 0x0001, &[], &mut wire).unwrap();
358 assert_eq!(&wire[..n], &[0x03, 0x01, 0x01, 0x03, 0x9D, 0xC8, 0x00]);
359 }
360
361 #[test]
362 fn spec_ok_example() {
363 let mut wire = [0u8; MAX_WIRE_FRAME];
366 let n = encode_frame(0x80, 0x0001, &[], &mut wire).unwrap();
367 assert_eq!(&wire[..n], &[0x03, 0x80, 0x01, 0x03, 0xF7, 0xC4, 0x00]);
368 }
369}