Skip to main content

hdds_micro/transport/hc12/
framing.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! Packet framing for HC-12 UART transport
5//!
6//! Since HC-12 is a raw UART bridge, we need framing to detect packet boundaries.
7//!
8//! ## Frame Format
9//!
10//! ```text
11//! +------+------+------+--------+---------+------+
12//! | SYNC | LEN  | SRC  | DATA   |   CRC   | SYNC |
13//! +------+------+------+--------+---------+------+
14//!   0xAA   1B     1B    0-50B     2B       0x55
15//! ```
16//!
17//! - SYNC_START (0xAA): Frame start marker
18//! - LEN: Payload length (0-50)
19//! - SRC: Source node ID
20//! - DATA: Payload bytes
21//! - CRC16: CRC-16-CCITT checksum
22//! - SYNC_END (0x55): Frame end marker
23
24use crate::error::{Error, Result};
25
26/// Frame start marker
27const SYNC_START: u8 = 0xAA;
28
29/// Frame end marker
30const SYNC_END: u8 = 0x55;
31
32/// Maximum payload size per frame
33const MAX_PAYLOAD: usize = 50;
34
35/// Frame overhead: start(1) + len(1) + src(1) + crc(2) + end(1) = 6
36pub const FRAME_OVERHEAD: usize = 6;
37
38/// Maximum frame size
39const MAX_FRAME_SIZE: usize = MAX_PAYLOAD + FRAME_OVERHEAD;
40
41/// Frame encoder for outgoing packets
42#[derive(Debug)]
43pub struct FrameEncoder {
44    // Stateless encoder
45}
46
47impl FrameEncoder {
48    /// Create a new frame encoder
49    pub const fn new() -> Self {
50        Self {}
51    }
52
53    /// Encode data into a frame
54    ///
55    /// # Arguments
56    ///
57    /// * `src_node` - Source node ID
58    /// * `data` - Payload data
59    /// * `buf` - Output buffer for frame
60    ///
61    /// # Returns
62    ///
63    /// Frame length
64    pub fn encode(&self, src_node: u8, data: &[u8], buf: &mut [u8]) -> Result<usize> {
65        if data.len() > MAX_PAYLOAD {
66            return Err(Error::BufferTooSmall);
67        }
68
69        let frame_len = data.len() + FRAME_OVERHEAD;
70        if buf.len() < frame_len {
71            return Err(Error::BufferTooSmall);
72        }
73
74        // Build frame
75        buf[0] = SYNC_START;
76        buf[1] = data.len() as u8;
77        buf[2] = src_node;
78
79        // Copy payload
80        buf[3..3 + data.len()].copy_from_slice(data);
81
82        // Calculate CRC over len + src + data
83        let crc = crc16_ccitt(&buf[1..3 + data.len()]);
84        buf[3 + data.len()] = (crc >> 8) as u8;
85        buf[4 + data.len()] = (crc & 0xFF) as u8;
86
87        // End marker
88        buf[5 + data.len()] = SYNC_END;
89
90        Ok(frame_len)
91    }
92}
93
94impl Default for FrameEncoder {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100/// Decoder state machine
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102enum DecoderState {
103    /// Waiting for start sync
104    WaitStart,
105    /// Got start, waiting for length
106    WaitLength,
107    /// Got length, waiting for source
108    WaitSource,
109    /// Receiving payload
110    Payload,
111    /// Waiting for CRC high byte
112    CrcHigh,
113    /// Waiting for CRC low byte
114    CrcLow,
115    /// Waiting for end sync
116    WaitEnd,
117}
118
119/// Frame decoder for incoming packets
120#[derive(Debug)]
121pub struct FrameDecoder {
122    /// Current state
123    state: DecoderState,
124
125    /// Payload buffer
126    buf: [u8; MAX_FRAME_SIZE],
127
128    /// Expected payload length
129    payload_len: usize,
130
131    /// Source node ID
132    src_node: u8,
133
134    /// Current position in buffer
135    pos: usize,
136
137    /// Received CRC
138    crc: u16,
139}
140
141impl FrameDecoder {
142    /// Create a new frame decoder
143    pub const fn new() -> Self {
144        Self {
145            state: DecoderState::WaitStart,
146            buf: [0u8; MAX_FRAME_SIZE],
147            payload_len: 0,
148            src_node: 0,
149            pos: 0,
150            crc: 0,
151        }
152    }
153
154    /// Reset decoder state
155    pub fn reset(&mut self) {
156        self.state = DecoderState::WaitStart;
157        self.pos = 0;
158        self.payload_len = 0;
159        self.src_node = 0;
160        self.crc = 0;
161    }
162
163    /// Feed a byte to the decoder
164    ///
165    /// # Returns
166    ///
167    /// `Some((src_node, payload))` when a complete frame is decoded
168    pub fn feed(&mut self, byte: u8) -> Result<Option<(u8, &[u8])>> {
169        match self.state {
170            DecoderState::WaitStart => {
171                if byte == SYNC_START {
172                    self.state = DecoderState::WaitLength;
173                    self.pos = 0;
174                }
175                Ok(None)
176            }
177
178            DecoderState::WaitLength => {
179                if byte > MAX_PAYLOAD as u8 {
180                    // Invalid length, reset
181                    self.reset();
182                    return Ok(None);
183                }
184                self.payload_len = byte as usize;
185                self.buf[0] = byte; // Store for CRC
186                self.state = DecoderState::WaitSource;
187                Ok(None)
188            }
189
190            DecoderState::WaitSource => {
191                self.src_node = byte;
192                self.buf[1] = byte; // Store for CRC
193                self.pos = 0;
194                if self.payload_len == 0 {
195                    self.state = DecoderState::CrcHigh;
196                } else {
197                    self.state = DecoderState::Payload;
198                }
199                Ok(None)
200            }
201
202            DecoderState::Payload => {
203                self.buf[2 + self.pos] = byte;
204                self.pos += 1;
205
206                if self.pos >= self.payload_len {
207                    self.state = DecoderState::CrcHigh;
208                }
209                Ok(None)
210            }
211
212            DecoderState::CrcHigh => {
213                self.crc = (byte as u16) << 8;
214                self.state = DecoderState::CrcLow;
215                Ok(None)
216            }
217
218            DecoderState::CrcLow => {
219                self.crc |= byte as u16;
220                self.state = DecoderState::WaitEnd;
221                Ok(None)
222            }
223
224            DecoderState::WaitEnd => {
225                if byte == SYNC_END {
226                    // Verify CRC
227                    let expected_crc = crc16_ccitt(&self.buf[..2 + self.payload_len]);
228
229                    if self.crc == expected_crc {
230                        // Valid frame! Reset state but keep data for return
231                        self.state = DecoderState::WaitStart;
232                        let payload_end = 2 + self.payload_len;
233                        self.payload_len = 0; // Reset for next frame
234                        self.crc = 0;
235                        self.pos = 0;
236                        return Ok(Some((self.src_node, &self.buf[2..payload_end])));
237                    }
238                }
239
240                // Invalid frame, reset
241                self.reset();
242                Ok(None)
243            }
244        }
245    }
246
247    /// Check if decoder is in middle of receiving a frame
248    pub const fn is_receiving(&self) -> bool {
249        !matches!(self.state, DecoderState::WaitStart)
250    }
251}
252
253impl Default for FrameDecoder {
254    fn default() -> Self {
255        Self::new()
256    }
257}
258
259/// CRC-16-CCITT calculation (polynomial 0x1021)
260fn crc16_ccitt(data: &[u8]) -> u16 {
261    let mut crc: u16 = 0xFFFF;
262
263    for &byte in data {
264        crc ^= (byte as u16) << 8;
265        for _ in 0..8 {
266            if crc & 0x8000 != 0 {
267                crc = (crc << 1) ^ 0x1021;
268            } else {
269                crc <<= 1;
270            }
271        }
272    }
273
274    crc
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_encoder_basic() {
283        let encoder = FrameEncoder::new();
284        let mut buf = [0u8; 64];
285
286        let len = encoder.encode(42, b"Hello", &mut buf).unwrap();
287
288        assert_eq!(len, 5 + FRAME_OVERHEAD); // 5 bytes payload + 6 overhead
289        assert_eq!(buf[0], SYNC_START);
290        assert_eq!(buf[1], 5); // length
291        assert_eq!(buf[2], 42); // src_node
292        assert_eq!(&buf[3..8], b"Hello");
293        assert_eq!(buf[len - 1], SYNC_END);
294    }
295
296    #[test]
297    fn test_encoder_empty_payload() {
298        let encoder = FrameEncoder::new();
299        let mut buf = [0u8; 64];
300
301        let len = encoder.encode(1, &[], &mut buf).unwrap();
302
303        assert_eq!(len, FRAME_OVERHEAD);
304        assert_eq!(buf[0], SYNC_START);
305        assert_eq!(buf[1], 0); // length
306        assert_eq!(buf[len - 1], SYNC_END);
307    }
308
309    #[test]
310    fn test_encoder_buffer_too_small() {
311        let encoder = FrameEncoder::new();
312        let mut buf = [0u8; 5]; // Too small
313
314        let result = encoder.encode(1, b"Hello", &mut buf);
315        assert_eq!(result, Err(Error::BufferTooSmall));
316    }
317
318    #[test]
319    fn test_encoder_payload_too_large() {
320        let encoder = FrameEncoder::new();
321        let mut buf = [0u8; 256];
322        let data = [0u8; 60]; // Too large
323
324        let result = encoder.encode(1, &data, &mut buf);
325        assert_eq!(result, Err(Error::BufferTooSmall));
326    }
327
328    #[test]
329    fn test_decoder_basic() {
330        let encoder = FrameEncoder::new();
331        let mut decoder = FrameDecoder::new();
332        let mut buf = [0u8; 64];
333
334        // Encode a frame
335        let len = encoder.encode(42, b"Test", &mut buf).unwrap();
336
337        // Feed to decoder (all but last byte)
338        for &byte in &buf[..len - 1] {
339            let result = decoder.feed(byte).unwrap();
340            assert!(result.is_none());
341        }
342
343        // Last byte should complete the frame
344        let result = decoder.feed(buf[len - 1]).unwrap();
345        assert!(result.is_some());
346
347        let (src, payload) = result.unwrap();
348        assert_eq!(src, 42);
349        assert_eq!(payload, b"Test");
350    }
351
352    #[test]
353    fn test_decoder_empty_payload() {
354        let encoder = FrameEncoder::new();
355        let mut decoder = FrameDecoder::new();
356        let mut buf = [0u8; 64];
357
358        let len = encoder.encode(1, &[], &mut buf).unwrap();
359
360        for &byte in &buf[..len - 1] {
361            assert!(decoder.feed(byte).unwrap().is_none());
362        }
363
364        let result = decoder.feed(buf[len - 1]).unwrap();
365        assert!(result.is_some());
366
367        let (src, payload) = result.unwrap();
368        assert_eq!(src, 1);
369        assert!(payload.is_empty());
370    }
371
372    #[test]
373    fn test_decoder_bad_crc() {
374        let encoder = FrameEncoder::new();
375        let mut decoder = FrameDecoder::new();
376        let mut buf = [0u8; 64];
377
378        let len = encoder.encode(42, b"Test", &mut buf).unwrap();
379
380        // Corrupt a byte
381        buf[5] ^= 0xFF;
382
383        // Feed to decoder
384        for &byte in &buf[..len] {
385            let _ = decoder.feed(byte);
386        }
387
388        // Should have reset (no valid frame)
389        assert!(!decoder.is_receiving());
390    }
391
392    #[test]
393    fn test_decoder_bad_sync() {
394        let mut decoder = FrameDecoder::new();
395
396        // Random bytes without sync
397        for b in &[0x12, 0x34, 0x56, 0x78] {
398            let result = decoder.feed(*b).unwrap();
399            assert!(result.is_none());
400        }
401
402        assert!(!decoder.is_receiving());
403    }
404
405    #[test]
406    fn test_decoder_multiple_frames() {
407        let encoder = FrameEncoder::new();
408        let mut decoder = FrameDecoder::new();
409        let mut buf1 = [0u8; 64];
410        let mut buf2 = [0u8; 64];
411
412        let len1 = encoder.encode(1, b"First", &mut buf1).unwrap();
413        let len2 = encoder.encode(2, b"Second", &mut buf2).unwrap();
414
415        // Decode first frame
416        for &byte in &buf1[..len1 - 1] {
417            assert!(decoder.feed(byte).unwrap().is_none());
418        }
419        let result = decoder.feed(buf1[len1 - 1]).unwrap();
420        let (src, payload) = result.unwrap();
421        assert_eq!(src, 1);
422        assert_eq!(payload, b"First");
423
424        // Decode second frame
425        for &byte in &buf2[..len2 - 1] {
426            assert!(decoder.feed(byte).unwrap().is_none());
427        }
428        let result = decoder.feed(buf2[len2 - 1]).unwrap();
429        let (src, payload) = result.unwrap();
430        assert_eq!(src, 2);
431        assert_eq!(payload, b"Second");
432    }
433
434    #[test]
435    fn test_crc16_known_values() {
436        // Test with known CRC values
437        assert_eq!(crc16_ccitt(b""), 0xFFFF);
438        assert_eq!(crc16_ccitt(b"123456789"), 0x29B1);
439    }
440
441    #[test]
442    fn test_roundtrip_various_sizes() {
443        let encoder = FrameEncoder::new();
444        let mut decoder = FrameDecoder::new();
445        let mut frame_buf = [0u8; 64];
446        let mut data_buf = [0u8; 50];
447
448        for size in [0usize, 1, 10, 25, 50] {
449            // Fill data buffer with sequential values
450            for (i, slot) in data_buf[..size].iter_mut().enumerate() {
451                *slot = i as u8;
452            }
453            let data = &data_buf[..size];
454
455            let len = encoder.encode(42, data, &mut frame_buf).unwrap();
456
457            for &byte in &frame_buf[..len - 1] {
458                assert!(decoder.feed(byte).unwrap().is_none());
459            }
460
461            let result = decoder.feed(frame_buf[len - 1]).unwrap();
462            assert!(result.is_some());
463
464            let (src, payload) = result.unwrap();
465            assert_eq!(src, 42);
466            assert_eq!(payload, data);
467        }
468    }
469}