use std::marker::PhantomData;
use futures_util::{Sink, Stream};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::bytes::{Buf, BufMut, BytesMut};
use tokio_util::codec::{Decoder, Encoder, FramedRead, FramedWrite};
#[derive(Debug)]
pub struct Codec<M> {
max_frame_len: usize,
_phantom: PhantomData<M>,
}
impl<M> Codec<M> {
pub fn new() -> Self {
Self::default()
}
pub fn max_frame_len(mut self, value: usize) -> Self {
self.max_frame_len = value;
self
}
}
impl<M> Default for Codec<M> {
fn default() -> Self {
Self {
max_frame_len: 1024 * 1024 * 128, _phantom: PhantomData,
}
}
}
impl<M> Encoder<M> for Codec<M>
where
M: Serialize,
{
type Error = CodecError;
fn encode(&mut self, item: M, dst: &mut BytesMut) -> Result<(), Self::Error> {
let frame_len =
postcard::serialize_with_flavor(&item, postcard::ser_flavors::Size::default())?;
if frame_len > self.max_frame_len {
return Err(CodecError::TooLargeMessage(frame_len, self.max_frame_len));
}
dst.put_u32(u32::try_from(frame_len).expect("already checked"));
dst.reserve(4 + frame_len);
let mut writer = dst.writer();
postcard::to_io(&item, &mut writer)?;
Ok(())
}
}
impl<M> Decoder for Codec<M>
where
M: DeserializeOwned,
{
type Item = M;
type Error = CodecError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.len() < 4 {
return Ok(None);
}
let bytes: [u8; 4] = src[..4].try_into().expect("checked available bytes");
let frame_len = u32::from_be_bytes(bytes) as usize;
if frame_len > self.max_frame_len {
return Err(CodecError::TooLargeMessage(frame_len, self.max_frame_len));
}
if src.len() < 4 + frame_len {
return Ok(None);
}
let item: M = postcard::from_bytes(&src[4..4 + frame_len])?;
src.advance(4 + frame_len);
Ok(Some(item))
}
}
pub fn into_codec_stream<M, T>(
rx: T,
) -> impl Stream<Item = Result<M, CodecError>> + Unpin + use<M, T>
where
M: for<'de> Deserialize<'de> + 'static,
T: AsyncRead + Unpin + 'static,
{
FramedRead::new(rx, Codec::<M>::new())
}
pub fn into_codec_sink<M, T>(tx: T) -> impl Sink<M, Error = CodecError>
where
M: Serialize + 'static,
T: AsyncWrite + Unpin + 'static,
{
FramedWrite::new(tx, Codec::<M>::new())
}
#[derive(Debug, Error)]
pub enum CodecError {
#[error(transparent)]
Postcard(#[from] postcard::Error),
#[error("too large message of {0} bytes (max allowed is {1})")]
TooLargeMessage(usize, usize),
#[error(transparent)]
Io(#[from] std::io::Error),
}
#[cfg(test)]
mod tests {
use futures_util::{FutureExt, SinkExt, StreamExt};
use p2panda_core::test_utils::TestLog;
use p2panda_core::{Body, Header};
use tokio::io::AsyncWriteExt;
use tokio_util::codec::{FramedRead, FramedWrite};
use super::{Codec, into_codec_sink, into_codec_stream};
#[tokio::test]
async fn decoding_exactly_one_frame() {
let (mut tx, rx) = tokio::io::duplex(64);
let mut stream = FramedRead::new(rx, Codec::<String>::new());
tx.write_all(&[0, 0, 0, 6]).await.unwrap();
tx.write_all(&[5]).await.unwrap();
tx.write_all("hello".as_bytes()).await.unwrap();
let message = stream.next().await;
assert_eq!(message.unwrap().unwrap(), "hello".to_string());
}
#[tokio::test]
async fn decoding_more_than_one_frame() {
let (mut tx, rx) = tokio::io::duplex(64);
let mut stream = FramedRead::new(rx, Codec::<String>::new());
tx.write_all(&[0, 0, 0, 6]).await.unwrap();
tx.write_all(&[5]).await.unwrap();
tx.write_all("hello".as_bytes()).await.unwrap();
tx.write_all(&[0, 0, 0, 10]).await.unwrap();
tx.write_all(&[9]).await.unwrap();
tx.write_all("aquariums".as_bytes()).await.unwrap();
let message = stream.next().await;
assert_eq!(message.unwrap().unwrap(), "hello".to_string());
let message = stream.next().await;
assert_eq!(message.unwrap().unwrap(), "aquariums".to_string());
}
#[tokio::test]
async fn decoding_incomplete_frame() {
let (mut tx, rx) = tokio::io::duplex(64);
let mut stream = FramedRead::new(rx, Codec::<String>::new());
tx.write_all(&[0, 0, 0, 6]).await.unwrap();
let message = stream.next().now_or_never();
assert!(message.is_none());
tx.write_all(&[5]).await.unwrap();
tx.write_all("h".as_bytes()).await.unwrap();
tx.write_all("ello".as_bytes()).await.unwrap();
let message = stream.next().await;
assert_eq!(message.unwrap().unwrap(), "hello".to_string());
}
#[tokio::test]
async fn decoding_too_large_message() {
let (mut tx, rx) = tokio::io::duplex(64);
let mut stream = FramedRead::new(rx, Codec::<String>::new().max_frame_len(4));
tx.write_all(&[0, 0, 0, 6]).await.unwrap();
tx.write_all(&[5]).await.unwrap();
tx.write_all("hello".as_bytes()).await.unwrap();
let message = stream.next().await;
assert!(message.unwrap().is_err());
}
#[tokio::test]
async fn encoding_too_large_message() {
let (tx, _rx) = tokio::io::duplex(64);
let mut sink = FramedWrite::new(tx, Codec::<String>::new().max_frame_len(4));
assert!(sink.send("hello".into()).await.is_err());
}
#[tokio::test]
async fn encoding() {
let (tx, _rx) = tokio::io::duplex(64);
let mut sink = FramedWrite::new(tx, Codec::<String>::new());
assert!(sink.feed("hello".into()).await.is_ok());
assert!(sink.feed("hello".into()).await.is_ok());
assert!(sink.feed("hello".into()).await.is_ok());
assert!(sink.flush().await.is_ok());
}
#[tokio::test]
async fn operations_stream() {
type Payload = (Header<u32>, Option<Body>);
let (tx_inner, rx_inner) = tokio::io::duplex(1024 * 100);
let mut tx = into_codec_sink::<Payload, _>(tx_inner);
let mut rx = into_codec_stream::<Payload, _>(rx_inner);
let log = TestLog::new();
for _ in 0..100 {
let operation = log.operation(b"boom boom boom", 32);
tx.send((operation.header, operation.body)).await.unwrap();
}
let mut i = 1;
loop {
if let Some(message) = rx.next().await {
if let Err(err) = message {
panic!("{err}");
}
i += 1;
if i == 100 {
break;
}
}
}
}
}