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}