#bencode #serialization #bittorrent #codec #deserialize

无需 std bendy

一个强制执行规范化规则的 Rust 库,用于编码和解码 bencode

12 个版本

0.4.0-beta.22022 年 8 月 1 日
0.4.0-beta.12021 年 6 月 1 日
0.3.3 2020 年 11 月 17 日
0.3.2 2020 年 6 月 4 日
0.1.0 2018 年 7 月 24 日

#190 in 编码

Download history 3490/week @ 2024-04-23 3248/week @ 2024-04-30 2275/week @ 2024-05-07 2638/week @ 2024-05-14 3277/week @ 2024-05-21 3725/week @ 2024-05-28 2754/week @ 2024-06-04 1899/week @ 2024-06-11 2473/week @ 2024-06-18 2598/week @ 2024-06-25 2262/week @ 2024-07-02 2894/week @ 2024-07-09 2234/week @ 2024-07-16 2281/week @ 2024-07-23 2674/week @ 2024-07-30 2170/week @ 2024-08-06

9,874 每月下载量
用于 8 crates

BSD-3-Clause

170KB
3.5K SLoC

弯曲

Build Status License: BSD-3-Clause

一个强制执行规范化规则的 Rust 库,用于编码和解码 bencode。 Bencode 是一种简单但非常有效的编码方案,起源于 BitTorrent 对等网络系统。


你可能想找


已知替代方案

这不是第一个实现 Bencode 的库。事实上,已经有几个实现了

为什么我应该使用它?

那么,你可能会问,为什么要添加另一个已经存在的东西的额外版本?

强制正确性

实现规范编码形式很简单。它归结为定义 一种处理无序数据的方式。下一步是 bendy 在编码前使用常规的 Bencode 规则对数据进行排序。如果您的数据已经排序,bendy 当然会跳过额外的排序步骤以提高效率。但 bendy 进一步确保 正确性:如果您向库提供您说已排序的数据,bendy 仍会进行就地验证以 确保您的数据实际上已排序,如果未排序则进行投诉。最后,一旦 bendy 序列化您的数据,它就完全是 Bencode。因此,它与任何其他 Bencode 库完全兼容。

请记住:目前 只有 bendy 在读取时会强制执行规范格式的正确性。

规范表示

Bendy 确保任何反序列化/序列化的往返操作都能产生精确的 相同正确 的二进制表示。这在处理无序集合或键值结构的数据时特别相关,尽管理论上顺序无关紧要,但实际上顺序很重要,尤其是当你想确保与数据结构相关的加密签名不会意外失效。

数据结构 默认实现 注释
Vec 定义自己的排序
VecDeque 定义自己的排序
LinkedList 定义自己的排序
HashMap 排序缺失但内容按键字节表示排序。
BTreeMap 定义自己的排序
HashSet (无序) 集合处理尚未定义
BTreeSet (无序) 集合处理尚未定义
BinaryHeap 排序缺失
迭代器 ~ emit_unchecked_list() 允许发出任何可迭代对象,但用户需要确保排序。

注意

  • 由于大多数列表类型已经定义了它们的内部排序,因此像 VecVecDequeLinkedList 这样的数据结构在编码过程中不会被排序!

  • 没有默认实现来处理通用迭代器。这是设计上的选择。 Bendy 无法从迭代器中判断底层结构是否需要排序,因此必须以原始数据形式接收数据。

用法

首先需要将 bendy 添加为项目依赖项

[dependencies]
bendy = "^0.3"

使用 ToBencode 编码

要编码已实现 ToBencode 特性的类型的对象,只需导入该特性,并在对象上调用 to_bencode() 函数即可。

use bendy::encoding::{ToBencode, Error};

let my_data = vec!["hello", "world"];
let encoded = my_data.to_bencode()?;

assert_eq!(b"l5:hello5:worlde", encoded.as_slice());

Ok::<(), Error>(())

实现 ToBencode

在大多数情况下,只需重写相关的 encode 函数,并保留 to_bencode 的默认实现。

该函数将为您提供 SingleItemEncoder,必须使用它来发出当前对象的任何相关组件。只要这些组件本身实现了 ToBencode,就只需将它们传递给编码器的 emit 函数即可,因为这会序列化实现该特性的任何类型。

除了 emit 之外,编码器还提供了一系列用于编码特定 bencode 原始数据类型(例如 emit_intemit_str)和嵌套 bencode 元素(例如 emit_dictemit_list)的函数。如果需要输出特定非默认数据类型,则应使用这些方法。

