如何使用 Serde 进行类型-长度-值 (TLV) 序列化?

use*_*749 6 rust serde

我需要根据TLV 格式使用Serde序列化一类结构。TLV 可以以树格式嵌套。

这些结构的字段通常被序列化,很像bincode,但在字段数据之前,我必须包含一个标签(理想情况下要关联)和字段数据的长度(以字节为单位)。

理想情况下,Serde 会识别需要这种序列化的结构,可能是通过让它们实现一个TLV特征。这部分是可选的,因为我还可以明确地注释这些结构中的每一个。

所以这个问题按优先级分为 3 个部分:

  1. 在执行该数据的序列化之前,如何获取长度数据(来自 Serde?)?

  2. 我如何将标签与结构相关联(尽管我想我也可以在结构中包含标签..)?

  3. 如何让 Serde 识别一类结构并应用自定义序列化?

请注意,1) 是此处的(核心)问题。如果 1) 可以用 Serde 解决,我会将 2) 和 3) 作为单独的问题发布。

Cae*_*sar 1

振作起来,长篇大论。另外,为了约定:我选择类型和长度均为无符号 4 字节大端字节序。让我们从简单的事情开始:

\n
\n
    \n
  1. 如何让 Serde 识别一类结构并应用自定义序列化?
  2. \n
\n
\n

这确实是一个单独的问题,但您可以通过#[serde(serialize_with = \xe2\x80\xa6)]属性来完成此操作,也可以在序列化程序中fn serialize_struct(self, name: &\'static str, _: usize)根据名称来完成此操作,具体取决于您的具体想法。

\n
\n
    \n
  1. 如何将标签与结构关联起来(尽管我想我也可以在结构内包含标签..)?
  2. \n
\n
\n

这是 serde 的一个已知限制,也是 protobuf 实现通常不基于 serde (例如prost)的原因,但有自己的派生 proc 宏,允许使用相应的标签注释结构和字段。您可能应该这样做,因为它干净且快速。但既然你问到了 serde,我会选择一个受以下启发的替代方案serde_protobuf:如果你从一个奇怪的角度来看它,serde 只是一个基于访问者的反射框架。它将为您提供有关您当前正在(反)序列化的类型的结构信息,例如,它会告诉您您访问的类型的类型、名称和字段。您所需要的只是一个(用户提供的)函数,将该类型信息映射到标签。例如:

\n
struct TLVSerializer<\'a> {\n    ttf: &\'a dyn Fn(TypeTagFor) -> u32,\n    \xe2\x80\xa6\n}\nimpl<\'a> Serializer for TLVSerializer<\'a> {\n    fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {\n        let tag = &(self.ttf)(TypeTagFor::Bool).to_be_bytes();\n        let len = &1u32.to_be_bytes();\n        todo!("write");\n    }\n\n    fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {\n        let tag = &(self.ttf)(TypeTagFor::Int {\n                    signed: true,\n                    width: 4,\n                })\n                .to_be_bytes();\n        let len = &4u32.to_be_bytes();\n        todo!("write");\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后,您需要编写一个提供标签的函数,例如:

