oxur_repl/protocol/
codec.rs1use super::{Request, Response};
10use std::io::{self, Read, Write};
11use thiserror::Error;
12
13const MAX_MESSAGE_SIZE: u32 = 10 * 1024 * 1024;
18
19#[derive(Debug, Error)]
21pub enum CodecError {
22 #[error("I/O error: {0}")]
24 Io(#[from] io::Error),
25
26 #[error("Serialization error: {0}")]
28 Serialization(#[from] postcard::Error),
29
30 #[error("Message size {0} exceeds maximum {MAX_MESSAGE_SIZE}")]
32 MessageTooLarge(u32),
33
34 #[error("Connection closed unexpectedly")]
36 UnexpectedEof,
37}
38
39pub type Result<T> = std::result::Result<T, CodecError>;
40
41pub struct PostcardCodec;
45
46impl PostcardCodec {
47 pub fn encode_request(request: &Request) -> Result<Vec<u8>> {
51 let payload = postcard::to_allocvec(request)?;
52 Self::frame_message(&payload)
53 }
54
55 pub fn encode_response(response: &Response) -> Result<Vec<u8>> {
59 let payload = postcard::to_allocvec(response)?;
60 Self::frame_message(&payload)
61 }
62
63 pub fn decode_request(data: &[u8]) -> Result<Request> {
69 let payload = Self::unframe_message(data)?;
70 Ok(postcard::from_bytes(payload)?)
71 }
72
73 pub fn decode_response(data: &[u8]) -> Result<Response> {
79 let payload = Self::unframe_message(data)?;
80 Ok(postcard::from_bytes(payload)?)
81 }
82
83 pub fn write_request<W: Write>(writer: &mut W, request: &Request) -> Result<()> {
89 let framed = Self::encode_request(request)?;
90 writer.write_all(&framed)?;
91 writer.flush()?;
92 Ok(())
93 }
94
95 pub fn write_response<W: Write>(writer: &mut W, response: &Response) -> Result<()> {
101 let framed = Self::encode_response(response)?;
102 writer.write_all(&framed)?;
103 writer.flush()?;
104 Ok(())
105 }
106
107 pub fn read_request<R: Read>(reader: &mut R) -> Result<Request> {
113 let payload = Self::read_framed_message(reader)?;
114 Ok(postcard::from_bytes(&payload)?)
115 }
116
117 pub fn read_response<R: Read>(reader: &mut R) -> Result<Response> {
123 let payload = Self::read_framed_message(reader)?;
124 Ok(postcard::from_bytes(&payload)?)
125 }
126
127 fn frame_message(payload: &[u8]) -> Result<Vec<u8>> {
130 let len = payload.len();
131 if len > MAX_MESSAGE_SIZE as usize {
132 return Err(CodecError::MessageTooLarge(len as u32));
133 }
134
135 let mut framed = Vec::with_capacity(4 + len);
136 framed.extend_from_slice(&(len as u32).to_le_bytes());
137 framed.extend_from_slice(payload);
138 Ok(framed)
139 }
140
141 fn unframe_message(data: &[u8]) -> Result<&[u8]> {
142 if data.len() < 4 {
143 return Err(CodecError::UnexpectedEof);
144 }
145
146 let len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
147 if len > MAX_MESSAGE_SIZE {
148 return Err(CodecError::MessageTooLarge(len));
149 }
150
151 let payload_start = 4;
152 let payload_end = payload_start + len as usize;
153
154 if data.len() < payload_end {
155 return Err(CodecError::UnexpectedEof);
156 }
157
158 Ok(&data[payload_start..payload_end])
159 }
160
161 fn read_framed_message<R: Read>(reader: &mut R) -> Result<Vec<u8>> {
162 let mut len_bytes = [0u8; 4];
164 reader.read_exact(&mut len_bytes)?;
165 let len = u32::from_le_bytes(len_bytes);
166
167 if len > MAX_MESSAGE_SIZE {
168 return Err(CodecError::MessageTooLarge(len));
169 }
170
171 let mut payload = vec![0u8; len as usize];
173 reader.read_exact(&mut payload)?;
174 Ok(payload)
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::protocol::{
182 MessageId, Operation, OperationResult, ReplMode, SessionId, SessionInfo, Status,
183 };
184 use std::io::Cursor;
185
186 #[test]
187 fn test_encode_decode_request() {
188 let request = Request {
189 id: MessageId::new(1),
190 session_id: SessionId::new("test-session"),
191 operation: Operation::Eval { code: "(+ 1 2)".to_string(), mode: ReplMode::Lisp },
192 };
193
194 let encoded = PostcardCodec::encode_request(&request).unwrap();
195 let decoded = PostcardCodec::decode_request(&encoded).unwrap();
196 assert_eq!(request, decoded);
197 }
198
199 #[test]
200 fn test_encode_decode_response() {
201 let response = Response {
202 request_id: MessageId::new(1),
203 session_id: SessionId::new("test-session"),
204 result: OperationResult::Success {
205 status: Status { tier: 1, cached: false, duration_ms: 5 },
206 value: Some("3".to_string()),
207 stdout: None,
208 stderr: None,
209 },
210 };
211
212 let encoded = PostcardCodec::encode_response(&response).unwrap();
213 let decoded = PostcardCodec::decode_response(&encoded).unwrap();
214 assert_eq!(response, decoded);
215 }
216
217 #[test]
218 fn test_write_read_request() {
219 let request = Request {
220 id: MessageId::new(42),
221 session_id: SessionId::new("write-read-test"),
222 operation: Operation::LsSessions,
223 };
224
225 let mut buffer = Vec::new();
226 PostcardCodec::write_request(&mut buffer, &request).unwrap();
227
228 let mut cursor = Cursor::new(buffer);
229 let decoded = PostcardCodec::read_request(&mut cursor).unwrap();
230 assert_eq!(request, decoded);
231 }
232
233 #[test]
234 fn test_write_read_response() {
235 let response = Response {
236 request_id: MessageId::new(99),
237 session_id: SessionId::new("response-test"),
238 result: OperationResult::Sessions {
239 sessions: vec![
240 SessionInfo {
241 id: SessionId::new("session1"),
242 name: None,
243 mode: ReplMode::Lisp,
244 eval_count: 0,
245 created_at: 0,
246 last_active_at: 0,
247 timeout_ms: 3600000,
248 },
249 SessionInfo {
250 id: SessionId::new("session2"),
251 name: None,
252 mode: ReplMode::Sexpr,
253 eval_count: 5,
254 created_at: 1000,
255 last_active_at: 1000,
256 timeout_ms: 3600000,
257 },
258 ],
259 },
260 };
261
262 let mut buffer = Vec::new();
263 PostcardCodec::write_response(&mut buffer, &response).unwrap();
264
265 let mut cursor = Cursor::new(buffer);
266 let decoded = PostcardCodec::read_response(&mut cursor).unwrap();
267 assert_eq!(response, decoded);
268 }
269
270 #[test]
271 fn test_message_too_large() {
272 let huge_code = "x".repeat((MAX_MESSAGE_SIZE + 1) as usize);
274 let request = Request {
275 id: MessageId::new(1),
276 session_id: SessionId::new("test"),
277 operation: Operation::Eval { code: huge_code, mode: ReplMode::Lisp },
278 };
279
280 let result = PostcardCodec::encode_request(&request);
281 assert!(matches!(result, Err(CodecError::MessageTooLarge(_))));
282 }
283
284 #[test]
285 fn test_unexpected_eof() {
286 let incomplete = vec![0x0A, 0x00, 0x00, 0x00]; let result = PostcardCodec::decode_request(&incomplete);
290 assert!(matches!(result, Err(CodecError::UnexpectedEof)));
291 }
292
293 #[test]
294 fn test_framing_format() {
295 let request = Request {
296 id: MessageId::new(1),
297 session_id: SessionId::new("test"),
298 operation: Operation::Interrupt,
299 };
300
301 let encoded = PostcardCodec::encode_request(&request).unwrap();
302
303 let len = u32::from_le_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]);
305 assert_eq!(len as usize, encoded.len() - 4);
306
307 let payload = &encoded[4..];
309 let decoded: Request = postcard::from_bytes(payload).unwrap();
310 assert_eq!(request, decoded);
311 }
312}