Skip to main content

sim_lib_net_core/
line.rs

1//! Incremental newline framing (LineDecoder).
2
3use crate::NetError;
4
5/// Default maximum line length for incremental stream decoders.
6pub const DEFAULT_MAX_LINE_BYTES: usize = 64 * 1024;
7
8/// Incremental newline framer.
9///
10/// Accumulates pushed bytes and yields complete lines split on `\n`, stripping
11/// a single trailing `\r` from each (so both `\n` and `\r\n` framing work). A
12/// partial final line with no terminating `\n` stays buffered across pushes
13/// until either a later push completes it or [`flush`](LineDecoder::flush) is
14/// called.
15///
16/// [`new`](LineDecoder::new) uses [`DEFAULT_MAX_LINE_BYTES`]. Untrusted streams
17/// should call [`push_checked`](LineDecoder::push_checked) so over-long lines
18/// are reported as [`NetError::LineTooLong`]. [`unbounded`](LineDecoder::unbounded)
19/// is the explicit opt-out for trusted in-memory inputs.
20///
21/// This is the framing half of `sim-lib-agent-runner-http`'s old per-decoder
22/// `line_buffer` loop, lifted into one shared, tested type.
23#[derive(Debug)]
24pub struct LineDecoder {
25    buf: Vec<u8>,
26    max_line_bytes: Option<usize>,
27}
28
29impl Default for LineDecoder {
30    fn default() -> Self {
31        Self::with_max_line_bytes(DEFAULT_MAX_LINE_BYTES)
32    }
33}
34
35impl LineDecoder {
36    /// Create an empty decoder using [`DEFAULT_MAX_LINE_BYTES`].
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Create an empty decoder with no line limit.
42    ///
43    /// Use this only for trusted, already-bounded inputs.
44    pub fn unbounded() -> Self {
45        Self {
46            buf: Vec::new(),
47            max_line_bytes: None,
48        }
49    }
50
51    /// Create an empty decoder that rejects any line longer than
52    /// `max_line_bytes`.
53    pub fn with_max_line_bytes(max_line_bytes: usize) -> Self {
54        Self {
55            buf: Vec::new(),
56            max_line_bytes: Some(max_line_bytes),
57        }
58    }
59
60    /// Append `bytes` and return every line completed by a `\n`. The trailing
61    /// `\r` of a `\r\n` pair is stripped. Bytes after the last `\n` remain
62    /// buffered.
63    ///
64    /// This compatibility convenience panics if the configured line cap is
65    /// exceeded. Use [`push_checked`](LineDecoder::push_checked) for untrusted
66    /// input so the caller can map the error into its own domain.
67    pub fn push(&mut self, bytes: &[u8]) -> Vec<Vec<u8>> {
68        self.push_checked(bytes)
69            .expect("line decoder cap exceeded; use push_checked for fallible handling")
70    }
71
72    /// Append `bytes` and return every completed line.
73    ///
74    /// The configured cap is checked before bytes are appended, so an over-long
75    /// completed line or still-unterminated line leaves the existing buffered
76    /// state unchanged.
77    pub fn push_checked(&mut self, bytes: &[u8]) -> Result<Vec<Vec<u8>>, NetError> {
78        self.check_append(bytes)?;
79        self.buf.extend_from_slice(bytes);
80        Ok(self.drain_complete_lines())
81    }
82
83    /// Take any buffered remainder (an unterminated final line). Returns `None`
84    /// when nothing is buffered. A single trailing `\r` is stripped to match
85    /// [`push`](LineDecoder::push).
86    ///
87    /// This compatibility convenience panics if the configured line cap is
88    /// exceeded. Use [`flush_checked`](LineDecoder::flush_checked) for
89    /// untrusted input.
90    pub fn flush(&mut self) -> Option<Vec<u8>> {
91        self.flush_checked()
92            .expect("line decoder cap exceeded; use flush_checked for fallible handling")
93    }
94
95    /// Take any buffered remainder, checking the configured cap first.
96    pub fn flush_checked(&mut self) -> Result<Option<Vec<u8>>, NetError> {
97        self.check_len(self.buf.len())?;
98        if self.buf.is_empty() {
99            return Ok(None);
100        }
101        let mut line = std::mem::take(&mut self.buf);
102        if line.last() == Some(&b'\r') {
103            line.pop();
104        }
105        Ok(Some(line))
106    }
107
108    /// Number of bytes currently buffered as a partial line.
109    pub fn buffered_len(&self) -> usize {
110        self.buf.len()
111    }
112
113    /// Borrow the bytes currently buffered as a partial (unterminated) line.
114    pub fn buffered(&self) -> &[u8] {
115        &self.buf
116    }
117
118    /// Whether any partial line is currently buffered.
119    pub fn has_buffered(&self) -> bool {
120        !self.buf.is_empty()
121    }
122
123    fn check_append(&self, bytes: &[u8]) -> Result<(), NetError> {
124        let Some(max) = self.max_line_bytes else {
125            return Ok(());
126        };
127        self.check_len(self.buf.len())?;
128        let mut line_len = self.buf.len();
129        for byte in bytes {
130            if *byte == b'\n' {
131                line_len = 0;
132            } else {
133                line_len = line_len.saturating_add(1);
134                if line_len > max {
135                    return Err(NetError::LineTooLong { max });
136                }
137            }
138        }
139        Ok(())
140    }
141
142    fn check_len(&self, len: usize) -> Result<(), NetError> {
143        if let Some(max) = self.max_line_bytes
144            && len > max
145        {
146            return Err(NetError::LineTooLong { max });
147        }
148        Ok(())
149    }
150
151    fn drain_complete_lines(&mut self) -> Vec<Vec<u8>> {
152        let mut lines = Vec::new();
153        while let Some(index) = self.buf.iter().position(|byte| *byte == b'\n') {
154            let mut line = self.buf.drain(..=index).collect::<Vec<u8>>();
155            line.pop(); // drop the '\n'
156            if line.last() == Some(&b'\r') {
157                line.pop();
158            }
159            lines.push(line);
160        }
161        lines
162    }
163}