惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
Spread Privacy
Spread Privacy
I
InfoQ
V
V2EX
S
Schneier on Security
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
L
Lohrmann on Cybersecurity
Recent Announcements
Recent Announcements
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Attack and Defense Labs
Attack and Defense Labs
云风的 BLOG
云风的 BLOG
The Hacker News
The Hacker News
S
SegmentFault 最新的问题
C
Cybersecurity and Infrastructure Security Agency CISA
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
GbyAI
GbyAI
Latest news
Latest news
S
Secure Thoughts
Project Zero
Project Zero
MongoDB | Blog
MongoDB | Blog
I
Intezer
Security Latest
Security Latest
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
N
Netflix TechBlog - Medium
V2EX - 技术
V2EX - 技术
量子位
T
Threatpost
T
The Blog of Author Tim Ferriss
Y
Y Combinator Blog
T
Tor Project blog
A
Arctic Wolf
Microsoft Security Blog
Microsoft Security Blog
T
The Exploit Database - CXSecurity.com
大猫的无限游戏
大猫的无限游戏
T
Tailwind CSS Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Check Point Blog
博客园 - Franky
Google DeepMind News
Google DeepMind News
The Register - Security
The Register - Security
The GitHub Blog
The GitHub Blog
L
LINUX DO - 热门话题

ccagml

使用valgrind观察luajit进程内存 - ccagml 跟着vscode插件学设计模式-工厂模式 - ccagml 拨开迷雾,探寻深夜游戏集群启动失败真相 - ccagml 从技能状态图标显示错误到给 LuaJIT 报告bug - ccagml 游戏系统MySQl执行超时问题排查 - ccagml lua中pairs和ipairs都做了什么操作 - ccagml lua中#号是怎么计算字符串长度的 - ccagml lua中#号是怎么计算长度的 - ccagml lua中tonumber做了什么 - ccagml
从生产环境报错学习protobuf编码规则 - ccagml
2023-01-29 · via ccagml

背景

个别玩家反馈,某个活动打开时,消费列表显示错误,经前端日志排查发现是因为该玩家的消费信息解析失败了

syntax = "proto3";

message Pay {
    '''
    string role_id = 2;
    '''
    int32 tc=22;
    '''
}

message MsgPayInfo {
    repeated Pay pay_list = 1;
    int32 flag=2;
}

经过就线上数据的排除测试,发现错误数据

  • 当tc的值是 127的浮点数时会报错,protobuf解析失败,例如:
lua脚本层发送数据 
    {pay_list = {{tc=127.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,127]

开始猜想

  • tc是 int32 类型,赋值时使用浮点数会出错
  • 尝试发送几个数据 可以发送浮点数,并且不会报错
lua脚本层发送数据 
    {pay_list = {{tc=128.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,128,1]

lua脚本层发送数据 
    {pay_list = {{tc=126.05}}}
protobuf编译后 
    uint8_t* = [10,3,176,1,126]

lua脚本层发送数据 
    {pay_list = {{tc=127}}}
protobuf编译后 
    uint8_t* = [10,3,176,1,127]

观察几个数据我们得出一些结论:

  1. 当发送tc<=127时,第二个数字是3
    • [10,3,176,1,126]
    • [10,3,176,1,127]
  2. 当发送tc>=128时,第二个数字是4
    • [10,4,176,1,128,1]
  3. 与之对应的是发送tc=128时,uint8数组长度多了1个数据

  4. 猜想第二个数字的3、4可能代表着后续数据长度

后续观察出错的数据

lua脚本层发送数据 
    {pay_list = {{tc=127.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,127]
  • 这个数据的第二个数字是4,而他的后续数据长度只有3
  • !!!可能是因为这样所以数据长度对不上,导致protobuf解析消息失败了!!!

查看官网的protobuf协议文档验证我们的猜想

官网文档的白话理解以报错数据为例子

lua脚本层发送数据 
    {pay_list = {{tc=127.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,127]
  • 第一个数据 10, 我们可以根据公式 (field_number << 3) | wire_type 将解析出字段编号,和后续读取数据的类型
// 10的二进制对应00001010
10 = 0b00001010
可以解析出
field_number = 0b00001 = 1
wire_type = 0b010 = 2

wire_type是什么意思呢?继续看文档

ID Name Used For
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 I64 fixed64, sfixed64, double
2 LEN string, bytes, embedded messages, packed repeated fields
3 SGROUP group start (deprecated)
4 EGROUP group end (deprecated)
5 I32 fixed32, sfixed32, float
  • 我们回到我们上面错误的例子
message MsgPayInfo {
    repeated Pay pay_list = 1;
    int32 flag=2;
}

// 10的二进制对应00001010
10 = 0b00001010
可以解析出
field_number = 0b00001 = 1 代表proto中编号为1的字段,既 pay_list
wire_type = 0b010 = 2

  • wire_type是2代表的,接下来是一个长度值

!!!我们的猜想是对的,第二位代表着数据长度值,而我们收到的会报错的数据,长度值不对!!!

我们继续看为什么当tc=128.05的时候可以发送成功

lua脚本层发送数据 
    {pay_list = {{tc=128.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,128,1]
  • 可以看出,第二位是4,而后续确实有4个数据,所以能解析成功

那么[176,1,128,1]又是如何解析出

message Pay {
    '''
    string role_id = 2;
    '''
    int32 tc=22;
    '''
}

