纸翼 · 加载中
6726 words
34 minutes
paipopAi对话玩具解析(七):从版本检查到双备份 OTA 安全升级
所属合集 Paipop AI 对话玩具源码解析 第 8 / 10 篇
  1. 1 杰理 AC79 应用启动与注册机制详解:从 REGISTER_APPLICATION 到 start_app
  2. 2 paipopAi对话玩具解析(一):从 APP_STA_START 到眼睛任务与摇一摇服务
  3. 3 paipopAi对话玩具解析(二):从按键与摇一摇到对话打断
  4. 4 paipopAi对话玩具解析(三):从按键启动到云端语音回答
  5. 5 paipopAi对话玩具解析(四):PaipopSDK 如何封装 LingXinSDK
  6. 6 paipopAi对话玩具解析(五):AI 播放时如何实现自然语音打断
  7. 7 paipopAi对话玩具解析(六):从开机联网到 AP 网页配网
  8. 8 paipopAi对话玩具解析(七):从版本检查到双备份 OTA 安全升级 正在阅读
  9. 9 paipopAi对话玩具解析(八):本地音量与云端动作指令
  10. 10 paipopAi对话玩具恢复延迟的定位与优化

paipopAi对话玩具解析(七):从版本检查到双备份 OTA 安全升级#

上一篇已经讲清楚设备怎样从 Wi-Fi 回调走到 DHCP 成功。就在这个成功边界上,配网模块除了通知产品状态机,还会调用:

paipop_ota_network_ready();

这并不意味着设备立刻开始擦写 Flash。OTA 是整个项目里风险最高的一条链路:它同时跨越网络协议、认证、版本策略、Flash 写入、Bootloader 切换和跨重启状态确认。任何一步把“收到”误当成“可信”、把“写完”误当成“可启动”,都可能把一次普通升级变成批量返修。

本文沿着当前 V1.2.8 的真实实现回答这些问题:

  • 为什么 DHCP 成功后还要等待设备连续空闲 3 秒?
  • App Key 从哪里来,为什么 HMAC 不能代替 HTTPS?
  • 后台返回了升级任务,设备为什么还要二次校验?
  • 只有 4 KiB 缓冲区,怎样下载并写入较大的固件?
  • SHA-256、双备份和数字签名分别解决什么问题?
  • 为什么必须先保存 pending 记录,再切换启动槽?
  • 重启后的新进程怎样知道上一轮升级是否成功?

先用一句话概括:

Paipop OTA 以 DHCP 和业务空闲作为启动门槛,用带时间戳的 HMAC-SHA256 请求领取升级任务,在设备侧再次检查版本、URL、电量和信号;下载时以 4 KiB 分块同时计算 SHA-256 并写入杰理双备份升级入口,完整校验后先持久化待确认记录,再让底层升级模块完成启动切换;新固件启动联网后,通过编译版本号与 pending 目标版本比对,完成跨重启结果闭环。

阅读导航#

  1. 杰理 AC79 应用启动与注册机制详解:从 REGISTER_APPLICATIONstart_app
  2. paipopAi 对话玩具解析(一):从 APP_STA_START 到眼睛任务与摇一摇服务
  3. paipopAi 对话玩具解析(二):从按键与摇一摇到对话打断
  4. paipopAi 对话玩具解析(三):从按键启动到云端语音回答
  5. paipopAi 对话玩具解析(四):PaipopSDK 如何封装 LingXinSDK
  6. paipopAi 对话玩具解析(五):AI 播放时如何实现自然语音打断
  7. paipopAi 对话玩具解析(六):从开机联网到 AP 网页配网
  8. 本文:从版本检查、双备份写入到重启确认。
  9. paipopAi 对话玩具解析(八):本地音量与云端动作指令

本文范围与证据边界#

本文基于正式工程 Paipop_YP_Toymain@24a83f9,产品版本为 V1.2.8 / 10208,目标平台为杰理 AC791N/WL82,分区身份为 8MB_dual_bank_paipop_v1

本次做的是静态源码与构建配置复核,没有重新烧录硬件、抓取 HTTPS 报文或执行断电破坏测试。因此结论需要分层:

结论可信度依据
OTA 任务、协议和凭据模块进入当前目标编译已验证board/wl82/Makefile 的源文件列表
网络/空闲门控、重试、流式写入和 pending 顺序已验证Paipop 第一方源码的直接控制流
当前目标启用了双备份布局已验证app_config.h 和发布身份
net_fclose(fd, 0) 后由公共升级模块完成最终切换与复位接口边界已验证玩具调用方式、公共升级接口和工程任务配置
任意下载中断都一定自动回滚不能仅凭应用层证明最终启动与回滚策略位于杰理升级模块和 Bootloader 边界
固件具备官方数字签名和安全启动未证明当前应用层只明确校验文件 SHA-256