实现整数编码

由于 bencode 有原生的整数支持,bendy 为 rust 的所有原生整数类型提供了默认实现。这允许对任何整数对象调用 to_bencode,并将这些对象传递给编码器的 emit_int 函数。

use bendy::encoding::{ToBencode, SingleItemEncoder, Error};

struct IntegerWrapper(i64);

impl ToBencode for IntegerWrapper {
    const MAX_DEPTH: usize = 0;

    fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
        encoder.emit_int(self.0)
    }
}

let example = IntegerWrapper(21);

let encoded = example.to_bencode()?;
assert_eq!(b"i21e", encoded.as_slice());

let encoded = 21.to_bencode()?;
assert_eq!(b"i21e", encoded.as_slice());

Ok::<(), Error>(())

编码字节字符串

bencode 原生支持另一种数据类型,即字节字符串。因此,bendy 为 String&str 提供了默认实现。

use bendy::encoding::{ToBencode, SingleItemEncoder, Error};

struct StringWrapper(String);

impl ToBencode for StringWrapper {
    const MAX_DEPTH: usize = 0;

    fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
        encoder.emit_str(&self.0)
    }
}

let example = StringWrapper("content".to_string());

let encoded = example.to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());

let encoded = "content".to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());

Ok::<(), Error>(())

由于将字节字符串表示为 Vec<u8> 是一个非常常见的模式,bendy 提供了 AsString 包装器。这可以用来封装任何实现 AsRef<[u8]> 的元素,使其作为 bencode 字符串输出,而不是列表。

use bendy::encoding::{ToBencode, SingleItemEncoder, Error, AsString};

struct ByteStringWrapper(Vec<u8>);

impl ToBencode for ByteStringWrapper {
    const MAX_DEPTH: usize = 0;

    fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
        let content = AsString(&self.0);
        encoder.emit(&content)
    }
}

let example = ByteStringWrapper(b"content".to_vec());

let encoded = example.to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());

let encoded = AsString(b"content").to_bencode()?;
assert_eq!(b"7:content", encoded.as_slice());

Ok::<(), Error>(())

编码字典

如果一个数据结构包含键值对,将其编码为 bencode 字典通常是一个好主意。对于具有多个成员的大多数结构也是如此,因为这有助于表示它们的名称以确保特定(可选)成员的存在。

注意:为了确保规范表示,bendy 要求通过 emit_dict 生成的字典键按升序排序,否则编码将失败并产生 UnsortedKeys 类型的错误。在这种情况下,可以使用 emit_and_sort_dict 而不是。

use bendy::encoding::{ToBencode, SingleItemEncoder, Error};

struct Example {
    label: String,
    counter: u64,
}

impl ToBencode for Example {
    const MAX_DEPTH: usize = 1;

    fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
        encoder.emit_dict(|mut e| {
            e.emit_pair(b"counter", &self.counter)?;
            e.emit_pair(b"label", &self.label)?;

            Ok(())
        })
    }
}

let example = Example { label: "Example".to_string(), counter: 0 };

let encoded = example.to_bencode()?;
assert_eq!(b"d7:counteri0e5:label7:Examplee", encoded.as_slice());

Ok::<(), Error>(())

编码列表

在编码列表时,bendy 假设列表内部的元素通过其在列表中的位置按固有顺序排序。因此,实现可以自由选择自己的排序方式。

use bendy::encoding::{ToBencode, SingleItemEncoder, Error};

struct Location(i64, i64);

impl ToBencode for Location {
    const MAX_DEPTH: usize = 1;

    fn encode(&self, encoder: SingleItemEncoder) -> Result<(), Error> {
        encoder.emit_list(|e| {
            e.emit_int(self.0)?;
            e.emit_int(self.1)
        })
    }
}

let example = Location(2, 3);

let encoded = example.to_bencode()?;
assert_eq!(b"li2ei3ee", encoded.as_slice());

Ok::<(), Error>(())

使用 FromBencode 解码

要解码已实现 FromBencode 特性的类型的对象,只需导入该特性并调用对象上的 from_bencode() 函数即可。

use bendy::decoding::{FromBencode, Error};

let encoded = b"l5:hello5:worlde".to_vec();
let decoded = Vec::<String>::from_bencode(&encoded)?;

assert_eq!(vec!["hello", "world"], decoded);

Ok::<(), Error>(())

实现 FromBencode

在大多数情况下,只需重写相关的 decode_bencode_object 函数,并保留 from_bencode 的默认实现即可。

