3、DoIP报文结构:通用DoIP报文头部与Payload结构
好,咱们今天来聊聊DoIP报文的结构。说实话,这个知识点是整个DoIP协议的基础。你如果搞不懂报文头里每个字节是干嘛的,后面做诊断通信的时候,出了问题你都不知道从哪查起。
我个人习惯,拿到一个协议,先看它的报文头。就像你收到一封信,先看信封上的地址、邮戳一样。DoIP的报文头,就是那个「信封」。
3.1 通用DoIP报文头部
先看一张表,这是DoIP报文头的固定结构。一共8个字节,一个不多,一个不少。
| 字节偏移 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | 协议版本 | 1 | 当前固定为 0x02 |
| 1 | 逆向协议版本 | 1 | 固定为 0xFD(即 ~0x02) |
| 2-3 | 负载类型 | 2 | 标识DoIP消息的具体类型 |
| 4-7 | 负载长度 | 4 | Payload部分的字节数 |
嗯,这里要注意。协议版本字段,目前ISO 13400-2里定义的是0x02。为什么不是0x01?我记得早期草案里确实是0x01,后来正式发布时改成了0x02。你在实际项目中如果抓到0x01的报文,那大概率是某个老旧的测试工具或者非标实现。
逆向协议版本这个字段,说白了就是个校验机制。它的值等于协议版本按位取反。比如0x02取反就是0xFD。接收方收到报文后,会检查这两个字段是否满足取反关系。如果不满足,直接丢弃报文。
3.2 负载类型(Payload Type)
负载类型占2个字节,它告诉接收方:这个DoIP报文到底是干嘛用的。是路由激活?是诊断请求?还是车辆信息查询?
我整理了一些常用的负载类型值,你记一下:
| 负载类型值(十六进制) | 含义 | 方向 |
|---|---|---|
| 0x0001 | 通用DoIP头部否定确认 | 双向 |
| 0x0002 | 车辆识别请求 | Tester → ECU |
| 0x0003 | 车辆识别响应 | ECU → Tester |
| 0x0004 | 路由激活请求 | Tester → ECU |
| 0x0005 | 路由激活响应 | ECU → Tester |
| 0x8001 | 诊断消息(DoIP封装UDS) | 双向 |
你想想看,为什么诊断消息的负载类型是0x8001,而不是0x0001?其实这是ISO故意设计的。高位为1的负载类型,表示这个报文携带的是诊断数据(即UDS消息)。而高位为0的,是DoIP自身的控制消息。这样设计,接收方在解析时只需要检查最高位,就能快速区分是控制报文还是诊断报文。
3.3 负载长度(Payload Length)
负载长度占4个字节,它表示紧跟在报文头后面的Payload部分有多少个字节。注意,这个长度不包含报文头本身的8个字节。
举个例子:
// 一个完整的DoIP报文(十六进制)
02 FD 80 01 00 00 00 05 10 03 00 00 00 00 00 00
// 解析:
// 02 → 协议版本
// FD → 逆向协议版本
// 80 01 → 负载类型(诊断消息)
// 00 00 00 05 → 负载长度 = 5
// 10 03 00 00 00 00 00 00 → Payload(5字节)
这里负载长度是5,表示后面的Payload有5个字节。Payload里装的就是UDS诊断消息——0x10 03(诊断会话控制请求)加上3个字节的源地址和目标地址信息。
3.4 DoIP Payload结构
说完了报文头,咱们来看看Payload。Payload的结构其实取决于负载类型。不同的负载类型,Payload的格式完全不同。
我以最常见的两种类型为例:
3.4.1 诊断消息(负载类型 0x8001)的Payload
诊断消息的Payload,说白了就是把UDS报文「装」进DoIP里。它的结构如下:
| 字节偏移 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0-1 | 源地址 | 2 | 发送方的逻辑地址 |
| 2-3 | 目标地址 | 2 | 接收方的逻辑地址 |
| 4 | UDS报文开始 | N | 实际的UDS诊断数据 |
你看,DoIP在UDS报文外面包了一层「地址信息」。源地址和目标地址各占2个字节,用来标识这条诊断消息是谁发给谁的。这样做的好处是,一条TCP连接上可以同时承载多个ECU之间的诊断通信。
3.4.2 路由激活请求(负载类型 0x0004)的Payload
路由激活请求的Payload就简单多了:
| 字节偏移 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0-1 | 源地址 | 2 | Tester的逻辑地址 |
| 2 | 激活类型 | 1 | 0x00=默认, 0x01=WWH-OBD |
| 3-6 | 保留(OEM特定) | 4 | 通常填0 |
嗯,这里有个坑。激活类型字段,ISO标准里只定义了0x00和0x01。但有些OEM会自己定义其他值,比如0x02表示「安全路由激活」。如果你在做Tester工具,最好把这个字段做成可配置的,免得换一个项目就要改代码。
3.5 小结
好了,咱们总结一下。DoIP报文结构其实不复杂,核心就是8字节的固定头部加上可变长度的Payload。头部里的协议版本和逆向协议版本是一对「校验码」,负载类型告诉你怎么解析Payload,负载长度告诉你Payload有多长。
我个人建议,你在写DoIP解析代码时,先把这8个字节的头部解析出来,做一次完整性检查。检查通过后,再根据负载类型去解析Payload。这样分层处理,代码可读性和可维护性都会好很多。
下一章,咱们会深入聊一聊路由激活的具体流程。到时候我会拿一个实际项目中的抓包数据,带大家一步步分析。敬请期待。