Files
story/docs/device-firmware-ab-protocol.md
T

114 lines
7.7 KiB
Markdown
Raw Normal View History

2026-08-26 06:41:07 +08:00
# 设备A/B分区固件升级与启动确认协议
## 1. 目的与适用边界
本协议用于把云端已经完成的签名制品、灰度命令和阶段回执,与量产终端常见的A/B双分区升级机制对齐。它定义设备必须持久化的升级状态、云端可观察阶段、启动健康窗口和自动回退结果,但不替代具体芯片、Bootloader、分区表、看门狗或厂商烧写SDK。当前`cmd/sim-device`实现的是协议级模拟器,用于在真机到位前发现状态机和联调问题;是否能安全写入真实分区、断电后继续启动及由Bootloader自动回退,仍必须在量产候选硬件上验收。
旧设备不声明或不启用A/B能力时继续使用既有`downloading → installing → confirmed/failed`链路。A/B模拟器只有在`SIM_FIRMWARE_AB_UPDATE=true`且提供绝对路径`SIM_FIRMWARE_STATE_FILE`时启用,不改变设备v2既有必填字段和命令终态。
## 2. 设计原则
1. 设备只信任安全存储或受控镜像预置的厂商公钥,不信任升级命令携带的公钥。
2. 制品大小、SHA-256和Ed25519签名全部通过后,才允许写入非活动分区。
3. 任何时刻都不得直接覆盖当前活动且已确认可启动的分区。
4. 设置下次启动分区和健康截止时间前,必须先持久化升级日志;云端回执不得先于本地耐久状态。
5. 新分区只有在核心进程、配置读取、设备身份和必要外设健康检查通过后才能确认。
6. 健康检查失败或超过截止时间时,由设备/Bootloader回到旧分区,并向云端提交带回退事实的稳定失败终态。
7. 设备重启后可重新拉取仍为`ACKNOWLEDGED`的同一命令;云端增加投递次数但不把状态回退到`DELIVERED`。
## 3. 设备侧状态机
```mermaid
stateDiagram-v2
[*] --> StableA: A分区已确认
StableA --> Downloading: 收到firmware_update
Downloading --> ArtifactVerified: 摘要和签名通过
Downloading --> Failed: 下载/摘要/签名失败
ArtifactVerified --> StagedB: 写入并校验非活动B分区
StagedB --> RebootPending: 持久化启动尝试和健康截止时间
RebootPending --> BootVerifyingB: 重启进入B分区
BootVerifyingB --> StableB: 健康检查通过并确认B
BootVerifyingB --> RolledBackA: 健康失败或超时
RolledBackA --> Failed: 上报已自动回退
StableB --> [*]
Failed --> [*]
```
当活动分区为B时,下一次升级使用完全对称的B→A流程。`power_loss_after_stage`保留`STAGED`日志,重启后从设置启动分区继续;`power_loss_during_boot`保留`REBOOT_PENDING`日志,后续启动继续执行健康检查。重复执行必须针对同一命令编号、版本、发布编号和摘要,任一不一致都以`LOCAL_UPDATE_STATE_CONFLICT`停止,不能覆盖未知的在途升级。
## 4. 本地耐久日志
模拟器状态文件只保存非敏感升级元数据,权限为当前进程可读写。字段如下:
| 字段 | 含义 |
|---|---|
| `phase` | `STAGED`、`REBOOT_PENDING`、`CONFIRMED`或`ROLLED_BACK` |
| `command_id`、`release_id` | 绑定当前可靠命令和云端发布记录 |
| `target_version`、`previous_version` | 目标版本和回退基线版本 |
| `sha256` | 已验证制品摘要,用于重启后核对同一升级意图 |
| `active_slot`、`previous_slot`、`target_slot` | 当前稳定分区、回退分区和待启动分区 |
| `boot_attempt_id` | 每次设置下次启动分区时生成的随机启动尝试编号 |
| `health_deadline` | 新分区必须完成健康确认的UTC截止时间 |
| `updated_at` | 最近一次耐久更新时刻 |
`CONFIRMED`和`ROLLED_BACK`记录作为下一次升级的本地稳定基线保留。量产实现应把等价数据写入具备掉电保护的Bootloader环境区或专用元数据分区,并使用冗余、校验和或事务写策略处理半写入,不能直接照搬普通文件实现。
## 5. 云端回执与主状态映射
命令仍只使用`ACKNOWLEDGED`、`SUCCEEDED`和`FAILED`三类状态。A/B细节放在可选`result_summary`中,云端将所有设备侧写入、重启和健康确认阶段保持为`firmware_assignment.status=INSTALLING`,避免扩展既有主状态枚举破坏客户端兼容。
| 回执阶段 | 命令状态 | 分配主状态 | 建议进度 | 关键字段 |
|---|---|---|---:|---|
| `downloading` | `ACKNOWLEDGED` | `DOWNLOADING` | 5—100 | `result_code`、`verified_sha256` |
| `installing` | `ACKNOWLEDGED` | `INSTALLING` | 80 | `INSTALL_STARTED` |
| `staging` | `ACKNOWLEDGED` | `INSTALLING` | 85 | `active_slot`、`target_slot` |
| `reboot_pending` | `ACKNOWLEDGED` | `INSTALLING` | 90 | `boot_attempt_id`、`boot_health_deadline` |
| `boot_verifying` | `ACKNOWLEDGED` | `INSTALLING` | 95 | `active_slot`、`previous_slot` |
| `confirmed` | `SUCCEEDED` | `CONFIRMED` | 100 | `installed_version`、`active_slot` |
| `rolled_back` | `FAILED` | `FAILED` | 95 | `rollback_applied=true`、`rollback_version`、`failed_slot` |
启动失败回退示例:
```json
{
"status": "FAILED",
"result_summary": {
"phase": "rolled_back",
"progress_percent": 95,
"result_code": "BOOT_HEALTH_FAILED_ROLLED_BACK",
"rollback_applied": true,
"active_slot": "A",
"failed_slot": "B",
"rollback_version": "0.2.0-sim",
"boot_attempt_id": "<随机编号>"
}
}
```
回退已经保护设备可启动性,但目标版本未成功,因此云端仍把命令和分配标记为失败、把批次暂停并停止扩量。运营页面从`last_result`展示非活动分区写入、等待重启、健康确认或已自动回退,不把“回到了可用旧版本”误报成升级成功。
## 6. 模拟器参数和故障注入
| 参数 | 默认值 | 作用 |
|---|---|---|
| `SIM_FIRMWARE_AB_UPDATE` | `false` | 显式启用A/B升级协议 |
| `SIM_FIRMWARE_STATE_FILE` | 无 | A/B耐久日志绝对路径;启用时必填 |
| `SIM_FIRMWARE_ACTIVE_SLOT` | `A` | 首次运行且没有稳定日志时的活动分区,只允许A/B |
| `SIM_FIRMWARE_CURRENT_VERSION` | `0.2.0-sim` | 首次运行的回退基线版本 |
| `SIM_FIRMWARE_BOOT_HEALTH_WINDOW` | `2m` | 启动健康窗口,范围1秒至30分钟 |
| `SIM_FIRMWARE_FAULT=power_loss_after_stage` | 无 | 写完非活动分区后模拟掉电,保留`STAGED` |
| `SIM_FIRMWARE_FAULT=power_loss_during_boot` | 无 | 新分区健康确认时模拟掉电,保留`REBOOT_PENDING` |
| `SIM_FIRMWARE_FAULT=boot_health_failed` | 无 | 模拟新分区健康失败并自动回退 |
## 7. 轻量纵向验证
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\smoke-sim-firmware.ps1 -Mode ABRollback
```
脚本创建一个临时签名版本和单设备批次。第一次运行下载、验签、写入B分区并持久化`REBOOT_PENDING`,断言云端为`ACKNOWLEDGED/INSTALLING/90/REBOOT_ARMED`;第二次运行模拟B分区健康失败,断言本地回到A分区、原版本恢复,云端收敛为`FAILED/FAILED/95/BOOT_HEALTH_FAILED_ROLLED_BACK`,批次暂停且观察设备为0。结束后精确清理数据库、对象存储、固件文件和本地状态文件。
同一脚本还生成独立的`firmware-device-evidence-v1`设备侧证据,读取包含7条去重阶段事件的云端批次响应,并调用`cmd/firmware-conformance`核对命令、发布、启动尝试、制品摘要、签名密钥、阶段顺序、时间相关性、分区和回退版本。证据字段及报告归档规则见[固件真机联调一致性证据采集与校验说明](firmware-conformance-evidence.md)。
该结果证明协议和云端状态闭环可联调,不证明真实Bootloader、闪存掉电一致性或真实外设健康检查已经通过。真机验收仍须覆盖至少3台量产候选终端、A→B和B→A、写入中断电、首次启动断电、健康超时、看门狗复位、连续升级、云端暂停和已验证版本回滚。