我发现了一个有趣的协议缓冲区陷阱。如果您有两条类似的消息,则可以使用C ++ API或命令行将一条消息解析为另一条消息。
limited documentation for ParseFromString并没有提到它不需要消耗所有字符串,如果没有使用它也不会失败。
我曾期望ParseFromString如果解析出类型为B的消息时无法解析类型为A的消息。毕竟,该消息包含额外的数据。然而,这种情况并非如此。 一个示例脚本演示了该问题:
#!/bin/sh
cat - >./foobar.proto <<EOF
syntax = "proto3";
package demo;
message A
{
uint64 foo = 1;
};
enum flagx {
y = 0;
z = 1;
}
message B {
uint64 foolish = 1;
flagx bar = 2;
};
EOF
cat - >./mess.B.in.txtfmt <<EOF
foolish: 10
bar: y
EOF
cat - >./mess.in.txtfmt <<EOF
foo: 10
EOF
protoc --encode=demo.A foobar.proto <./mess.A.in.txtfmt >./mess.A.proto
protoc --encode=demo.B foobar.proto <./mess.B.in.txtfmt >./mess.B.proto
protoc --decode=demo.A foobar.proto >./mess.out.txtfmt <./mess.B.proto
echo "in: "
cat mess.B.in.txtfmt
echo "out: "
cat mess.out.txtfmt
echo "xxd mess.A.proto:"
xxd mess.A.proto
echo "xxd mess.B.proto:"
xxd mess.B.proto
输出为:
in:
foolish: 10
bar: 20
out:
foo: 10
xxd mess.A.proto:
00000000: 080a
xxd mess.B.proto:
00000000: 080a
因此,二进制消息对于消息A和消息B都是相同的。
如果您更改协议,以便使用另一个varint(uint64)代替枚举,则会得到不同的二进制消息,但是 ParseFromString仍将成功地将较长的消息解析为较短的消息。
真正令人困惑的是,它似乎也能够将较短的消息解析为较长的消息。
这是错误还是功能?
答案 0 :(得分:1)
我认为这是设计使然,但文档可能会更好。
如果您尝试使用API而不先阅读有线格式,可能会引起混淆。接线格式与您所期望的与API无关。
接线格式强调紧凑而不是正确。如果您想检查邮件的正确性,可以使用其他方法。
您可能(可能应该或必须)在消息中包含以下一项或多项:
关于能够将较短的消息解析为较长的消息的第二点是,因为在协议缓冲区3中所有字段都是可选的。 协议缓冲区2具有必填字段的概念。删除它引起了一些争议(例如,参见Why required and optional is removed in Protocol Buffers 3和https://capnproto.org/faq.html#how-do-i-make-a-field-required-like-in-protocol-buffers)。 消息中不包含具有默认值(通常为0)的字段。字段名称也用数字代替。因此,两种“不同”协议的消息都可能很容易被两者解释。