Skip to main content

melin_wire_protocol/
blocking.rs

1//! Blocking (synchronous) frame reader/writer for dedicated I/O threads.
2//!
3//! Same length-prefixed framing as the async TCP/UDS transports, but uses
4//! `std::io::Read`/`Write` directly. Used by the server's reader and
5//! response threads to avoid tokio task scheduling overhead on the hot path.
6
7use std::io::{self, BufReader, BufWriter, Read, Write};
8
9/// Bound on one frame's payload, after the 4-byte length prefix: the
10/// same limit on every transport. A reader refuses a frame declaring
11/// more, and [`BlockingFrameWriter`] refuses to write one, so an
12/// oversized frame is an error at its source rather than a dropped
13/// connection at the far end.
14pub const MAX_FRAME_SIZE: usize = 1024;
15
16/// Blocking frame reader. Reads length-prefixed frames from any `Read` source.
17///
18/// Uses `BufReader` to amortize read syscalls — a single recv fills the
19/// buffer with many frames, so subsequent `read_exact` calls hit the
20/// buffer instead of making kernel transitions. This is critical for
21/// round-trip latency: without buffering, each frame requires 2 read
22/// syscalls (4-byte length prefix + payload).
23///
24/// Generic over the reader type so it works with both `std::net::TcpStream`
25/// and `std::os::unix::net::UnixStream`.
26pub struct BlockingFrameReader<R> {
27    reader: BufReader<R>,
28    /// Reusable frame buffer — avoids a heap allocation per frame.
29    /// Fixed at MAX_FRAME_SIZE (1 KiB); the valid slice is `&buf[..len]`.
30    buf: [u8; MAX_FRAME_SIZE],
31    /// Length of the last successfully read frame (valid bytes in `buf`).
32    frame_len: usize,
33}
34
35impl<R: Read> BlockingFrameReader<R> {
36    pub fn new(reader: R) -> Self {
37        Self {
38            reader: BufReader::new(reader),
39            buf: [0u8; MAX_FRAME_SIZE],
40            frame_len: 0,
41        }
42    }
43
44    /// Read the next complete frame into the internal buffer.
45    /// Returns a borrowed slice of the frame payload, or `None` on clean
46    /// disconnect. The slice is valid until the next `read_frame()` call.
47    pub fn read_frame(&mut self) -> io::Result<Option<&[u8]>> {
48        // Read the 4-byte length prefix.
49        let mut len_buf = [0u8; 4];
50        match self.reader.read_exact(&mut len_buf) {
51            Ok(()) => {}
52            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
53            Err(e) => return Err(e),
54        }
55
56        let len = u32::from_le_bytes(len_buf) as usize;
57        if len > MAX_FRAME_SIZE {
58            return Err(io::Error::new(
59                io::ErrorKind::InvalidData,
60                format!("frame too large: {len} bytes (max {MAX_FRAME_SIZE})"),
61            ));
62        }
63
64        self.reader.read_exact(&mut self.buf[..len])?;
65        self.frame_len = len;
66
67        Ok(Some(&self.buf[..len]))
68    }
69
70    /// The frame the last successful [`read_frame`](Self::read_frame)
71    /// returned, for callers that decided what it was and now want it
72    /// back without holding the borrow across that decision. Empty
73    /// before the first read.
74    pub fn frame(&self) -> &[u8] {
75        &self.buf[..self.frame_len]
76    }
77
78    /// Borrow the underlying reader. Mirrors `BufReader::get_ref` — used
79    /// by callers that need to reach the raw stream for socket-level
80    /// configuration (`set_read_timeout`, `set_nodelay`, …) without
81    /// going through the framed layer.
82    pub fn get_ref(&self) -> &R {
83        self.reader.get_ref()
84    }
85}
86
87/// Blocking frame writer. Writes length-prefixed frames to any `Write` sink.
88///
89/// Uses `BufWriter` to batch small writes (length prefix + payload) into
90/// fewer syscalls. Flushed explicitly after each batch.
91pub struct BlockingFrameWriter<W: Write> {
92    writer: BufWriter<W>,
93}
94
95impl<W: Write> BlockingFrameWriter<W> {
96    pub fn new(writer: W) -> Self {
97        Self {
98            writer: BufWriter::new(writer),
99        }
100    }
101
102    /// Write a complete frame (prepends the 4-byte LE length prefix).
103    pub fn write_frame(&mut self, data: &[u8]) -> io::Result<()> {
104        self.write_frame_parts(&[data])
105    }
106
107    /// Write one frame whose payload is `parts` laid end to end, with
108    /// the length prefix of their total. Spares a caller that holds a
109    /// frame in pieces — a header it computes and a body it was given —
110    /// from assembling them in a buffer of its own: each part goes
111    /// straight into the write buffer, and the frame reaches the socket
112    /// whole at the next flush. A part that arrives while the buffer is
113    /// too full to take it still lands on the socket in order, only in a
114    /// separate write.
115    ///
116    /// A frame over [`MAX_FRAME_SIZE`] is refused with
117    /// [`io::ErrorKind::InvalidInput`] before any of it is written: the
118    /// far end would drop the connection on it, and this is the error
119    /// that says why.
120    pub fn write_frame_parts(&mut self, parts: &[&[u8]]) -> io::Result<()> {
121        let len: usize = parts.iter().map(|part| part.len()).sum();
122        if len > MAX_FRAME_SIZE {
123            return Err(io::Error::new(
124                io::ErrorKind::InvalidInput,
125                format!("frame too large: {len} bytes (max {MAX_FRAME_SIZE})"),
126            ));
127        }
128        self.writer.write_all(&(len as u32).to_le_bytes())?;
129        for part in parts {
130            self.writer.write_all(part)?;
131        }
132        Ok(())
133    }
134
135    /// Flush buffered data to the underlying writer.
136    pub fn flush(&mut self) -> io::Result<()> {
137        self.writer.flush()
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use std::net::{TcpListener, TcpStream};
145
146    #[test]
147    fn frame_round_trip_blocking() {
148        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
149        let addr = listener.local_addr().unwrap();
150
151        let client = TcpStream::connect(addr).unwrap();
152        let (server, _) = listener.accept().unwrap();
153
154        let mut writer = BlockingFrameWriter::new(client);
155        let mut reader = BlockingFrameReader::new(server);
156
157        let data = b"hello melin";
158        writer.write_frame(data).unwrap();
159        writer.flush().unwrap();
160
161        let received = reader.read_frame().unwrap().unwrap();
162        assert_eq!(received, data);
163    }
164
165    #[test]
166    fn frame_parts_arrive_as_one_frame() {
167        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
168        let addr = listener.local_addr().unwrap();
169
170        let client = TcpStream::connect(addr).unwrap();
171        let (server, _) = listener.accept().unwrap();
172
173        let mut writer = BlockingFrameWriter::new(client);
174        let mut reader = BlockingFrameReader::new(server);
175
176        writer
177            .write_frame_parts(&[&7u64.to_le_bytes(), &[0x10], b"body"])
178            .unwrap();
179        writer.write_frame_parts(&[]).unwrap();
180        writer.flush().unwrap();
181
182        let expected = [&7u64.to_le_bytes()[..], &[0x10], b"body"].concat();
183        assert_eq!(reader.read_frame().unwrap().unwrap(), expected);
184        assert_eq!(reader.read_frame().unwrap().unwrap(), b"");
185    }
186
187    #[test]
188    fn an_oversized_frame_is_refused_before_anything_is_written() {
189        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
190        let addr = listener.local_addr().unwrap();
191
192        let client = TcpStream::connect(addr).unwrap();
193        let (server, _) = listener.accept().unwrap();
194
195        let mut writer = BlockingFrameWriter::new(client);
196        let mut reader = BlockingFrameReader::new(server);
197
198        // One byte over the cap, split across parts: the total is what
199        // counts.
200        let body = vec![0xAB; MAX_FRAME_SIZE];
201        let err = writer.write_frame_parts(&[&[0x01], &body]).unwrap_err();
202        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
203
204        // Nothing of it went out: the frame at the cap that follows is the
205        // first thing the reader sees, and it is whole.
206        writer.write_frame(&body).unwrap();
207        writer.flush().unwrap();
208        assert_eq!(reader.read_frame().unwrap().unwrap(), &body[..]);
209    }
210
211    #[test]
212    fn clean_disconnect_returns_none() {
213        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
214        let addr = listener.local_addr().unwrap();
215
216        let client = TcpStream::connect(addr).unwrap();
217        let (server, _) = listener.accept().unwrap();
218
219        drop(client);
220
221        let mut reader = BlockingFrameReader::new(server);
222        let result = reader.read_frame().unwrap();
223        assert!(result.is_none());
224    }
225
226    #[test]
227    fn oversized_frame_rejected() {
228        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
229        let addr = listener.local_addr().unwrap();
230
231        let mut client = TcpStream::connect(addr).unwrap();
232        let (server, _) = listener.accept().unwrap();
233
234        // Send a length prefix claiming 2 MiB.
235        let fake_len = 2_000_000u32;
236        client.write_all(&fake_len.to_le_bytes()).unwrap();
237
238        let mut reader = BlockingFrameReader::new(server);
239        let result = reader.read_frame();
240        assert!(result.is_err());
241    }
242
243    #[test]
244    fn multiple_frames_in_sequence() {
245        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
246        let addr = listener.local_addr().unwrap();
247
248        let client = TcpStream::connect(addr).unwrap();
249        let (server, _) = listener.accept().unwrap();
250
251        let mut writer = BlockingFrameWriter::new(client);
252        let mut reader = BlockingFrameReader::new(server);
253
254        for i in 0u32..100 {
255            writer.write_frame(&i.to_le_bytes()).unwrap();
256        }
257        writer.flush().unwrap();
258
259        for i in 0u32..100 {
260            let frame = reader.read_frame().unwrap().unwrap();
261            assert_eq!(frame, i.to_le_bytes());
262        }
263    }
264}