1use crate::ABSOLUTE_MAX_FRAME_BYTES;
2use crate::error::{Error, Result};
3use zeroize::Zeroizing;
4
5pub struct FrameDecoder {
8 limit: usize,
9 payload: Zeroizing<Vec<u8>>,
10}
11
12impl std::fmt::Debug for FrameDecoder {
14 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 formatter
16 .debug_struct("FrameDecoder")
17 .field("limit", &self.limit)
18 .field("buffered", &self.payload.len())
19 .finish()
20 }
21}
22
23impl FrameDecoder {
24 pub fn new(limit: usize) -> Result<Self> {
25 validate_limit(limit)?;
26 Ok(Self {
27 limit,
28 payload: Zeroizing::new(Vec::new()),
29 })
30 }
31
32 pub const fn limit(&self) -> usize {
33 self.limit
34 }
35
36 pub fn set_limit(&mut self, limit: usize) -> Result<()> {
37 validate_limit(limit)?;
38 if !self.payload.is_empty() {
39 return Err(Error::Protocol("cannot change a frame limit mid-frame"));
40 }
41 self.limit = limit;
42 Ok(())
43 }
44
45 pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<Zeroizing<Vec<u8>>>> {
46 let mut frames = Vec::new();
47 for &byte in bytes {
48 if byte == b'\n' {
49 if self.payload.is_empty() {
50 return Err(Error::Protocol("zero-length frame"));
51 }
52 std::str::from_utf8(&self.payload)
53 .map_err(|_| Error::Protocol("frame payload is not valid UTF-8"))?;
54 frames.push(std::mem::replace(
55 &mut self.payload,
56 Zeroizing::new(Vec::new()),
57 ));
58 } else {
59 if self.payload.len() >= self.limit {
60 return Err(Error::Protocol("frame exceeds the active limit"));
61 }
62 self.reserve_one();
63 self.payload.push(byte);
64 }
65 }
66 Ok(frames)
67 }
68
69 fn reserve_one(&mut self) {
72 if self.payload.len() < self.payload.capacity() {
73 return;
74 }
75 let capacity = self
76 .payload
77 .capacity()
78 .saturating_mul(2)
79 .clamp(256.min(self.limit), self.limit);
80 let mut grown = Zeroizing::new(Vec::with_capacity(capacity));
81 grown.extend_from_slice(&self.payload);
82 self.payload = grown;
83 }
84
85 pub fn finish_eof(&self) -> Result<()> {
86 if self.payload.is_empty() {
87 Ok(())
88 } else {
89 Err(Error::Protocol("truncated frame"))
90 }
91 }
92}
93
94pub fn encode(payload: &[u8], limit: usize) -> Result<Vec<u8>> {
95 validate_payload(payload, limit)?;
96 let mut frame = Vec::with_capacity(payload.len() + 1);
97 frame.extend_from_slice(payload);
98 frame.push(b'\n');
99 Ok(frame)
100}
101
102fn validate_limit(limit: usize) -> Result<()> {
103 if limit == 0 || limit > ABSOLUTE_MAX_FRAME_BYTES {
104 Err(Error::Protocol("frame limit is outside the absolute bound"))
105 } else {
106 Ok(())
107 }
108}
109
110fn validate_payload(payload: &[u8], limit: usize) -> Result<()> {
111 validate_limit(limit)?;
112 if payload.is_empty() {
113 return Err(Error::Protocol("zero-length frame"));
114 }
115 if payload.len() > limit {
116 return Err(Error::Protocol("frame exceeds the active limit"));
117 }
118 if payload.contains(&b'\n') || payload.contains(&b'\r') {
119 return Err(Error::Protocol("frame payload must be single-line JSON"));
120 }
121 std::str::from_utf8(payload)
122 .map_err(|_| Error::Protocol("frame payload is not valid UTF-8"))?;
123 Ok(())
124}
125
126#[cfg(feature = "tokio")]
127pub(crate) struct AsyncFrameReader<R> {
128 reader: tokio::io::BufReader<R>,
129 decoder: Option<FrameDecoder>,
133}
134
135#[cfg(feature = "tokio")]
136impl<R> AsyncFrameReader<R>
137where
138 R: tokio::io::AsyncRead + Unpin,
139{
140 pub(crate) fn new(reader: R) -> Self {
141 Self {
142 reader: tokio::io::BufReader::new(reader),
143 decoder: None,
144 }
145 }
146
147 pub(crate) async fn read_frame(&mut self, limit: usize) -> Result<Option<Zeroizing<Vec<u8>>>> {
150 use tokio::io::AsyncBufReadExt;
151 match &mut self.decoder {
152 Some(decoder) if decoder.payload.is_empty() => decoder.set_limit(limit)?,
153 Some(_) => {}
154 None => self.decoder = Some(FrameDecoder::new(limit)?),
155 }
156 let decoder = self.decoder.as_mut().expect("decoder initialized above");
157 loop {
158 let (consumed, mut frames) = {
159 let available = self.reader.fill_buf().await?;
160 if available.is_empty() {
161 decoder.finish_eof()?;
162 return Ok(None);
163 }
164 let consumed = available
165 .iter()
166 .position(|byte| *byte == b'\n')
167 .map_or(available.len(), |position| position + 1);
168 (consumed, decoder.push(&available[..consumed])?)
169 };
170 self.reader.consume(consumed);
171 if let Some(frame) = frames.pop() {
172 debug_assert!(frames.is_empty());
173 return Ok(Some(frame));
174 }
175 }
176 }
177}
178
179#[cfg(feature = "tokio")]
186pub async fn read_frame<R>(reader: &mut R, limit: usize) -> Result<Option<Zeroizing<Vec<u8>>>>
187where
188 R: tokio::io::AsyncRead + Unpin,
189{
190 use tokio::io::AsyncReadExt;
191 let mut decoder = FrameDecoder::new(limit)?;
192 let mut byte = [0_u8; 1];
193 loop {
194 match reader.read(&mut byte).await? {
195 0 => {
196 decoder.finish_eof()?;
197 return Ok(None);
198 }
199 _ => {
200 let mut frames = decoder.push(&byte)?;
201 if let Some(frame) = frames.pop() {
202 return Ok(Some(frame));
203 }
204 }
205 }
206 }
207}
208
209#[cfg(feature = "tokio")]
210pub async fn write_frame<W>(writer: &mut W, payload: &[u8], limit: usize) -> Result<()>
211where
212 W: tokio::io::AsyncWrite + Unpin,
213{
214 use tokio::io::AsyncWriteExt;
215 let frame = Zeroizing::new(encode(payload, limit)?);
216 writer.write_all(&frame).await?;
217 writer.flush().await?;
218 Ok(())
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 #[test]
225 fn accepts_every_chunk_boundary_and_multiple_frames() {
226 let all = [
227 encode(br#"{\"a\":1}"#, 1024).unwrap(),
228 encode(br#"{\"b\":2}"#, 1024).unwrap(),
229 ]
230 .concat();
231 let mut decoder = FrameDecoder::new(1024).unwrap();
232 let mut decoded = Vec::new();
233 for byte in all {
234 decoded.extend(decoder.push(&[byte]).unwrap());
235 }
236 decoder.finish_eof().unwrap();
237 assert_eq!(decoded.len(), 2);
238 }
239 #[test]
240 fn rejects_a_missing_delimiter_at_the_bound() {
241 let mut decoder = FrameDecoder::new(4).unwrap();
242 assert!(decoder.push(b"12345").is_err());
243 }
244
245 #[test]
246 fn decoder_debug_hides_the_buffered_payload() {
247 let mut decoder = FrameDecoder::new(1024).unwrap();
248 decoder.push(b"{\"value\":\"hunter2").unwrap();
249 let debug = format!("{decoder:?}");
250 assert!(!debug.contains("hunter2"), "{debug}");
251 assert!(debug.contains("buffered"), "{debug}");
252 }
253
254 #[test]
255 fn decoder_grows_to_the_limit_without_exceeding_it() {
256 let mut decoder = FrameDecoder::new(1000).unwrap();
257 let payload = vec![b'a'; 1000];
258 decoder.push(&payload).unwrap();
259 assert!(decoder.payload.capacity() <= 1000);
260 assert!(decoder.push(b"a").is_err());
261 let frames = decoder.push(b"").unwrap();
262 assert!(frames.is_empty());
263 }
264
265 #[cfg(feature = "tokio")]
266 #[tokio::test]
267 async fn async_reader_keeps_a_partial_frame_when_the_read_is_cancelled() {
268 use tokio::io::AsyncWriteExt;
269 let (mut writer, reader) = tokio::io::duplex(64);
270 let mut reader = AsyncFrameReader::new(reader);
271 writer.write_all(b"{\"a\":").await.unwrap();
272 tokio::select! {
275 biased;
276 _ = reader.read_frame(1024) => panic!("half a frame must not complete"),
277 _ = std::future::ready(()) => {}
278 }
279 writer.write_all(b"1}\n").await.unwrap();
280 let frame = reader.read_frame(1024).await.unwrap().unwrap();
281 assert_eq!(&*frame, b"{\"a\":1}");
282 }
283
284 #[cfg(feature = "tokio")]
285 #[tokio::test]
286 async fn async_reader_reuses_one_buffered_chunk_across_frames() {
287 use std::pin::Pin;
288 use std::sync::Arc;
289 use std::sync::atomic::{AtomicUsize, Ordering};
290 use std::task::{Context, Poll};
291 use tokio::io::{AsyncRead, ReadBuf};
292
293 struct CountingReader {
294 bytes: Vec<u8>,
295 position: usize,
296 reads: Arc<AtomicUsize>,
297 }
298
299 impl AsyncRead for CountingReader {
300 fn poll_read(
301 mut self: Pin<&mut Self>,
302 _context: &mut Context<'_>,
303 buffer: &mut ReadBuf<'_>,
304 ) -> Poll<std::io::Result<()>> {
305 self.reads.fetch_add(1, Ordering::Relaxed);
306 let available = &self.bytes[self.position..];
307 let read = available.len().min(buffer.remaining());
308 buffer.put_slice(&available[..read]);
309 self.position += read;
310 Poll::Ready(Ok(()))
311 }
312 }
313
314 let reads = Arc::new(AtomicUsize::new(0));
315 let source = CountingReader {
316 bytes: b"{\"a\":1}\n{\"b\":2}\n".to_vec(),
317 position: 0,
318 reads: Arc::clone(&reads),
319 };
320 let mut reader = AsyncFrameReader::new(source);
321 let first = reader.read_frame(1024).await.unwrap().unwrap();
322 let second = reader.read_frame(1024).await.unwrap().unwrap();
323 assert_eq!(&*first, b"{\"a\":1}");
324 assert_eq!(&*second, b"{\"b\":2}");
325 assert_eq!(reads.load(Ordering::Relaxed), 1);
326 }
327}