本文不会展示真实设备身份、App Key、签名、领取 Token 或带鉴权参数的下载 URL。当前配置中仍有联调固定值和允许 HTTP 的测试开关,这是需要整改的工程事实,不应复制到公开文章或量产固件。

一、先画出完整生命周期#

一次 OTA 横跨两次启动:

旧固件本次启动
→ DHCP成功
→ 网络与业务连续稳定3秒
→ 准备或读取OTA凭据
→ HMAC鉴权检查更新
→ 校验升级任务
→ 流式下载并写入非活动区
→ 校验长度与SHA-256
→ 保存pending记录
→ 正常关闭升级流,底层安排切换和复位
新固件下次启动
→ DHCP成功并再次进入OTA任务
→ 读取pending记录
→ 比较目标version_code与当前编译版本
→ 上报success或boot_not_target
→ 后台接受后清除pending

所以,“下载进度 100%”最多表示字节接收完毕,绝不等于升级成功。真正的成功至少要跨过内容校验、升级模块收尾、启动槽切换、新固件启动和结果上报五个边界。

二、三个模块怎样分工#

当前 OTA 不是一个大函数,而是三个第一方模块协作:

模块责任不负责什么
paipop_ota.c生命周期、业务门控、下载、Flash 写入、进度、pending 和重试不拼认证请求细节
paipop_ota_protocol.c时间同步、HMAC、HTTP POST、JSON 严格解析、任务与状态接口不决定玩具何时可升级
paipop_ota_credentials.c固定配置/VM/首次领取三种凭据来源、校验和清零不下载固件

再往下,httpclinet_update、双备份写入任务和 Bootloader 属于杰理平台边界;眼睛、按键、摇一摇和 FSM 属于产品协作边界。

这种拆分让协议变化不必侵入 Flash 状态机,也让玩具交互规则不必知道 HMAC 和 JSON 字段。

三、paipop_ota_init() 为什么不检查更新#

应用启动时调用:

paipop_ota_init();

它只清理控制结构、设置初始化标志并打印版本,不访问网络。真正的启动信号来自 Wi-Fi DHCP 成功后的:

paipop_ota_network_ready();

check_started 保证一次开机只创建一个 OTA 检查线程。网络反复断开、重连不会不断创建重复线程;network_ready 只是实时条件,线程内部会等待它恢复。

这两个变量不能混为一谈:

check_started:本次启动是否已经创建过工作线程
network_ready:当前网络是否仍然满足运行条件

网络断开或模块反初始化时,如果存在活动 HTTP,代码调用 http_ops.quit() 主动打断阻塞读取。否则线程可能一直卡在网络栈内部,无法及时释放升级资源。

四、为什么不是固定延时,而是连续稳定 3 秒#

旧实现可以用“联网后睡眠若干秒”给系统让路;当前 V1.2.8 改成了更精确的稳定窗口。

OTA 只有同时满足以下条件,才开始累计稳定时间:

network_ready == true
Wi-Fi状态 == DHCP_SUCC
FSM状态 == ENGINE_EXIT 或 ENGINE_INIT_FAILED
当前没有提示音播放

四项条件连续成立 3000 ms 才通过。任何一项失效,stable_since 都会清零重新计算。

这与简单的 msleep(3000) 有本质区别:

简单睡眠:三秒后继续,不关心期间是否断网或进入对话
稳定窗口:条件必须在整段三秒内持续为真

ENGINE_EXIT 在这个项目里表示语音引擎已退出本轮活动、设备等待下一次触发,不是程序异常退出。ENGINE_INIT_FAILED 允许云端对话暂时不可用时仍然修复固件。

检查更新阶段并不设置 ota.busy,用户仍可触发正常交互;如果状态变化,OTA 会重新等待空闲。只有真正开始下载和写入后才置 busy,缩短对产品功能的冻结时间。

五、凭据为什么有三条来源#

OTA 后台凭据与 PaipopSDK 的 PaipopKey 是两套系统,不能混用。当前加载顺序可以概括为:

编译配置中存在App Key
→ 直接使用
否则读取专用VM记录
→ 有效则使用
否则使用一次性provision token领取
→ 保存VM
→ 立即读回比对