该函数将为您提供 bencode Object 的表示,必须对其进行处理以接收预期数据类型的任何相关组件。只要这些组件自己实现 FromBencode,只需在预期数据类型的元素上调用 decode_bencode_object 即可,因为这将反序列化实现特质的任何类型。

除了 from_bencode 之外,bencode Object 表示还提供了一组辅助函数,可以将自身转换为特定的 bencode 原始类型和容器(例如 bytes_orinteger_or_elsetry_into_list)。然后可以使用这些函数来恢复实际元素。

解码整数

由于 bencode 有原生整数支持,bendy 为所有 Rust 原生整数类型提供了默认实现。这使得可以在任何整数类型上调用 from_bencode

注意:如果需要处理无法通过默认数据类型表示的大整数,可以在解码过程中始终访问该数字的字符串版本。

use bendy::decoding::{FromBencode, Object, Error};

#[derive(Debug, Eq, PartialEq)]
struct IntegerWrapper(i64);

impl FromBencode for IntegerWrapper {
    const EXPECTED_RECURSION_DEPTH: usize = 0;

    fn decode_bencode_object(object: Object) -> Result<Self, Error> {
        // This is an example for content handling. It would also be possible
        // to call  `i64::decode_bencode_object(object)` directly.
        let content = object.try_into_integer()?;
        let number = content.parse::<i64>()?;

        Ok(IntegerWrapper(number))
    }
}

let encoded = b"i21e".to_vec();

let example = IntegerWrapper::from_bencode(&encoded)?;
assert_eq!(IntegerWrapper(21), example);

let example = i64::from_bencode(&encoded)?;
assert_eq!(21, example);

Ok::<(), Error>(())

解码字节字符串

在大多数情况下,可以通过 String::from_utf8str::from_utf8 将字符串从其 bencode 表示作为字节序列恢复。

use bendy::decoding::{FromBencode, Object, Error};

#[derive(Debug, Eq, PartialEq)]
struct StringWrapper(String);

impl FromBencode for StringWrapper {
    const EXPECTED_RECURSION_DEPTH: usize = 0;

    fn decode_bencode_object(object: Object) -> Result<Self, Error> {
        // This is an example for content handling. It would also be possible
        // to call  `String::decode_bencode_object(object)` directly.
        let content = object.try_into_bytes()?;
        let content = String::from_utf8(content.to_vec())?;

        Ok(StringWrapper(content))
    }
}

let encoded = b"7:content".to_vec();

let example = StringWrapper::from_bencode(&encoded)?;
assert_eq!(StringWrapper("content".to_string()), example);

let example = String::from_bencode(&encoded)?;
assert_eq!("content".to_string(), example);

Ok::<(), Error>(())

如果内容是非 UTF-8 编码的字符串或实际的字节序列,则可以使用 AsString 包装器将 bencode 字符串对象作为类型为 Vec<u8> 的对象的字节序列恢复。

use bendy::{
    decoding::{FromBencode, Object, Error},
    encoding::AsString,
};

#[derive(Debug, Eq, PartialEq)]
struct ByteStringWrapper(Vec<u8>);

impl FromBencode for ByteStringWrapper {
    const EXPECTED_RECURSION_DEPTH: usize = 0;

    fn decode_bencode_object(object: Object) -> Result<Self, Error> {
        let content = AsString::decode_bencode_object(object)?;
        Ok(ByteStringWrapper(content.0))
    }
}

let encoded = b"7:content".to_vec();

let example = ByteStringWrapper::from_bencode(&encoded)?;
assert_eq!(ByteStringWrapper(b"content".to_vec()), example);

let example = AsString::from_bencode(&encoded)?;
assert_eq!(b"content".to_vec(), example.0);

Ok::<(), Error>(())

解码字典

将 bencode 对象解包成字典将提供字典解码器,可以用来访问包含的键值对。

为了在处理大型或多个嵌套字典时提高错误处理能力,解码模块提供了一个 ResultExt 特性,允许在错误情况下添加上下文描述。如果嵌套了多个上下文调用,它们将以点分法的样式连接起来。

use bendy::decoding::{FromBencode, Object, Error, ResultExt};

#[derive(Debug, Eq, PartialEq)]
struct Example {
    label: String,
    counter: u64,
}

impl FromBencode for Example {
    const EXPECTED_RECURSION_DEPTH: usize = 1;

