0x01 protobuf的基本概念
protobuf通过定义".proto"文件来描述数据的结构。.proto文件中用"Message"所表示所需要序列化的数据的格式。Message由field组成,Field类似Java或者C++中成员变量,通常一个field的定义包含修饰符、类型、名称和ID。下面看一个简单的.proto文件的例子:
然后利用protoc工具生成.h和.cpp文件,并且编写代码
得到十六进制流数据为:
0x02 protobuf流的反解析
2.1 Varint编码
Protobuf的二进制使用Varint编码。Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。
Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。
例如:十六进制流里面其中两个字节:0x95 0x01,则其转换运算为:(0x95 & 0x7F) | (0x01 << 0x7) = 0x5 | 0x80 = 0x95。
若其中四个字节:0x9D 0xF4 0xC1 0xCB 0x05,则其转换运算为:
(0x9D & 0x7F) | (0xF4 & 0x7F)<<7 | (0xC1 & 0x7F)<<E | (0xCB & 0x7F)<<0x15 | 05<<0x1C
= 1D | 3A00 | 104000 | 9600000 | 50000000
= 59707A1D
2.2 数值类型
Protobuf经序列化后以二进制数据流形式存储,这个数据流是一系列key-Value对。Key用来标识具体的Field,在解包的时候,Protobuf根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 Field。
Key 的定义如下:
(field_number << 3) | wire_type
Key由两部分组成。第一部分是 field_number,比如消息chatTest.content1中 的 field_number 为 1。第二部分为 wire_type。表示 Value 的传输类型。Wire Type 可能的类型如下表所示:
type | Meaning | Used For |
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimi | string, bytes, embedded messages, packed repeated fields |
3 | Start group | Groups (deprecated) |
4 | End group | Groups (deprecated) |
5 | 32-bit | fixed32, sfixed32, float |
以上面生成的十六进制流我们可以开始分析
required和optional不会有任何字节来表示这个修饰符。
repeated会存在相同的field_number。
0A -> field_num=1, type=2;
05 -> 代表字符串长度05
-> "hello"
20->value=0x20;
1a->field_num=3, type=2;
06->结构体长度06
0a->field_num=1,type=2;
04->字符串长度04
61 61 61 61 ->value="aaaa"
1a->field_num=3, type=2;
05->结构体长度05
0a->field_num=1,type=2;
03->字符串长度03
61 61 61 61 ->value="bbb"
2.3 protoc 进行反序列化
上面的步骤是手动解析的过程,而利用google提供的工具可以帮助我们自动化的解析以上过程,在面对复杂的protobuf结构的时候能达到事半功倍的效果。按下面步骤来做:
首先配置java环境
其次安装jython,这里
然后编写Python脚本,protobuf.py
其中testprotobuf.bin是我们的protobuf流文件。
最后运行脚本
利用反序列化的结构来推测.proto的message的结构及每个字段的含义,就能达到protobuf流反解析的目的了。
上述方案是采用python调用protoc的命令,由于protobuf是开源的,是在牛逼可以看他的源码,看他怎么解析出来的,我看了下他的源码,由于太菜和项目时间比较紧,每太看明白,暂时采用上述方法。