use std::io::{self, ErrorKind};
use serde::Serialize;
use serde::de::DeserializeOwned;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
pub const PROTOCOL: u32 = 2;
pub const MAX_FRAME_BYTES: usize = 256 << 20;
const PREFIX_BYTES: usize = 4;
const PROTOCOL_BYTES: usize = 4;
#[derive(Debug)]
pub struct Connection<S> {
pub(crate) stream: S,
}
impl<S> Connection<S> {
pub fn new(stream: S) -> Connection<S> {
Connection { stream }
}
}
impl<S: AsyncRead + AsyncWrite + Unpin> Connection<S> {
pub async fn agree_on_protocol(&mut self) -> io::Result<()> {
self.agree_on(PROTOCOL).await
}
async fn agree_on(&mut self, ours: u32) -> io::Result<()> {
self.stream.write_all(&protocol_frame(ours)).await?;
let theirs = self.read_protocol().await?;
if theirs != ours {
return Err(io::Error::new(
ErrorKind::Unsupported,
format!(
"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"
),
));
}
Ok(())
}
async fn read_protocol(&mut self) -> io::Result<u32> {
let len = self.read_prefix().await?.ok_or_else(|| {
io::Error::new(
ErrorKind::UnexpectedEof,
"the other side closed before saying its protocol",
)
})?;
if len != PROTOCOL_BYTES {
return Err(invalid_data(format!(
"the first frame is {len} bytes long, not a protocol number"
)));
}
let mut number = [0; PROTOCOL_BYTES];
self.stream.read_exact(&mut number).await?;
Ok(u32::from_le_bytes(number))
}
}
impl<S: AsyncWrite + Unpin> Connection<S> {
pub async fn send(&mut self, message: &impl Serialize) -> io::Result<()> {
let frame = frame(message)?;
self.stream.write_all(&frame).await
}
}
impl<S: AsyncRead + Unpin> Connection<S> {
pub async fn receive<M: DeserializeOwned>(&mut self) -> io::Result<Option<M>> {
let Some(len) = self.read_prefix().await? else {
return Ok(None);
};
let mut body = vec![0; len];
self.stream.read_exact(&mut body).await?;
postcard::from_bytes(&body).map(Some).map_err(invalid_data)
}
async fn read_prefix(&mut self) -> io::Result<Option<usize>> {
let mut prefix = [0; PREFIX_BYTES];
let mut filled = 0;
while filled < PREFIX_BYTES {
match self.stream.read(&mut prefix[filled..]).await? {
0 if filled == 0 => return Ok(None),
0 => return Err(cut_short()),
read => filled += read,
}
}
accepted(u32::from_le_bytes(prefix) as usize).map(Some)
}
}
fn protocol_frame(protocol: u32) -> [u8; PREFIX_BYTES + PROTOCOL_BYTES] {
let mut frame = [0; PREFIX_BYTES + PROTOCOL_BYTES];
frame[..PREFIX_BYTES].copy_from_slice(&(PROTOCOL_BYTES as u32).to_le_bytes());
frame[PREFIX_BYTES..].copy_from_slice(&protocol.to_le_bytes());
frame
}
fn frame(message: &impl Serialize) -> io::Result<Vec<u8>> {
let mut frame = postcard::to_extend(message, vec![0; PREFIX_BYTES]).map_err(invalid_data)?;
let len = accepted(frame.len() - PREFIX_BYTES)?;
frame[..PREFIX_BYTES].copy_from_slice(&(len as u32).to_le_bytes());
Ok(frame)
}
fn accepted(len: usize) -> io::Result<usize> {
if len > MAX_FRAME_BYTES {
return Err(invalid_data(format!(
"a frame of {len} bytes, over the {MAX_FRAME_BYTES} a connection accepts"
)));
}
Ok(len)
}
fn cut_short() -> io::Error {
io::Error::new(
ErrorKind::UnexpectedEof,
"the other side closed inside a frame",
)
}
fn invalid_data(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
io::Error::new(ErrorKind::InvalidData, error)
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::DuplexStream;
fn pair() -> (Connection<DuplexStream>, Connection<DuplexStream>) {
let (one, other) = tokio::io::duplex(64);
(Connection::new(one), Connection::new(other))
}
#[tokio::test]
async fn a_message_comes_back_as_sent() {
let (mut sender, mut receiver) = pair();
tokio::spawn(async move { sender.send(&(7u32, "seven".to_string())).await });
let received: Option<(u32, String)> = receiver.receive().await.unwrap();
assert_eq!(received, Some((7, "seven".into())));
}
#[tokio::test]
async fn two_sides_of_one_build_agree_and_go_on_to_the_messages() {
let (mut one, mut other) = pair();
let (agreed, agreed_too) = tokio::join!(one.agree_on_protocol(), other.agree_on_protocol());
agreed.unwrap();
agreed_too.unwrap();
tokio::spawn(async move { one.send(&7u32).await });
assert_eq!(other.receive::<u32>().await.unwrap(), Some(7));
}
#[test]
fn the_protocol_frame_is_a_count_of_four_then_the_number() {
assert_eq!(protocol_frame(1), [4, 0, 0, 0, 1, 0, 0, 0]);
assert_eq!(protocol_frame(258), [4, 0, 0, 0, 2, 1, 0, 0]);
}
#[tokio::test]
async fn two_sides_of_different_builds_each_refuse_naming_both_numbers() {
let (mut one, mut other) = pair();
let (refused, refused_too) = tokio::join!(one.agree_on(1), other.agree_on(2));
let (refused, refused_too) = (refused.unwrap_err(), refused_too.unwrap_err());
assert_eq!(refused.kind(), ErrorKind::Unsupported);
assert_eq!(refused_too.kind(), ErrorKind::Unsupported);
assert!(
refused.to_string().contains("protocol 2")
&& refused.to_string().contains("protocol 1"),
"{refused}"
);
assert!(
refused_too.to_string().contains("protocol 1")
&& refused_too.to_string().contains("protocol 2"),
"{refused_too}"
);
}
#[tokio::test]
async fn closing_before_the_protocol_frame_is_an_error() {
let (one, mut other) = pair();
drop(one);
let error = other.agree_on_protocol().await.unwrap_err();
assert!(
matches!(
error.kind(),
ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe
),
"{error}"
);
}
#[tokio::test]
async fn a_first_frame_that_is_not_a_number_is_refused() {
let (mut one, mut other) = pair();
tokio::spawn(async move { one.send(&"a message before the protocol").await });
let error = other.agree_on_protocol().await.unwrap_err();
assert_eq!(error.kind(), ErrorKind::InvalidData);
}
#[test]
fn the_count_is_four_bytes_little_endian_before_the_message() {
let frame = frame(&vec![1u8, 2, 3]).unwrap();
assert_eq!(frame, [4, 0, 0, 0, 3, 1, 2, 3]);
}
#[tokio::test]
async fn a_count_over_the_limit_is_refused_before_a_body_is_read() {
let (mut sender, mut receiver) = pair();
sender
.stream
.write_all(&u32::MAX.to_le_bytes())
.await
.unwrap();
let error = receiver.receive::<Vec<u8>>().await.unwrap_err();
assert_eq!(error.kind(), ErrorKind::InvalidData);
assert!(
error.to_string().contains(&MAX_FRAME_BYTES.to_string()),
"the error names the limit: {error}"
);
}
#[test]
fn a_frame_at_the_limit_is_accepted_and_one_over_it_refused() {
assert_eq!(accepted(MAX_FRAME_BYTES).unwrap(), MAX_FRAME_BYTES);
assert_eq!(
accepted(MAX_FRAME_BYTES + 1).unwrap_err().kind(),
ErrorKind::InvalidData
);
}
#[tokio::test]
async fn closing_between_frames_is_none() {
let (sender, mut receiver) = pair();
drop(sender);
assert_eq!(receiver.receive::<u32>().await.unwrap(), None);
}
#[tokio::test]
async fn closing_inside_a_frame_is_an_error() {
let (mut sender, mut receiver) = pair();
sender.stream.write_all(&[8, 0]).await.unwrap();
drop(sender);
let error = receiver.receive::<u32>().await.unwrap_err();
assert_eq!(error.kind(), ErrorKind::UnexpectedEof);
}
}