在外部消息中包括预编码的协议缓冲区消息

Include pre-encoded protocol buffer message within outer message

本文关键字:消息 协议 预编码 缓冲区 包括 外部      更新时间:2023-10-16

有没有一种方法可以在C++中创建一个包含预编码内部消息的协议缓冲区消息,而无需解析然后重新序列化内部消息?

为了澄清,请考虑以下消息定义:

message Inner {
    required int i = 1;
    // ... more fields ...
}
message Outer {
    repeated Inner inners = 1;
    // ... more fields ...
}

假设您有一个10字节数组的集合,每个数组都包含一个内部的编码版本。您想要创建一个包含10 Inners的Outer。您不想手动编码,因为Outer有其他字段,并且可能本身包含在其他消息中。有没有一种方法可以让协议缓冲区直接复制预编码的内部?

没有干净的方法,但有一些技巧性的方法。一种是定义这样的第二条消息:

message RawOuter {
    repeated bytes inners = 1;
    // ... same fields as Outer ...
}

除了inners重复字段已从类型Inner变为类型bytes之外,RawOuterOuter相同。如果使用Inner的编码实例填充inners,然后序列化RawOuter,则会得到与使用解析的verison构建Outer完全相同的结果。也就是说,嵌套消息的有线格式与包含该嵌套消息的序列化的bytes字段的有线格式相同。这是protobuf编码的一个有趣的可利用的怪癖。

不过,这次黑客攻击有一些问题。特别是,如果您试图构建一个嵌入在其他proto中的Outer实例,那么它不会很好地工作,因为您可能不想维护每个包含消息的两个副本,一个使用Outer,另一个使用RawOuter

另一个更为棘手的选项是将编码的消息注入到Outer实例的UnknownFieldSet中。

Outer outer;
for (auto& inner: inners) {
  outer.mutable_unknown_fields()
      ->AddLengthDelimited(1, inner);
}

UnknownFieldSet用于存储解析时看到的与.proto文件中定义的任何已知字段号不匹配的字段。其想法是,这允许您编写一个代理服务器,只需接收消息并将其转发到另一个服务器,而无需每次向协议添加新字段时重新编译代理。在这里,我们通过在其中粘贴一个值来滥用它,该值实际上对应于已知的字段,但实现不会注意到,因此它会很好地写出这些字段。

这种方法的主要问题是,如果其他人同时检查您的Outer实例,他们会觉得inners列表是空的,因为这些值实际上隐藏在其他地方。这是一个非常丑陋的黑客攻击,以后可能会再次困扰你。只有当您测量了性能差异并发现差异很大时,我才会推荐它。

还要注意,序列化代码总是最后写入未知字段,而已知字段是按字段号顺序写入的。解析器应该接受任何顺序,但偶尔你会发现有人将未解析的数据用作哈希映射键或其他东西,如果字段被重新排序,这种情况就会完全崩溃。

顺便说一句,您可以通过将字符串交换到适当位置而不是复制来提高这两种方法的性能,即

raw_outer->add_inners()->swap(inner);

outer->mutable_unknown_fields()->AddLengthDelimited(1)->swap(inner);