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/// Maximum frame payload size (1 KiB). Same limit as the async transports.
10const MAX_FRAME_SIZE: usize = 1024;
11
12/// Blocking frame reader. Reads length-prefixed frames from any `Read` source.
13///
14/// Uses `BufReader` to amortize read syscalls — a single recv fills the
15/// buffer with many frames, so subsequent `read_exact` calls hit the
16/// buffer instead of making kernel transitions. This is critical for
17/// round-trip latency: without buffering, each frame requires 2 read
18/// syscalls (4-byte length prefix + payload).
19///
20/// Generic over the reader type so it works with both `std::net::TcpStream`
21/// and `std::os::unix::net::UnixStream`.
22pub struct BlockingFrameReader<R> {
23    reader: BufReader<R>,
24    /// Reusable frame buffer — avoids a heap allocation per frame.
25    /// Fixed at MAX_FRAME_SIZE (1 KiB); the valid slice is `&buf[..len]`.
26    buf: [u8; MAX_FRAME_SIZE],
27    /// Length of the last successfully read frame (valid bytes in `buf`).
28    frame_len: usize,
29}
30
31impl<R: Read> BlockingFrameReader<R> {
32    pub fn new(reader: R) -> Self {
33        Self {
34            reader: BufReader::new(reader),
35            buf: [0u8; MAX_FRAME_SIZE],
36            frame_len: 0,
37        }
38    }
39
40    /// Read the next complete frame into the internal buffer.
41    /// Returns a borrowed slice of the frame payload, or `None` on clean
42    /// disconnect. The slice is valid until the next `read_frame()` call.
43    pub fn read_frame(&mut self) -> io::Result<Option<&[u8]>> {
44        // Read the 4-byte length prefix.
45        let mut len_buf = [0u8; 4];
46        match self.reader.read_exact(&mut len_buf) {
47            Ok(()) => {}
48            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
49            Err(e) => return Err(e),
50        }
51
52        let len = u32::from_le_bytes(len_buf) as usize;
53        if len > MAX_FRAME_SIZE {
54            return Err(io::Error::new(
55                io::ErrorKind::InvalidData,
56                format!("frame too large: {len} bytes (max {MAX_FRAME_SIZE})"),
57            ));
58        }
59
60        self.reader.read_exact(&mut self.buf[..len])?;
61        self.frame_len = len;
62
63        Ok(Some(&self.buf[..len]))
64    }
65
66    /// Borrow the underlying reader. Mirrors `BufReader::get_ref` — used
67    /// by callers that need to reach the raw stream for socket-level
68    /// configuration (`set_read_timeout`, `set_nodelay`, …) without
69    /// going through the framed layer.
70    pub fn get_ref(&self) -> &R {
71        self.reader.get_ref()
72    }
73}
74
75/// Blocking frame writer. Writes length-prefixed frames to any `Write` sink.
76///
77/// Uses `BufWriter` to batch small writes (length prefix + payload) into
78/// fewer syscalls. Flushed explicitly after each batch.
79pub struct BlockingFrameWriter<W: Write> {
80    writer: BufWriter<W>,
81}
82
83impl<W: Write> BlockingFrameWriter<W> {
84    pub fn new(writer: W) -> Self {
85        Self {
86            writer: BufWriter::new(writer),
87        }
88    }
89
90    /// Write a complete frame (prepends the 4-byte LE length prefix).
91    pub fn write_frame(&mut self, data: &[u8]) -> io::Result<()> {
92        let len = data.len() as u32;
93        self.writer.write_all(&len.to_le_bytes())?;
94        self.writer.write_all(data)?;
95        Ok(())
96    }
97
98    /// Flush buffered data to the underlying writer.
99    pub fn flush(&mut self) -> io::Result<()> {
100        self.writer.flush()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use std::net::{TcpListener, TcpStream};
108
109    #[test]
110    fn frame_round_trip_blocking() {
111        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
112        let addr = listener.local_addr().unwrap();
113
114        let client = TcpStream::connect(addr).unwrap();
115        let (server, _) = listener.accept().unwrap();
116
117        let mut writer = BlockingFrameWriter::new(client);
118        let mut reader = BlockingFrameReader::new(server);
119
120        let data = b"hello trading";
121        writer.write_frame(data).unwrap();
122        writer.flush().unwrap();
123
124        let received = reader.read_frame().unwrap().unwrap();
125        assert_eq!(received, data);
126    }
127
128    #[test]
129    fn clean_disconnect_returns_none() {
130        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
131        let addr = listener.local_addr().unwrap();
132
133        let client = TcpStream::connect(addr).unwrap();
134        let (server, _) = listener.accept().unwrap();
135
136        drop(client);
137
138        let mut reader = BlockingFrameReader::new(server);
139        let result = reader.read_frame().unwrap();
140        assert!(result.is_none());
141    }
142
143    #[test]
144    fn oversized_frame_rejected() {
145        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
146        let addr = listener.local_addr().unwrap();
147
148        let mut client = TcpStream::connect(addr).unwrap();
149        let (server, _) = listener.accept().unwrap();
150
151        // Send a length prefix claiming 2 MiB.
152        let fake_len = 2_000_000u32;
153        client.write_all(&fake_len.to_le_bytes()).unwrap();
154
155        let mut reader = BlockingFrameReader::new(server);
156        let result = reader.read_frame();
157        assert!(result.is_err());
158    }
159
160    #[test]
161    fn multiple_frames_in_sequence() {
162        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
163        let addr = listener.local_addr().unwrap();
164
165        let client = TcpStream::connect(addr).unwrap();
166        let (server, _) = listener.accept().unwrap();
167
168        let mut writer = BlockingFrameWriter::new(client);
169        let mut reader = BlockingFrameReader::new(server);
170
171        for i in 0u32..100 {
172            writer.write_frame(&i.to_le_bytes()).unwrap();
173        }
174        writer.flush().unwrap();
175
176        for i in 0u32..100 {
177            let frame = reader.read_frame().unwrap().unwrap();
178            assert_eq!(frame, i.to_le_bytes());
179        }
180    }
181}