Skip to main content

sim_lib_net_core/
line.rs

1//! Incremental newline framing (LineDecoder).
2
3/// Incremental newline framer.
4///
5/// Accumulates pushed bytes and yields complete lines split on `\n`, stripping
6/// a single trailing `\r` from each (so both `\n` and `\r\n` framing work). A
7/// partial final line with no terminating `\n` stays buffered across pushes
8/// until either a later push completes it or [`flush`](LineDecoder::flush) is
9/// called.
10///
11/// This is the framing half of `sim-lib-agent-runner-http`'s old per-decoder
12/// `line_buffer` loop, lifted into one shared, tested type.
13#[derive(Debug, Default)]
14pub struct LineDecoder {
15    buf: Vec<u8>,
16}
17
18impl LineDecoder {
19    /// Create an empty decoder.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Append `bytes` and return every line completed by a `\n`. The trailing
25    /// `\r` of a `\r\n` pair is stripped. Bytes after the last `\n` remain
26    /// buffered.
27    pub fn push(&mut self, bytes: &[u8]) -> Vec<Vec<u8>> {
28        self.buf.extend_from_slice(bytes);
29        let mut lines = Vec::new();
30        while let Some(index) = self.buf.iter().position(|byte| *byte == b'\n') {
31            let mut line = self.buf.drain(..=index).collect::<Vec<u8>>();
32            line.pop(); // drop the '\n'
33            if line.last() == Some(&b'\r') {
34                line.pop();
35            }
36            lines.push(line);
37        }
38        lines
39    }
40
41    /// Take any buffered remainder (an unterminated final line). Returns `None`
42    /// when nothing is buffered. A single trailing `\r` is stripped to match
43    /// [`push`](LineDecoder::push).
44    pub fn flush(&mut self) -> Option<Vec<u8>> {
45        if self.buf.is_empty() {
46            return None;
47        }
48        let mut line = std::mem::take(&mut self.buf);
49        if line.last() == Some(&b'\r') {
50            line.pop();
51        }
52        Some(line)
53    }
54
55    /// Number of bytes currently buffered as a partial line.
56    pub fn buffered_len(&self) -> usize {
57        self.buf.len()
58    }
59
60    /// Borrow the bytes currently buffered as a partial (unterminated) line.
61    pub fn buffered(&self) -> &[u8] {
62        &self.buf
63    }
64
65    /// Whether any partial line is currently buffered.
66    pub fn has_buffered(&self) -> bool {
67        !self.buf.is_empty()
68    }
69}