4. DoIP诊断通信:诊断请求/响应机制、DoIP诊断报文封装、诊断会话控制、安全访问
好,我们进入第四章。这一章讲的是DoIP诊断通信的核心机制。说白了,就是诊断仪怎么通过以太网跟ECU对话,ECU又怎么回应你。
我个人习惯把DoIP诊断通信拆成四个部分来看:请求/响应怎么配对、报文怎么封装、会话怎么切换、安全怎么解锁。咱们一个一个来。
4.1 诊断请求/响应机制
先讲最基础的。DoIP的诊断请求和响应,本质上还是UDS那套东西。你发一个诊断请求,ECU回一个诊断响应。只不过在DoIP里,这些UDS消息被包在了TCP或者UDP的Payload里。
我记得刚做DoIP项目时,有个同事问我:“那诊断请求丢了怎么办?” 嗯,这里要注意:DoIP的诊断通信默认走TCP。TCP有重传机制,所以应用层不需要再额外做超时重传。但如果你用UDP发诊断消息(比如某些DoIP实体状态请求),那就得自己处理丢包了。
诊断请求/响应的基本流程是这样的:
- 诊断仪发送一个诊断请求(比如10 03,请求扩展会话)
- ECU收到后,处理请求,返回诊断响应(比如50 03,表示成功进入扩展会话)
- 如果请求格式错误或不被支持,ECU返回否定响应(比如7F 10 12,表示子功能不支持)
这里有个坑。我曾经遇到过一个问题:诊断仪发了请求,ECU没响应。查了半天,发现是TCP连接没建立。DoIP要求必须先建立TCP连接(走逻辑地址激活流程),然后才能发诊断请求。你想想看,如果连Socket都没连上,ECU根本不会理你。
4.2 DoIP诊断报文封装
接下来看报文封装。DoIP诊断报文不是直接把UDS消息扔到TCP流里就完事了。它外面包了一层DoIP头部。
DoIP头部结构如下:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 协议版本 | 1 | 当前固定为0x02 |
| 逆协议版本 | 1 | 0xFD(取反校验) |
| Payload类型 | 2 | 0x8001表示诊断消息 |
| Payload长度 | 4 | 后面跟着的诊断数据长度 |
| 诊断数据 | 可变 | UDS消息(如10 03) |
说白了,DoIP头部就是给诊断消息加了个“信封”。信封上写着:这是诊断消息(Payload类型=0x8001),长度是多少。ECU收到后,先解析头部,再取出里面的UDS消息去处理。
我建议你在抓包时重点关注Payload类型字段。如果类型不对,ECU会直接丢弃。比如你发了个0x8002(诊断消息的ACK),但ECU期望的是0x8001,那就会出问题。
代码示例:封装一个DoIP诊断请求
// 伪代码:封装DoIP诊断请求
uint8_t doip_header[8];
uint8_t uds_request[] = {0x10, 0x03}; // 诊断会话控制请求
doip_header[0] = 0x02; // 协议版本
doip_header[1] = 0xFD; // 逆协议版本
doip_header[2] = 0x80; // Payload类型高字节
doip_header[3] = 0x01; // Payload类型低字节 (0x8001)
doip_header[4] = 0x00; // Payload长度高字节
doip_header[5] = 0x00;
doip_header[6] = 0x00;
doip_header[7] = sizeof(uds_request); // Payload长度低字节
// 发送:doip_header + uds_request
send(socket, doip_header, 8, 0);
send(socket, uds_request, sizeof(uds_request), 0);
4.3 诊断会话控制
诊断会话控制,对应UDS的0x10服务。DoIP本身不定义会话,它只是传输通道。会话控制还是UDS那套逻辑。
常见的会话模式有:
- 默认会话(0x01): 上电后的初始状态,功能受限
- 扩展会话(0x03): 允许读写数据、执行例程
- 编程会话(0x02): 允许刷写固件
- 安全系统会话(0x04): 某些安全相关操作
为什么要有会话控制?说白了就是权限管理。你想想看,如果谁都能随便刷写ECU,那车早就废了。所以ECU通过会话状态来限制你能做什么操作。
我建议你在开发时,先确认当前ECU处于哪个会话。很多诊断服务(比如0x2E写入数据)只在扩展会话或编程会话下才支持。如果你在默认会话下发0x2E,ECU会返回否定响应(7F 2E 7F,服务不支持当前会话)。
会话切换的流程:
- 诊断仪发送10 03(请求扩展会话)
- ECU返回50 03(肯定响应)
- 此时ECU进入扩展会话,会话定时器开始计时(通常5秒)
- 如果5秒内没有新的诊断请求,ECU自动退回默认会话
嗯,这里要注意会话定时器。我曾经在测试时发现,ECU总是莫名其妙退回默认会话。后来发现是诊断仪发请求的间隔超过了5秒。解决方案是:要么缩短请求间隔,要么在ECU端把会话超时时间改长一点(比如改成30秒)。
4.4 安全访问
安全访问,对应UDS的0x27服务。它的作用是:防止未授权的诊断操作。
安全访问的流程:
- 诊断仪发送27 01(请求种子)
- ECU返回67 01 + 种子(比如4字节随机数)
- 诊断仪根据种子计算密钥,发送27 02 + 密钥
- ECU验证密钥,如果正确返回67 02(解锁成功)
说白了就是“你证明你是你”。ECU给你一个随机数(种子),你用约定的算法算出密钥,发回去。ECU算一遍,对上了就解锁。
这里有个常见的坑:种子和密钥的算法。不同OEM的算法不一样。有的用简单的异或,有的用AES加密。我建议你在开发时,先确认好算法文档。我曾经遇到过算法文档写错了,导致密钥一直算不对,折腾了两天。
安全访问的等级:
| 安全等级 | 子功能 | 说明 |
|---|---|---|
| Level 1 | 0x01/0x02 | 基础解锁,允许读写数据 |
| Level 2 | 0x03/0x04 | 高级解锁,允许刷写 |
| Level 3 | 0x05/0x06 | 厂商专用,允许特殊操作 |
我个人习惯在代码里加一个安全访问状态机。因为安全访问有次数限制(比如连续3次失败就锁定ECU一段时间)。如果不做状态机,很容易在并发请求时搞乱状态。
最后总结一下:DoIP诊断通信,本质上就是UDS over TCP。DoIP负责传输,UDS负责逻辑。会话控制和安全访问是UDS层面的权限管理。你只要把DoIP头部封装好,TCP连接管理好,剩下的就是UDS那套东西了。
下一章我们会讲DoIP和SOME/IP的协同开发,看看这两个协议怎么在同一个ECU里共存。嗯,那才是真正有意思的地方。