shep_core/protocol/
wire.rs1use core::fmt;
7
8use bytes::Bytes;
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11use tokio_util::codec::LengthDelimitedCodec;
12
13pub const MAX_FRAME_BYTES: usize = 16 * 1024 * 1024;
15
16#[must_use]
18pub fn codec() -> LengthDelimitedCodec {
19 LengthDelimitedCodec::builder()
20 .length_field_type::<u32>()
21 .max_frame_length(MAX_FRAME_BYTES)
22 .new_codec()
23}
24
25pub fn encode_frame<T: Serialize>(value: &T) -> Result<Bytes, WireError> {
32 let vec = serde_json::to_vec(value).map_err(|e| WireError::Json(e.to_string()))?;
33 if vec.len() > MAX_FRAME_BYTES {
34 return Err(WireError::FrameTooLarge(vec.len()));
35 }
36 Ok(Bytes::from(vec)) }
38
39pub fn decode_frame<T: DeserializeOwned>(frame: &[u8]) -> Result<T, WireError> {
45 serde_json::from_slice(frame).map_err(|e| WireError::Json(e.to_string()))
46}
47
48#[non_exhaustive]
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum WireError {
55 Json(String),
57 FrameTooLarge(usize),
59}
60
61impl fmt::Display for WireError {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 Self::Json(m) => write!(f, "wire frame JSON error: {m}"),
65 Self::FrameTooLarge(n) => {
66 write!(
67 f,
68 "frame of {n} bytes exceeds the {MAX_FRAME_BYTES}-byte limit"
69 )
70 }
71 }
72 }
73}
74
75impl core::error::Error for WireError {}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::protocol::{Envelope, Request};
81
82 #[test]
83 fn encode_decode_round_trip() {
84 let env = Envelope {
85 id: 9,
86 deadline_ms: Some(5000),
87 body: Request::Ping,
88 };
89 let bytes = encode_frame(&env).unwrap();
90 let back: Envelope = decode_frame(&bytes).unwrap();
91 assert_eq!(back, env);
92 }
93
94 #[test]
95 fn decode_rejects_garbage_with_json_error() {
96 assert!(matches!(
97 decode_frame::<Envelope>(b"not json"),
98 Err(WireError::Json(_))
99 ));
100 }
101
102 #[test]
103 fn codec_uses_u32_prefix_and_max_frame() {
104 let c = codec();
105 assert_eq!(c.max_frame_length(), MAX_FRAME_BYTES);
106 }
107
108 #[tokio::test]
109 async fn framed_stream_round_trip() {
110 use futures_util::{SinkExt, StreamExt};
111 use tokio_util::codec::{FramedRead, FramedWrite};
112
113 let (client, server) = tokio::io::duplex(64 * 1024);
114 let mut writer = FramedWrite::new(client, codec());
115 let mut reader = FramedRead::new(server, codec());
116
117 let env = Envelope {
118 id: 1,
119 deadline_ms: None,
120 body: Request::ListFlock,
121 };
122 writer.send(encode_frame(&env).unwrap()).await.unwrap();
123
124 let frame = reader.next().await.unwrap().unwrap();
125 let back: Envelope = decode_frame(&frame).unwrap();
126 assert_eq!(back, env);
127 }
128}