4、DoIP报文结构:通用报文头格式、协议版本与类型字段、负载类型与长度、DoIP报文封装与解析
DoIP 报文长什么样?说白了,就是一堆字节按特定顺序排好。我刚开始接触 DoIP 时,看着抓包工具里的十六进制数据,说实话有点懵。后来摸清了规律,发现它其实挺有章法的。
这一节,咱们就把 DoIP 报文拆开来看。从通用头格式,到每个字段的含义,再到怎么组装和解析。你跟着我走一遍,以后看报文就跟看自己家地址一样熟。
4.1 通用报文头格式
每个 DoIP 报文,开头都固定有 8 个字节的通用头。这 8 个字节就像快递单上的收件人信息,没有它,数据就送不到地方。
| 字节偏移 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | 协议版本 | 1 | 当前 DoIP 协议版本号 |
| 1 | 反向协议版本 | 1 | 协议版本取反,用于校验 |
| 2-3 | 负载类型 | 2 | 标识报文的具体用途 |
| 4-7 | 负载长度 | 4 | 后续负载数据的字节数 |
这 8 个字节,顺序不能乱。我见过一些新手自己拼报文时把字节顺序搞反了,结果 ECU 根本不认。嗯,这里要注意:DoIP 用的是网络字节序,也就是大端模式。
4.2 协议版本与类型字段
协议版本字段,目前 ISO 13400 定义的版本是 0x02。为什么不是 0x01?我记得早期草案确实是 0x01,后来正式发布时改成了 0x02。如果你在抓包时看到 0x01,那基本是原型设备,不是量产件。
反向协议版本,就是把协议版本按位取反。比如 0x02 取反就是 0xFD。这个字段干嘛用?说白了就是做个简单校验,防止报文在传输过程中被干扰。我在项目中遇到过一台网关,它收到报文后会先检查这个字段,对不上就直接丢弃,连日志都不打。排查了好久才发现是发送端把反向版本算错了。
协议版本对照表
| 版本值 | 反向值 | 含义 |
|---|---|---|
| 0x00 | 0xFF | ISO 13400 草案版本(已废弃) |
| 0x01 | 0xFE | 早期草案版本 |
| 0x02 | 0xFD | 当前标准版本 |
| 0x03-0xFF | 对应取反 | 保留给未来扩展 |
4.3 负载类型与长度
负载类型占 2 个字节,它告诉接收方:这个报文是干嘛的。是路由激活请求?还是诊断消息?还是车辆信息请求?全靠这个字段区分。
常见的负载类型值,我整理了一张表,你收藏一下,以后查起来方便:
| 负载类型值 | 报文名称 | 方向 |
|---|---|---|
| 0x0001 | 通用 DoIP 头否定确认 | 双向 |
| 0x0002 | 车辆识别请求 | 客户端 → 车辆 |
| 0x0004 | 车辆识别响应 | 车辆 → 客户端 |
| 0x0005 | 路由激活请求 | 客户端 → 车辆 |
| 0x0006 | 路由激活响应 | 车辆 → 客户端 |
| 0x8001 | 诊断消息 | 双向 |
| 0x8002 | 诊断消息确认 | 车辆 → 客户端 |
负载长度占 4 个字节,表示通用头之后还有多少字节。注意,这个长度只算负载本身,不包括通用头的 8 个字节。你想想看,如果负载长度填了 0,那就说明这个报文只有头,没有数据体。
个人习惯:我在写解析代码时,会先读前 8 个字节,然后根据负载长度字段的值,再读相应数量的字节。这样不会多读也不会少读,内存分配也准确。
4.4 DoIP 报文封装与解析
封装,就是把数据按照 DoIP 格式组装起来。解析,就是反过来,从字节流里提取出各个字段。这两个过程互为逆操作。
4.4.1 封装过程
假设我们要发送一条诊断请求,负载类型是 0x8001,诊断数据是 10 03(会话控制请求)。封装步骤如下:
- 确定协议版本:0x02
- 计算反向版本:0xFD
- 设置负载类型:0x80 0x01
- 计算负载长度:诊断数据 2 字节 + 逻辑地址 4 字节 = 6 字节
- 填充负载数据:源地址 + 目标地址 + 诊断数据
最终拼出来的报文是这样的:
02 FD 80 01 00 00 00 06 0E 80 0E 00 10 03
|--通用头--| |--负载长度--| |--负载数据--|
我来解释一下:
0E 80 是源逻辑地址(测试设备),
0E 00 是目标逻辑地址(ECU),
10 03 就是诊断请求本身。
4.4.2 解析过程
解析时,我习惯按这个流程走:
- 读取前 8 字节,提取协议版本和反向版本
- 校验反向版本是否正确(协议版本取反后是否匹配)
- 提取负载类型,判断报文用途
- 提取负载长度,准备接收后续数据
- 根据负载类型,按对应格式解析负载数据
我曾经踩过的坑:有一次解析诊断消息时,我忘了检查负载长度是否超过实际接收到的数据长度。结果 ECU 返回了一个很长的响应,我的缓冲区不够大,直接内存越界了。从那以后,我每次解析都会先做长度校验,宁可多写几行代码,也不能让程序崩溃。
4.5 DoIP 报文结构总览
下面这张图,把 DoIP 报文的整体结构画出来了。你一看就明白:通用头是固定的,负载部分根据类型不同而变化。
4.6 实战中的注意事项
- 字节序问题:DoIP 统一使用大端字节序。如果你在 x86 平台上开发,记得用 htons/htonl 转换。
- 负载长度上限:理论上负载长度最大是 0xFFFFFFFF,但实际中受限于 TCP 缓冲区大小,一般不会超过 64KB。
- 版本校验:反向协议版本是简单的校验手段,但不要完全依赖它。我建议在应用层再加一层 CRC 校验。
- 超时处理:发送完报文后,如果在一定时间内没收到响应,要主动重发或报错。这个超时时间,我一般设 2 秒。
一个小技巧:调试时可以用 Wireshark 抓包,然后对照 DoIP 规范逐字节比对。我当年就是这么学会的,虽然慢,但记得牢。