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

推荐订阅源

Security Latest
Security Latest
Recent Commits to openclaw:main
Recent Commits to openclaw:main
O
OpenAI News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 最新话题
N
News | PayPal Newsroom
S
Secure Thoughts
The Last Watchdog
The Last Watchdog
Help Net Security
Help Net Security
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
T
The Exploit Database - CXSecurity.com
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园_首页
博客园 - 司徒正美
The Cloudflare Blog
D
DataBreaches.Net
Jina AI
Jina AI
L
LINUX DO - 热门话题
宝玉的分享
宝玉的分享
Project Zero
Project Zero
量子位
Spread Privacy
Spread Privacy
Cisco Talos Blog
Cisco Talos Blog
J
Java Code Geeks
T
Troy Hunt's Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
Hugging Face - Blog
Hugging Face - Blog
T
Threat Research - Cisco Blogs
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
AI
AI
C
CXSECURITY Database RSS Feed - CXSecurity.com
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Apple Machine Learning Research
Apple Machine Learning Research
Attack and Defense Labs
Attack and Defense Labs
V
V2EX
人人都是产品经理
人人都是产品经理
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
博客园 - 三生石上(FineUI控件)
Cyberwarzone
Cyberwarzone

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的长度