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/// Exact on-wire length after HDLC escaping and delimiter insertion.
12pub(crate) fn framed_len(data: &[u8]) -> usize {
13    data.len()
14        .saturating_add(
15            data.iter()
16                .filter(|&&byte| byte == FLAG || byte == ESC)
17                .count(),
18        )
19        .saturating_add(2)
20}
21
22/// Escape special bytes in data (FLAG and ESC).
23pub fn escape(data: &[u8]) -> Vec<u8> {
24    let mut out = Vec::with_capacity(data.len());
25    for &b in data {
26        match b {
27            ESC => {
28                out.push(ESC);
29                out.push(ESC ^ ESC_MASK);
30            }
31            FLAG => {
32                out.push(ESC);
33                out.push(FLAG ^ ESC_MASK);
34            }
35            _ => out.push(b),
36        }
37    }
38    out
39}
40
41/// Wrap data in HDLC frame: [FLAG] + escape(data) + [FLAG].
42pub fn frame(data: &[u8]) -> Vec<u8> {
43    let mut out = Vec::with_capacity(framed_len(data));
44    out.push(FLAG);
45    for &byte in data {
46        match byte {
47            ESC | FLAG => {
48                out.push(ESC);
49                out.push(byte ^ ESC_MASK);
50            }
51            _ => out.push(byte),
52        }
53    }
54    out.push(FLAG);
55    out
56}
57
58/// Unescape HDLC-escaped data.
59fn unescape(data: &[u8]) -> Vec<u8> {
60    let mut out = Vec::with_capacity(data.len());
61    let mut i = 0;
62    while i < data.len() {
63        if data[i] == ESC && i + 1 < data.len() {
64            match data[i + 1] {
65                byte if byte == FLAG ^ ESC_MASK => {
66                    out.push(FLAG);
67                    i += 2;
68                }
69                byte if byte == ESC ^ ESC_MASK => {
70                    out.push(ESC);
71                    i += 2;
72                }
73                _ => {
74                    out.push(ESC);
75                    i += 1;
76                }
77            }
78        } else {
79            out.push(data[i]);
80            i += 1;
81        }
82    }
83    out
84}
85
86/// Streaming HDLC frame decoder.
87///
88/// Accumulates bytes via `feed()` and yields complete decoded frames.
89/// Matches the decode loop in `TCPInterface.py:381-394`.
90pub struct Decoder {
91    buffer: Vec<u8>,
92    offset: usize,
93    min_frame_size: usize,
94    max_frame_size: Option<usize>,
95    max_buffer_size: usize,
96}
97
98/// Complete frames and non-empty frames rejected by configured size bounds.
99#[derive(Debug, Default, Eq, PartialEq)]
100pub struct DecodeBatch {
101    pub frames: Vec<Vec<u8>>,
102    pub invalid_frame_lengths: Vec<usize>,
103}
104
105impl Decoder {
106    pub fn new() -> Self {
107        Self::with_limits(HEADER_MINSIZE, 64 * 1024)
108    }
109
110    /// Construct a decoder for protocols with a different minimum frame size.
111    pub fn with_min_frame_size(min_frame_size: usize) -> Self {
112        Self::with_limits(min_frame_size, 64 * 1024)
113    }
114
115    pub fn with_limits(min_frame_size: usize, max_buffer_size: usize) -> Self {
116        Decoder {
117            buffer: Vec::new(),
118            offset: 0,
119            min_frame_size,
120            max_frame_size: None,
121            max_buffer_size: max_buffer_size.max(2),
122        }
123    }
124
125    /// Construct a decoder with Reticulum's TCP-style frame bounds.
126    ///
127    /// Decoded frames must be strictly larger than `HEADER_MINSIZE` and no
128    /// larger than the interface hardware MTU plus on-wire IFAC bytes. The
129    /// unterminated encoded tail is retained up to twice the hardware MTU.
130    pub fn reticulum(hardware_mtu: usize, ifac_size: usize) -> Self {
131        Decoder {
132            buffer: Vec::new(),
133            offset: 0,
134            min_frame_size: HEADER_MINSIZE.saturating_add(1),
135            max_frame_size: Some(hardware_mtu.saturating_add(ifac_size)),
136            max_buffer_size: hardware_mtu.saturating_mul(2).max(2),
137        }
138    }
139
140    /// Feed raw bytes into the decoder and return any complete frames.
141    pub fn feed(&mut self, chunk: &[u8]) -> Vec<Vec<u8>> {
142        self.feed_with_diagnostics(chunk).frames
143    }
144
145    /// Feed raw bytes and retain the decoded lengths of non-empty frames that
146    /// were rejected by the configured minimum or maximum.
147    pub fn feed_with_diagnostics(&mut self, chunk: &[u8]) -> DecodeBatch {
148        self.buffer.extend_from_slice(chunk);
149        let mut decoded = DecodeBatch::default();
150
151        loop {
152            // Find first FLAG in the unconsumed tail.
153            let start = match self.buffer[self.offset..].iter().position(|&b| b == FLAG) {
154                Some(pos) => self.offset + pos,
155                None => {
156                    // No FLAG found, discard buffer.
157                    self.buffer.clear();
158                    self.offset = 0;
159                    break;
160                }
161            };
162
163            // Find second FLAG after the opening marker.
164            let end = match self.buffer[start + 1..].iter().position(|&b| b == FLAG) {
165                Some(pos) => start + 1 + pos,
166                None => {
167                    if self.buffer.len() - self.offset > self.max_buffer_size {
168                        self.buffer.clear();
169                        self.offset = 0;
170                    }
171                    break;
172                }
173            };
174
175            // Extract bytes between the two FLAGs
176            let between = &self.buffer[start + 1..end];
177            let unescaped = unescape(between);
178
179            let frame_len = unescaped.len();
180            let within_maximum = self
181                .max_frame_size
182                .is_none_or(|max_frame_size| frame_len <= max_frame_size);
183            if frame_len >= self.min_frame_size && within_maximum {
184                decoded.frames.push(unescaped);
185            } else if frame_len != 0 {
186                decoded.invalid_frame_lengths.push(frame_len);
187            }
188
189            // Keep the closing FLAG as the next opening marker. Avoid a
190            // front-drain per frame; compact only after consuming half.
191            self.offset = end;
192            if self.offset > 0 && self.offset >= self.buffer.len() / 2 {
193                self.buffer.drain(..self.offset);
194                self.offset = 0;
195            }
196        }
197
198        decoded
199    }
200}
201
202impl Default for Decoder {
203    fn default() -> Self {
204        Self::new()
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn escape_passthrough() {
214        let data = b"hello world";
215        assert_eq!(escape(data), data.to_vec());
216    }
217
218    #[test]
219    fn escape_flag() {
220        assert_eq!(escape(&[FLAG]), vec![ESC, FLAG ^ ESC_MASK]);
221        assert_eq!(escape(&[0x7E]), vec![0x7D, 0x5E]);
222    }
223
224    #[test]
225    fn escape_esc() {
226        assert_eq!(escape(&[ESC]), vec![ESC, ESC ^ ESC_MASK]);
227        assert_eq!(escape(&[0x7D]), vec![0x7D, 0x5D]);
228    }
229
230    #[test]
231    fn escape_mixed() {
232        let data = [0x01, FLAG, 0x02, ESC, 0x03];
233        let expected = vec![0x01, ESC, FLAG ^ ESC_MASK, 0x02, ESC, ESC ^ ESC_MASK, 0x03];
234        assert_eq!(escape(&data), expected);
235    }
236
237    #[test]
238    fn frame_structure() {
239        let data = b"test";
240        let framed = frame(data);
241        assert_eq!(framed[0], FLAG);
242        assert_eq!(*framed.last().unwrap(), FLAG);
243        assert_eq!(&framed[1..framed.len() - 1], &escape(data));
244    }
245
246    #[test]
247    fn canonical_frame_matches_wrapped_escape_for_every_byte() {
248        let data: Vec<u8> = (0..=u8::MAX).collect();
249        let mut expected = Vec::with_capacity(escape(&data).len() + 2);
250        expected.push(FLAG);
251        expected.extend_from_slice(&escape(&data));
252        expected.push(FLAG);
253
254        let framed = frame(&data);
255
256        assert_eq!(framed, expected);
257        assert_eq!(framed.capacity(), framed.len());
258    }
259
260    #[test]
261    fn framed_len_matches_exact_encoded_length() {
262        for data in [
263            Vec::new(),
264            vec![0x01, 0x02],
265            vec![FLAG, ESC, 0x03],
266            vec![FLAG; 1_024],
267        ] {
268            assert_eq!(framed_len(&data), frame(&data).len());
269        }
270    }
271
272    #[test]
273    fn roundtrip_all_bytes() {
274        // Frame all 256 byte values, decode back
275        let data: Vec<u8> = (0..=255).collect();
276        let framed = frame(&data);
277
278        let mut decoder = Decoder::new();
279        let frames = decoder.feed(&framed);
280        assert_eq!(frames.len(), 1);
281        assert_eq!(frames[0], data);
282    }
283
284    #[test]
285    fn unescape_preserves_unknown_and_trailing_escape_bytes() {
286        let mut decoder = Decoder::with_min_frame_size(0);
287
288        let frames = decoder.feed(&[FLAG, ESC, 0x00, ESC, FLAG]);
289
290        assert_eq!(frames, vec![vec![ESC, 0x00, ESC]]);
291    }
292
293    #[test]
294    fn decoder_single_frame() {
295        // A frame with enough data (>= HEADER_MINSIZE = 19 bytes)
296        let data: Vec<u8> = (0..32).collect();
297        let framed = frame(&data);
298
299        let mut decoder = Decoder::new();
300        let frames = decoder.feed(&framed);
301        assert_eq!(frames.len(), 1);
302        assert_eq!(frames[0], data);
303    }
304
305    #[test]
306    fn decoder_two_frames_one_chunk() {
307        let data1: Vec<u8> = (0..24).collect();
308        let data2: Vec<u8> = (100..130).collect();
309        let mut combined = frame(&data1);
310        // The closing FLAG of frame1 is the opening FLAG of frame2
311        // But frame() adds its own opening FLAG, so two adjacent frames
312        // share the FLAG byte. We can just concatenate since the closing
313        // FLAG of frame1 serves as opening FLAG of frame2.
314        let framed2 = frame(&data2);
315        // Skip the opening FLAG of frame2 since frame1's closing FLAG serves that role
316        combined.extend_from_slice(&framed2[1..]);
317
318        let mut decoder = Decoder::new();
319        let frames = decoder.feed(&combined);
320        assert_eq!(frames.len(), 2);
321        assert_eq!(frames[0], data1);
322        assert_eq!(frames[1], data2);
323    }
324
325    #[test]
326    fn decoder_split_frame() {
327        let data: Vec<u8> = (0..32).collect();
328        let framed = frame(&data);
329
330        // Split in the middle
331        let mid = framed.len() / 2;
332        let mut decoder = Decoder::new();
333
334        let frames1 = decoder.feed(&framed[..mid]);
335        assert_eq!(frames1.len(), 0); // incomplete
336
337        let frames2 = decoder.feed(&framed[mid..]);
338        assert_eq!(frames2.len(), 1);
339        assert_eq!(frames2[0], data);
340    }
341
342    #[test]
343    fn decoder_does_not_drop_large_coalesced_batches() {
344        let payload = vec![0x42; 32];
345        let encoded = frame(&payload);
346        let count = 20_000;
347        let mut batch = Vec::with_capacity(encoded.len() * count);
348        for index in 0..count {
349            if index == 0 {
350                batch.extend_from_slice(&encoded);
351            } else {
352                batch.extend_from_slice(&encoded[1..]);
353            }
354        }
355        let mut decoder = Decoder::with_limits(HEADER_MINSIZE, 64 * 1024);
356        let frames = decoder.feed(&batch);
357        assert_eq!(frames.len(), count);
358        assert!(frames.iter().all(|decoded| decoded == &payload));
359    }
360
361    #[test]
362    fn decoder_defers_compaction_until_consumed_prefix_reaches_half() {
363        let first = vec![0x31; 32];
364        let second = vec![0x42; 200];
365        let mut stream = frame(&first);
366        stream.extend_from_slice(&escape(&second));
367        let mut decoder = Decoder::with_limits(HEADER_MINSIZE, 1024);
368
369        assert_eq!(decoder.feed(&stream), vec![first]);
370        assert!(decoder.offset > 0);
371        assert!(decoder.buffer.len() - decoder.offset > decoder.offset);
372
373        assert_eq!(decoder.feed(&[FLAG]), vec![second]);
374        assert_eq!(decoder.buffer, vec![FLAG]);
375        assert_eq!(decoder.offset, 0);
376    }
377
378    #[test]
379    fn decoder_drops_short() {
380        // Frame with < HEADER_MINSIZE (19) bytes of payload
381        let data = vec![0x01, 0x02, 0x03]; // only 3 bytes
382        let framed = frame(&data);
383
384        let mut decoder = Decoder::new();
385        let frames = decoder.feed(&framed);
386        assert_eq!(frames.len(), 0); // dropped as too short
387    }
388
389    #[test]
390    fn reticulum_decoder_enforces_strict_minimum_and_ifac_adjusted_maximum() {
391        let hardware_mtu = 64;
392        let ifac_size = 8;
393        let mut decoder = Decoder::reticulum(hardware_mtu, ifac_size);
394        let lengths = [
395            HEADER_MINSIZE,
396            HEADER_MINSIZE + 1,
397            hardware_mtu + ifac_size,
398            hardware_mtu + ifac_size + 1,
399        ];
400        let mut encoded = vec![FLAG];
401        for length in lengths {
402            encoded.extend_from_slice(&frame(&vec![0x42; length])[1..]);
403        }
404
405        let decoded = decoder.feed_with_diagnostics(&encoded);
406
407        assert_eq!(
408            decoded.frames.iter().map(Vec::len).collect::<Vec<_>>(),
409            vec![HEADER_MINSIZE + 1, hardware_mtu + ifac_size]
410        );
411        assert_eq!(
412            decoded.invalid_frame_lengths,
413            vec![HEADER_MINSIZE, hardware_mtu + ifac_size + 1]
414        );
415    }
416
417    #[test]
418    fn empty_frames_are_ignored_without_invalid_frame_diagnostics() {
419        let mut decoder = Decoder::reticulum(64, 0);
420
421        let decoded = decoder.feed_with_diagnostics(&[FLAG, FLAG, FLAG]);
422
423        assert!(decoded.frames.is_empty());
424        assert!(decoded.invalid_frame_lengths.is_empty());
425    }
426
427    #[test]
428    fn oversized_complete_frame_is_dropped_and_next_frame_is_recovered() {
429        let mut decoder = Decoder::reticulum(64, 0);
430        let oversized = frame(&[0x55; 65]);
431        let valid = frame(&[0x33; HEADER_MINSIZE + 1]);
432        let mut encoded = oversized;
433        encoded.extend_from_slice(&valid[1..]);
434
435        let decoded = decoder.feed_with_diagnostics(&encoded);
436
437        assert_eq!(decoded.frames, vec![vec![0x33; HEADER_MINSIZE + 1]]);
438        assert_eq!(decoded.invalid_frame_lengths, vec![65]);
439    }
440
441    #[test]
442    fn fragmented_frame_larger_than_legacy_buffer_limit_is_preserved() {
443        let hardware_mtu = 70 * 1024;
444        let payload = vec![0x42; hardware_mtu];
445        let encoded = frame(&payload);
446        let split = 65 * 1024;
447        let mut decoder = Decoder::reticulum(hardware_mtu, 0);
448
449        let first = decoder.feed_with_diagnostics(&encoded[..split]);
450        assert!(first.frames.is_empty());
451        assert!(first.invalid_frame_lengths.is_empty());
452        assert_eq!(decoder.buffer.len(), split);
453
454        let second = decoder.feed_with_diagnostics(&encoded[split..]);
455        assert_eq!(second.frames, vec![payload]);
456        assert!(second.invalid_frame_lengths.is_empty());
457    }
458
459    #[test]
460    fn unterminated_tail_is_dropped_only_after_twice_hardware_mtu() {
461        let hardware_mtu = 32;
462        let mut decoder = Decoder::reticulum(hardware_mtu, 0);
463        let mut exact_limit = vec![FLAG];
464        exact_limit.extend(vec![0x22; hardware_mtu * 2 - 1]);
465
466        assert!(decoder.feed(&exact_limit).is_empty());
467        assert_eq!(decoder.buffer.len(), hardware_mtu * 2);
468
469        assert!(decoder.feed(&[0x22]).is_empty());
470        assert!(decoder.buffer.is_empty());
471
472        let payload = vec![0x44; HEADER_MINSIZE + 1];
473        assert_eq!(decoder.feed(&frame(&payload)), vec![payload]);
474    }
475
476    #[test]
477    fn decoded_length_not_escaped_wire_length_controls_frame_limit() {
478        let hardware_mtu = 64;
479        let payload = vec![FLAG; hardware_mtu];
480        let encoded = frame(&payload);
481        assert!(encoded.len() > hardware_mtu * 2);
482        let mut decoder = Decoder::reticulum(hardware_mtu, 0);
483
484        let decoded = decoder.feed_with_diagnostics(&encoded);
485
486        assert_eq!(decoded.frames, vec![payload]);
487        assert!(decoded.invalid_frame_lengths.is_empty());
488    }
489}