use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use crate::wire::{Frame, WireError};
pub const MAX_FRAME_BYTES: u32 = 24 * 1024 * 1024;
#[derive(Debug)]
pub enum CodecError {
Io(std::io::Error),
FrameTooLarge(u32),
Wire(WireError),
Eof,
}
impl std::fmt::Display for CodecError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CodecError::Io(e) => write!(f, "sync stream io: {e}"),
CodecError::FrameTooLarge(n) => {
write!(f, "sync frame declared {n} bytes, over {MAX_FRAME_BYTES}")
},
CodecError::Wire(e) => write!(f, "{e}"),
CodecError::Eof => write!(f, "sync stream closed at a frame boundary"),
}
}
}
impl std::error::Error for CodecError {}
impl From<WireError> for CodecError {
fn from(e: WireError) -> Self {
CodecError::Wire(e)
}
}
pub async fn write_frame<W: AsyncWrite + Unpin>(
w: &mut W,
frame: &Frame,
) -> Result<(), CodecError> {
let body = frame.encode();
let len = u32::try_from(body.len()).map_err(|_| CodecError::FrameTooLarge(u32::MAX))?;
w.write_all(&len.to_be_bytes()).await.map_err(CodecError::Io)?;
w.write_all(&body).await.map_err(CodecError::Io)?;
Ok(())
}
pub async fn read_frame<R: AsyncRead + Unpin>(r: &mut R) -> Result<Frame, CodecError> {
read_frame_within(r, MAX_FRAME_BYTES).await
}
pub async fn read_frame_within<R: AsyncRead + Unpin>(
r: &mut R,
max_bytes: u32,
) -> Result<Frame, CodecError> {
let mut len_buf = [0u8; 4];
match r.read_exact(&mut len_buf).await {
Ok(_) => {},
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Err(CodecError::Eof),
Err(e) => return Err(CodecError::Io(e)),
}
let len = u32::from_be_bytes(len_buf);
if len > max_bytes {
return Err(CodecError::FrameTooLarge(len));
}
let mut body = vec![0u8; len as usize];
r.read_exact(&mut body).await.map_err(CodecError::Io)?;
Ok(Frame::decode(&body)?)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn frames_roundtrip_over_a_duplex() {
let (mut a, mut b) = tokio::io::duplex(64 * 1024);
let sent = vec![
Frame::Hello { account_id: [3; 32], have: vec![[1; 32]] },
Frame::Entries { entries: vec![vec![9, 9, 9]], more: false },
Frame::Done,
];
let to_send = sent.clone();
let writer = tokio::spawn(async move {
for f in &to_send {
write_frame(&mut a, f).await.unwrap();
}
});
let mut got = Vec::new();
loop {
match read_frame(&mut b).await {
Ok(f) => got.push(f),
Err(CodecError::Eof) => break,
Err(e) => panic!("unexpected {e}"),
}
}
writer.await.unwrap();
assert_eq!(got, sent);
}
#[tokio::test]
async fn an_oversized_length_prefix_is_refused_before_allocating() {
let (mut a, mut b) = tokio::io::duplex(64);
a.write_all(&(MAX_FRAME_BYTES + 1).to_be_bytes()).await.unwrap();
drop(a);
assert!(matches!(read_frame(&mut b).await, Err(CodecError::FrameTooLarge(_))));
}
#[tokio::test]
async fn a_capped_read_refuses_a_prefix_over_its_smaller_limit() {
let (mut a, mut b) = tokio::io::duplex(64);
a.write_all(&2048u32.to_be_bytes()).await.unwrap();
drop(a);
assert!(matches!(
read_frame_within(&mut b, 1024).await,
Err(CodecError::FrameTooLarge(2048)),
));
}
}