A Rust library with building blocks for composing tokio-util codecs
This library was inspired by scodec.
Decoding communication protocols from byte streams usually involves the combination of multiple steps, e.g. decode the header and then the payload. Also, decoders often have state, e.g. we have multiple decoders for the payload where we select the appropriate one based on the header.
However, we may find ourselves repeating the same sequence of decoding steps multiple times and possibly judging their correctness only as part of a larger sequence, not in terms of the individual steps; again multiple times.
A similar, yet perhaps less complicate, scenario happens to encoding.
To tackle this, tokio-util-codec-compose
library builds atop the great tokio-util
and encapsulates some patterns I have seen when implementing codecs for communication protocols, for both stateless and stateful protocols.
Conceptually, you can think of a Decoder<Item = T>
as an Option<T>
in the sense that you can
map
it, sequence it with an and_then
, etc. That with an extra twist:
decoders can carry state around while decoding a frame, e.g. wait for N
bytes, then decide whether to read M
or Q
bytes, and so on. This might translate
into a state-machine which explicitly state tracking, which may get tedious.
For some decoding patterns, you can leverage the compositional operations provided by this library, you can build complex decoders out of simpler building blocks that you can develop, test, and reason about, in isolation.
As an example, here's a decoder for SOCKS v4 CONNECT requests with no validation interleaved with decoding:
```rust use tokioutilcodeccompose::{ decode::DecoderExt, primitives::{delimitedby, ipv4, uint16be, uint8}, }; use anyhow::Result; use bytes::BytesMut; use std::{io, net::Ipv4Addr}; use tokioutil::codec::Decoder;
fn main() -> Result<()> { let mut decoder = socksrequestdecoder();
// SOCKS v4 request to CONNECT "Fred" to 66.102.7.99:80
let mut src = BytesMut::from("\x04\x01\x00\x50\x42\x66\x07\x63\x46\x72\x65\x64\x00");
let res = decoder.decode(&mut src)?;
assert_eq!(
Some(SocksRequest {
version: Version::V4,
command: Command::Connect,
destination_port: Port(80),
destination_ip: "66.102.7.99".parse()?,
user_id: "Fred".into(),
}),
res
);
Ok(())
}
fn socksrequestdecoder() -> impl Decoder
fn version() -> impl Decoder
fn command() -> impl Decoder
fn port() -> impl Decoder
fn userid() -> impl Decoder
type SocksRequestParts = ((((Version, Command), Port), Ipv4Addr), String);
fn fromparts( ((((version, command), destinationport), destinationip), userid): SocksRequestParts, ) -> SocksRequest { SocksRequest { version, command, destinationport, destinationip, user_id, } }
struct SocksRequest { version: Version, command: Command, destinationport: Port, destinationip: Ipv4Addr, user_id: String, }
enum Version { V4, }
impl TryFrom
enum Command { Connect, }
impl TryFrom
struct Port(u16);
impl From
See more examples.
Contributions are more than welcome! If you encounter any issue, have feature requests, or want to make improvements, please open an issue or submit a pull request.
This library is licensed under the MIT License. Please refer to the LICENSE file for more information.