首次领取最多尝试三次,间隔是 0、10 秒和 60 秒;只有网络、超时等可恢复错误才继续,明显的参数或认证错误会提前停止。

VM 记录包含:

magic + schema + length
设备SN的SHA-256摘要
base_url + app_id + app_key + key_version
CRC32

设备摘要用于防止一台设备误读另一身份的记录,CRC32 用于发现掉电或存储损坏。写入后再次加载,并比对 App Key 与 Key Version,形成“写入—读回—验证”闭环。

但要说清安全边界:CRC32 不是消息认证码,VM 也不是安全芯片。它们能发现随机损坏,不能防止有能力读取或改写 Flash 的攻击者。量产方案应使用每台设备独立密钥、安全存储、调试口保护和服务端撤销机制。

六、HMAC 请求是怎样生成的#

检查和状态上报都使用带签名的 POST。设备先保证系统时间不早于合理纪元;时间无效时启动独立 NTP 任务,最多等待 15 秒,每 200 ms 轮询一次。

随后把三个字段分别做 RFC 3986 百分号编码,并按固定次序构造规范串:

app_id=<encoded>&sn=<encoded>&timestamp=<encoded>

签名计算为:

digest = HMAC-SHA256(app_key, canonical_string)
signature = Base64(digest)

请求头携带 app_id、设备身份、毫秒时间戳、签名和 key_version。App Key 本身不会通过网络发送。服务端用同一密钥重算并比较,时间戳则可用于限制重放窗口。

实现还有两类输入防护:

  • 头字段拒绝 CR、LF、控制字符,避免请求头注入;
  • URL 拒绝换行、#@、localhost 和 127.0.0.1,限制明显的解析歧义和本地回环目标。

不过 HMAC 只证明“请求方掌握密钥、签名字段未被改动”,不加密通信,也不自动验证响应服务器身份。因此量产环境仍必须使用 HTTPS 并校验证书。当前源码允许 HTTP 的宏只能属于受控联调配置。

七、检查更新到底上传了什么#

设备调用 OTA check 接口时,上送:

device_unique_id
hardware_model
board
current_version / current_version_code
partition_layout
battery_percent / charging
wifi_rssi

这些字段服务于三个目标:身份定位、固件兼容性匹配和发布策略判断。

版本名是给人看的,真正用于单调升级判断的是整数 version_code。硬件型号、板型和分区布局则防止把“同名版本”的错误二进制发给不兼容设备。

充电或外部供电场景会把有效电量按 100% 报告,避免 USB 联调机因电池采样被后台误拦截;普通电池读数会限制在 0~100。

响应不是看到 JSON 就接受。协议层要求:

  • HTTP 为 2xx,业务 code == 200data 必须是对象;
  • has_update 必须是布尔值;
  • 有更新时,Task ID、Firmware ID、目标版本、下载 URL、过期时间、大小和 SHA-256 都必须存在且类型正确;
  • version_code 必须是无小数的合法 32 位整数;
  • 过期时间必须是 JavaScript 安全整数范围内的非负整数;
  • SHA-256 必须恰好是 64 个十六进制字符。

这种严格解析很重要。嵌入式 C 代码不应把服务端响应当成天然可信输入。

八、协议解析通过后,为什么还要本地二次校验#

协议层只验证“响应格式成立”;业务层还要判断“当前设备现在是否应该执行”。paipop_ota_validate_offer() 会继续检查:

任务ID、版本、URL、大小和SHA-256有效
target_version_code > 当前version_code
下载URL不是即将过期
电量 >= 30%
Wi-Fi RSSI >= -75 dBm

如果设备时间有效,URL 至少还要剩 30 秒;下载前会再次检查,避免排队等待后拿着已经失效的临时 URL 开始写 Flash。

这体现两层校验:

协议校验:这个响应能否被安全解析?
业务校验:这个任务是否适用于此设备、此时刻?

后台策略是第一道门,本地硬门槛是最后一道门。即使后台误配,低版本覆盖高版本、低电量升级或弱信号下载也应在设备侧被拒绝。

九、检查与下载怎样重试#

检查更新最多三轮,计划延时为:

立即、60秒、300秒

但不是所有结果都重试:命中更新、明确无更新、凭据不可用或认证失败都会结束本轮;只有其余瞬态错误进入下一轮。每一轮之前都重新等待网络和业务稳定。

下载最多尝试两次,失败间隔 2 秒。若临时 URL 已过期,代码最多重新 check 一次获取新任务信息,并重新上报 detected/confirmed,而不是无限刷新。