\n
enum TypeTagFor {\n    Bool,\n    Int { width: u8, signed: bool },\n    Struct { name: &\'static str },\n    // ...\n}\nfn foobar_type_tag_for(ttf: TypeTagFor) -> u32 {\n    match ttf {\n        TypeTagFor::Int {\n            width: 4,\n            signed: true,\n        } => 0x69333200,\n        TypeTagFor::Bool => 0x626f6f6c,\n        _ => unreachable!(),\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果您只有一组 \xe2\x86\x92 类型标记映射,您也可以将其直接放入序列化器中。

\n
\n
    \n
  1. 在执行数据序列化之前,如何获取长度数据(从 Serde?)?
  2. \n
\n
\n

简短的回答是:不能。如果不检查整个结构就无法知道长度(例如其中可能有 Vec)。但这也告诉您需要做什么:您需要首先检查整个结构,推断长度,然后进行序列化。您正好有一种方法可以检查手头的整个结构:serde。因此,您将编写一个序列化器,它实际上不会序列化任何内容,而仅记录长度:

\n
struct TLVLenVisitor;\nimpl Serializer for TLVLenVisitor {\n    type Ok = usize;\n    type SerializeSeq = TLVLenSumVisitor;\n\n    fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {\n        Ok(4)\n    }\n    fn serialize_str(self, str: &str) -> Result<Self::Ok, Self::Error> {\n        Ok(str.len())\n    }\n    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {\n        Ok(TLVLenSumVisitor { sum: 0 })\n    }\n}\nstruct TLVLenSumVisitor {\n    sum: usize,\n}\nimpl serde::ser::SerializeSeq for TLVLenSumVisitor {\n    type Ok = usize;\n    fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Self::Error> {\n        // The length of a sequence is the length of all its parts, plus the bytes for type tag and length\n        self.sum += value.serialize(TLVLenVisitor)? + HEADER_LEN;\n        Ok(())\n    }\n    fn end(self) -> Result<Self::Ok, Self::Error> {\n        Ok(self.sum)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

幸运的是,序列化是非破坏性的,因此您可以使用第一个序列化程序来获取长度,然后在第二遍中执行实际的序列化:

\n
    let len = foobar.serialize(TLVLenVisitor).unwrap();\n    foobar.serialize(TLVSerializer {\n        target: &mut File::create("foobar").unwrap(), // No seeking performed on the file\n        len,\n        ttf: &foobar_type_tag_for,\n    })\n    .unwrap();\n
Run Code Online (Sandbox Code Playgroud)\n

由于您已经知道要序列化的内容的长度,因此第二个序列化器相对简单:

\n
struct TLVSerializer<\'a> {\n    target: &\'a mut dyn Write, // Using dyn to reduce verbosity of the example\n    len: usize,\n    ttf: &\'a dyn Fn(TypeTagFor) -> u32,\n}\nimpl<\'a> Serializer for TLVSerializer<\'a> {\n    type Ok = ();\n    type SerializeSeq = TLVSeqSerializer<\'a>;\n\n    // Glossing over error handling here.\n    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {\n        self.target\n            .write_all(&(self.ttf)(TypeTagFor::Seq).to_be_bytes())\n            .unwrap();\n        // Normally, there\'d be no way to find the length here.\n        // But since TLVSerializer has been told, there\'s no problem\n        self.target\n            .write_all(&u32::try_from(self.len).unwrap().to_be_bytes())\n            .unwrap();\n        Ok(TLVSeqSerializer {\n            target: self.target,\n            ttf: self.ttf,\n        })\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

您可能遇到的唯一障碍是唯一TLVLenVisitor给您一个长度。但是您有许多递归嵌套的 TLV 结构。当您想要写出其中一个嵌套结构(例如 Vec)时,您只需TLVLenVisitor为每个元素再次运行即可。

\n
struct TLVSeqSerializer<\'a> {\n    target: &\'a mut dyn Write,\n    ttf: &\'a dyn Fn(TypeTagFor) -> u32,\n}\nimpl<\'a> serde::ser::SerializeSeq for TLVSeqSerializer<\'a> {\n    type Ok = ();\n\n    fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Self::Error> {\n        value.serialize(TLVSerializer {\n            // Getting the length of a subfield here\n            len: value.serialize(TLVLenVisitor)?,\n            target: self.target,\n            ttf: self.ttf,\n        })\n    }\n\n    fn end(self) -> Result<Self::Ok, Self::Error> {\n        Ok(())\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Playground
\n这也意味着您可能必须对正在序列化的结构进行多次传递。如果速度不是最重要的并且你的内存有限,这可能没问题,但总的来说,我认为这不是一个好主意。您可能会尝试在一次传递中获得整个结构中的所有长度,这是可以完成的,但它要么很脆弱(因为您必须依赖于访问顺序),要么很困难(因为你必须构建一个包含所有长度的阴影结构)。

\n

另外,请注意,此方法期望同一结构的两个序列化器调用遍历相同的结构。但是 的实现者Serialize完全有能力动态生成随机数据或通过内部可变性改变自身。这将使该序列化器生成无效数据。您可以忽略该问题,因为它是牵强的,或者对调用添加检查end并确保写入的长度与实际写入的数据匹配。

\n
\n

真的,我认为最好不要担心在序列化之前找到长度并首先将序列化结果写入内存。为此,您可以首先将所有长度字段作为虚拟值写入Vec<u8>

\n
struct TLVSerializer<\'a> {\n    target: &\'a mut Vec<u8>,\n    ttf: &\'a dyn Fn(TypeTagFor) -> u32,\n}\nimpl<\'a> Serializer for TLVSerializer<\'a> {\n    type Ok = ();\n    type SerializeSeq = TLVSeqSerializer<\'a>;\n    \n    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {\n        let idx = self.target.len();\n        self.target\n            .extend((self.ttf)(TypeTagFor::Seq).to_be_bytes());\n        // Writing dummy length here\n        self.target.extend(u32::MAX.to_be_bytes());\n        Ok(TLVSeqSerializer {\n            target: self.target,\n            idx,\n            ttf: self.ttf,\n        })\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后,在序列化内容并知道其长度后,您可以覆盖虚拟对象:

\n
struct TLVSeqSerializer<\'a> {\n    target: &\'a mut Vec<u8>,\n    idx: usize, // This is how it knows where it needs to write the length\n    ttf: &\'a dyn Fn(TypeTagFor) -> u32,\n}\nimpl<\'a> serde::ser::SerializeSeq for TLVSeqSerializer<\'a> {\n    type Ok = ();\n\n    fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Self::Error> {\n        value.serialize(TLVSerializer {\n            target: self.target,\n            ttf: self.ttf,\n        })\n    }\n\n    fn end(self) -> Result<Self::Ok, Self::Error> {\n        end(self.target, self.idx)\n    }\n}\n\nfn end(target: &mut Vec<u8>, idx: usize) -> Result<(), std::fmt::Error> {\n    let len = u32::try_from(target.len() - idx - HEADER_LEN)\n        .unwrap()\n        .to_be_bytes();\n    target[idx + 4..][..4].copy_from_slice(&len);\n    Ok(())\n}\n
Run Code Online (Sandbox Code Playgroud)\n

游乐场。就这样,使用 serde 进行单遍 TLV 序列化。

\n