#tokio-util #codec #decoder #decoding #step #multiple #header

tokio-util-codec-compose

tokio-util编解码器组合的构建模块

2 个版本

0.1.1 2023年5月20日
0.1.0 2023年5月20日

#2276解析器实现

MIT 许可证

53KB
929

tokio-util-codec-compose

Check

一个Rust库,用于构建tokio-util编解码器的模块

这个库受到scodec的启发。

概述

从字节流中解码通信协议通常涉及多个步骤的组合,例如,先解码头部然后解码有效负载。解码器通常具有状态,例如,我们对有效负载有多个解码器,根据头部选择合适的解码器。

然而,我们可能发现自己重复执行相同的解码步骤多次,并且可能只将其作为更大序列的一部分来评估其正确性,而不是针对单个步骤;同样多次。

编码也发生了类似但可能不那么复杂的情况。

为了解决这个问题,tokio-util-codec-compose 库建立在伟大的 tokio-util 之上,并封装了一些我在实现通信协议编解码器时看到的模式,包括无状态和有状态协议。

特性

  • 将字节序列解码为数据类型的原语
  • 将简单的解码器组合成更强大的解码器

路线图

  • 添加更多组合器
  • 扁平化嵌套元组

示例

解码

从概念上讲,你可以将 Decoder<Item = T> 视为一个 Option<T>,因为你可以对它进行映射、序列化等操作。此外,解码器可以在解码帧时携带状态,例如等待 N 字节,然后决定是否读取 MQ 字节,依此类推。这可能会转化为一个显式状态跟踪的状态机,这可能变得令人厌烦。

对于某些解码模式,你可以利用这个库提供的组合操作,你可以从可以独立开发、测试和推理的简单构建块中构建复杂的解码器。

例如,这里是一个用于验证与解码交织的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