Skip to main content

rns_net/
hdlc.rs

1//! HDLC framing for TCP transport.
2//!
3//! Matches Python `TCPInterface.py` HDLC encoding/decoding.
4
5use rns_core::constants::HEADER_MINSIZE;
6
7const FLAG: u8 = 0x7E;
8const ESC: u8 = 0x7D;
9const ESC_MASK: u8 = 0x20;
10
11/// Escape special bytes in data (FLAG and ESC).
12pub fn escape(data: &[u8]) -> Vec<u8> {
13    let mut out = Vec::with_capacity(data.len());
14    for &b in data {
15        match b {
16            ESC => {
17                out.push(ESC);
18                out.push(ESC ^ ESC_MASK);
19            }
20            FLAG => {
21                out.push(ESC);
22                out.push(FLAG ^ ESC_MASK);
23            }
24            _ => out.push(b),
25        }
26    }
27    out
28}
29
30/// Wrap data in HDLC frame: [FLAG] + escape(data) + [FLAG].
31pub fn frame(data: &[u8]) -> Vec<u8> {
32    let escaped = escape(data);
33    let mut out = Vec::with_capacity(escaped.len() + 2);
34    out.push(FLAG);
35    out.extend_from_slice(&escaped);
36    out.push(FLAG);
37    out
38}
39
40/// Unescape HDLC-escaped data.
41fn unescape(data: &[u8]) -> Vec<u8> {
42    let mut out = Vec::with_capacity(data.len());
43    let mut i = 0;
44    while i < data.len() {
45        if data[i] == ESC && i + 1 < data.len() {
46            out.push(data[i + 1] ^ ESC_MASK);
47            i += 2;
48        } else {
49            out.push(data[i]);
50            i += 1;
51        }
52    }
53    out
54}
55
56/// Streaming HDLC frame decoder.
57///
58/// Accumulates bytes via `feed()` and yields complete decoded frames.
59/// Matches the decode loop in `TCPInterface.py:381-394`.
60pub struct Decoder {
61    buffer: Vec<u8>,
62}
63
64impl Decoder {
65    pub fn new() -> Self {
66        Decoder { buffer: Vec::new() }
67    }
68
69    /// Feed raw bytes into the decoder and return any complete frames.
70    pub fn feed(&mut self, chunk: &[u8]) -> Vec<Vec<u8>> {
71        self.buffer.extend_from_slice(chunk);
72        let mut frames = Vec::new();
73
74        loop {
75            // Find first FLAG
76            let start = match self.buffer.iter().position(|&b| b == FLAG) {
77                Some(pos) => pos,
78                None => {
79                    // No FLAG found, discard buffer
80                    self.buffer.clear();
81                    break;
82                }
83            };
84
85            // Trim garbage before first FLAG
86            if start > 0 {
87                self.buffer.drain(..start);
88            }
89
90            // Find second FLAG (after position 0)
91            let end = match self.buffer[1..].iter().position(|&b| b == FLAG) {
92                Some(pos) => pos + 1, // offset back to buffer index
93                None => break,        // incomplete frame, wait for more data
94            };
95
96            // Extract bytes between the two FLAGs
97            let between = &self.buffer[1..end];
98            let unescaped = unescape(between);
99
100            // Only yield frames that meet minimum size
101            if unescaped.len() >= HEADER_MINSIZE {
102                frames.push(unescaped);
103            }
104
105            // Keep the closing FLAG as the opening FLAG of the next frame
106            // (matches Python: frame_buffer = frame_buffer[frame_end:])
107            self.buffer.drain(..end);
108        }
109
110        frames
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn escape_passthrough() {
120        let data = b"hello world";
121        assert_eq!(escape(data), data.to_vec());
122    }
123
124    #[test]
125    fn escape_flag() {
126        assert_eq!(escape(&[FLAG]), vec![ESC, FLAG ^ ESC_MASK]);
127        assert_eq!(escape(&[0x7E]), vec![0x7D, 0x5E]);
128    }
129
130    #[test]
131    fn escape_esc() {
132        assert_eq!(escape(&[ESC]), vec![ESC, ESC ^ ESC_MASK]);
133        assert_eq!(escape(&[0x7D]), vec![0x7D, 0x5D]);
134    }
135
136    #[test]
137    fn escape_mixed() {
138        let data = [0x01, FLAG, 0x02, ESC, 0x03];
139        let expected = vec![0x01, ESC, FLAG ^ ESC_MASK, 0x02, ESC, ESC ^ ESC_MASK, 0x03];
140        assert_eq!(escape(&data), expected);
141    }
142
143    #[test]
144    fn frame_structure() {
145        let data = b"test";
146        let framed = frame(data);
147        assert_eq!(framed[0], FLAG);
148        assert_eq!(*framed.last().unwrap(), FLAG);
149        assert_eq!(&framed[1..framed.len() - 1], &escape(data));
150    }
151
152    #[test]
153    fn roundtrip_all_bytes() {
154        // Frame all 256 byte values, decode back
155        let data: Vec<u8> = (0..=255).collect();
156        let framed = frame(&data);
157
158        let mut decoder = Decoder::new();
159        let frames = decoder.feed(&framed);
160        assert_eq!(frames.len(), 1);
161        assert_eq!(frames[0], data);
162    }
163
164    #[test]
165    fn decoder_single_frame() {
166        // A frame with enough data (>= HEADER_MINSIZE = 19 bytes)
167        let data: Vec<u8> = (0..32).collect();
168        let framed = frame(&data);
169
170        let mut decoder = Decoder::new();
171        let frames = decoder.feed(&framed);
172        assert_eq!(frames.len(), 1);
173        assert_eq!(frames[0], data);
174    }
175
176    #[test]
177    fn decoder_two_frames_one_chunk() {
178        let data1: Vec<u8> = (0..24).collect();
179        let data2: Vec<u8> = (100..130).collect();
180        let mut combined = frame(&data1);
181        // The closing FLAG of frame1 is the opening FLAG of frame2
182        // But frame() adds its own opening FLAG, so two adjacent frames
183        // share the FLAG byte. We can just concatenate since the closing
184        // FLAG of frame1 serves as opening FLAG of frame2.
185        let framed2 = frame(&data2);
186        // Skip the opening FLAG of frame2 since frame1's closing FLAG serves that role
187        combined.extend_from_slice(&framed2[1..]);
188
189        let mut decoder = Decoder::new();
190        let frames = decoder.feed(&combined);
191        assert_eq!(frames.len(), 2);
192        assert_eq!(frames[0], data1);
193        assert_eq!(frames[1], data2);
194    }
195
196    #[test]
197    fn decoder_split_frame() {
198        let data: Vec<u8> = (0..32).collect();
199        let framed = frame(&data);
200
201        // Split in the middle
202        let mid = framed.len() / 2;
203        let mut decoder = Decoder::new();
204
205        let frames1 = decoder.feed(&framed[..mid]);
206        assert_eq!(frames1.len(), 0); // incomplete
207
208        let frames2 = decoder.feed(&framed[mid..]);
209        assert_eq!(frames2.len(), 1);
210        assert_eq!(frames2[0], data);
211    }
212
213    #[test]
214    fn decoder_drops_short() {
215        // Frame with < HEADER_MINSIZE (19) bytes of payload
216        let data = vec![0x01, 0x02, 0x03]; // only 3 bytes
217        let framed = frame(&data);
218
219        let mut decoder = Decoder::new();
220        let frames = decoder.feed(&framed);
221        assert_eq!(frames.len(), 0); // dropped as too short
222    }
223}