2 个版本
0.1.1 | 2023年5月20日 |
---|---|
0.1.0 | 2023年5月20日 |
#2276 在 解析器实现
53KB
929 行
tokio-util-codec-compose
一个Rust库,用于构建tokio-util编解码器的模块
这个库受到scodec的启发。
概述
从字节流中解码通信协议通常涉及多个步骤的组合,例如,先解码头部然后解码有效负载。解码器通常具有状态,例如,我们对有效负载有多个解码器,根据头部选择合适的解码器。
然而,我们可能发现自己重复执行相同的解码步骤多次,并且可能只将其作为更大序列的一部分来评估其正确性,而不是针对单个步骤;同样多次。
编码也发生了类似但可能不那么复杂的情况。
为了解决这个问题,tokio-util-codec-compose
库建立在伟大的 tokio-util
之上,并封装了一些我在实现通信协议编解码器时看到的模式,包括无状态和有状态协议。
特性
- 将字节序列解码为数据类型的原语
- 将简单的解码器组合成更强大的解码器
路线图
- 添加更多组合器
- 扁平化嵌套元组
示例
解码
从概念上讲,你可以将 Decoder<Item = T>
视为一个 Option<T>
,因为你可以对它进行映射、序列化等操作。此外,解码器可以在解码帧时携带状态,例如等待 N
字节,然后决定是否读取 M
或 Q
字节,依此类推。这可能会转化为一个显式状态跟踪的状态机,这可能变得令人厌烦。
对于某些解码模式,你可以利用这个库提供的组合操作,你可以从可以独立开发、测试和推理的简单构建块中构建复杂的解码器。
例如,这里是一个用于验证与解码交织的SOCKS v4 CONNECT请求的解码器
use tokio_util_codec_compose::{
decode::DecoderExt,
primitives::{delimited_by, ipv4, uint16_be, uint8},
};
use anyhow::Result;
use bytes::BytesMut;
use std::{io, net::Ipv4Addr};
use tokio_util::codec::Decoder;
fn main() -> Result<()> {
let mut decoder = socks_request_decoder();
// 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 socks_request_decoder() -> impl Decoder<Item = SocksRequest, Error = anyhow::Error> {
version()
.then(command())
.then(port())
.then(ipv4())
.then(user_id())
.map(from_parts)
.map_err(|e| anyhow::format_err!("could not decode socks request, reason: {e}"))
}
fn version() -> impl Decoder<Item = Version, Error = io::Error> {
uint8().try_map_into()
}
fn command() -> impl Decoder<Item = Command, Error = io::Error> {
uint8().try_map_into()
}
fn port() -> impl Decoder<Item = Port, Error = io::Error> {
uint16_be().map_into()
}
fn user_id() -> impl Decoder<Item = String, Error = tokio_util::codec::AnyDelimiterCodecError> {
delimited_by([b'\x00'], 255).map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
}
type SocksRequestParts = ((((Version, Command), Port), Ipv4Addr), String);
fn from_parts(
((((version, command), destination_port), destination_ip), user_id): SocksRequestParts,
) -> SocksRequest {
SocksRequest {
version,
command,
destination_port,
destination_ip,
user_id,
}
}
#[derive(Debug, PartialEq, Eq)]
struct SocksRequest {
version: Version,
command: Command,
destination_port: Port,
destination_ip: Ipv4Addr,
user_id: String,
}
#[derive(Debug, PartialEq, Eq)]
enum Version {
V4,
}
impl TryFrom<u8> for Version {
type Error = io::Error;
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value {
0x04 => Ok(Version::V4),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"unexpected version {value}",
)),
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum Command {
Connect,
}
impl TryFrom<u8> for Command {
type Error = io::Error;
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value {
0x01 => Ok(Command::Connect),
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
"unexpected command {value}",
)),
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct Port(u16);
impl From<u16> for Port {
fn from(value: u16) -> Self {
Port(value)
}
}
查看更多 示例。
贡献
欢迎贡献!如果您遇到任何问题,有功能请求或想要进行改进,请打开一个问题或提交一个pull请求。
许可协议
此库受MIT许可协议许可。有关更多信息,请参阅许可协议文件。
依赖
~2.4–3.5MB
~54K SLoC