Skip to main content

serial_codec/
framing.rs

1//! Byte stream framing: turn arbitrary read chunks into complete frames.
2//!
3//! The parsers follow the semantics of the Node.js `@serialport/parser-*`
4//! packages (their test suites are ported in this module's tests), with these
5//! deliberate differences:
6//!
7//! - Time is passed in (`now: Instant`) instead of using timers, so a polling
8//!   read loop drives timeouts with [`Framer::poll`] / [`Framer::deadline`].
9//! - Frames are bytes. Node's `encoding` option (readline, regex) only changes how
10//!   Node renders a frame as a string; decoding is left to the caller.
11//! - Buffers are bounded (`maxBufferSize`, default 64 KiB; SLIP uses the default,
12//!   byteLength and ccTalk are bounded by their frame size, ready keeps only a
13//!   marker-sized tail): a stream that never completes a frame cannot grow memory
14//!   forever. Delimiter, readline, regex, inter-byte timeout, packetLength and SLIP
15//!   emit the full buffer as a frame; start/end drops the overlong open frame
16//!   (keeping a later start marker).
17//! - Work per pushed byte is bounded: no parser rescans its whole buffer on every
18//!   push, so a stream without delimiters fed byte by byte stays linear. `regex`
19//!   looks back [`REGEX_LOOKBACK`] bytes for a match that started in earlier data,
20//!   and rescans the whole buffer only each time it has doubled: a longer match
21//!   that arrives in pieces is still found, at the latest when the buffered text
22//!   has doubled (or with the next match).
23//! - Unknown option names are rejected (a typo would otherwise silently fall back
24//!   to a default); the Node names and the aliases listed on each field are accepted.
25//! - `ready` finds its marker after a false start: Node resets its match on a
26//!   mismatching byte without re-checking that byte, so `"RREADY"` never matches
27//!   `"READY"`.
28//! - `packetLength` scans for a multi-byte delimiter with a sliding window: Node
29//!   drops the *newest* byte on a mismatch, so it only finds a multi-byte delimiter
30//!   at stream start or right after a packet (`00 ab ab ba` misses `0xababba`).
31//!   At end of stream only a started packet is flushed (Node also flushes the
32//!   partial delimiter window, byte-reversed). A packet ends once it is *at least*
33//!   `len + packetOverhead` long, so a config where the header outgrows that
34//!   cannot buffer forever.
35//! - `regex` keeps an incomplete UTF-8 sequence for the next chunk (Node decodes
36//!   each chunk alone and turns a split character into U+FFFD). The pattern is
37//!   [`regex_lite`] syntax, and capture groups are not spliced into the output
38//!   the way JavaScript `String.split` does.
39//! - Empty frames are never emitted (Node streams drop empty pushes too).
40
41use serde::{Deserialize, Serialize};
42use std::fmt;
43use std::time::{Duration, Instant};
44
45/// Default cap on buffered, not yet framed bytes.
46pub const DEFAULT_MAX_BUFFER: usize = 65_536;
47
48/// How far back (in bytes) the `regex` framer re-scans old text on every push for
49/// a match that may end in newly pushed data.
50pub const REGEX_LOOKBACK: usize = 32;
51
52/// A stateful framer fed with read chunks.
53pub trait Framer: Send {
54    /// Feed bytes received at `now`; complete frames are appended to `out`.
55    fn push(&mut self, data: &[u8], now: Instant, out: &mut Vec<Vec<u8>>);
56    /// Emit frames that complete by time alone (inter-byte timeout).
57    fn poll(&mut self, _now: Instant, _out: &mut Vec<Vec<u8>>) {}
58    /// When [`poll`](Framer::poll) next has something to do.
59    fn deadline(&self) -> Option<Instant> {
60        None
61    }
62    /// End of stream: the buffered partial frame, if any.
63    fn flush(&mut self) -> Option<Vec<u8>>;
64}
65
66/// Invalid parser options.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct ConfigError(pub String);
69
70impl fmt::Display for ConfigError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "invalid parser options: {}", self.0)
73    }
74}
75
76impl std::error::Error for ConfigError {}
77
78fn bad<T>(msg: &str) -> Result<T, ConfigError> {
79    Err(ConfigError(msg.into()))
80}
81
82/// Bytes given as a UTF-8 string or as an array of byte values.
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(untagged)]
85pub enum ByteSeq {
86    Text(String),
87    Bytes(Vec<u8>),
88}
89
90impl ByteSeq {
91    pub fn to_vec(&self) -> Vec<u8> {
92        match self {
93            ByteSeq::Text(s) => s.as_bytes().to_vec(),
94            ByteSeq::Bytes(b) => b.clone(),
95        }
96    }
97}
98
99impl From<&str> for ByteSeq {
100    fn from(s: &str) -> Self {
101        ByteSeq::Text(s.into())
102    }
103}
104
105impl From<&[u8]> for ByteSeq {
106    fn from(b: &[u8]) -> Self {
107        ByteSeq::Bytes(b.to_vec())
108    }
109}
110
111fn non_empty(seq: &ByteSeq, name: &str) -> Result<Vec<u8>, ConfigError> {
112    let v = seq.to_vec();
113    if v.is_empty() {
114        return bad(&format!("\"{name}\" has a 0 length"));
115    }
116    Ok(v)
117}
118
119fn max_buf(v: Option<usize>) -> Result<usize, ConfigError> {
120    match v {
121        Some(0) => bad("\"maxBufferSize\" must be greater than 0"),
122        Some(n) => Ok(n),
123        None => Ok(DEFAULT_MAX_BUFFER),
124    }
125}
126
127fn find(hay: &[u8], needle: &[u8], from: usize) -> Option<usize> {
128    if from > hay.len() || needle.len() > hay.len() - from {
129        return None;
130    }
131    hay[from..]
132        .windows(needle.len())
133        .position(|w| w == needle)
134        .map(|p| p + from)
135}
136
137fn emit(out: &mut Vec<Vec<u8>>, frame: &[u8]) {
138    if !frame.is_empty() {
139        out.push(frame.to_vec());
140    }
141}
142
143fn take_flush(buf: &mut Vec<u8>) -> Option<Vec<u8>> {
144    let rest = std::mem::take(buf);
145    (!rest.is_empty()).then_some(rest)
146}
147
148/// Parser selection, as sent from JavaScript (`{ type: "readline", … }`).
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(
151    tag = "type",
152    rename_all = "camelCase",
153    rename_all_fields = "camelCase",
154    deny_unknown_fields
155)]
156pub enum FramerConfig {
157    /// Split on a byte sequence (`@serialport/parser-delimiter`).
158    Delimiter {
159        delimiter: ByteSeq,
160        #[serde(default)]
161        include_delimiter: bool,
162        #[serde(default)]
163        max_buffer_size: Option<usize>,
164    },
165    /// Split on `"\n"` by default (`@serialport/parser-readline`).
166    Readline {
167        #[serde(default)]
168        delimiter: Option<ByteSeq>,
169        #[serde(default)]
170        include_delimiter: bool,
171        #[serde(default)]
172        max_buffer_size: Option<usize>,
173        /// Node's string-rendering option; accepted and ignored (frames are bytes).
174        #[serde(default)]
175        encoding: Option<String>,
176    },
177    /// Fixed-size frames (`@serialport/parser-byte-length`).
178    ByteLength { length: usize },
179    /// A frame ends after `interval_ms` of silence (`@serialport/parser-inter-byte-timeout`).
180    InterByteTimeout {
181        #[serde(alias = "interval")]
182        interval_ms: u64,
183        #[serde(default)]
184        max_buffer_size: Option<usize>,
185    },
186    /// Delimiter + little-endian length field (`@serialport/parser-packet-length`).
187    PacketLength {
188        /// One value or a list; read big-endian over `delimiterBytes` wire bytes.
189        #[serde(default)]
190        delimiter: Option<OneOrMany>,
191        #[serde(default)]
192        delimiter_bytes: Option<usize>,
193        #[serde(default)]
194        packet_overhead: Option<usize>,
195        #[serde(default)]
196        length_bytes: Option<usize>,
197        #[serde(default)]
198        length_offset: Option<usize>,
199        #[serde(default)]
200        max_len: Option<usize>,
201        #[serde(default)]
202        max_buffer_size: Option<usize>,
203    },
204    /// Drop everything until a marker, then pass data through (`@serialport/parser-ready`).
205    Ready { delimiter: ByteSeq },
206    /// Split on a regular expression over UTF-8 text (`@serialport/parser-regex`).
207    Regex {
208        regex: String,
209        #[serde(default)]
210        max_buffer_size: Option<usize>,
211        /// Node's string-rendering option; accepted and ignored (frames are bytes).
212        #[serde(default)]
213        encoding: Option<String>,
214    },
215    /// Frames between a start and an end marker (`@serialport/parser-start-end`).
216    StartEnd {
217        start_delimiter: ByteSeq,
218        end_delimiter: ByteSeq,
219        #[serde(default)]
220        include_start_delimiter: bool,
221        #[serde(default)]
222        include_end_delimiter: bool,
223        #[serde(default)]
224        max_buffer_size: Option<usize>,
225    },
226    /// SLIP (RFC 1055) decoder (`@serialport/parser-slip-encoder` `SlipDecoder`).
227    Slip(SlipOptions),
228    /// ccTalk frames, length in byte 1 (`@serialport/parser-cctalk`).
229    CcTalk {
230        /// Drop a partial frame after this gap; 0 disables. Default 50.
231        #[serde(default, alias = "maxDelayBetweenBytesMs")]
232        max_delay_ms: Option<u64>,
233    },
234}
235
236/// A number or a list of numbers (Node accepts both for `delimiter`).
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(untagged)]
239pub enum OneOrMany {
240    One(u32),
241    Many(Vec<u32>),
242}
243
244impl FramerConfig {
245    /// Validate options and build the framer.
246    pub fn build(&self) -> Result<Box<dyn Framer>, ConfigError> {
247        Ok(match self {
248            FramerConfig::Delimiter {
249                delimiter,
250                include_delimiter,
251                max_buffer_size,
252            } => Box::new(DelimiterFramer::new(
253                non_empty(delimiter, "delimiter")?,
254                *include_delimiter,
255                max_buf(*max_buffer_size)?,
256            )),
257            FramerConfig::Readline {
258                delimiter,
259                include_delimiter,
260                max_buffer_size,
261                encoding: _,
262            } => {
263                let delimiter = match delimiter {
264                    Some(d) => non_empty(d, "delimiter")?,
265                    None => b"\n".to_vec(),
266                };
267                Box::new(DelimiterFramer::new(
268                    delimiter,
269                    *include_delimiter,
270                    max_buf(*max_buffer_size)?,
271                ))
272            }
273            FramerConfig::ByteLength { length } => {
274                if *length == 0 {
275                    return bad("\"length\" must be greater than 0");
276                }
277                Box::new(ByteLengthFramer {
278                    length: *length,
279                    buf: Vec::new(),
280                })
281            }
282            FramerConfig::InterByteTimeout {
283                interval_ms,
284                max_buffer_size,
285            } => {
286                if *interval_ms == 0 {
287                    return bad("\"intervalMs\" must be greater than 0");
288                }
289                Box::new(InterByteTimeoutFramer {
290                    interval: Duration::from_millis(*interval_ms),
291                    max: max_buf(*max_buffer_size)?,
292                    buf: Vec::new(),
293                    deadline: None,
294                })
295            }
296            FramerConfig::PacketLength {
297                delimiter,
298                delimiter_bytes,
299                packet_overhead,
300                length_bytes,
301                length_offset,
302                max_len,
303                max_buffer_size,
304            } => {
305                let p = PacketLengthFramer {
306                    delimiters: match delimiter {
307                        None => vec![0xaa],
308                        Some(OneOrMany::One(d)) => vec![*d],
309                        Some(OneOrMany::Many(d)) => d.clone(),
310                    },
311                    delimiter_bytes: delimiter_bytes.unwrap_or(1),
312                    packet_overhead: packet_overhead.unwrap_or(2),
313                    length_bytes: length_bytes.unwrap_or(1),
314                    length_offset: length_offset.unwrap_or(1),
315                    max_len: max_len.unwrap_or(0xff),
316                    max: max_buf(*max_buffer_size)?,
317                    buf: Vec::new(),
318                    started: false,
319                };
320                if p.delimiters.is_empty() || !(1..=4).contains(&p.delimiter_bytes) {
321                    return bad("\"delimiter\" needs 1..=4 \"delimiterBytes\"");
322                }
323                if !(1..=8).contains(&p.length_bytes) {
324                    return bad("\"lengthBytes\" must be 1..=8");
325                }
326                if p.length_offset < p.delimiter_bytes {
327                    return bad("\"lengthOffset\" overlaps the delimiter");
328                }
329                // The header must fit in the buffer, or no packet could ever start.
330                if p.length_offset
331                    .checked_add(p.length_bytes)
332                    .is_none_or(|end| end > p.max)
333                {
334                    return bad("\"lengthOffset\" + \"lengthBytes\" exceeds \"maxBufferSize\"");
335                }
336                Box::new(p)
337            }
338            FramerConfig::Ready { delimiter } => Box::new(ReadyFramer {
339                delimiter: non_empty(delimiter, "delimiter")?,
340                tail: Vec::new(),
341                ready: false,
342            }),
343            FramerConfig::Regex {
344                regex,
345                max_buffer_size,
346                encoding: _,
347            } => Box::new(RegexFramer {
348                regex: regex_lite::Regex::new(regex)
349                    .map_err(|e| ConfigError(format!("\"regex\": {e}")))?,
350                max: max_buf(*max_buffer_size)?,
351                text: String::new(),
352                pending: Vec::new(),
353                scanned: 0,
354                full_scan: 0,
355            }),
356            FramerConfig::StartEnd {
357                start_delimiter,
358                end_delimiter,
359                include_start_delimiter,
360                include_end_delimiter,
361                max_buffer_size,
362            } => Box::new(StartEndFramer {
363                start: non_empty(start_delimiter, "startDelimiter")?,
364                end: non_empty(end_delimiter, "endDelimiter")?,
365                include_start: *include_start_delimiter,
366                include_end: *include_end_delimiter,
367                max: max_buf(*max_buffer_size)?,
368                buf: Vec::new(),
369                open: None,
370                scan_from: 0,
371            }),
372            FramerConfig::Slip(o) => Box::new(SlipDecoder::new(o.clone())),
373            FramerConfig::CcTalk { max_delay_ms } => Box::new(CcTalkFramer {
374                max_delay: Duration::from_millis(max_delay_ms.unwrap_or(50)),
375                buf: Vec::new(),
376                last: None,
377            }),
378        })
379    }
380}
381
382/// Split on a delimiter; also used for readline.
383struct DelimiterFramer {
384    delimiter: Vec<u8>,
385    include: bool,
386    max: usize,
387    buf: Vec<u8>,
388}
389
390impl DelimiterFramer {
391    fn new(delimiter: Vec<u8>, include: bool, max: usize) -> Self {
392        Self {
393            delimiter,
394            include,
395            max,
396            buf: Vec::new(),
397        }
398    }
399}
400
401impl Framer for DelimiterFramer {
402    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
403        // Only the tail that can still hold part of a delimiter needs rescanning.
404        let mut from = self.buf.len().saturating_sub(self.delimiter.len() - 1);
405        self.buf.extend_from_slice(data);
406        let mut start = 0;
407        while let Some(pos) = find(&self.buf, &self.delimiter, from.max(start)) {
408            let end = pos
409                + if self.include {
410                    self.delimiter.len()
411                } else {
412                    0
413                };
414            emit(out, &self.buf[start..end]);
415            start = pos + self.delimiter.len();
416            from = start;
417        }
418        self.buf.drain(..start);
419        // ponytail: an overlong line is cut at max bytes, like a full receive buffer.
420        while self.buf.len() >= self.max {
421            let rest = self.buf.split_off(self.max);
422            out.push(std::mem::replace(&mut self.buf, rest));
423        }
424    }
425
426    fn flush(&mut self) -> Option<Vec<u8>> {
427        take_flush(&mut self.buf)
428    }
429}
430
431struct ByteLengthFramer {
432    length: usize,
433    buf: Vec<u8>,
434}
435
436impl Framer for ByteLengthFramer {
437    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
438        self.buf.extend_from_slice(data);
439        let full = self.buf.len() / self.length * self.length;
440        for frame in self.buf[..full].chunks(self.length) {
441            out.push(frame.to_vec());
442        }
443        self.buf.drain(..full);
444    }
445
446    fn flush(&mut self) -> Option<Vec<u8>> {
447        take_flush(&mut self.buf)
448    }
449}
450
451struct InterByteTimeoutFramer {
452    interval: Duration,
453    max: usize,
454    buf: Vec<u8>,
455    deadline: Option<Instant>,
456}
457
458impl Framer for InterByteTimeoutFramer {
459    fn push(&mut self, data: &[u8], now: Instant, out: &mut Vec<Vec<u8>>) {
460        // A late poll must not merge a packet that already timed out with new data.
461        self.poll(now, out);
462        for &b in data {
463            self.buf.push(b);
464            if self.buf.len() >= self.max {
465                out.push(std::mem::take(&mut self.buf));
466            }
467        }
468        if !data.is_empty() {
469            self.deadline = Some(now + self.interval);
470        }
471    }
472
473    fn poll(&mut self, now: Instant, out: &mut Vec<Vec<u8>>) {
474        if self.deadline.is_some_and(|d| now >= d) {
475            self.deadline = None;
476            emit(out, &std::mem::take(&mut self.buf));
477        }
478    }
479
480    fn deadline(&self) -> Option<Instant> {
481        self.deadline.filter(|_| !self.buf.is_empty())
482    }
483
484    fn flush(&mut self) -> Option<Vec<u8>> {
485        self.deadline = None;
486        take_flush(&mut self.buf)
487    }
488}
489
490struct PacketLengthFramer {
491    delimiters: Vec<u32>,
492    delimiter_bytes: usize,
493    packet_overhead: usize,
494    length_bytes: usize,
495    length_offset: usize,
496    max_len: usize,
497    max: usize,
498    buf: Vec<u8>,
499    started: bool,
500}
501
502impl Framer for PacketLengthFramer {
503    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
504        for &b in data {
505            self.buf.push(b);
506            if !self.started {
507                // Sliding window over the last `delimiter_bytes` bytes, read big-endian
508                // (wire order), the same value Node builds by prepending bytes.
509                if self.buf.len() > self.delimiter_bytes {
510                    self.buf.remove(0);
511                }
512                if self.buf.len() == self.delimiter_bytes {
513                    let v = self.buf.iter().fold(0u32, |a, &x| (a << 8) | x as u32);
514                    self.started = self.delimiters.contains(&v);
515                }
516                continue;
517            }
518            let end = self.length_offset + self.length_bytes;
519            if self.buf.len() >= end {
520                let len = self.buf[self.length_offset..end]
521                    .iter()
522                    .rev()
523                    .fold(0u64, |a, &x| (a << 8) | x as u64);
524                let total = len.saturating_add(self.packet_overhead as u64);
525                if self.buf.len() as u64 >= total || len > self.max_len as u64 {
526                    out.push(std::mem::take(&mut self.buf));
527                    self.started = false;
528                    continue;
529                }
530            }
531            if self.buf.len() >= self.max {
532                // A packet that can never complete within maxBufferSize.
533                out.push(std::mem::take(&mut self.buf));
534                self.started = false;
535            }
536        }
537    }
538
539    fn flush(&mut self) -> Option<Vec<u8>> {
540        let started = std::mem::take(&mut self.started);
541        let rest = std::mem::take(&mut self.buf);
542        (started && !rest.is_empty()).then_some(rest)
543    }
544}
545
546struct ReadyFramer {
547    delimiter: Vec<u8>,
548    tail: Vec<u8>,
549    ready: bool,
550}
551
552impl Framer for ReadyFramer {
553    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
554        if self.ready {
555            emit(out, data);
556            return;
557        }
558        let keep = self.tail.len();
559        self.tail.extend_from_slice(data);
560        if let Some(pos) = find(&self.tail, &self.delimiter, 0) {
561            self.ready = true;
562            let rest = pos + self.delimiter.len();
563            emit(out, &self.tail[rest.max(keep)..]);
564            self.tail = Vec::new();
565        } else {
566            let cut = self.tail.len().saturating_sub(self.delimiter.len() - 1);
567            self.tail.drain(..cut);
568        }
569    }
570
571    fn flush(&mut self) -> Option<Vec<u8>> {
572        None
573    }
574}
575
576struct RegexFramer {
577    regex: regex_lite::Regex,
578    max: usize,
579    text: String,
580    /// Incomplete UTF-8 sequence at the end of the last chunk.
581    pending: Vec<u8>,
582    /// Length of `text` at the last search.
583    scanned: usize,
584    /// Length of `text` at the last search from its start.
585    full_scan: usize,
586}
587
588impl RegexFramer {
589    fn decode(&mut self, data: &[u8]) {
590        let mut bytes = std::mem::take(&mut self.pending);
591        bytes.extend_from_slice(data);
592        let mut rest: &[u8] = &bytes;
593        loop {
594            match std::str::from_utf8(rest) {
595                Ok(s) => {
596                    self.text.push_str(s);
597                    return;
598                }
599                Err(e) => {
600                    let (ok, tail) = rest.split_at(e.valid_up_to());
601                    self.text
602                        .push_str(std::str::from_utf8(ok).unwrap_or_default());
603                    match e.error_len() {
604                        Some(n) => {
605                            self.text.push('\u{FFFD}');
606                            rest = &tail[n..];
607                        }
608                        None => {
609                            self.pending = tail.to_vec();
610                            return;
611                        }
612                    }
613                }
614            }
615        }
616    }
617}
618
619impl Framer for RegexFramer {
620    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
621        self.decode(data);
622        // Matches that ended in old text were split off by an earlier push, so only
623        // a match reaching into the new text can be missing. Look for one from a
624        // short lookback, and from the start each time the text has doubled (linear
625        // in total). The full split runs only when there is something to split, and
626        // then consumes everything before the last match.
627        let mut from = self.scanned.saturating_sub(REGEX_LOOKBACK);
628        if self.text.len() >= 2 * self.full_scan {
629            from = 0;
630            self.full_scan = self.text.len();
631        }
632        while !self.text.is_char_boundary(from) {
633            from -= 1;
634        }
635        if self.regex.find_at(&self.text, from).is_some() {
636            let mut parts: Vec<&str> = self.regex.split(&self.text).collect();
637            let last = parts.pop().unwrap_or_default().to_string();
638            for p in parts {
639                emit(out, p.as_bytes());
640            }
641            self.text = last;
642            self.full_scan = self.text.len();
643        }
644        self.scanned = self.text.len();
645        if self.text.len() >= self.max {
646            out.push(std::mem::take(&mut self.text).into_bytes());
647            self.scanned = 0;
648            self.full_scan = 0;
649        }
650    }
651
652    fn flush(&mut self) -> Option<Vec<u8>> {
653        self.scanned = 0;
654        self.full_scan = 0;
655        let mut rest = std::mem::take(&mut self.text).into_bytes();
656        rest.append(&mut self.pending);
657        (!rest.is_empty()).then_some(rest)
658    }
659}
660
661struct StartEndFramer {
662    start: Vec<u8>,
663    end: Vec<u8>,
664    include_start: bool,
665    include_end: bool,
666    max: usize,
667    buf: Vec<u8>,
668    /// Start of the open frame (a start marker found, no end yet).
669    open: Option<usize>,
670    /// Where the next search (for a start marker, or for the end of the open
671    /// frame) resumes; everything before it has been searched.
672    scan_from: usize,
673}
674
675impl Framer for StartEndFramer {
676    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
677        self.buf.extend_from_slice(data);
678        let mut consumed = 0;
679        loop {
680            let s = match self.open {
681                Some(s) => s,
682                None => {
683                    let Some(s) = find(&self.buf, &self.start, self.scan_from) else {
684                        let tail = self.buf.len().saturating_sub(self.start.len() - 1);
685                        self.scan_from = tail.max(consumed);
686                        break;
687                    };
688                    self.open = Some(s);
689                    self.scan_from = s + self.start.len();
690                    s
691                }
692            };
693            let Some(e) = find(&self.buf, &self.end, self.scan_from) else {
694                let tail = self.buf.len().saturating_sub(self.end.len() - 1);
695                self.scan_from = tail.max(s + self.start.len());
696                break;
697            };
698            let from = if self.include_start {
699                s
700            } else {
701                s + self.start.len()
702            };
703            let to = if self.include_end {
704                e + self.end.len()
705            } else {
706                e
707            };
708            emit(out, &self.buf[from..to]);
709            consumed = e + self.end.len();
710            self.open = None;
711            self.scan_from = consumed;
712        }
713        self.buf.drain(..consumed);
714        self.open = self.open.map(|s| s - consumed);
715        self.scan_from -= consumed;
716        if self.buf.len() >= self.max {
717            // Rescanned from scratch: once per max bytes at most.
718            self.open = None;
719            self.scan_from = 0;
720            // An open frame that never ends: keep only from the last start marker.
721            match self
722                .buf
723                .windows(self.start.len())
724                .rposition(|w| w == self.start)
725            {
726                Some(p) if p > 0 => {
727                    self.buf.drain(..p);
728                }
729                _ => self.buf.clear(),
730            }
731        }
732    }
733
734    fn flush(&mut self) -> Option<Vec<u8>> {
735        self.open = None;
736        self.scan_from = 0;
737        take_flush(&mut self.buf)
738    }
739}
740
741/// SLIP framing bytes (RFC 1055 defaults; `start`/`esc_start` optional).
742#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
743#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
744pub struct SlipOptions {
745    #[serde(alias = "START")]
746    pub start: Option<u8>,
747    #[serde(alias = "ESC")]
748    pub esc: u8,
749    #[serde(alias = "END")]
750    pub end: u8,
751    #[serde(alias = "ESC_START")]
752    pub esc_start: Option<u8>,
753    #[serde(alias = "ESC_END")]
754    pub esc_end: u8,
755    #[serde(alias = "ESC_ESC")]
756    pub esc_esc: u8,
757}
758
759impl Default for SlipOptions {
760    fn default() -> Self {
761        Self {
762            start: None,
763            esc: 0xdb,
764            end: 0xc0,
765            esc_start: None,
766            esc_end: 0xdc,
767            esc_esc: 0xdd,
768        }
769    }
770}
771
772/// SLIP-encode one packet. `bluetooth_quirk` adds a leading END and skips empty packets.
773pub fn slip_encode(data: &[u8], o: &SlipOptions, bluetooth_quirk: bool) -> Vec<u8> {
774    if bluetooth_quirk && data.is_empty() {
775        return Vec::new();
776    }
777    let mut v = Vec::with_capacity(data.len() * 2 + 3);
778    if bluetooth_quirk {
779        v.push(o.end);
780    }
781    if let Some(s) = o.start {
782        v.push(s);
783    }
784    for &b in data {
785        match b {
786            b if Some(b) == o.start && o.esc_start.is_some() => {
787                v.extend([o.esc, o.esc_start.unwrap_or_default()])
788            }
789            b if b == o.end => v.extend([o.esc, o.esc_end]),
790            b if b == o.esc => v.extend([o.esc, o.esc_esc]),
791            b => v.push(b),
792        }
793    }
794    v.push(o.end);
795    v
796}
797
798/// SLIP decoder with Node `SlipDecoder` semantics.
799pub struct SlipDecoder {
800    o: SlipOptions,
801    buf: Vec<u8>,
802    escape: bool,
803    started: bool,
804}
805
806impl SlipDecoder {
807    pub fn new(o: SlipOptions) -> Self {
808        Self {
809            o,
810            buf: Vec::new(),
811            escape: false,
812            started: false,
813        }
814    }
815}
816
817impl Framer for SlipDecoder {
818    fn push(&mut self, data: &[u8], _now: Instant, out: &mut Vec<Vec<u8>>) {
819        let o = &self.o;
820        for &raw in data {
821            let mut byte = raw;
822            if Some(byte) == o.start {
823                self.started = true;
824                continue;
825            }
826            if o.start.is_none() {
827                self.started = true;
828            }
829            if self.escape {
830                if o.start.is_some() && Some(byte) == o.esc_start {
831                    byte = o.start.unwrap_or_default();
832                } else if byte == o.esc_esc {
833                    byte = o.esc;
834                } else if byte == o.esc_end {
835                    byte = o.end;
836                } else {
837                    // Invalid escape: end the frame, the byte starts the next one.
838                    emit(out, &std::mem::take(&mut self.buf));
839                }
840            } else if byte == o.esc {
841                self.escape = true;
842                continue;
843            } else if byte == o.end {
844                emit(out, &std::mem::take(&mut self.buf));
845                self.escape = false;
846                self.started = false;
847                continue;
848            }
849            self.escape = false;
850            if self.started {
851                self.buf.push(byte);
852                if self.buf.len() >= DEFAULT_MAX_BUFFER {
853                    out.push(std::mem::take(&mut self.buf));
854                }
855            }
856        }
857    }
858
859    fn flush(&mut self) -> Option<Vec<u8>> {
860        self.escape = false;
861        self.started = false;
862        take_flush(&mut self.buf)
863    }
864}
865
866struct CcTalkFramer {
867    max_delay: Duration,
868    buf: Vec<u8>,
869    last: Option<Instant>,
870}
871
872impl Framer for CcTalkFramer {
873    fn push(&mut self, data: &[u8], now: Instant, out: &mut Vec<Vec<u8>>) {
874        if !self.max_delay.is_zero() {
875            if self
876                .last
877                .is_some_and(|t| now.duration_since(t) > self.max_delay)
878            {
879                self.buf.clear();
880            }
881            self.last = Some(now);
882        }
883        self.buf.extend_from_slice(data);
884        while self.buf.len() > 1 && self.buf.len() >= self.buf[1] as usize + 5 {
885            let n = self.buf[1] as usize + 5;
886            out.push(self.buf.drain(..n).collect());
887        }
888    }
889
890    fn flush(&mut self) -> Option<Vec<u8>> {
891        // Node's ccTalk parser has no flush: a partial frame is dropped.
892        self.buf.clear();
893        None
894    }
895}
896
897#[cfg(test)]
898mod tests;