4、DoIP消息格式:通用消息头结构、Payload类型定义、否定响应码
好,咱们今天聊聊DoIP的消息格式。说实话,这是整个协议里最“死板”但也最核心的部分。你想想看,车上的ECU和诊断仪要对话,总得有个统一的“信封”吧?这个信封怎么封口、里面装什么、对方看不懂怎么回——就是咱们这节课要讲的内容。
4.1 通用消息头结构:这个“信封”长什么样?
DoIP的通用消息头,说白了就是前8个字节。我习惯把这8个字节分成四部分来记:协议版本、版本取反、负载类型、负载长度。嗯,咱们一个一个看。
| 字节偏移 | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | 协议版本 | 1 | 当前固定为0x02(ISO 13400-2:2012) |
| 1 | 版本取反 | 1 | 协议版本的按位取反,用于校验 |
| 2-3 | 负载类型 | 2 | 标识后续Payload的具体含义 |
| 4-7 | 负载长度 | 4 | Payload的字节数(不含消息头) |
这里有个小细节我特别想强调——版本取反这个字段。我在项目中遇到过好几次,有人把版本号写对了,但取反算错了,结果诊断仪直接丢弃报文。你想想看,接收方收到报文后,会把版本字段和取反字段做异或,结果必须等于0xFF。否则,嗯,直接扔掉,不解释。
核心要点:消息头校验失败时,接收方不会回复任何否定响应码,直接静默丢弃。这是DoIP的设计哲学——连错误响应都不给,避免无效通信。
再说说负载长度。这个字段是4个字节,最大能表示4GB的Payload。但实际项目中,我建议你控制在64KB以内。为什么?因为很多MCU的接收缓冲区就这么大,你发个几MB的数据过去,ECU直接内存溢出复位了。我曾经吃过这个亏,后来老老实实加了分片传输。
4.2 Payload类型定义:信封里装什么?
负载类型字段占2个字节,目前定义了大概20多种。我把最常用的几个列出来,你记牢了,面试和干活都用得上。
| 负载类型值(十六进制) | 名称 | 方向 | 用途 |
|---|---|---|---|
| 0x0001 | 通用DoIP头否定确认 | ECU → 测试仪 | 消息头校验失败时回复 |
| 0x0002 | 车辆识别请求 | 测试仪 → ECU | 获取车辆基本信息 |
| 0x0003 | 车辆识别响应 | ECU → 测试仪 | 返回VIN、EID等信息 |
| 0x0004 | 路由激活请求 | 测试仪 → ECU | 建立诊断逻辑连接 |
| 0x0005 | 路由激活响应 | ECU → 测试仪 | 确认激活结果 |
| 0x8001 | 诊断消息 | 双向 | 承载UDS诊断请求/响应 |
我个人习惯把负载类型分成三类:控制类(0x0001-0x00FF)、数据类(0x0100-0x7FFF)、诊断类(0x8000-0xFFFF)。你看0x8001就是诊断消息,这是咱们最常用的。说白了,你平时用诊断仪读故障码、刷写固件,走的都是这个类型。
小技巧:在AUTOSAR的DoIP模块配置中,负载类型对应的处理函数是通过函数指针表实现的。我建议你把这张表做成可配置的,方便后期扩展自定义负载类型。别问我怎么知道的——我被客户要求加过私有协议,当时没预留接口,改得想哭。
4.3 否定响应码:对方说“不”的时候
DoIP的否定响应码,和UDS的NRC不太一样。UDS的NRC是0x10-0x7F,而DoIP的否定响应码是单独的枚举值,放在通用DoIP头否定确认(0x0001)的Payload里。
我整理了一份最全的否定响应码表,你收藏好:
| 代码(十进制) | 代码(十六进制) | 含义 | 触发场景 |
|---|---|---|---|
| 0x00 | 0x00 | 否定确认 | 通用否定,具体原因见子错误码 |
| 0x01 | 0x01 | 不支持的协议版本 | 版本字段不是0x02,或取反校验失败 |
| 0x02 | 0x02 | 不支持的消息类型 | 负载类型值未定义或不被支持 |
| 0x03 | 0x03 | 连接被拒绝 | TCP连接数已达上限 |
| 0x04 | 0x04 | 未知的负载长度 | 负载长度与实际接收数据不匹配 |
| 0x05 | 0x05 | 无效的负载数据 | Payload内容格式错误 |
| 0x06 | 0x06 | 路由激活被拒绝 | 客户端认证失败或激活条件不满足 |
| 0x07 | 0x07 | 未知的源地址 | 逻辑地址未在路由表中注册 |
这里有个坑,我必须要提醒你——0x00否定确认。这个代码本身不告诉你具体错在哪,它后面会跟一个1字节的子错误码。但很多初学者只看主错误码,不看子错误码,排查问题的时候绕了一大圈。我曾经花了一整天,最后发现是子错误码0x13(TLS握手失败),而主错误码只是0x00。
避坑指南:当收到0x00否定确认时,一定要解析后续的子错误码。子错误码的定义在ISO 13400-2的附录中有详细说明。另外,有些ECU实现不规范,0x00后面可能没有子错误码——遇到这种情况,我建议你直接升级ECU固件,别惯着。
再说一个实际案例。有一次我在做DoIP网关集成测试,发现车辆识别请求总是返回0x02(不支持的消息类型)。我检查了负载类型字段,0x0002没错啊。后来抓包一看,原来是消息头里的协议版本写成了0x01(老版本)。接收方先校验版本,发现不对,直接回了0x02。嗯,这里要注意——否定响应码的优先级:版本校验 > 类型校验 > 长度校验 > 内容校验。版本不对,后面的都不看了。
最后,我建议你在实现DoIP协议栈时,把否定响应码的生成逻辑做成一个独立模块。每个错误码对应一个处理函数,方便调试。我在AUTOSAR项目中就是这么做的,后来客户要求增加私有错误码,我只用加一个枚举值和对应的处理函数,半小时搞定。
好了,消息格式这块就讲这么多。下一节咱们聊聊DoIP的连接建立流程,那才是真正开始“握手”的地方。