本文档剖析 otxFlash 模块中基于 ASAM MCD3D 协议栈的 DoIP(Diagnostics over IP)文件下载子系统。该系统提供了三种下载策略——MCD FlashJob 模式、VBF 手动模式与 BIN 手动模式——并通过 MCDCmdTrans 低层 UDS 命令引擎与 MCDCmdBoardcast 广播通信单例,构建了完整的 ECU 刷写通信链路。
Sources: MCDFileDownloader.hpp
架构总览:三种下载策略的协同体系
MCDFileDownloader 是整个子系统的命名核心,但它并非唯一的下载实现。系统实际包含 三个下载器类,它们并非通过继承关系关联,而是在 ECUFlasherImpl::downloadFile 中根据 ECU 类型进行策略选择。三者的共同点在于:都实现 asam::mcd::MCDEventHandler 接口以接收 MCD3D 运行时的事件回调,都持有 ECUFlasher*、ECU*、File* 三个核心依赖指针。
graph TB
subgraph "策略选择层"
EFI["ECUFlasherImpl::downloadFile()"]
end
subgraph "下载器层"
MCD["MCDFileDownloader<br/>MCD FlashJob 模式"]
VBF["VbfFileDownloader<br/>VBF 手动 UDS 模式"]
BIN["BinFileDownloader<br/>BIN 手动 UDS 模式"]
end
subgraph "通信层"
TRANS["MCDCmdTrans<br/>点对点 UDS 命令引擎"]
BOARD["MCDCmdBoardcast<br/>广播通信单例"]
end
subgraph "MCD3D 运行时"
PROV["MCDProjectProvider<br/>共享 MCD 对象"]
LL["MCDLogicalLink<br/>逻辑链路"]
HEX["eMCDHEXSERVICE<br/>Hex 服务原语"]
FJ["MCDFlashJob<br/>刷写作业"]
end
EFI -->|"域控 TCAM/BGM/CDC/ACU"| BIN
EFI -->|"其他 ECU"| VBF
EFI -.->|"FLASHER_USE_MCD_METHOD=0 时"| MCD
BIN --> TRANS
VBF --> TRANS
BIN --> BOARD
VBF --> BOARD
MCD --> PROV
MCD --> FJ
TRANS --> LL
TRANS --> HEX
TRANS --> PROV
BOARD --> LL
BOARD --> PROV
style MCD fill:#f9f,stroke:#333,stroke-dasharray: 5 5关键设计决策:MCDFileDownloader 通过 MCD3D 的 MCDFlashJob API 进行刷写,依赖 ODX/PDX 项目数据库中预定义的 Flash Session 和 DataBlock 配置,属于声明式刷写。而 VbfFileDownloader 和 BinFileDownloader 则绕过 FlashJob 抽象层,通过 MCDCmdTrans 直接构造 UDS 诊断帧(0x34 RequestDownload、0x36 TransferData、0x37 TransferExit 等),属于命令式刷写。当前编译配置 FLASHER_USE_MCD_METHOD=1,因此 实际使用的是 VbfFileDownloader 和 BinFileDownloader,MCDFileDownloader 作为备用路径存在。
Sources: ECUFlasherImpl.cpp
MCDCmdTrans:UDS 命令引擎的深度剖析
MCDCmdTrans 是手动刷写模式的核心通信引擎。它在构造时建立到指定 ECU 的 MCD 逻辑链路(LogicalLink),并创建一个 Hex 服务原语(eMCDHEXSERVICE) 用于收发原始 UDS 诊断帧。
ECU 变体名映射
构造函数中包含一个硬编码的 ECU 变体名重映射表,将简化的 ECU 名称映射到 ODX 数据库中定义的标准变体名和逻辑链路名:
| 匹配模式 | 映射变体名 | 映射逻辑链路名 |
|---|---|---|
| 含 "BGM" | EV_BGM_SOC | LL_BGM_SOC_DoIP |
| 含 "ACU" | EV_ACU_SOC | LL_ACU_SOC_DoIP |
| 含 "TCAM" | EV_TCAM | LL_TCAM_DoIP |
| 含 "CDC" | EV_CDC_SOC | LL_CDC_SOC_DoIP |
这一映射使得上层调用者可以使用简化的 ECU 名称,而不必关心 ODX 数据库中的精确命名约定。
Sources: MCDCmdTrans.cpp
逻辑链路建立流程
链路建立的完整流程为:从 MCDProjectProvider 获取共享的 MCDSystem、MCDProject 和 MCDInterface 对象 → 调用 mcdProject->createLogicalLinkByVariantAndInterface() 创建逻辑链路 → 如果链路状态为 eCREATED,依次调用 open() 和 gotoOnline() 将链路激活至在线状态。任何异常都会将 isValid 标记为 false,后续所有命令调用都会静默返回空数据。
Sources: MCDCmdTrans.cpp
sendCommand:同步 UDS 帧收发
sendCommand 是 MCDCmdTrans 的最底层通信原语。它将二进制数据封装为 asam::mcd::MCDValue(类型为 eA_BYTEFIELD),通过 hexCop->getRequest()->enterPDU(pdu) 注入请求 PDU,然后调用 hexCop->executeSync() 同步执行。执行结果通过 result->getResponses()->getItemByIndex(0) 获取响应,并提取响应的 bytefield 返回。发送和接收的数据都会被记录到 ECU 对象的命令日志中(通过 ecu->addCmd()),用于调试和追溯。
关于内存管理:每次 executeSync() 返回的 MCDResult* 对象都需要调用者手动 delete,源码注释明确指出这是 JVM 创建的克隆版本,必须显式释放以避免内存泄漏。
Sources: MCDCmdTrans.cpp
UDS 刷写命令集
MCDCmdTrans 封装了完整的 UDS 刷写命令序列,每个命令返回 bool 表示成功/失败:
| 方法 | UDS SID | 功能描述 |
|---|---|---|
EnterProgramMode() | 0x10 0x82 | 进入编程会话(先发 0x10 0x82 再等待 1 秒) |
EnterSystem() | 0x22 F1 86 / 0x10 0x02 | 检查并进入默认会话,读取 DID F186 确认状态 |
getEcuSignatureMode() | 0x22 D0 1C | 读取 ECU 签名模式(Prod/Dev) |
SecurityAccess(level) | 0x27 0x01/0x02 | 安全访问 SeedToKey 认证,支持 6 个安全级别 |
SecurityAccess_ECOS(level) | 0x27 0x01/0x02 | ECOS 变体的安全访问(总是用 pinCode 作为 mask) |
EraseMemory(addr, size) | 0x31 0x01 0xFF | 擦除指定地址范围的 Flash 内存 |
RequestDownload(...) | 0x34 | 请求下载,支持 ≤4GB(0x00 0x44 格式)和 >4GB(0x00 0x88 格式)两种长度编码 |
RequestTransferExit() | 0x37 | 结束数据传输 |
ActivateSbl(addr) | 0x31 0x01 0x03 0x01 | 激活 Secondary Bootloader |
VerifyAuthenticity(...) | 0x31 0x01 0x02 0x08/0x12 | 验证刷写文件签名(domain 模式用 ASCII 字符串,hex 模式用二进制) |
FinishProgram() | 0x31 0x01 0x02 0x05 | 完成编程(检查 9 字节响应中的第 9 字节为 0) |
Sources: MCDCmdTrans.cpp
RequestDownload 的帧长度自适应
RequestDownload 展现了精心的协议适配逻辑。当数据长度 nDataLength ≤ 0xFFFFFFFF(4GB)时,使用 11 字节的传统格式(0x34 0x00 0x44 后跟 4 字节地址和 4 字节长度)。当超过 4GB 时,自动切换到 19 字节的扩展格式(0x34 0x00 0x88,前 8 字节地址清零,后 8 字节承载 64 位长度)。ECU 响应中的 nFrameSize 由响应的第二个字节决定:0x20 表示 2 字节长度字段、0x30 表示 3 字节、0x40 表示 4 字节。最终 nFrameSize 会被钳制到 MAX_FLASH_FRAME_SIZE (0xFFFFFF00) 以下,并减去 2(因为 TransferData 帧需要 0x36 和计数器两个字节)。
Sources: MCDCmdTrans.cpp
SeedToKey 安全访问算法
安全访问流程为:发送 0x27 0x01(安全级别由参数 iLevel 决定具体 SID) → 从响应中提取 3 字节 Seed → 结合 Mask 通过 Calc_Key 算法计算 3 字节 Key → 通过 0x27 0x02 发送 Key。
Calc_Key 采用 24 位线性反馈移位寄存器(LFSR) 变体算法,对 8 个输入字节逐一处理,每个字节执行 8 轮位迭代。初始状态 v3 = 0xC541A9,反馈多项式基于 0x109028。最终输出是一个 24 位值,取高 4 位、中 8 位、低 8 位组成 3 字节 Key。代码受 #ifdef _VM_PROTECT_ 保护,在启用虚拟化保护时关键计算逻辑会被包裹在 VMStart()/VMEnd() 之间。
特殊安全级别 level=7 不走 SeedToKey 流程,而是直接使用固定的 16 字节 Key。
Sources: MCDCmdTrans.cpp
安全掩码(Security Mask)映射表
Get_SecurityMask 维护了一个庞大的 DoIP 地址到 5 字节安全掩码 的映射表,覆盖 50+ 个 ECU 地址(0x1001 至 0x1BB7)。每个 ECU 有其独有的掩码,用于 SeedToKey 计算。默认掩码为 \x41\xAA\x42\xBB\x43。
Sources: MCDCmdTrans.cpp
MCDCmdBoardcast:功能组广播通信单例
MCDCmdBoardcast 采用 单例模式,通过 "LL_FG_UDSOnDoIP" 逻辑链路实现功能组寻址(Functional Addressing) 广播。构造时不仅打开链路并上线,还会发送 eMCDSTARTCOMMUNICATION 原语以初始化广播通信。其 sendCommand 方法与 MCDCmdTrans::sendCommand 结构相同,但创建的是 eMCDHEXSERVICE 类型的临时 Primitive(每次发送后销毁重建)。
关键用途:在 BinFileDownloader 处理 PBL(fileType=3)刷写完成后,通过广播发送 0x11 0x81(ECU Reset)命令重置所有 ECU,然后调用 otx_reconnect_vehicle_ex 重新建立车辆连接并等待 170 秒(约 3 分钟)让所有 ECU 完成重启。
Sources: MCDCmdBoardcast.cpp
VbfFileDownloader:VBF 格式的命令式刷写
VbfFileDownloader 实现了对 VBF(Versatile Binary Format) 文件的完整刷写流程。它通过 VBFParserSmall 解析 VBF 文件结构,提取擦除区域列表、数据块、签名信息和 SBL 激活地址。
完整刷写序列
sequenceDiagram
participant VBF as VbfFileDownloader
participant CMD as MCDCmdTrans
participant ECU as ECU (DoIP)
VBF->>CMD: new MCDCmdTrans(ecu)
Note over CMD: 建立逻辑链路
loop 每个擦除区域
VBF->>CMD: EraseMemory(addr, size)
CMD->>ECU: 0x31 0x01 0xFF [addr][size]
ECU-->>CMD: 0x71 0x01 0xFF
end
loop 每个 VBF Block
VBF->>CMD: RequestDownload(formatId, addr, len)
CMD->>ECU: 0x34 0x00 0x44 [addr][len]
ECU-->>CMD: 0x74 0x20/0x30/0x40 [maxLen]
loop TransferData 帧序列
VBF->>CMD: SendFrame_Cmd36(0x36 [ctr] [data])
CMD->>ECU: TransferData Frame
end
VBF->>CMD: RequestTransferExit()
CMD->>ECU: 0x37
ECU-->>CMD: 0x77
end
opt VBF v2.6 签名验证
VBF->>CMD: VerifyAuthenticity(signature, hex)
CMD->>ECU: 0x31 0x01 0x02 0x12 [sig]
end
opt SBL 文件 (fileType=1)
VBF->>CMD: ActivateSbl(callAddr)
CMD->>ECU: 0x31 0x01 0x03 0x01 [addr]
endTransferData 计数器与进度上报
每个 TransferData 帧以 0x36 开头、后跟 1 字节计数器(nCTR,从 1 开始,溢出后归零)。每发送一帧后,更新 ECU 级别的累计下载量(mECU->setDownloadedSize)和文件级别的增量(mEcuFile->increaseDownloadedSize),然后向 Flasher 发布 ECUFlasherFileDownloadProgressEventArg 进度事件。当 ECU 累计下载量达到总量时,发布 progress=100 的最终进度事件。
Sources: VbfFileDownloader.cpp
签名验证策略
对于 VBF v2.6 格式,根据 ECU 的 signatureMode 配置采用不同的验证策略:
- 0(自动):优先用
isProd标志选择 Prod/Dev 签名,失败后回退到另一个 - 1(仅 Dev):仅验证 Dev 签名
- 2(仅 Prod):仅验证 Prod 签名
Sources: VbfFileDownloader.cpp
BinFileDownloader:BIN 格式与域控刷写
BinFileDownloader 专用于 域控制器(TCAM、BGM、CDC、ACU) 的 BIN 格式文件刷写。它直接从磁盘流式读取文件,无需 VBF 解析器。
刷写流程差异
与 VbfFileDownloader 的关键差异在于:
- 擦除策略:调用
EraseMemory()无参数重载(实际使用默认参数uMem=0, uSize=0,表示整片擦除),而非按 VBF 擦除列表逐区域擦除 - 下载寻址:
RequestDownload(0, 0, fileSize)使用格式标识符 0 和内存地址 0(由 ECU 自行决定写入位置) - 退出传输:显式调用
RequestTransferExit() - 域名验证:
VerifyAuthenticity(keyInfo, true)使用isDomain=true模式(ASCII 字符串而非 hex) - PBL 特殊处理:当
fileType=3时,刷写完成后发送 ECU Reset → 重建连接 → 等待 170 秒 → 发送EnterProgramMode→EnterSystem→ 安全访问
安全访问双通道回退
PBL 刷写后的安全访问采用 双通道回退策略:根据 securityAccessMode,优先尝试 SecurityAccess_ECOS(pinCode) 或 SecurityAccess("FFFFFFFF"),失败后自动切换到另一通道。这覆盖了普通刷写(优先 pinCode)和换件刷写(优先 FFFFFFFF)两种场景。
Sources: BinFileDownloader.cpp
transferFile 分帧传输实现
与 VBF 的 Block 结构不同,BIN 的 transferFile 直接从文件流读取。帧大小被限制到 MIN(1MB, nFrameSize),使用 fread 逐帧读取文件内容,通过 SendFrame_Cmd36 发送。进度更新采用 oldProgress 去重机制——仅当百分比变化时才发布进度事件,避免重复通知。
Sources: BinFileDownloader.cpp
MCDFileDownloader:MCD FlashJob 声明式刷写(备用路径)
MCDFileDownloader 是三种策略中唯一使用 MCD3D FlashJob API 的实现。它通过 ODX 数据库中的 FlashJob 定义来驱动刷写流程,而非手动构造 UDS 命令。
核心流程
- 通过
MCDProjectProvider获取共享的MCDSystem、MCDProject、MCDInterface - 创建
MCDLogicalLink并上线 - 通过
getDbDiagComPrimitivesByType(eMCDDBFLASHJOB)从数据库加载刷写作业定义 - 设置 FlashDataBlock 的
activeFileName为待刷写文件路径 - 配置作业参数(
PA_PINCode、PA_FlashFileFlag) - 注册
MCDEventHandler回调并调用job->executeSync() - 从响应中提取
PA_JobStatuHEX和PA_AdditErrorInfor
onPrimitiveProgressInfo:进度回调的关键实现
MCDFileDownloader 的 onPrimitiveProgressInfo 方法是进度上报的参考实现。MCD3D 运行时以 0-100 的整数值回调进度,下载器将其转换为实际下载字节数:downloadedSize = fileSize × (progress / 100)。通过与 mEcuFile->getLastDownloadedSize() 比较计算增量 increase,仅当 increase > 0 时才更新 flasher->mDownloadedSize ——这是一个关键的防倒退保护:防止 Java 层计算的进度从 99% 回退到 98% 导致的统计偏差。最终重新计算精确百分比(基于实际累计字节数而非 MCD 的整数值),发布 ECUFlasherFileDownloadProgressEventArg 事件。
所有更新操作都在 std::lock_guard<std::mutex> lock(flasher->mFieldLock) 保护下进行,确保多 ECU 并行刷写场景下的线程安全。
Sources: MCDFileDownloader.cpp
异常抛出的事件处理器
MCDFileDownloader 中绝大多数 MCDEventHandler 虚函数(约 40 个)的实现体都是 throw -1,并注释"不可能发生的事件我们抛出异常,提醒开发者注意"。这是防御性编程的极端实践:将 FlashJob 执行期间不应触发的事件(如 onLinkStateOnline、onSystemLocked 等)显式标记为异常,一旦触发即崩溃以暴露问题。唯一正常实现的回调是 onFirstEvent()、onLastEvent()(空操作)、onPrimitiveJobInfo()(空操作)和 onPrimitiveProgressInfo()(进度上报)。
Sources: MCDFileDownloader.cpp
ECUFlasherImpl::downloadFile:策略选择枢纽
ECUFlasherImpl::downloadFile 是三种下载策略的统一入口和路由枢纽:
if (FLASHER_USE_MCD_METHOD > 0) {
if (ecuName 包含 "TCAM"/"BGM"/"CDC"/"ACU") {
使用 BinFileDownloader
} else {
使用 VbfFileDownloader
}
} else {
使用 MCDFileDownloader // 模拟进度
}每个下载器在调用前后都会发布 ECUFlasherFileEventArg(开始事件)和 ECUFlasherFileDownloadEndEventArg(结束事件,携带错误码和消息),形成完整的文件下载事件对。
Sources: ECUFlasherImpl.cpp
数据模型:ECU、File 与 DownloadArgs
ECU 类封装了单个 ECU 的完整刷写上下文:名称、ID、逻辑链路名、优先级、安全访问配置、签名模式、SBL 激活地址、文件列表以及动态下载统计(总大小、已下载大小)。其 m_vecCmd 成员记录所有发送/接收的 UDS 命令日志。
File 类描述待刷写文件:路径、大小、文件类型(0=Data、1=SBL、2=App、3=PBL)、PIN 码、密钥信息、以及运行时下载进度(累计下载量、最近一次增量)。内嵌 VBFParserSmall parser 用于 VBF 格式解析。
DownloadArgs 类是一个简单的参数聚合体,将 File、ECUFlasher 和下载大小打包传递,用于 TaskPool 的任务参数封装。
Sources: ECU.hpp, File.hpp, DownloadArgs.hpp
关键设计决策与模式总结
| 维度 | MCDFileDownloader | VbfFileDownloader | BinFileDownloader |
|---|---|---|---|
| 通信方式 | MCD FlashJob API | MCDCmdTrans 手动 UDS | MCDCmdTrans 手动 UDS |
| 文件格式 | ODX DataBlock 配置 | VBF 解析器驱动 | 裸 BIN 文件流 |
| 擦除策略 | FlashJob 内置 | VBF 擦除列表逐区 | 整片擦除 |
| 签名验证 | FlashJob 内置 | MCDCmdTrans 二进制 | MCDCmdTrans 域名 |
| 进度上报 | MCD 事件驱动 | 手动逐帧计算 | 手动去重计算 |
| 适用场景 | 通用(备用) | 普通 ECU | 域控制器 |
| 线程安全 | 锁保护进度区 | 无显式锁 | 无显式锁 |
核心模式:
- 策略模式:
ECUFlasherImpl::downloadFile根据 ECU 类型和编译宏选择下载器 - 模板方法:三个下载器共享相同的生命周期(开始事件 → 下载 → 结束事件)
- 单例模式:
MCDCmdBoardcast全局唯一广播通信实例 - 代理模式:
MCDLogicalLink是底层 DoIP 通信的代理对象 - 观察者模式:
MCDEventHandler接口驱动进度回调
关于后续阅读,推荐按以下顺序深入:
- 了解刷写器的整体调用链:刷写器架构总览
- 理解事件如何被消费:事件驱动模型:刷写生命周期事件的发布与订阅机制
- 了解 VBF 文件的解析细节:VBF 文件解析器:VBFHeader、VBFBlock 与 RSA 签名验证
- 查看不同文件下载器的完整实现:BinFileDownloader 与 VbfFileDownloader:不同格式刷写文件下载器
- 了解 SeedToKey 算法的更多细节:SeedToKey 与 CRC16:诊断安全访问的密钥计算