3、FOTA关键技术选型:分片传输、断点续传、校验机制、安全加密
做FOTA升级,说白了就是给远在千里之外的设备“换脑子”。
这事儿看着简单,实际坑特别多。我这些年踩过的雷,十个手指头都数不过来。今天咱们就把几个关键技术掰开揉碎了讲清楚。
3.1 分片传输:大文件怎么拆?
LoRaWAN的MTU很小,一个包最多就两百多字节。你想想看,一个固件动辄几百KB,怎么传?
答案就是分片。把大固件切成小块,一块一块发。
分片大小怎么定?
我个人习惯用256字节一片。为什么?
- LoRaWAN一个包的有效载荷最多242字节(DR0场景下更少)
- 去掉协议头,256字节的片内数据刚好能塞进两个包
- 256是2的幂,计算偏移量时位运算快
分片协议结构(我常用的)
typedef struct {
uint16_t fragment_index; // 片序号,从0开始
uint16_t total_fragments; // 总片数
uint8_t data[256]; // 片内数据
uint16_t crc16; // 本片校验
} __attribute__((packed)) fota_fragment_t;
避坑指南:我曾经遇到过一个问题——分片序号用uint8_t,结果固件超过255片就溢出了。后来改成uint16_t才解决。嗯,这里要注意,别省那一个字节。
3.2 断点续传:设备断电了怎么办?
LoRaWAN升级最怕什么?传了一半,设备没电了,或者信号断了。
没有断点续传,就得从头再来。一个256KB的固件,在SF12速率下可能要传好几个小时。重传?用户早骂娘了。
我的实现方案:
- 接收状态持久化:每收到一片,就把当前进度写到Flash里
- 进度记录结构:
typedef struct {
uint32_t magic; // 魔数,0xFOTA2024
uint16_t last_received_idx; // 最后收到的片序号
uint16_t total_fragments; // 总片数
uint32_t firmware_crc32; // 固件整体CRC
uint8_t received_bitmap[32];// 位图,记录哪些片已收(支持256片)
} fota_progress_t;
小技巧:用位图记录已收片,比用数组省空间。256片只需要32字节。设备重启后,服务器问一句“你收到哪了?”,设备把位图发回去,服务器接着没传完的继续发。
断点续传的边界情况:
- 如果设备在写Flash时断电,可能导致进度记录损坏。我一般写两份备份,互为校验。
- 如果服务器端也重启了,需要设备上报进度。所以服务器也要维护一个会话状态。
3.3 校验机制:怎么保证数据没传错?
LoRaWAN的空中干扰很常见。一个比特错了,整个片就废了。
校验机制分两层:
第一层:片内校验
每个分片自带CRC16。收到后先算一遍,不对就请求重传这一片。
第二层:整体校验
所有片收完后,把固件拼起来,算一个CRC32或者SHA256。跟服务器下发的固件哈希对比。
我推荐的双重校验流程:
// 伪代码
bool verify_fragment(fota_fragment_t *frag) {
uint16_t calc_crc = crc16(frag->data, frag->data_len);
if (calc_crc != frag->crc16) {
// 请求重传
request_retransmit(frag->fragment_index);
return false;
}
// 写入Flash
write_to_flash(frag);
return true;
}
bool verify_firmware() {
uint32_t calc_crc = crc32(flash_base, firmware_size);
if (calc_crc != expected_crc32) {
// 整体校验失败,标记固件无效
mark_firmware_invalid();
return false;
}
// 校验通过,跳转执行
jump_to_firmware();
return true;
}
避坑指南:我曾经遇到过CRC32碰撞的问题——两个不同的固件算出来一样的CRC32。概率极低,但万一碰上了呢?后来我改用SHA256做整体校验。虽然计算慢一点,但安全多了。
3.4 安全加密:防止固件被篡改
你想想看,如果固件在传输过程中被人篡改了,设备执行了恶意代码,后果是什么?
轻则设备变砖,重则整个网络被控制。所以加密不是可选项,是必选项。
FRITZ框架简介
FRITZ是我参与设计的一个轻量级FOTA安全框架。核心思想就三点:
- 固件签名:用私钥对固件哈希签名,设备用公钥验签
- 传输加密:每个分片用AES-128加密,密钥由会话协商生成
- 防回滚:固件版本号递增,防止攻击者用旧版本漏洞攻击
FRITZ框架的加密流程:
// 服务器端
1. 计算固件SHA256哈希
2. 用私钥对哈希签名 (ECDSA)
3. 将固件分片,每片用AES-128-CTR加密
4. 发送加密分片 + 签名
// 设备端
1. 接收加密分片,用会话密钥解密
2. 所有片收齐后,拼成完整固件
3. 计算固件SHA256哈希
4. 用公钥验证签名
5. 验证通过,写入执行区
密钥管理:
- 每个设备出厂时烧录唯一的公钥对
- 会话密钥通过LoRaWAN的FRMPayload加密通道协商
- 私钥保存在服务器HSM中,绝不外泄
重要提醒:千万不要把私钥硬编码在固件里!我曾经见过一个项目,私钥直接写在源码里,结果固件被反编译后,整个产品线的设备都被攻破了。密钥一定要用安全元件(SE)或者TEE来保护。
3.5 综合选型建议
说了这么多,到底怎么选?我根据项目经验给个参考:
| 场景 | 分片大小 | 校验方式 | 加密方式 | 断点续传 |
|---|---|---|---|---|
| 小批量(<100台) | 128字节 | CRC16+CRC32 | AES-128 | 可选 |
| 中等规模 | 256字节 | CRC16+SHA256 | AES-256 | 必需 |
| 大规模/高安全 | 256字节 | CRC16+ECDSA签名 | AES-256+会话密钥 | 必需+位图记录 |
我个人建议,除非你的产品真的对成本极度敏感,否则直接上最高安全配置。为什么?因为FOTA升级是一次性的投入,但安全漏洞的修复成本可能是天价。
好了,关键技术选型就聊到这儿。下一章咱们讲讲怎么把这些技术落地到代码里。