物联网网关OTA升级需防篡改、断电变砖及状态同步:服务端下发分块哈希列表、总长与签名清单;网关用mbedtls逐块校验并原子写安全头;依赖心跳上报与时间戳实现断点续传闭环。

为什么不能直接用 HTTP GET 下载固件包
物联网网关通常资源受限(内存 curl 或 HTTPClient::GET 会把整个二进制块加载进 RAM,极易触发 OOM 或看门狗复位;更严重的是,一旦网络中断,整个下载失败,重试就得从头来——这在弱网环境(如 NB-IoT、LoRaWAN 回传)下几乎不可用。
实操建议:
- 必须分块流式下载:每次只请求并处理一个固定大小的 chunk(推荐 4KB–16KB),写入 Flash 前校验该 chunk 的
HMAC-SHA256(密钥预置在安全区) - 维护一个轻量元数据文件(如
/ota/state.json),记录已成功写入的offset、chunk_hash、total_size和signature,格式精简,避免 JSON 解析开销 - 禁用 TCP Nagle 算法(
setsockopt(..., TCP_NODELAY, ...)),减少小包延迟,尤其对 RTT > 1s 的链路敏感
如何用 mbedtls 实现带断点信息的签名验证
OTA 升级最危险的环节不是下载慢,而是加载了被篡改或截断的固件。单纯校验整个包的 SHA256 不够——攻击者可替换末尾未下载完的 chunk,而你校验的是“当前已存部分”的哈希,结果依然合法。
正确做法是服务端下发三样东西:固件分块后的每个 chunk_hash 列表、总长度 total_size、以及用私钥对这两项拼接后签名的 manifest_sig。网关端用 mbedtls 验证时,必须先解析本地 /ota/state.json,还原出当前已收 chunk 的 offset 范围,再按需计算对应位置的期望 chunk_hash,最后比对。
关键代码逻辑示意:
// 假设已读取 state.json 得到 current_offset = 12288 (3 × 4096)
// 则下一个该验的 chunk 索引是 3,查 manifest 中第 4 项 hash
uint8_t expected_hash[32];
memcpy(expected_hash, manifest.chunk_hashes[3], 32);
// 下载到 buffer 后立即计算
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx);
mbedtls_sha256_update(&ctx, rx_buffer, CHUNK_SIZE);
mbedtls_sha256_finish(&ctx, actual_hash);
if (memcmp(expected_hash, actual_hash, 32) != 0) {
// 丢弃该 chunk,不更新 state.json,等待重传
return -1;
}
Flash 写入阶段怎么避免升级中途掉电变砖
网关意外断电是 OTA 最常见的“变砖”原因。不能等全部 chunk 下完再擦除旧固件——那意味着新固件在写入过程中始终没有可运行镜像。
标准做法是采用 A/B 分区(app_a / app_b),但嵌入式 Flash 空间紧张,常改用“单区 + 安全头”方案:
- 固件起始 512 字节为自定义 header,含 magic(
0x4F544121)、version、valid_flag(0xAA55 表示有效)、crc32_of_payload - 下载时先写 payload 到临时区(如最后一 block),校验通过后再原子写 header 到主区首部(一次 512B 编程操作)
- Bootloader 启动时仅检查 header 中的
valid_flag和crc32,两者全对才跳转;否则回退到上一版本(需保留旧固件副本)
注意:Flash 编程前必须确认目标 block 已擦除(FLASH_ErasePage()),且 header 所在页不能与其他关键数据(如 WiFi 配置)混写,否则擦除会连带破坏配置。
服务端下发策略必须配合网关心跳与状态上报
网关不会主动告诉你“我卡在 chunk #27 了”。如果服务端盲目推送后续 chunk,而网关因内存不足或校验失败没确认,就会形成堆积和错位。
必须约定最小可行状态机:
- 网关每次成功写入一个 chunk 后,POST 上报
{\"offset\": 28672, \"hash\": \"a1b2c3...\", \"ts\": 1717021111}到/ota/ack - 服务端收到后,才推送下一个 chunk;超时(如 30s)未收到 ack,则暂停下发,触发告警而非重推
- 网关重启后首次连接,应主动 GET
/ota/status?sn=ABC123拉取当前升级状态(包括最新允许续传的 offset),而不是依赖本地 state.json——因为该文件可能因 Flash 写异常损坏
这个闭环里最容易被忽略的是时间戳 ts:它不是为了日志,而是让服务端识别“重复上报”(比如 ACK 包网络延迟导致重复到达),避免误判为新 chunk 成功。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