    fn decode_bencode_object(object: Object) -> Result<Self, Error> {
        let mut counter = None;
        let mut label = None;

        let mut dict = object.try_into_dictionary()?;
        while let Some(pair) = dict.next_pair()? {
            match pair {
                (b"counter", value) => {
                    counter = u64::decode_bencode_object(value)
                        .context("counter")
                        .map(Some)?;
                },
                (b"label", value) => {
                    label = String::decode_bencode_object(value)
                        .context("label")
                        .map(Some)?;
                },
                (unknown_field, _) => {
                    return Err(Error::unexpected_field(String::from_utf8_lossy(
                        unknown_field,
                    )));
                },
            }
        }

        let counter = counter.ok_or_else(|| Error::missing_field("counter"))?;
        let label= label.ok_or_else(|| Error::missing_field("label"))?;

        Ok(Example { counter, label })
    }
}

let encoded = b"d7:counteri0e5:label7:Examplee".to_vec();
let expected = Example { label: "Example".to_string(), counter: 0 };

let example = Example::from_bencode(&encoded)?;
assert_eq!(expected, example);

Ok::<(), Error>(())

解码一个列表

将 bencode 对象解包成列表将提供列表解码器,可以用来访问包含的元素。

use bendy::decoding::{FromBencode, Object, Error};

#[derive(Debug, PartialEq, Eq)]
struct Location(i64, i64);

impl FromBencode for Location {
    const EXPECTED_RECURSION_DEPTH: usize = 1;

    fn decode_bencode_object(object: Object) -> Result<Self, Error> {
        let mut list = object.try_into_list()?;

        let x = list.next_object()?.ok_or(Error::missing_field("x"))?;
        let x = i64::decode_bencode_object(x)?;

        let y = list.next_object()?.ok_or(Error::missing_field("y"))?;
        let y = i64::decode_bencode_object(y)?;

        Ok(Location(x, y))
    }
}

let encoded = b"li2ei3ee".to_vec();
let expected = Location(2, 3);

let example = Location::from_bencode(&encoded)?;
assert_eq!(expected, example);

Ok::<(), Error>(())

可选:递归解析的限制

什么?

该库允许设置解包和编码时预期的递归深度限制。如果设置了,解析器将使用此值作为验证任何嵌套数据结构的上限,并在检测到额外的嵌套级别时错误地终止。

虽然编码限制本身主要是为了增加 bendy 用户对其验证代码的信心,但解码限制应用于避免解析格式错误或恶意的外部数据。

  • 编码限制可以通过任何 ToBencode 特性的实现中的 MAX_DEPTH 常量来设置。
  • 解码限制可以通过任何 FromBencode 特性的实现中的 EXPECTED_RECURSION_DEPTH 常量来设置。

如何?

嵌套级别的计算始终从零开始,当解析器进入嵌套的 bencode 元素(即列表、字典)时递增,并在相关的元素结束时递减。因此,任何解码为 bencode 字符串或整数的值都不会影响嵌套限制。

Serde 支持

当启用 serde 功能时,Bendy 支持 serde

[dependencies]
bendy = { version = "^0.3", features = ["std", "serde"] }
serde = { version = "1.0", features = ["derive"] }

启用该功能后,可以使用 bendy::serde::from_bytesbendy::serde::to_bytes 分别将值序列化和反序列化为 bencode

# #[cfg(not(feature = "serde"))]
# fn main() {}
# #[cfg(feature = "serde")]
# fn main() -> Result<(), bendy::serde::Error> {

use serde_derive::{Deserialize, Serialize};

#[serde(crate = "serde_")]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Foo {
    bar: String,
}

let value = Foo {
    bar: "hello".into(),
};

let bencode = bendy::serde::to_bytes(&value)?;
assert_eq!(bencode, b"d3:bar5:helloe");

let deserialized = bendy::serde::from_bytes::<Foo>(&bencode)?;
assert_eq!(deserialized, value);

Ok(())

# }

有关 Rust 类型在 bencode 中的表示的信息,请参阅 serde 模块文档

不安全代码的使用

解析器不需要任何不安全代码即可工作,但它仍然包含一个不安全的调用到 str::from_utf8_unchecked。此调用用于在解析器将表示传入整数的字节转换为 &str 之后避免重复 UTF-8 检查。

免责声明:可能通过依赖于 snafu 包引入更多不安全代码。

贡献

我们欢迎每个人都提出问题、打开问题或提供合并请求。每个合并请求都将被审查,或者将其合并到主树中,或者提供反馈,说明所需的更改。

此存储库中的所有代码均受 BSD-3-Clause 许可证的约束。

依赖项

~1.5MB
~39K SLoC