这是“有限重试”而不是“失败就死循环”。量产设备若无退避地持续请求,服务端故障时会形成惊群和自我放大流量。

十、4 KiB 流式下载怎样工作#

下载缓冲区为:

#define PAIPOP_OTA_DOWNLOAD_BUF_SIZE (4 * 1024)

核心循环可以抽象成:

HTTP读取最多4 KiB
→ 检查累计长度不能超过offer.size
→ SHA-256上下文累计update
→ net_fwrite写入升级流
→ 检查返回长度必须等于输入长度
→ 更新进度与眼睛显示

因此 RAM 占用与固件总大小基本解耦。设备不需要先把整个 OTA 包放进内存,也不需要普通文件系统作为中转。

开始写入前还要求:

HTTP状态码 == 200
响应头存在
Content-Length == offer.size

结束时再次要求:

downloaded == offer.size
计算所得SHA-256 == 后台摘要

前面的 Content-Length 检查防止明显错误,后面的实际计数防止连接提前结束,SHA-256 防止内容变化。三者解决的是不同故障,不能相互替代。

十一、net_fopen() 不是普通文件写入#

代码使用:

update_fd = net_fopen(CONFIG_UPGRADE_OTA_FILE_NAME, "w");
net_fwrite(update_fd, data, len, 0);

这里的“文件”是杰理网络升级模块暴露的虚拟写入口。数据继续流向双备份升级任务,写到非活动固件区域,而不是创建一个常规磁盘文件。

工程同时满足几个构建条件:

  • CONFIG_DOUBLE_BANK_ENABLE 已开启;
  • Paipop OTA 三个源文件进入目标 Makefile;
  • 公共升级代码和 updatedw_update 任务进入最终程序;
  • 完整 USB 基线使用 8 MB 双备份布局;
  • 后台上传版本化的 db_update_files_data_<版本>.bin,而不是把 jl_isd.fw 与 OTA 包混用。

单备份设备不能靠一次应用 OTA 凭空变成双备份布局。分区变更必须先通过完整 USB 烧录建立正确基线。

当前工程还在 Flash 尾部保留独立音频资源区域。不能仅凭 Firmware OTA 成功就假设所有外部资源一定同步更新;资源是否进入升级包必须以打包清单和实机验证为准。

十二、ota.busy 怎样保护产品交互#

真正下载开始时:

ota.busy = 1;
paipop_fsm_cancel_push_to_talk();
paipop_eye_ota_begin();

按键、摇一摇、FSM 和眼睛服务都会查询 paipop_ota_is_busy()

  • 新的对话触发被拦截;
  • 摇晃打断不再启动另一条语音链;
  • 普通随机眼睛动作暂停;
  • OTA 十段式进度覆盖普通状态图标。

眼睛显示按 10% 粒度更新,而后台状态上报携带实际进度。显示层不需要为每个数据块刷新 LCD,协议层仍能获得更准确的遥测。

失败路径会关闭升级流并传入 abort 标志、释放 SHA 上下文和 4 KiB 缓冲、清除 busy、恢复眼睛。成功路径故意不清 busy,因为设备已经进入“等待底层复位”的不可交互阶段。

十三、为什么必须先保存 pending,再正常关闭升级流#

下载和哈希都通过后,顺序是:

保存pending记录
→ 上报downloaded
→ net_fclose(update_fd, 0)
→ 底层完成镜像收尾、启动信息更新并安排复位

pending 记录包含目标版本号、目标版本名、Task ID、总字节数、阶段、magic、schema 和校验值。

其校验是一个类似 FNV-1a 的 32 位非加密校验,只用于识别随机损坏,不应描述成数字签名或防篡改机制。

为什么 pending 必须在切换前落盘?因为复位之后,原进程的 RAM 全部消失。若先触发切换再写记录,设备可能已经重启,却没有任何持久状态告诉新固件“上一轮任务是谁、目标是什么、结果应上报给谁”。

如果 pending 保存失败,本次升级直接失败,不进入启动切换。

net_fclose(fd, 0) 的 0 表示正常完成;失败时使用 net_fclose(fd, 1) 中止。玩具层成功后不再主动调用 system_reset(),避免在底层尚未完成镜像校验或启动信息写入时抢先复位。

十四、新固件如何完成跨重启确认#

下一次启动、联网并稳定后,OTA 线程先处理 pending,再检查新任务。

判断规则很直接:

pending.target_version_code == 当前编译version_code
→ 上报success
否则
→ 上报failed / boot_not_target

只有状态接口返回并确认 accepted == true,设备才清除 pending。上报时断网,记录会继续存在,下次启动或重新进入流程时还能补报。

这里需要避免过度解读:版本不匹配只能证明“当前没有运行目标版本”,可能是切换失败、底层拒绝镜像或其他启动路径。应用层没有读到一个权威的 Bootloader rollback reason,因此不会武断上报 rollback

这正是证据边界意识:有 ROLLBACK 枚举,不代表当前代码已经能可靠识别所有回滚。

十五、三种完整性机制不要混淆#

机制当前作用能解决不能解决
HMAC-SHA256请求鉴权证明请求方掌握 App Key、保护签名字段不加密链路,不证明固件官方签发
文件 SHA-256下载内容校验发现下载内容与后台摘要不同攻击者同时替换文件和摘要时无能为力
双备份写入与启动隔离下载失败时不直接覆盖当前活动槽不天然等于签名验证、安全启动或健康回滚

成熟量产链路还应增加:

HTTPS与证书校验
发布私钥签名固件摘要
设备内置公钥验签
Bootloader安全启动
新固件试运行与健康确认
失败阈值触发自动回滚

一句面试表达是:

双备份解决可恢复性,哈希解决传输完整性,HMAC解决设备请求认证;固件来源真实性要靠非对称数字签名,启动阶段的持续可信还要靠安全启动和可确认回滚。

十六、状态上报为什么不只是日志#

设备会沿生命周期上报:

detected
→ confirmed
→ downloading
→ downloaded
→ success / failed

上报包含 Task ID、目标版本、当前版本、进度、下载字节数、总大小和稳定错误码。Task ID 标识一次发布任务,Firmware ID 标识固件实体,Key Version 标识认证密钥版本,三者职责不同。

进度上报失败不会中断一份仍在安全下载的镜像,否则短暂的遥测故障反而会放大成升级失败。相反,pending 保存失败会阻止切换,因为它影响跨重启结果闭环。

这是“控制面”和“数据面”的取舍:进度遥测可以暂时丢失,固件内容和启动状态不能含糊。

十七、异常路径逐个追问#

下载中断网#

network_down() 清网络标志并 quit 当前 HTTP。读取返回失败,升级流以 abort 方式关闭,活动槽不应被应用层覆盖。

服务端大小写错#

响应头 Content-Length 与 offer.size 不一致时,在写入主体前拒绝;即使头部碰巧一致,结束后的实际字节计数仍会再检查。

文件被截断或内容变化#

字节数或 SHA-256 任一不匹配都进入失败路径,不正常关闭升级流。

URL 排队期间过期#

下载前再次校验过期时间;最多重新检查一次获取新 URL,不沿用旧地址无限重试。

用户此时按键或摇晃#

下载阶段 ota.busy 使事件层与 FSM 拒绝新交互,避免音频、网络和 Flash 高负载链并发。

写完后突然断电#

应用层已经先保存 pending,双备份又避免直接原地覆盖当前槽;但断电点能否保证原子启动选择,最终取决于杰理升级模块与 Bootloader,必须用分阶段断电测试验证,不能只凭调用顺序宣称百分百安全。

新固件启动但暂时无网#

pending 不会因为“已经启动过一次”就清除。只有后台接受结果后才清,恢复网络后仍能补报。

十八、怎样测试这套 OTA#

至少准备旧版本完整 USB 基线和版本号更高的 OTA 目标包。后台硬件型号、板型、分区布局、文件大小和 SHA-256 必须与目标产物一致。

正常用例:

旧版本启动
→ DHCP成功
→ 空闲稳定3秒
→ check命中新版本
→ detected/confirmed/downloading/downloaded
→ 自动复位
→ 启动日志显示目标version_code
→ 联网后上报success

异常矩阵至少覆盖:

注入点预期
检查接口超时、5xx有限重试,不阻塞主业务
401/403结束本轮,定位凭据或时间问题
电量 29%、RSSI -76 dBm本地拒绝任务
目标版本等于或低于当前不执行降级
Content-Length 错误写入前或收尾时失败
下载 10%/50%/99% 断网或断电旧槽仍可启动,升级不得误报 success
SHA-256 错误abort,不切换启动目标
pending 写入失败不调用正常 finalize
新固件首次启动无网pending 保留,联网后补报
后台暂时拒绝 acceptedpending 保留并重试

