waos_dev_protocol/
connection.rs1use std::io::{self, ErrorKind};
15
16use serde::Serialize;
17use serde::de::DeserializeOwned;
18use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
19
20pub const PROTOCOL: u32 = 3;
25
26pub const MAX_FRAME_BYTES: usize = 256 << 20;
29
30const PREFIX_BYTES: usize = 4;
31
32const PROTOCOL_BYTES: usize = 4;
34
35#[derive(Debug)]
39pub struct Connection<S> {
40 pub(crate) stream: S,
41}
42
43impl<S> Connection<S> {
44 pub fn new(stream: S) -> Connection<S> {
45 Connection { stream }
46 }
47}
48
49impl<S: AsyncRead + AsyncWrite + Unpin> Connection<S> {
50 pub async fn agree_on_protocol(&mut self) -> io::Result<()> {
56 self.agree_on(PROTOCOL).await
57 }
58
59 async fn agree_on(&mut self, ours: u32) -> io::Result<()> {
60 self.stream.write_all(&protocol_frame(ours)).await?;
61 let theirs = self.read_protocol().await?;
62 if theirs != ours {
63 return Err(io::Error::new(
64 ErrorKind::Unsupported,
65 format!(
66 "the other side speaks protocol {theirs} and this side protocol {ours}: cargo waos and the WaOS it reaches must be built from the same waos-dev-protocol"
67 ),
68 ));
69 }
70 Ok(())
71 }
72
73 async fn read_protocol(&mut self) -> io::Result<u32> {
75 let len = self.read_prefix().await?.ok_or_else(|| {
76 io::Error::new(
77 ErrorKind::UnexpectedEof,
78 "the other side closed before saying its protocol",
79 )
80 })?;
81 if len != PROTOCOL_BYTES {
82 return Err(invalid_data(format!(
83 "the first frame is {len} bytes long, not a protocol number"
84 )));
85 }
86 let mut number = [0; PROTOCOL_BYTES];
87 self.stream.read_exact(&mut number).await?;
88 Ok(u32::from_le_bytes(number))
89 }
90}
91
92impl<S: AsyncWrite + Unpin> Connection<S> {
93 pub async fn send(&mut self, message: &impl Serialize) -> io::Result<()> {
95 let frame = frame(message)?;
96 self.stream.write_all(&frame).await
97 }
98}
99
100impl<S: AsyncRead + Unpin> Connection<S> {
101 pub async fn receive<M: DeserializeOwned>(&mut self) -> io::Result<Option<M>> {
103 let Some(len) = self.read_prefix().await? else {
104 return Ok(None);
105 };
106 let mut body = vec![0; len];
107 self.stream.read_exact(&mut body).await?;
108 postcard::from_bytes(&body).map(Some).map_err(invalid_data)
109 }
110
111 async fn read_prefix(&mut self) -> io::Result<Option<usize>> {
114 let mut prefix = [0; PREFIX_BYTES];
115 let mut filled = 0;
116 while filled < PREFIX_BYTES {
117 match self.stream.read(&mut prefix[filled..]).await? {
118 0 if filled == 0 => return Ok(None),
119 0 => return Err(cut_short()),
120 read => filled += read,
121 }
122 }
123 accepted(u32::from_le_bytes(prefix) as usize).map(Some)
124 }
125}
126
127fn protocol_frame(protocol: u32) -> [u8; PREFIX_BYTES + PROTOCOL_BYTES] {
129 let mut frame = [0; PREFIX_BYTES + PROTOCOL_BYTES];
130 frame[..PREFIX_BYTES].copy_from_slice(&(PROTOCOL_BYTES as u32).to_le_bytes());
131 frame[PREFIX_BYTES..].copy_from_slice(&protocol.to_le_bytes());
132 frame
133}
134
135fn frame(message: &impl Serialize) -> io::Result<Vec<u8>> {
137 let mut frame = postcard::to_extend(message, vec![0; PREFIX_BYTES]).map_err(invalid_data)?;
138 let len = accepted(frame.len() - PREFIX_BYTES)?;
139 frame[..PREFIX_BYTES].copy_from_slice(&(len as u32).to_le_bytes());
141 Ok(frame)
142}
143
144fn accepted(len: usize) -> io::Result<usize> {
146 if len > MAX_FRAME_BYTES {
147 return Err(invalid_data(format!(
148 "a frame of {len} bytes, over the {MAX_FRAME_BYTES} a connection accepts"
149 )));
150 }
151 Ok(len)
152}
153
154fn cut_short() -> io::Error {
155 io::Error::new(
156 ErrorKind::UnexpectedEof,
157 "the other side closed inside a frame",
158 )
159}
160
161fn invalid_data(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
162 io::Error::new(ErrorKind::InvalidData, error)
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 use tokio::io::DuplexStream;
170
171 fn pair() -> (Connection<DuplexStream>, Connection<DuplexStream>) {
172 let (one, other) = tokio::io::duplex(64);
173 (Connection::new(one), Connection::new(other))
174 }
175
176 #[tokio::test]
177 async fn a_message_comes_back_as_sent() {
178 let (mut sender, mut receiver) = pair();
179 tokio::spawn(async move { sender.send(&(7u32, "seven".to_string())).await });
180 let received: Option<(u32, String)> = receiver.receive().await.unwrap();
181 assert_eq!(received, Some((7, "seven".into())));
182 }
183
184 #[tokio::test]
185 async fn two_sides_of_one_build_agree_and_go_on_to_the_messages() {
186 let (mut one, mut other) = pair();
187 let (agreed, agreed_too) = tokio::join!(one.agree_on_protocol(), other.agree_on_protocol());
188 agreed.unwrap();
189 agreed_too.unwrap();
190 tokio::spawn(async move { one.send(&7u32).await });
191 assert_eq!(other.receive::<u32>().await.unwrap(), Some(7));
192 }
193
194 #[test]
195 fn the_protocol_frame_is_a_count_of_four_then_the_number() {
196 assert_eq!(protocol_frame(1), [4, 0, 0, 0, 1, 0, 0, 0]);
197 assert_eq!(protocol_frame(258), [4, 0, 0, 0, 2, 1, 0, 0]);
198 }
199
200 #[tokio::test]
201 async fn two_sides_of_different_builds_each_refuse_naming_both_numbers() {
202 let (mut one, mut other) = pair();
203 let (refused, refused_too) = tokio::join!(one.agree_on(1), other.agree_on(2));
204 let (refused, refused_too) = (refused.unwrap_err(), refused_too.unwrap_err());
205 assert_eq!(refused.kind(), ErrorKind::Unsupported);
206 assert_eq!(refused_too.kind(), ErrorKind::Unsupported);
207 assert!(
208 refused.to_string().contains("protocol 2")
209 && refused.to_string().contains("protocol 1"),
210 "{refused}"
211 );
212 assert!(
213 refused_too.to_string().contains("protocol 1")
214 && refused_too.to_string().contains("protocol 2"),
215 "{refused_too}"
216 );
217 }
218
219 #[tokio::test]
220 async fn closing_before_the_protocol_frame_is_an_error() {
221 let (one, mut other) = pair();
222 drop(one);
223 let error = other.agree_on_protocol().await.unwrap_err();
224 assert!(
225 matches!(
226 error.kind(),
227 ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe
228 ),
229 "{error}"
230 );
231 }
232
233 #[tokio::test]
234 async fn a_first_frame_that_is_not_a_number_is_refused() {
235 let (mut one, mut other) = pair();
236 tokio::spawn(async move { one.send(&"a message before the protocol").await });
237 let error = other.agree_on_protocol().await.unwrap_err();
238 assert_eq!(error.kind(), ErrorKind::InvalidData);
239 }
240
241 #[test]
242 fn the_count_is_four_bytes_little_endian_before_the_message() {
243 let frame = frame(&vec![1u8, 2, 3]).unwrap();
244 assert_eq!(frame, [4, 0, 0, 0, 3, 1, 2, 3]);
246 }
247
248 #[tokio::test]
249 async fn a_count_over_the_limit_is_refused_before_a_body_is_read() {
250 let (mut sender, mut receiver) = pair();
251 sender
252 .stream
253 .write_all(&u32::MAX.to_le_bytes())
254 .await
255 .unwrap();
256 let error = receiver.receive::<Vec<u8>>().await.unwrap_err();
258 assert_eq!(error.kind(), ErrorKind::InvalidData);
259 assert!(
260 error.to_string().contains(&MAX_FRAME_BYTES.to_string()),
261 "the error names the limit: {error}"
262 );
263 }
264
265 #[test]
266 fn a_frame_at_the_limit_is_accepted_and_one_over_it_refused() {
267 assert_eq!(accepted(MAX_FRAME_BYTES).unwrap(), MAX_FRAME_BYTES);
268 assert_eq!(
269 accepted(MAX_FRAME_BYTES + 1).unwrap_err().kind(),
270 ErrorKind::InvalidData
271 );
272 }
273
274 #[tokio::test]
275 async fn closing_between_frames_is_none() {
276 let (sender, mut receiver) = pair();
277 drop(sender);
278 assert_eq!(receiver.receive::<u32>().await.unwrap(), None);
279 }
280
281 #[tokio::test]
282 async fn closing_inside_a_frame_is_an_error() {
283 let (mut sender, mut receiver) = pair();
284 sender.stream.write_all(&[8, 0]).await.unwrap();
285 drop(sender);
286 let error = receiver.receive::<u32>().await.unwrap_err();
287 assert_eq!(error.kind(), ErrorKind::UnexpectedEof);
288 }
289}