#macro-derive #byte #fields #representation #bit #attributes #bebytes

macro 无std bebytes_derive

用于生成/解析具有自定义位字段的二进制消息表示的宏

13个不稳定版本 (3个破坏性版本)

0.5.1 2024年6月28日
0.5.0 2024年6月28日
0.4.0 2024年1月14日
0.3.0 2023年12月29日
0.2.12 2023年7月5日

#144#fields

Download history 1/week @ 2024-05-20 4/week @ 2024-06-10 307/week @ 2024-06-24 42/week @ 2024-07-01 535/week @ 2024-07-29

每月558次下载
3 个crate中使用(通过 bebytes

MIT 许可证

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 个属性,即 possize。位置属性定义了位应该开始的位置。例如,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