176 = 0b10110000
field_number = 0b10110 = 22   代表proto中编号为22的字段,既 tc  !!!这是错的,凑巧对上了,后续会说明!!!
wire_type = 0b000 = 0

  • wire_type是0代表的,接下来是一个VARINT

开始有了新疑问?只有5位代表编号,也就31,线上使用的字段不止31

message Pay {
    '''
    string role_id = 2;
    '''
    int32 tc=22;
    '''
    int32 tc1=1;
    int32 tc15=15;
    int32 tc16=16;
    int32 tc30=30;
    int32 tc31=31;
    int32 tc32=32;
}

尝试发送几个数据

lua脚本层发送数据 
    {pay_list = {{tc1=1}}}
protobuf编译后 
    uint8_t* = [10,2,8,1]

lua脚本层发送数据 
    {pay_list = {{tc15=15}}}
protobuf编译后 
    uint8_t* = [10,2,120,15]

lua脚本层发送数据 
    {pay_list = {{tc16=16}}}
protobuf编译后 
    uint8_t* = [10,3,128,1,16]

lua脚本层发送数据 
    {pay_list = {{tc30=30}}}
protobuf编译后 
    uint8_t* = [10,3,240,1,30]

lua脚本层发送数据 
    {pay_list = {{tc31=31}}}
protobuf编译后 
    uint8_t* = [10,3,248,1,31]

lua脚本层发送数据 
    {pay_list = {{tc32=32}}}
protobuf编译后 
    uint8_t* = [10,3,128,2,32]


!!!发现几个结论!!!

  1. 当字段编号小于<=15时候 数据[120,15]长度是2
  2. 当字段编号在 16-31的时候,数据[248,1,31]长度是3
  3. 当字段编号在 大于31的时候, 数据[128,2,32]第二位会发生变化

我们文档查看发生了什么

回到我们的例子[176,1,128,1]

lua脚本层发送数据 
    {pay_list = {{tc=128.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,128,1]

[176,1]用来解析字段长度

  • 数字:176
    • field_number = 0b10110
      • 根据varints白话第一点,还有后续数据
      • 保留其余部分 0b0110
  • 数字:1
    • 因为176中数据未取完,继续读取
    • 1 = 0b00000001
  • 根据 varints 白话理解 第三点 以小端序合并数据
    • 00000001 0110 = 000000010110 = 22
    • 得到字段编号为22

[128,1] 是我们的数据 128

  • 数字:128
    • 0b128 = 10000000
    • 根据varints白话第一点,还有后续数据
    • 保留其余部分 0b0000000
  • 数字:1
    • 因为128中数据未取完,继续读取
    • 1 = 0b00000001
  • 根据 varints 白话理解 第三点 以小端序合并数据
  • 00000001 0000000 = 000000010000000 = 128
  • 得到数据128

解决

  • 将数据 * 100 取整后在发送给前端

额外的收获

  • 当我们字段编号>=16的时候,会导致数据多使用一个 uint8的长度