ITPub博客

首页 > 应用开发 > Go语言 > JT808转义处理的一个致命Bug,让我排查了三天

JT808转义处理的一个致命Bug,让我排查了三天

原创 Go语言 作者:suoten 时间:2026-09-21 21:31:32 0 删除 编辑

JT808协议里有一条规则,写在标准文档最不起眼的位置:

消息体中如果出现0x7e,需要转义为0x7d 0x02;出现0x7d,转义为0x7d 0x01。

看起来很简单。我第一次实现的时候,也是这么想的。

Bug是怎么出现的

去年做一个运输车辆平台,JT808解析跑了一段时间都正常。直到接了某厂商的一批终端,问题来了:大约5%的位置上报消息解析失败,校验码不匹配。

抓包看原始数据,帧头和帧尾的0x7e都在,校验码也对得上。但解析出来的消息体内容是乱的。

我把转义还原的代码翻来覆去看了几遍,逻辑没问题:遍历字节,遇到0x7d就看下一个字节,如果是0x01还原成0x7d,0x02还原成0x7e。

代码长这样:

func Unescape(data []byte) []byte { var out []byte for i := 0; i < len(data); i++ { if data[i] == 0x7d && i+1 < len(data) { if data[i+1] == 0x01 { out = append(out, 0x7d) i++ continue } if data[i+1] == 0x02 { out = append(out, 0x7e) i++ continue } } out = append(out, data[i]) } return out } 逻辑上,这没问题。但为什么解析失败?

三天排查过程

第一天,我怀疑是终端发送数据有误。联系厂商,厂商说设备没问题,是平台解析错误。扯皮了半天,没结果。

第二天,我把失败的那几条原始数据单独拿出来,手动做转义还原,发现还原出来的数据长度不对。原始消息体属性里的长度是42字节,但还原后只有40字节。

少了2个字节。

哪里少的?我逐字节对比,发现有两个相邻的0x7d 0x01,被还原成了一个0x7d,但后一个0x01被吃掉了。

等等,不对。0x7d 0x01应该还原成0x7d,后面的0x01是独立的字节,不应该被吃掉。

我重新看代码,发现问题了:

if data[i+1] == 0x01 { out = append(out, 0x7d) i++ // 这里跳过了下一个字节 continue } 当遇到0x7d 0x01时,还原成0x7d,然后i++跳过下一个字节。但如果下一个字节本身也是数据的一部分,就被跳过了。

举个例子:原始数据里有 0x7d 0x01 0x7d 0x01。按照标准,第一个0x7d 0x01还原成0x7d,第二个0x7d 0x01也还原成0x7d。但我的代码在处理第一个0x7d 0x01后,i++跳到了第二个0x7d 0x01的位置,继续循环时i又加1,直接跳过了第二个0x7d。

结果就是:还原后的数据少了后面的0x7d。

正确的实现

修复方式很简单:在跳过下一个字节之前,先记录当前字节,然后i只加1(因为for循环本身会加1)。

func Unescape(data []byte) []byte { var out []byte for i := 0; i < len(data); i++ { if data[i] == 0x7d && i+1 < len(data) { if data[i+1] == 0x01 { out = append(out, 0x7d) i++ continue } if data[i+1] == 0x02 { out = append(out, 0x7e) i++ continue } } out = append(out, data[i]) } return out } 看起来和之前一样?其实关键在for循环的条件。Go的for循环每次迭代后自动i++。当我们在循环体内执行i++时,实际上跳过了两个字节。

正确的做法是:如果当前字节是0x7d,并且下一个字节是0x01或0x02,那么消费两个字节,还原成一个字节。但i应该只加1,而不是加2。

我之前的代码在i++之后,for循环又执行一次i++,导致多跳了一个字节。

修复后的代码应该是: func Unescape(data []byte) []byte { var out []byte i := 0 for i < len(data) { if data[i] == 0x7d && i+1 < len(data) { if data[i+1] == 0x01 { out = append(out, 0x7d) i += 2 continue } if data[i+1] == 0x02 { out = append(out, 0x7e) i += 2 continue } } out = append(out, data[i]) i++ } return out } 用i += 2明确表示消费了两个字节,就不会出现多跳的问题。

这个Bug给我的启示

转义逻辑看起来简单,但在边界条件下容易出错。特别是当转义字符连续出现、或者转义字符出现在数据末尾时,必须仔细处理。

JTE在pkg/jt808的转义实现中,专门写了单元测试覆盖这些边界场景。如果你也在写JT808解析,建议至少测试以下几种情况:

连续多个0x7d 0x01

0x7d 0x01紧跟着0x7d 0x02

数据以0x7d结尾

数据以0x7d 0x01结尾

一个小小的转义Bug,让我排查了三天。你猜我第三天晚上在想什么?不是愤怒,是庆幸——庆幸问题出在开发环境,不是生产环境。如果是生产环境,5%的位置数据丢失,客户早就炸了。

项目仓库:

Gitee:suoten/jt-engine GitHub:suoten/JTEngine

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050062/viewspace-3183395/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-05-07

  • 博文量
    69
  • 访问量
    55000