6.6 AUTOSAR集成实战:UDS服务的实现
一个场景:客户要求你的产品支持UDS诊断协议,特别是安全访问服务(Service 0x27)。按照汽车行业标准,诊断仪需要先通过种子-密钥挑战才能解锁安全等级,然后才能执行敏感操作。密钥计算需要用到HSM中存储的秘密数据。
一个问题浮出水面:AUTOSAR的UDS服务是异步回调模式,而HSM的API调用也需要时间。如何将它们结合起来?状态机设计需要注意什么?
一个隐喻:这像接力赛跑——UDS模块接到诊断仪的请求,把接力棒(seed请求)传给HSM模块,HSM跑完一圈(获取随机数、计算密钥),再把接力棒传回UDS,最终交给诊断仪。每个环节都不能掉棒。
UDS安全访问服务概述
什么是UDS Service 0x27?
UDS(Unified Diagnostic Services)是汽车诊断标准协议。Service 0x27(SecurityAccess)用于实现诊断仪对ECU的安全访问。
UDS 27服务流程
诊断仪 ECU
│ │
│──── 27 01(请求种子)────│
│ │
│←─── 67 01 + seed ────────│
│ │
│ 计算key │
│ │
│──── 27 02 + key ─────────│
│ │
│←─── 67 02(验证成功)───│
│ 或 │
│←─── 7F 27 35(验证失败,密钥无效)──│
服务流程:
- 诊断仪发送
27 01(子功能0x01),请求种子(seed) - ECU生成随机数作为种子,返回
67 01 + seed - 诊断仪用预定义算法计算key
- 诊断仪发送
27 02 + key,提交密钥 - ECU验证key,返回成功或失败响应
安全等级
不同的安全等级对应不同的操作权限:
| 安全等级 | 子功能 | 权限 |
|---|---|---|
| Level 1 | 0x01/0x02 | 扩展诊断模式 |
| Level 2 | 0x11/0x12 | 编程模式 |
| Level 3 | 0x21/0x22 | 工厂配置 |
每个等级使用不同的种子-密钥算法和秘密数据。
HSM在UDS 27中的角色
HSM在UDS 27服务中承担两个关键任务:
- 提供安全的随机数(种子):调用
hsm_get_random生成不可预测的seed - 存储密钥计算的秘密数据(mask):HSM中存储的mask用于key计算算法
HSM参与UDS 27流程
诊断仪 ECU/UDS HSM
│ │ │
│─ 27 01 ──────│ │
│ │─ hsm_get_random ─│
│ │ │
│ │←─ 随机数 ────│
│ │ │
│←─ 67 01 + seed ─│ │
│ │ │
│ 计算key │ │
│ │ │
│─ 27 02 + key ─│ │
│ │─ hsm_read_file ─│
│ │ │←─ mask
│ │ │
│ │ 计算key │
│ │ 比对验证 │
│ │ │
│←─ 67 02 或 7F 27 ─│ │
AUTOSAR异步回调模式
AUTOSAR DCM的回调机制
AUTOSAR的DCM(Diagnostic Communication Manager)模块采用异步回调模式处理UDS服务。当收到诊断请求时,DCM会调用用户注册的回调函数,回调函数返回DCM_E_PENDING表示处理未完成,DCM会持续调用直到返回最终结果。
// DCM回调函数原型
Std_ReturnType SecurityAccess_Callback(
uint8_t* Key,
uint8_t* seed,
Dcm_NegativeResponseCodeType* ErrorCode
);
返回值:
| 返回值 | 含义 |
|---|---|
| E_OK / RTE_E_OK | 处理成功 |
| E_NOT_OK | 处理失败,返回否定响应 |
| DCM_E_PENDING | 处理进行中,DCM会再次调用 |
为什么需要异步模式?
HSM操作不是即时完成的:
- I2C/SPI通信需要时间
- HSM内部处理需要时间
- 多步骤操作(PIN验证→选择文件→读取)需要多次调用
同步模式会阻塞整个系统,而异步模式允许其他任务继续运行。
状态机设计
状态机结构
将UDS 27的HSM操作设计为状态机:
状态机流程
STEP_INITIAL(初始状态)
↓ hsm_verify_pin
STEP_PIN_VERIFIED(PIN已验证)
↓ hsm_select_device_file
STEP_FILE_SELECTED(文件已选择)
↓ hsm_read_file
STEP_KEY_PROCESSED(密钥已处理)
↓ 计算key、比对验证
返回结果
状态机代码实现
typedef enum {
STEP_INITIAL,
STEP_PIN_VERIFIED,
STEP_FILE_SELECTED,
STEP_KEY_PROCESSED
} SecurityAccess_StepType;
Std_ReturnType SecurityAccess_AuthenticationProcess(
uint8_t* Key, // 上位机发送的key
uint8_t* seed, // 随机数seed
Dcm_NegativeResponseCodeType* ErrorCode,
uint32_t hsm_file_id // HSM文件ID(存储mask)
) {
static SecurityAccess_StepType step = STEP_INITIAL;
static uint8_t long_key[16] = {0}; // 从HSM读取的mask
static uint32_t long_key_len = 0;
se_error_t result;
switch (step) {
case STEP_INITIAL:
{
// 步骤1:验证PIN
pin_t pin;
uint8_t admin_pin_key[16] = HSM_PIN_KEY;
memcpy(pin.pin_value, admin_pin_key, 16);
pin.owner = ADMIN_PIN;
pin.pin_len = 16;
result = hsm_verify_pin(&pin);
if (result == HSM_OK) {
step = STEP_PIN_VERIFIED;
return DCM_E_PENDING; // 继续处理
}
else if (result == HSM_ERR_PENDING) {
return DCM_E_PENDING; // HSM仍在处理
}
else {
step = STEP_INITIAL; // 失败,重置状态
*ErrorCode = DCM_E_REQUESTSEQUENCEERROR;
return E_NOT_OK;
}
}
break;
case STEP_PIN_VERIFIED:
{
// 步骤2:选择文件
result = hsm_select_device_file(hsm_file_id);
if (result == HSM_OK) {
step = STEP_FILE_SELECTED;
return DCM_E_PENDING;
}
else if (result == HSM_ERR_PENDING) {
return DCM_E_PENDING;
}
else {
step = STEP_INITIAL;
*ErrorCode = DCM_E_REQUESTSEQUENCEERROR;
return E_NOT_OK;
}
}
break;
case STEP_FILE_SELECTED:
{
// 步骤3:读取mask
result = hsm_read_file(0, 16, long_key, &long_key_len);
if (result == HSM_OK) {
step = STEP_KEY_PROCESSED;
return DCM_E_PENDING;
}
else if (result == HSM_ERR_PENDING) {
return DCM_E_PENDING;
}
else {
step = STEP_INITIAL;
*ErrorCode = DCM_E_REQUESTSEQUENCEERROR;
return E_NOT_OK;
}
}
break;
case STEP_KEY_PROCESSED:
{
// 步骤4:计算并验证key
uint8_t key_result[4] = {0};
seed_to_key(seed, long_key, key_result); // 客户特定的算法
step = STEP_INITIAL; // 重置状态
if (compare_arrays(key_result, Key, 4)) {
*ErrorCode = 0;
return RTE_E_OK; // 验证成功
}
else {
*ErrorCode = DCM_E_INVALIDKEY;
return E_NOT_OK; // 验证失败
}
}
break;
default:
step = STEP_INITIAL;
*ErrorCode = DCM_E_REQUESTSEQUENCEERROR;
return E_NOT_OK;
}
*ErrorCode = DCM_E_REQUESTSEQUENCEERROR;
return E_NOT_OK;
}
状态机的关键点
- 静态变量保存状态:
static SecurityAccess_StepType step保存当前状态,跨多次调用保持 - 静态变量保存数据:
static uint8_t long_key[16]保存从HSM读取的mask - 返回DCM_E_PENDING:处理未完成时返回pending,DCM会再次调用
- 失败时重置状态:任何步骤失败,将状态重置为
STEP_INITIAL - 成功时重置状态:最终步骤完成后,重置状态准备下次请求
为什么使用static变量?
AUTOSAR回调函数会被DCM模块反复调用,每次调用都是独立的函数执行。非静态变量会在每次调用时重新初始化,无法保存进度。静态变量在函数多次调用之间保持值,适合状态机场景。
密钥计算函数
seed_to_key函数根据客户需求实现特定的密钥计算算法:
uint8_t* seed_to_key(
const uint8_t* in_seed, // 随机数seed
uint8_t* in_long_key, // HSM中的mask
uint8_t* verify_key // 计算结果
) {
// 客户定义的算法
// 例如:将seed和mask按特定规则组合运算
for (int i = 0; i < 4; i++) {
verify_key[i] = in_seed[i] ^ in_long_key[i];
verify_key[i] = (verify_key[i] + in_long_key[i+4]) & 0xFF;
}
return verify_key;
}
不同客户有不同的算法,具体实现需要根据客户需求文档。
集成测试
测试流程
使用诊断仪工具测试UDS 27服务:
测试步骤
1. 切换到扩展模式
发送:10 03
响应:50 03
2. 请求种子
发送:27 01
响应:67 01 XX XX XX XX(4字节seed)
3. 计算key(本地计算)
使用seed和预知的mask,按算法计算key
4. 提交key
发送:27 02 YY YY YY YY(4字节key)
响应:67 02(成功)或 7F 27(失败)
5. 切换到编程模式
发送:10 02
响应:50 02
6. 请求Level 2种子
发送:27 11
响应:67 11 XX XX XX XX
7. 提交Level 2 key
发送:27 12 YY YY YY YY
响应:67 12(成功)或 7F 27(失败)
逻辑分析仪验证
用逻辑分析仪抓取SPI通信数据,验证HSM交互:
SPI波形分析
27 01请求时的SPI通信:
帧1:获取随机数请求 → 20 00 05 00 84 00 00 08 CRC
帧2:随机数响应 → 20 00 0A [8字节随机数] 90 00 CRC
27 02请求时的SPI通信:
帧1:PIN验证请求 → ...
帧2:选择文件请求 → ...
帧3:读取mask请求 → ...
验证每个步骤的数据是否符合预期。
设计注意事项
状态重置时机
状态必须在以下时机重置:
- 处理失败时:重置状态,准备接受新请求
- 处理成功时:重置状态,准备接受新请求
- 超时时:AUTOSAR可能有超时机制,状态需要重置
不要在处理中途重置状态,会导致数据丢失。
错误码选择
AUTOSAR定义了多种否定响应码:
| 错误码 | 含义 | 适用场景 |
|---|---|---|
| 0x24 | requestSequenceError | 请求序列错误 |
| 0x35 | invalidKey | 密钥无效 |
| 0x26 | conditionsNotCorrect | 条件不满足 |
| 0x36 | securityAccessDenied | 安全访问被拒绝 |
选择合适的错误码,便于诊断仪理解失败原因。
多安全等级处理
不同安全等级使用不同的HSM文件ID:
// 配置映射
typedef struct {
uint8_t level; // 安全等级(0x01, 0x11, 0x21...)
uint32_t file_id; // HSM文件ID
} SecurityLevelConfig;
SecurityLevelConfig config[] = {
{0x01, 0x0006}, // Level 1使用文件6的mask
{0x11, 0x0007}, // Level 2使用文件7的mask
{0x21, 0x0008}, // Level 3使用文件8的mask
};
根据请求的子功能选择对应的配置。
小结
AUTOSAR集成像搭建桥梁——一边是异步回调模式的UDS服务,一边是耗时操作的HSM API。状态机设计就是桥梁的结构,保证两边的数据流动顺畅。
关键设计点:
- 状态机管理多步骤流程
- 静态变量保存跨调用数据
- 返回DCM_E_PENDING让DCM持续调用
- 失败和成功都要重置状态
下一节,我们将介绍TCP命令接口——看开发调试的便捷方案。
【下集预告】
开发阶段怎么快速操作HSM?
TCP命令怎么设计?状态机怎么触发?
上位机怎么写入数据?
下一节,TCP命令接口。