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
114impl Default for Decoder {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn escape_passthrough() {
126        let data = b"hello world";
127        assert_eq!(escape(data), data.to_vec());
128    }
129
130    #[test]
131    fn escape_flag() {
132        assert_eq!(escape(&[FLAG]), vec![ESC, FLAG ^ ESC_MASK]);
133        assert_eq!(escape(&[0x7E]), vec![0x7D, 0x5E]);
134    }
135
136    #[test]
137    fn escape_esc() {
138        assert_eq!(escape(&[ESC]), vec![ESC, ESC ^ ESC_MASK]);
139        assert_eq!(escape(&[0x7D]), vec![0x7D, 0x5D]);
140    }
141
142    #[test]
143    fn escape_mixed() {
144        let data = [0x01, FLAG, 0x02, ESC, 0x03];
145        let expected = vec![0x01, ESC, FLAG ^ ESC_MASK, 0x02, ESC, ESC ^ ESC_MASK, 0x03];
146        assert_eq!(escape(&data), expected);
147    }
148
149    #[test]
150    fn frame_structure() {
151        let data = b"test";
152        let framed = frame(data);
153        assert_eq!(framed[0], FLAG);
154        assert_eq!(*framed.last().unwrap(), FLAG);
155        assert_eq!(&framed[1..framed.len() - 1], &escape(data));
156    }
157
158    #[test]
159    fn roundtrip_all_bytes() {
160        // Frame all 256 byte values, decode back
161        let data: Vec<u8> = (0..=255).collect();
162        let framed = frame(&data);
163
164        let mut decoder = Decoder::new();
165        let frames = decoder.feed(&framed);
166        assert_eq!(frames.len(), 1);
167        assert_eq!(frames[0], data);
168    }
169
170    #[test]
171    fn decoder_single_frame() {
172        // A frame with enough data (>= HEADER_MINSIZE = 19 bytes)
173        let data: Vec<u8> = (0..32).collect();
174        let framed = frame(&data);
175
176        let mut decoder = Decoder::new();
177        let frames = decoder.feed(&framed);
178        assert_eq!(frames.len(), 1);
179        assert_eq!(frames[0], data);
180    }
181
182    #[test]
183    fn decoder_two_frames_one_chunk() {
184        let data1: Vec<u8> = (0..24).collect();
185        let data2: Vec<u8> = (100..130).collect();
186        let mut combined = frame(&data1);
187        // The closing FLAG of frame1 is the opening FLAG of frame2
188        // But frame() adds its own opening FLAG, so two adjacent frames
189        // share the FLAG byte. We can just concatenate since the closing
190        // FLAG of frame1 serves as opening FLAG of frame2.
191        let framed2 = frame(&data2);
192        // Skip the opening FLAG of frame2 since frame1's closing FLAG serves that role
193        combined.extend_from_slice(&framed2[1..]);
194
195        let mut decoder = Decoder::new();
196        let frames = decoder.feed(&combined);
197        assert_eq!(frames.len(), 2);
198        assert_eq!(frames[0], data1);
199        assert_eq!(frames[1], data2);
200    }
201
202    #[test]
203    fn decoder_split_frame() {
204        let data: Vec<u8> = (0..32).collect();
205        let framed = frame(&data);
206
207        // Split in the middle
208        let mid = framed.len() / 2;
209        let mut decoder = Decoder::new();
210
211        let frames1 = decoder.feed(&framed[..mid]);
212        assert_eq!(frames1.len(), 0); // incomplete
213
214        let frames2 = decoder.feed(&framed[mid..]);
215        assert_eq!(frames2.len(), 1);
216        assert_eq!(frames2[0], data);
217    }
218
219    #[test]
220    fn decoder_drops_short() {
221        // Frame with < HEADER_MINSIZE (19) bytes of payload
222        let data = vec![0x01, 0x02, 0x03]; // only 3 bytes
223        let framed = frame(&data);
224
225        let mut decoder = Decoder::new();
226        let frames = decoder.feed(&framed);
227        assert_eq!(frames.len(), 0); // dropped as too short
228    }
229}