protobufs 格式的原始解码器

Posted

技术标签:

【中文标题】protobufs 格式的原始解码器【英文标题】:raw decoder for protobufs format 【发布时间】:2011-09-08 06:16:37 【问题描述】:

我想找到一种方法,将二进制 protobuf 消息转换为人类可读的包含数据的描述,而不使用 .proto 文件。

背景是我有一条 .proto 消息,它被 android 上的解析器拒绝,但原因尚不完全清楚。我可以手动浏览消息,但它相当乏味。

我试过protoc --decode_raw,但它只是给出错误“无法解析输入。”。我谷歌希望/期待有人会做一个很好的网络实用程序可以做到这一点,但没有发现任何明显的东西。

我只是希望得到一些输出,例如:

field 1: varint: 128
field 4: string: "foo"

欢迎任何指向正确方向的指针!

【问题讨论】:

邮件是否有可能包含一些附加的标头?跳过前 1,2,3,4... 字节后尝试解码。 其中一个问题消息实际上只有 4 个字节:18、4、48、0(十进制)。 (也许它实际上被截断了?) 18 = "field 2, length-prefixed"(大概意思是:子消息),4 = 4(下一个片段的长度,以字节为单位),48 = "field 6, varint" , 0 = 0。因此您至少缺少 2 个字节(不满足广告的子消息大小) 这肯定可以解释为什么我会遇到问题 - 谢谢 Marc! 有一个related question about non "raw" decoding(使用已知的.proto)。 【参考方案1】:

为了后代: Google 的协议缓冲区tools 具有解码原始缓冲区的能力。

只需将未知缓冲区发送给它并传递--decode_raw 标志

$ protoc --decode_raw < has_no_proto.buff
2 
  2: "Error retrieving information from server. [RH-02]"

所以这里有一个字段 2 设置为嵌入消息的消息,该消息又包含 它的第二个字段设置为一个字符串,告诉我我惹恼了 Google Play。

类型信息不明确(看起来它会尝试将所有二进制数据显示为字符串——但满足您对 varint/string/submessage 区分的要求)。

【讨论】:

.buff文件的格式是什么?我有一个仅包含原始协议缓冲区字节的二进制文件,但不断收到“无法解析输入”。 在这种情况下,它是原始协议缓冲区字节输出。你确定你的文件是正确的吗? (留意编辑添加的魔法字节) @DeepSpace101 也许您在一个文件中有多个消息?见***.com/a/34632255/357313【参考方案2】:

如Michel de Ruiter's answer 中所述,您的protobuf 消息可能包含length-prefix。假设确实如此,这个答案应该会有所帮助。

(注意:对于下面的大多数命令,我假设您的 protobuf 消息存储在一个名为 input 的文件中。)

protoc --decode_raw + dd 用于单个消息:

如果它只是一条消息,那么您确实可以利用protoc --decode_raw,但您需要先剥离长度前缀标头。假设标头长度为 4 个字节,您可以使用 dd 将标头从 input 中剥离,然后将输出馈送到 protoc

dd bs=1 skip=4 if=input 2>/dev/null | protoc --decode_raw

protoc-decode-lenprefix --decode_raw 单个消息:

我还写了一个script 自动处理标题剥离:

protoc-decode-lenprefix --decode_raw < input

此脚本只是protoc --decode_raw 之上的一个包装器,但会处理解析长度前缀标头,然后调用protoc

现在,这个脚本在这种情况下并不是很有用,因为我们可以使用上面的 dd 技巧来去除标题。但是,假设我们有一个数据流(例如,一个文件或 TCP 流),其中包含多个带有长度前缀标头的消息......

protoc-decode-lenprefix --decode_raw 用于消息流:

假设input 包含多个由长度前缀标头构成的protobuf 消息,而不是输入文件中的单个protobuf 消息。在这种情况下,不可能使用dd 技巧,因为您需要实际读取长度前缀标头的内容以确定流中后续消息的长度,因此下一个标头+消息在前面多少个字节。因此,您不必担心所有这些,只需再次使用protoc-decode-lenprefix

protoc-decode-lenprefix --decode_raw < input

protoc-decode-lenprefix --decode ... foo.proto 用于消息流

此脚本还可用于完全解码以长度为前缀的消息(而不仅仅是“原始解码”它们)。它假定您可以访问定义 protobuf 消息的 .proto 文件,就像包装的 protoc 命令一样。调用语法与protoc --decode 相同。例如,将dd 技巧与protoc --decode 一起使用,输入为Mesos task.info 文件,语法如下所示:

dd bs=1 skip=4 if=task.info 2>/dev/null | \
protoc --decode mesos.internal.Task \
                      -I MESOS_CODE/src -I MESOS_CODE/include \
                      MESOS_CODE/src/messages/messages.proto

并且使用protoc-decode-lenprefix时参数相同

cat task.info | \
protoc-decode-lenprefix --decode mesos.internal.Task \
                      -I MESOS_CODE/src -I MESOS_CODE/include \
                      MESOS_CODE/src/messages/messages.proto

【讨论】:

【参考方案3】:

如果您碰巧有一个包含(多个?)长度前缀 protobuf 消息的二进制文件,protoc ‒‒decode_raw &lt; file 无法解析它,因为长度前缀。解决此问题的一种简单方法是将文件拆分为连续的消息,然后然后将每个消息转换为protoc

我的看法:

var fs = File.OpenRead(filename));
var buffer = new byte[4096];
int size;
for (int part = 1; Serializer.TryReadLengthPrefix(fs, PrefixStyle.Base128, out size); part++) 
  long startPosition = fs.Position;
  using (var writer = File.OpenWrite(string.Format("0[1].pb", filename, part))) 
    for (int bytesToRead = size; bytesToRead > 0; ) 
      int bytesRead = fs.Read(buffer, 0, Math.Min(bytesToRead, buffer.Length));
      bytesToRead -= bytesRead;
      if (bytesRead <= 0) // End of file.
        break;
      writer.Write(buffer, 0, bytesRead);
    
  

【讨论】:

【参考方案4】:

您可以尝试通过 wireshark 插件强制它,或者您可以借用某些实现的“阅读器”部分(例如,我知道我会如何在 C# 中执行此操作,但我怀疑这就是您的意思)。

但是,要小心 - 协议缓冲区中的字符串并不真正意味着“字符串” - 它可能是:

UTF-8 字符串 任意数据的原始 BLOB 子消息 “打包”数组 (可能是我忘记的其他东西)

【讨论】:

谢谢马克!我没有意识到没有 .proto 就无法区分字符串和子消息等(尽管我想您可以进行基于启发式的猜测) @JosephH 是的,你可以尝试 UTF-8 - 如果它有效,你会非常接近;其他的虽然更棘手......【参考方案5】:

也许试试https://pb-decode.online。我是作者,欢迎提出建议:)

【讨论】:

以上是关于protobufs 格式的原始解码器的主要内容,如果未能解决你的问题,请参考以下文章

如何检查数据/有效载荷是否可以被protobuf解码

解码没有模式的protobuf

Netty系列化之Google Protobuf编解码

Go是如何实现protobuf的编解码的: 原理

Go是如何实现protobuf的编解码的: 源码

十一.Netty入门到超神系列-Netty使用Protobuf编码解码