Skip to main content

p2panda_net/
codec.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Utility methods to encode or decode wire protocol messages in [Postcard] format with message
4//! framing using 4 bytes frame length prefixes.
5//!
6//! ## Example
7//!
8//! Encoding for string "hello" (10 bytes):
9//!
10//! ```plain
11//!           Postcard varint prefix
12//!              |
13//! Frame Length |
14//!  (4 bytes)   |    "hello" string (5 bytes)
15//!     |        |          |
16//! [----------] | [-h----e----l----l----o-]
17//! [0, 0, 0, 6, 5, 104, 101, 108, 108, 111]
18//! ============ ===========================
19//!    PREFIX              MESSAGE
20//! ```
21//!
22//! [Postcard]: https://postcard.jamesmunns.com/wire-format
23use std::marker::PhantomData;
24
25use futures_util::{Sink, Stream};
26use serde::de::DeserializeOwned;
27use serde::{Deserialize, Serialize};
28use thiserror::Error;
29use tokio::io::{AsyncRead, AsyncWrite};
30use tokio_util::bytes::{Buf, BufMut, BytesMut};
31use tokio_util::codec::{Decoder, Encoder, FramedRead, FramedWrite};
32
33/// Implementation of the tokio codec traits to encode- and decode length-prefixed postcard data as
34/// a framed stream.
35#[derive(Debug)]
36pub struct Codec<M> {
37    max_frame_len: usize,
38    _phantom: PhantomData<M>,
39}
40
41impl<M> Codec<M> {
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    pub fn max_frame_len(mut self, value: usize) -> Self {
47        self.max_frame_len = value;
48        self
49    }
50}
51
52impl<M> Default for Codec<M> {
53    fn default() -> Self {
54        Self {
55            max_frame_len: 1024 * 1024 * 128, // megabytes
56            _phantom: PhantomData,
57        }
58    }
59}
60
61impl<M> Encoder<M> for Codec<M>
62where
63    M: Serialize,
64{
65    type Error = CodecError;
66
67    fn encode(&mut self, item: M, dst: &mut BytesMut) -> Result<(), Self::Error> {
68        // Find out how large this message will be.
69        let frame_len =
70            postcard::serialize_with_flavor(&item, postcard::ser_flavors::Size::default())?;
71        if frame_len > self.max_frame_len {
72            return Err(CodecError::TooLargeMessage(frame_len, self.max_frame_len));
73        }
74
75        // Append the encoded frame_len + message bytes to the buffer instead of replacing it, we
76        // might already have previously encoded items in it.
77
78        // Encode frame_len prefix (first four bytes) in big-endian order.
79        dst.put_u32(u32::try_from(frame_len).expect("already checked"));
80
81        // Increase buffer size for message when necessary.
82        dst.reserve(4 + frame_len);
83
84        // Encode message (remaining bytes).
85        let mut writer = dst.writer();
86        postcard::to_io(&item, &mut writer)?;
87
88        Ok(())
89    }
90}
91
92impl<M> Decoder for Codec<M>
93where
94    M: DeserializeOwned,
95{
96    type Item = M;
97    type Error = CodecError;
98
99    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
100        // Decode frame length.
101        if src.len() < 4 {
102            return Ok(None);
103        }
104
105        // Keep a reference of the buffer to not advance the main buffer itself (yet).
106        let bytes: [u8; 4] = src[..4].try_into().expect("checked available bytes");
107        let frame_len = u32::from_be_bytes(bytes) as usize;
108        if frame_len > self.max_frame_len {
109            return Err(CodecError::TooLargeMessage(frame_len, self.max_frame_len));
110        }
111
112        // Decode message.
113        if src.len() < 4 + frame_len {
114            return Ok(None);
115        }
116        let item: M = postcard::from_bytes(&src[4..4 + frame_len])?;
117
118        src.advance(4 + frame_len);
119
120        Ok(Some(item))
121    }
122}
123
124/// Returns a reader for your wire-protocol messages, automatically decoding byte-streams and
125/// handling the framing.
126pub fn into_codec_stream<M, T>(
127    rx: T,
128) -> impl Stream<Item = Result<M, CodecError>> + Unpin + use<M, T>
129where
130    M: for<'de> Deserialize<'de> + 'static,
131    T: AsyncRead + Unpin + 'static,
132{
133    FramedRead::new(rx, Codec::<M>::new())
134}
135
136/// Returns a writer for your wire-protocol messages, automatically encoding it as a framed
137/// byte-stream.
138pub fn into_codec_sink<M, T>(tx: T) -> impl Sink<M, Error = CodecError>
139where
140    M: Serialize + 'static,
141    T: AsyncWrite + Unpin + 'static,
142{
143    FramedWrite::new(tx, Codec::<M>::new())
144}
145
146/// Errors which can occur while decoding or encoding streams of postcard bytes.
147#[derive(Debug, Error)]
148pub enum CodecError {
149    #[error(transparent)]
150    Postcard(#[from] postcard::Error),
151
152    #[error("too large message of {0} bytes (max allowed is {1})")]
153    TooLargeMessage(usize, usize),
154
155    #[error(transparent)]
156    Io(#[from] std::io::Error),
157}
158
159#[cfg(test)]
160mod tests {
161    use futures_util::{FutureExt, SinkExt, StreamExt};
162    use p2panda_core::test_utils::TestLog;
163    use p2panda_core::{Body, Header};
164    use tokio::io::AsyncWriteExt;
165    use tokio_util::codec::{FramedRead, FramedWrite};
166
167    use super::{Codec, into_codec_sink, into_codec_stream};
168
169    #[tokio::test]
170    async fn decoding_exactly_one_frame() {
171        let (mut tx, rx) = tokio::io::duplex(64);
172        let mut stream = FramedRead::new(rx, Codec::<String>::new());
173
174        // Frame-length (big-endian) of 6 bytes (1 byte postcard varint + 5 byte string).
175        tx.write_all(&[0, 0, 0, 6]).await.unwrap();
176
177        // Postcard prefix for indicating the string length.
178        tx.write_all(&[5]).await.unwrap();
179
180        // Message, the actual string.
181        tx.write_all("hello".as_bytes()).await.unwrap();
182
183        let message = stream.next().await;
184        assert_eq!(message.unwrap().unwrap(), "hello".to_string());
185    }
186
187    #[tokio::test]
188    async fn decoding_more_than_one_frame() {
189        let (mut tx, rx) = tokio::io::duplex(64);
190        let mut stream = FramedRead::new(rx, Codec::<String>::new());
191
192        // Frame-length (big-endian) of 6 bytes (1 byte postcard varint + 5 byte string).
193        tx.write_all(&[0, 0, 0, 6]).await.unwrap();
194
195        tx.write_all(&[5]).await.unwrap();
196        tx.write_all("hello".as_bytes()).await.unwrap();
197
198        // Another frame for another message of 10 bytes (1 byte postcard varint + 9 byte string).
199        tx.write_all(&[0, 0, 0, 10]).await.unwrap();
200
201        tx.write_all(&[9]).await.unwrap();
202        tx.write_all("aquariums".as_bytes()).await.unwrap();
203
204        let message = stream.next().await;
205        assert_eq!(message.unwrap().unwrap(), "hello".to_string());
206
207        let message = stream.next().await;
208        assert_eq!(message.unwrap().unwrap(), "aquariums".to_string());
209    }
210
211    #[tokio::test]
212    async fn decoding_incomplete_frame() {
213        let (mut tx, rx) = tokio::io::duplex(64);
214        let mut stream = FramedRead::new(rx, Codec::<String>::new());
215
216        // Frame-length (big-endian) of 6 bytes (1 byte postcard varint + 5 byte string).
217        tx.write_all(&[0, 0, 0, 6]).await.unwrap();
218
219        // Attempt to decode an incomplete frame, the decoder should not yield anything.
220        let message = stream.next().now_or_never();
221        assert!(message.is_none());
222
223        // Complete the data item in the buffer.
224        tx.write_all(&[5]).await.unwrap();
225        tx.write_all("h".as_bytes()).await.unwrap();
226        tx.write_all("ello".as_bytes()).await.unwrap();
227
228        let message = stream.next().await;
229        assert_eq!(message.unwrap().unwrap(), "hello".to_string());
230    }
231
232    #[tokio::test]
233    async fn decoding_too_large_message() {
234        let (mut tx, rx) = tokio::io::duplex(64);
235        let mut stream = FramedRead::new(rx, Codec::<String>::new().max_frame_len(4));
236
237        tx.write_all(&[0, 0, 0, 6]).await.unwrap();
238        tx.write_all(&[5]).await.unwrap();
239        tx.write_all("hello".as_bytes()).await.unwrap();
240
241        let message = stream.next().await;
242        assert!(message.unwrap().is_err());
243    }
244
245    #[tokio::test]
246    async fn encoding_too_large_message() {
247        let (tx, _rx) = tokio::io::duplex(64);
248        let mut sink = FramedWrite::new(tx, Codec::<String>::new().max_frame_len(4));
249        assert!(sink.send("hello".into()).await.is_err());
250    }
251
252    #[tokio::test]
253    async fn encoding() {
254        let (tx, _rx) = tokio::io::duplex(64);
255        let mut sink = FramedWrite::new(tx, Codec::<String>::new());
256        assert!(sink.feed("hello".into()).await.is_ok());
257        assert!(sink.feed("hello".into()).await.is_ok());
258        assert!(sink.feed("hello".into()).await.is_ok());
259        assert!(sink.flush().await.is_ok());
260    }
261
262    #[tokio::test]
263    async fn operations_stream() {
264        type Payload = (Header<u32>, Option<Body>);
265
266        // Give stream a large enough buffer size since we're creating all messages up-front before
267        // consuming them.
268        let (tx_inner, rx_inner) = tokio::io::duplex(1024 * 100);
269
270        let mut tx = into_codec_sink::<Payload, _>(tx_inner);
271        let mut rx = into_codec_stream::<Payload, _>(rx_inner);
272
273        // Create 100 operations, encode and send bytes to receiver.
274        let log = TestLog::new();
275        for _ in 0..100 {
276            let operation = log.operation(b"boom boom boom", 32);
277            tx.send((operation.header, operation.body)).await.unwrap();
278        }
279
280        // Receiver writes bytes into buffer, attempts decoding and returns header/body tuple 100
281        // times.
282        let mut i = 1;
283        loop {
284            if let Some(message) = rx.next().await {
285                if let Err(err) = message {
286                    panic!("{err}");
287                }
288
289                i += 1;
290
291                if i == 100 {
292                    break;
293                }
294            }
295        }
296    }
297}