13个不稳定版本 (3个破坏性版本)
0.5.1 | 2024年6月28日 |
---|---|
0.5.0 |
|
0.4.0 | 2024年1月14日 |
0.3.0 | 2023年12月29日 |
0.2.12 | 2023年7月5日 |
#144 在 #fields
每月558次下载
在 3 个crate中使用(通过 bebytes)
51KB
796 行
BeBytes Derive
BeBytes Derive 是一个过程宏crate,它提供了一个自定义 derive 宏,用于为Rust中的网络结构体生成序列化和反序列化方法。该宏生成将结构体转换为字节表示(序列化)以及相反操作(反序列化)的代码,使用大端序。它的目的是通过自动化 Rust 结构体和字节数组之间的转换来简化与网络协议和消息格式的工作。
注意:BeBytes Derive 目前处于开发阶段,尚未在生产环境中彻底测试。请谨慎使用,并在您的特定用例中进行适当的测试和验证。
使用方法
要使用 BeBytes Derive,请在您的 Cargo.toml
文件中将它添加为依赖项
[dependencies]
bebytes_derive = "0.2"
然后,从 bebytes_derive crate 中导入 BeBytes trait 并为您的结构体使用 derive
use bebytes_derive::BeBytes;
#[derive(BeBytes)]
struct MyStruct {
// Define your struct fields here...
}
BeBytes derive 宏将为您的结构体生成以下方法
try_from_be_bytes(&[u8]) -> Result<(Self, usize), Box<dyn std::error::Error>>
: 将字节数组转换为结构体实例的方法。它返回一个包含反序列化结构体和已消耗字节数的结果。to_be_bytes(&self) -> Vec<u8>
: 将结构体转换为字节数表示的方法。它返回一个包含序列化字节的Vec<u8>
。field_size(&self) -> usize
: 计算结构体大小的方法。
示例
以下是一个展示 BeBytes Derive 使用方法的示例
use bebytes_macro::BeBytes;
#[derive(Debug, BeBytes)]
struct MyStruct {
#[U8(size(1), pos(0))]
field1: u8,
#[U8(size(4), pos(1))]
field2: u8,
#[U8(size(3), pos(5))]
field3: u8,
field4: u32,
}
fn main() {
let my_struct = MyStruct {
field1: 1,
field2: 7,
field3: 12,
field4: 0
};
let bytes = my_struct.to_be_bytes();
println!("Serialized bytes: {:?}", bytes);
let deserialized = MyStruct::try_from_be_bytes(&bytes).unwrap();
println!("Deserialized struct: {:?}", deserialized);
}
在这个示例中,我们定义了一个名为 MyStruct 的结构体,包含四个字段。使用 #[U8]
属性来指定字段的序列化大小和位置。BeBytes derive 宏为结构体生成序列化和反序列化方法,使我们能够轻松地将它转换为字节并反向转换。
工作原理
U8
属性允许定义 2 个属性,即 pos
和 size
。位置属性定义了位应该开始的位置。例如,pos(0), size(4) 指定字段应该只占用 4 位,并从左到右的起始位置为 0。宏将位移动,以便它们在结果字节数组中占据正确的位置,当使用 .to_be_bytes()
时。所以一个 pos(0) 和 size(4) 的 4
将变为:
4 => 00000100 移位并掩码 => 0100
字段按照大端顺序顺序读取/写入,并且必须完成 8 的倍数。这意味着带有 U8
属性的字段必须在提供下一个非 U8
字节之前完成一个字节。例如,以下结构体将抛出一个编译时错误,指出 U8
属性必须加起来等于一个完整的字节。
#[derive(Debug, BeBytes)]
struct WrongStruct {
#[U8(size(1), pos(0))]
field1: u8,
#[U8(size(4), pos(1))]
field2: u8,
field3: f32,
}
将抛出一个编译时错误,指出 U8
属性必须加起来等于一个完整的字节。
只要遵循上述规则,您就可以使用 Rust 无符号整数作为类型来创建自定义的位序列,派生的实现将为您处理复杂的移位和掩码操作。其中一个优点是,我们不需要中间向量实现来解析位组或单个位。
多字节值
该宏支持从u8到u128的所有无符号类型。这些类型可以使用与u8类型相同的方式使用
- 使用u16
#[derive(BeBytes, Debug, PartialEq)]
struct U16 {
#[U8(size(1), pos(0))]
first: u8,
#[U8(size(14), pos(1))]
second: u16,
#[U8(size(1), pos(15))]
fourth: u8,
}
- 使用u32
#[derive(BeBytes, Debug, PartialEq)]
struct U32 {
#[U8(size(1), pos(0))]
first: u8,
#[U8(size(30), pos(1))]
second: u32,
#[U8(size(1), pos(31))]
fourth: u8,
}
以此类推。
这里同样适用相同的规则。您的U8
字段必须完整一个字节,即使它们跨越多个字节。
以下原始类型可以与U8
属性一起使用:u8, u16, u32, u64, u128, i8, i16, i32, i64, i128
枚举
仅支持具有命名字段的枚举,值以字节为单位读取/写入。例如
#[derive(BeBytes, Debug, PartialEq)]
pub enum DummyEnum {
SetupResponse = 1,
ServerStart = 2,
SetupRequest = 3,
}
选项
支持选项,只要内部类型是原始类型即可。例如
#[derive(BeBytes, Debug, PartialEq)]
pub struct NestedStruct {
pub dummy_struct: DummyStruct,
pub optional_number: Option<i32>,
pub error_estimate: ErrorEstimate,
}
字节数组和向量
由于编译时已知大小,您可以传递静态字节数组。例如
#[derive(BeBytes, Debug, PartialEq)]
pub struct DummyStruct {
pub dummy0: [u8; 2],
#[U8(size(1), pos(0))]
pub dummy1: u8,
#[U8(size(7), pos(1))]
pub dummy2: u8,
}
向量支持,但您必须提供您计划读取的字节数的提示,或者仅将其用作结构体中的最后一个字段。这是因为在读取下一个字段时,宏需要知道它应该从缓冲区中读取多少字节才能正确对齐。如果您不提供提示,您仍然可以使用向量,只要它是最后一个结构体的最后一个字段的最后一个字段。您可以通过以下方式提供提示:
With(size(#))
With(size(#))将告诉宏从缓冲区中读取多少字节以填充注解向量。例如
#[derive(BeBytes, Debug, PartialEq)]
pub struct DummyStruct {
pub dummy0: [u8; 2],
#[With(size(10))]
pub vecty: Vec<u8>,
pub dummy1: u8,
}
From(fieldname)
From(fieldname)将告诉宏使用具有Example的字段值
#[derive(BeBytes, Debug, PartialEq)]
pub struct ErrorEstimate {
pub ipv4: u8,
#[From(ipv4)]
pub address: Vec<u8>,
pub rest: u8,
}
这允许您将值作为传入的缓冲区的一部分读取,例如DNS数据包,其中域名由指定下一部分名称长度的数字交错。 (3www7example3com)
最后一个字段
如果您不提供提示并尝试在结构体中间使用向量,宏将在编译时抛出错误。
注意:如果向量用作另一个结构体的最后一个字段,但该结构体不是父结构体的最后一个字段,则宏将读取整个缓冲区并尝试将其作为向量的值。这可能不是您想要的,所以请勿这样做。
嵌套字段
理论上,您可以嵌套结构体,但请注意填充向量。我尚未实现或测试任何防止您这样做的方法,所以除非它们占据最后一个位置,否则请不要在嵌套结构体中放入未提示的向量。
#[derive(BeBytes, Debug, PartialEq)]
pub struct NestedStruct {
pub dummy_struct: DummyStruct,
pub error_estimate: ErrorEstimate,
}
贡献
我这样做是为了好玩,但所有帮助都受到欢迎。
许可
此项目根据MIT许可证许可。
依赖项
~250–690KB
~16K SLoC