还要做长时间与批量测试:连续 A/B 槽往返升级、Flash 擦写寿命、弱网抖动、NTP 失败、看门狗复位、多个版本跨越和后台灰度暂停。

十九、当前实现仍有哪些量产差距#

1. 联调凭据不能进入公开和量产配置#

固定 App Key、固定设备身份和允许 HTTP 的测试开关需要迁移到私有构建配置或工厂注入流程。日志也不能输出密钥、签名、领取 Token 或完整临时 URL。

2. 需要固件数字签名#

当前 SHA-256 由同一后台响应提供,主要用于传输一致性。应由离线发布私钥签名镜像摘要,设备或 Bootloader 用固化公钥验签。

3. 需要明确的试运行与回滚协议#

新槽第一次启动应处于 trial 状态,完成看门狗、关键任务、网络或业务健康检查后主动 confirm;超时或连续崩溃由 Bootloader 回到旧槽,并留下可读取原因。

4. 周期检查要加入随机抖动#

当前一次开机只启动一轮检查。量产后若增加周期任务,应使用指数退避和随机抖动,避免大批设备整点访问后台。

5. 资源版本要显式建模#

应用固件、眼睛素材、提示音和配置可能具有不同生命周期。后台应记录包类型、资源版本和兼容范围,设备不能只凭应用 version_code 推断所有资源都已同步。

6. 发布平台需要自动止损#

灰度阶段应监控下载失败、启动未达目标、设备离线和回滚率,超过阈值自动暂停 Task,而不是把异常继续扩散到全量设备。

二十、面试官高频追问#

“为什么不用 malloc 整个固件?”#

AC791N 的可用 RAM 远小于完整固件,整包缓存会造成峰值内存和碎片风险。4 KiB 流式读取能边哈希边写入,RAM 开销近似常量。

“下载完成为什么还不能说升级成功?”#

因为还没有证明升级模块接受镜像、启动信息已安全切换、Bootloader 能启动新槽、新固件能稳定运行。最终成功必须在重启后确认目标版本。

“SHA-256 已经匹配,还需要 HTTPS 和签名吗?”#

需要。SHA-256 只与后台给出的摘要比较;若通信被同时篡改,文件和摘要可以一起变化。HTTPS保护链路和服务器身份,数字签名证明发布者身份。

“为什么 pending 不在重启后立刻清除?”#

因为状态上报可能失败。只有后台确认接受,才说明设备端与发布平台对任务结果达成一致;提前清除会永久丢失结果。

force_update 为什么没有强制打断用户?”#

当前实现解析并保存该字段,但仍遵守空闲门控。强制升级若要改变产品行为,必须明确设计提示、超时、电量、紧急取消和数据一致性,不能因为后台一个布尔值就粗暴中断对话。

“双备份就绝对不会变砖吗?”#

不能这样承诺。它显著降低写一半覆盖当前固件的风险,但 Bootloader、启动元数据原子性、Flash 故障、分区配置错误和硬件损坏仍需测试与保护。

总结#

Paipop OTA 可以分成七层:

业务门控层
DHCP + FSM空闲 + 提示音结束 + 连续稳定3秒
身份认证层
凭据领取/VM + NTP + HMAC-SHA256
发布策略层
硬件/板型/布局/版本/电量/RSSI
传输校验层
HTTP状态 + Content-Length + 实际字节数 + SHA-256
平台写入层
net_update + 4 KiB流式写入 + 双备份非活动槽
切换事务层
先保存pending,再正常finalize并由底层安排复位
结果闭环层
新固件比对version_code + 后台accepted后清pending

最值得记住的是四个“不等于”:

  1. DHCP 成功不等于现在适合升级,还要等待业务连续空闲;
  2. 后台返回任务不等于设备必须执行,还要本地做兼容性和环境校验;
  3. 下载 100% 不等于镜像可信,还要检查实际长度和 SHA-256;
  4. 镜像写完不等于升级成功,还要跨重启确认目标版本。

把这四条边界讲清楚,面试官继续追问 HMAC、流式下载、Flash 掉电、双备份、Bootloader 回滚或量产安全时,就可以从系统设计回答,而不是停留在“调用了一个 OTA 接口”。

paipopAi对话玩具解析(七):从版本检查到双备份 OTA 安全升级
https://blog.huangzy.xyz/posts/paipopai对话玩具解析七从版本检查到双备份ota安全升级/
Author
纸翼
Published at
2026-09-10
License
CC BY-NC-SA 4.0

Some information may be outdated