protobuf流的反解析Message

0x01 protobuf的基本概念


protobuf通过定义".proto"文件来描述数据的结构。.proto文件中用"Message"所表示所需要序列化的数据的格式。Message由field组成,Field类似Java或者C++中成员变量,通常一个field的定义包含修饰符、类型、名称和ID。下面看一个简单的.proto文件的例子:


[plain]  view plain  copy
  1. package testInfo;  
  2. message Chats   
  3. {  
  4.     optional string content1 = 1;  
  5.     required int32 content2 = 2;  
  6.       
  7.     message EmbMsg  
  8.     {  
  9.         optional string a = 1;  
  10.     }  
  11.     repeated EmbMsg a = 3;  
  12. }  
然后利用protoc工具生成.h和.cpp文件,并且编写代码

[cpp]  view plain  copy
  1. testInfo::Chats chatTest;  
  2. chatTest.set_content1("hello");  
  3. chatTest.set_content2(32);  
  4. testInfo::Chats::EmbMsg *pMsg = chatTest.add_a();  
  5. pMsg->set_a("aaaa");  
  6. pMsg = chatTest.add_a();  
  7. pMsg->set_a("bbb");  
  8. std::string outputStr;  
  9. chatTest.SerializeToString(&outputStr);  
  10. printf("str %s", outputStr.c_str());  
得到十六进制流数据为:

[plain]  view plain  copy
  1. 0x00DE94C8  0a 05 68 65 6c 6c 6f 10 20 1a 06 0a 04 61 61 61  ..hello. ....aaa  
  2. 0x00DE94D8  61 1a 05 0a 03 62 62 62  


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 可能的类型如下表所示:

typeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimistring, bytes, embedded messages, packed repeated fields
3Start groupGroups (deprecated)
4End groupGroups (deprecated)
532-bit fixed32, sfixed32, float
   

以上面生成的十六进制流我们可以开始分析

required和optional不会有任何字节来表示这个修饰符。

repeated会存在相同的field_number。

[plain]  view plain  copy
  1. 0a 05  
0A -> field_num=1, type=2;

05 -> 代表字符串长度05


[plain]  view plain  copy
  1. 68 65 6c 6c 6f  
-> "hello"


[plain]  view plain  copy
  1. 10 20  
10->field_num=2, type=0;

20->value=0x20;

[plain]  view plain  copy
  1. 1a 06 0a 04 61 61 61 61 1a 05 0a 03 62 62 62  
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

[python]  view plain  copy
  1. import subprocess  
  2. def decode(data):  
  3.     process = subprocess.Popen([r'D:\protobuf\protoc.exe''--decode_raw'],  
  4.     stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)  
  5.   
  6.     output = error = None  
  7.     try:  
  8.         output, error = process.communicate(data)  
  9.     except OSError:  
  10.         pass  
  11.     finally:  
  12.         if process.poll() != 0:  
  13.             process.wait()  
  14.     return output  
  15.   
  16. f = open(r"D:\testprotobuf.bin""rb")  
  17. data = f.read()  
  18. print 'data:/n',decode(data)  
  19. f.close()  

其中testprotobuf.bin是我们的protobuf流文件。

最后运行脚本

[plain]  view plain  copy
  1. C:\Users\Administrator>cd C:\jython2.7.0  
  2.   
  3. C:\jython2.7.0>java -jar jython.jar D:\task\qq-ups\protobuf.py  
  4. data:/n 1: "hello"  
  5. 2: 32  
  6. 3 {  
  7.   1: "aaaa"  
  8. }  
  9. 3 {  
  10.   1: "bbb"  
  11. }  

利用反序列化的结构来推测.proto的message的结构及每个字段的含义,就能达到protobuf流反解析的目的了。

上述方案是采用python调用protoc的命令,由于protobuf是开源的,是在牛逼可以看他的源码,看他怎么解析出来的,我看了下他的源码,由于太菜和项目时间比较紧,每太看明白,暂时采用上述方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值