# 设备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、写入中断电、首次启动断电、健康超时、看门狗复位、连续升级、云端暂停和已验证版本回滚。