12 个版本
0.4.0-beta.2 | 2022 年 8 月 1 日 |
---|---|
0.4.0-beta.1 | 2021 年 6 月 1 日 |
0.3.3 | 2020 年 11 月 17 日 |
0.3.2 | 2020 年 6 月 4 日 |
0.1.0 | 2018 年 7 月 24 日 |
#190 in 编码
9,874 每月下载量
用于 8 crates
170KB
3.5K SLoC
弯曲
一个强制执行规范化规则的 Rust 库,用于编码和解码 bencode。 Bencode 是一种简单但非常有效的编码方案,起源于 BitTorrent 对等网络系统。
你可能想找
已知替代方案
这不是第一个实现 Bencode 的库。事实上,已经有几个实现了
- Toby Padilla 的 serde-bencode
- Arjan Topolovec 的 rust-bencode,
- Murarth 的 bencode,
- 以及 Jonas Hermsmeier 的 rust-bencode
为什么我应该使用它?
那么,你可能会问,为什么要添加另一个已经存在的东西的额外版本?
强制正确性
实现规范编码形式很简单。它归结为定义 一种处理无序数据的方式。下一步是 bendy 在编码前使用常规的 Bencode 规则对数据进行排序。如果您的数据已经排序,bendy 当然会跳过额外的排序步骤以提高效率。但 bendy 进一步确保 正确性:如果您向库提供您说已排序的数据,bendy 仍会进行就地验证以 确保您的数据实际上已排序,如果未排序则进行投诉。最后,一旦 bendy 序列化您的数据,它就完全是 Bencode。因此,它与任何其他 Bencode 库完全兼容。
请记住:目前 只有 bendy 在读取时会强制执行规范格式的正确性。
规范表示
Bendy 确保任何反序列化/序列化的往返操作都能产生精确的 相同 和 正确 的二进制表示。这在处理无序集合或键值结构的数据时特别相关,尽管理论上顺序无关紧要,但实际上顺序很重要,尤其是当你想确保与数据结构相关的加密签名不会意外失效。
数据结构 | 默认实现 | 注释 |
---|---|---|
Vec | ✔ | 定义自己的排序 |
VecDeque | ✔ | 定义自己的排序 |
LinkedList | ✔ | 定义自己的排序 |
HashMap | ✔ | 排序缺失但内容按键字节表示排序。 |
BTreeMap | ✔ | 定义自己的排序 |
HashSet | ✘ | (无序) 集合处理尚未定义 |
BTreeSet | ✘ | (无序) 集合处理尚未定义 |
BinaryHeap | ✘ | 排序缺失 |
迭代器 | ~ | emit_unchecked_list() 允许发出任何可迭代对象,但用户需要确保排序。 |
注意
-
由于大多数列表类型已经定义了它们的内部排序,因此像
Vec
、VecDeque
和LinkedList
这样的数据结构在编码过程中不会被排序! -
没有默认实现来处理通用迭代器。这是设计上的选择。
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_int
和 emit_str
)和嵌套 bencode 元素(例如 emit_dict
和 emit_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_or
、integer_or_else
或 try_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_utf8
和 str::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_bytes
和 bendy::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