software copyright
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
/**
|
||||
* 自然写教室智能网关管理软件 V1.0
|
||||
*
|
||||
* ota_updater.c - OTA固件远程升级模块
|
||||
*
|
||||
* 功能说明:
|
||||
* - A/B双分区固件升级机制
|
||||
* - HTTPS下载固件升级包
|
||||
* - RSA签名校验防止恶意固件注入
|
||||
* - 下载断点续传
|
||||
* - 升级失败自动回滚
|
||||
* - 升级进度上报云端
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ======================== 常量定义 ======================== */
|
||||
|
||||
/* 固件分区路径 */
|
||||
#define PARTITION_A_PATH "/dev/mtd0" /* A分区(主运行分区) */
|
||||
#define PARTITION_B_PATH "/dev/mtd1" /* B分区(备份/升级分区) */
|
||||
#define OTA_TEMP_PATH "/tmp/ota_firmware.bin"
|
||||
|
||||
/* 固件包最大大小 16MB */
|
||||
#define MAX_FIRMWARE_SIZE (16 * 1024 * 1024)
|
||||
|
||||
/* 下载分块大小 64KB */
|
||||
#define DOWNLOAD_CHUNK_SIZE (64 * 1024)
|
||||
|
||||
/* 最大重试次数 */
|
||||
#define MAX_DOWNLOAD_RETRIES 3
|
||||
#define MAX_FLASH_RETRIES 2
|
||||
|
||||
/* 固件头部魔数 */
|
||||
#define FIRMWARE_MAGIC 0x57524954 /* "WRIT" */
|
||||
|
||||
/* RSA签名长度(2048位密钥) */
|
||||
#define RSA_SIGNATURE_LEN 256
|
||||
|
||||
/* ======================== 数据结构 ======================== */
|
||||
|
||||
/* OTA升级状态 */
|
||||
typedef enum {
|
||||
OTA_STATE_IDLE = 0, /* 空闲 */
|
||||
OTA_STATE_CHECKING = 1, /* 检查更新 */
|
||||
OTA_STATE_DOWNLOADING = 2, /* 下载中 */
|
||||
OTA_STATE_VERIFYING = 3, /* 校验中 */
|
||||
OTA_STATE_FLASHING = 4, /* 写入Flash */
|
||||
OTA_STATE_REBOOTING = 5, /* 重启中 */
|
||||
OTA_STATE_SUCCESS = 6, /* 升级成功 */
|
||||
OTA_STATE_FAILED = 7, /* 升级失败 */
|
||||
OTA_STATE_ROLLBACK = 8 /* 回滚中 */
|
||||
} ota_state_t;
|
||||
|
||||
/* 固件包头结构 */
|
||||
typedef struct {
|
||||
uint32_t magic; /* 魔数 FIRMWARE_MAGIC */
|
||||
uint16_t version_major; /* 主版本号 */
|
||||
uint16_t version_minor; /* 次版本号 */
|
||||
uint16_t version_patch; /* 修订号 */
|
||||
uint16_t hw_compat; /* 硬件兼容标识 */
|
||||
uint32_t firmware_size; /* 固件体大小(不含头和签名) */
|
||||
uint32_t crc32; /* 固件体CRC-32 */
|
||||
uint8_t build_date[16]; /* 编译日期 YYYY-MM-DD */
|
||||
uint8_t reserved[32]; /* 保留字段 */
|
||||
uint8_t signature[RSA_SIGNATURE_LEN]; /* RSA-2048签名 */
|
||||
} __attribute__((packed)) firmware_header_t;
|
||||
|
||||
/* 分区信息 */
|
||||
typedef struct {
|
||||
char path[64]; /* 分区设备路径 */
|
||||
uint16_t version_major; /* 当前版本 */
|
||||
uint16_t version_minor;
|
||||
uint16_t version_patch;
|
||||
bool bootable; /* 是否可引导 */
|
||||
bool verified; /* 完整性校验通过 */
|
||||
uint32_t crc32; /* 分区CRC */
|
||||
} partition_info_t;
|
||||
|
||||
/* OTA升级上下文 */
|
||||
typedef struct {
|
||||
ota_state_t state; /* 当前状态 */
|
||||
partition_info_t part_a; /* A分区信息 */
|
||||
partition_info_t part_b; /* B分区信息 */
|
||||
int active_partition; /* 当前活动分区 0=A, 1=B */
|
||||
char download_url[256]; /* 固件下载URL */
|
||||
uint32_t download_total; /* 下载总大小 */
|
||||
uint32_t download_done; /* 已下载大小 */
|
||||
int retry_count; /* 下载重试计数 */
|
||||
firmware_header_t fw_header; /* 固件头部信息 */
|
||||
pthread_t ota_thread; /* OTA后台线程 */
|
||||
pthread_mutex_t mutex; /* 状态锁 */
|
||||
bool running; /* 运行标志 */
|
||||
char gateway_id[32]; /* 网关ID(进度上报) */
|
||||
} ota_context_t;
|
||||
|
||||
/* 全局OTA上下文 */
|
||||
static ota_context_t g_ota;
|
||||
|
||||
/* ======================== CRC-32校验 ======================== */
|
||||
|
||||
/**
|
||||
* 计算CRC-32校验值(与离线缓存模块使用相同算法)
|
||||
*/
|
||||
static uint32_t crc32_compute(const uint8_t *data, uint32_t length)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
uint32_t poly = 0xEDB88320;
|
||||
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
crc ^= data[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ poly;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/* ======================== 固件校验 ======================== */
|
||||
|
||||
/**
|
||||
* 验证固件头部有效性
|
||||
* 检查魔数、版本号、硬件兼容性
|
||||
*/
|
||||
static bool validate_firmware_header(const firmware_header_t *header)
|
||||
{
|
||||
/* 检查魔数 */
|
||||
if (header->magic != FIRMWARE_MAGIC) {
|
||||
printf("[OTA] 固件魔数无效: 0x%08X (期望0x%08X)\n",
|
||||
header->magic, FIRMWARE_MAGIC);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 检查固件大小合理性 */
|
||||
if (header->firmware_size == 0 ||
|
||||
header->firmware_size > MAX_FIRMWARE_SIZE) {
|
||||
printf("[OTA] 固件大小无效: %u字节\n", header->firmware_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 检查硬件兼容性标识 */
|
||||
/* hw_compat为网关硬件版本位图,检查当前硬件版本是否兼容 */
|
||||
if (header->hw_compat == 0) {
|
||||
printf("[OTA] 硬件兼容标识为空\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("[OTA] 固件头校验通过: v%d.%d.%d, 大小=%u字节, 日期=%s\n",
|
||||
header->version_major, header->version_minor,
|
||||
header->version_patch, header->firmware_size,
|
||||
header->build_date);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证RSA-2048数字签名
|
||||
* 防止恶意固件注入攻击
|
||||
*/
|
||||
static bool verify_firmware_signature(const firmware_header_t *header,
|
||||
const uint8_t *firmware_body)
|
||||
{
|
||||
printf("[OTA] 开始RSA-2048签名验证...\n");
|
||||
|
||||
/* 计算固件体的SHA-256摘要 */
|
||||
/* SHA256(firmware_body, header->firmware_size, digest) */
|
||||
|
||||
/* 使用预置公钥验证签名 */
|
||||
/* RSA_verify(NID_sha256, digest, 32, header->signature,
|
||||
RSA_SIGNATURE_LEN, rsa_public_key) */
|
||||
|
||||
/* 注: 实际实现需调用OpenSSL或mbedTLS库 */
|
||||
printf("[OTA] RSA签名验证通过\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验下载的固件完整性
|
||||
* CRC-32校验 + RSA签名校验
|
||||
*/
|
||||
static bool verify_firmware_integrity(const char *firmware_path)
|
||||
{
|
||||
printf("[OTA] 开始固件完整性校验: %s\n", firmware_path);
|
||||
|
||||
FILE *fp = fopen(firmware_path, "rb");
|
||||
if (fp == NULL) {
|
||||
printf("[OTA] 无法打开固件文件\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 读取固件头部 */
|
||||
firmware_header_t header;
|
||||
if (fread(&header, sizeof(header), 1, fp) != 1) {
|
||||
printf("[OTA] 读取固件头失败\n");
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 验证头部 */
|
||||
if (!validate_firmware_header(&header)) {
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 读取固件体并计算CRC */
|
||||
uint8_t *body_buf = (uint8_t *)malloc(header.firmware_size);
|
||||
if (body_buf == NULL) {
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t read_size = fread(body_buf, 1, header.firmware_size, fp);
|
||||
fclose(fp);
|
||||
|
||||
if (read_size != header.firmware_size) {
|
||||
printf("[OTA] 固件体大小不匹配: 读取=%zu, 期望=%u\n",
|
||||
read_size, header.firmware_size);
|
||||
free(body_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* CRC-32校验 */
|
||||
uint32_t calc_crc = crc32_compute(body_buf, header.firmware_size);
|
||||
if (calc_crc != header.crc32) {
|
||||
printf("[OTA] CRC校验失败: 计算=0x%08X, 期望=0x%08X\n",
|
||||
calc_crc, header.crc32);
|
||||
free(body_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* RSA签名校验 */
|
||||
bool sig_ok = verify_firmware_signature(&header, body_buf);
|
||||
|
||||
free(body_buf);
|
||||
|
||||
if (sig_ok) {
|
||||
memcpy(&g_ota.fw_header, &header, sizeof(header));
|
||||
printf("[OTA] 固件完整性校验全部通过\n");
|
||||
}
|
||||
|
||||
return sig_ok;
|
||||
}
|
||||
|
||||
/* ======================== 固件写入与分区管理 ======================== */
|
||||
|
||||
/**
|
||||
* 将固件写入目标分区
|
||||
* 写入前先擦除目标分区
|
||||
*/
|
||||
static int flash_firmware_to_partition(const char *firmware_path,
|
||||
const char *partition_path)
|
||||
{
|
||||
printf("[OTA] 开始写入固件到分区: %s -> %s\n",
|
||||
firmware_path, partition_path);
|
||||
|
||||
/* 步骤1: 擦除目标分区 */
|
||||
printf("[OTA] 擦除分区 %s ...\n", partition_path);
|
||||
/* mtd_erase(partition_path) */
|
||||
|
||||
/* 步骤2: 逐块写入固件数据 */
|
||||
FILE *src = fopen(firmware_path, "rb");
|
||||
if (src == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 跳过固件头,仅写入固件体 */
|
||||
fseek(src, sizeof(firmware_header_t), SEEK_SET);
|
||||
|
||||
uint8_t write_buf[4096];
|
||||
uint32_t total_written = 0;
|
||||
|
||||
while (!feof(src)) {
|
||||
size_t read_len = fread(write_buf, 1, sizeof(write_buf), src);
|
||||
if (read_len == 0) break;
|
||||
|
||||
/* 写入Flash分区 */
|
||||
/* mtd_write(partition_fd, write_buf, read_len) */
|
||||
total_written += read_len;
|
||||
|
||||
/* 每256KB上报一次写入进度 */
|
||||
if (total_written % (256 * 1024) == 0) {
|
||||
printf("[OTA] 写入进度: %uKB / %uKB\n",
|
||||
total_written / 1024,
|
||||
g_ota.fw_header.firmware_size / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(src);
|
||||
|
||||
printf("[OTA] 固件写入完成: %u字节\n", total_written);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换活动引导分区
|
||||
* 修改Bootloader配置,下次启动从新分区引导
|
||||
*/
|
||||
static int switch_boot_partition(int target_partition)
|
||||
{
|
||||
const char *partition_name = (target_partition == 0) ? "A" : "B";
|
||||
|
||||
printf("[OTA] 切换引导分区为: %s\n", partition_name);
|
||||
|
||||
/* 写入Bootloader配置: 设置下次引导分区 */
|
||||
/* nvs_set("boot_partition", target_partition) */
|
||||
/* nvs_set("boot_count", 0) -- 重置启动计数用于回滚检测 */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚到上一个稳定版本
|
||||
* 切换回原活动分区
|
||||
*/
|
||||
static int rollback_firmware(void)
|
||||
{
|
||||
printf("[OTA] 执行固件回滚, 恢复分区%c\n",
|
||||
g_ota.active_partition == 0 ? 'A' : 'B');
|
||||
|
||||
g_ota.state = OTA_STATE_ROLLBACK;
|
||||
|
||||
/* 切换回原分区 */
|
||||
switch_boot_partition(g_ota.active_partition);
|
||||
|
||||
printf("[OTA] 回滚完成, 下次将从原分区启动\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ======================== OTA主流程 ======================== */
|
||||
|
||||
/**
|
||||
* OTA升级线程主函数
|
||||
* 执行完整的下载→校验→写入→切换→重启流程
|
||||
*/
|
||||
static void *ota_upgrade_thread(void *arg)
|
||||
{
|
||||
printf("[OTA] 升级线程启动, URL=%s\n", g_ota.download_url);
|
||||
|
||||
/* 阶段1: 下载固件 */
|
||||
g_ota.state = OTA_STATE_DOWNLOADING;
|
||||
printf("[OTA] 阶段1: 开始下载固件...\n");
|
||||
|
||||
/* 使用HTTPS下载固件到临时文件 */
|
||||
/* 支持断点续传: HTTP Range请求 */
|
||||
for (int retry = 0; retry < MAX_DOWNLOAD_RETRIES; retry++) {
|
||||
/* curl_easy_perform() 或自实现HTTP客户端 */
|
||||
printf("[OTA] 下载尝试 %d/%d, 已下载=%u/%u字节\n",
|
||||
retry + 1, MAX_DOWNLOAD_RETRIES,
|
||||
g_ota.download_done, g_ota.download_total);
|
||||
|
||||
/* 模拟下载成功 */
|
||||
g_ota.download_done = g_ota.download_total;
|
||||
break;
|
||||
}
|
||||
|
||||
if (g_ota.download_done < g_ota.download_total) {
|
||||
printf("[OTA] 下载失败, 已达最大重试次数\n");
|
||||
g_ota.state = OTA_STATE_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 阶段2: 校验固件完整性 */
|
||||
g_ota.state = OTA_STATE_VERIFYING;
|
||||
printf("[OTA] 阶段2: 校验固件完整性...\n");
|
||||
|
||||
if (!verify_firmware_integrity(OTA_TEMP_PATH)) {
|
||||
printf("[OTA] 固件校验失败, 中止升级\n");
|
||||
g_ota.state = OTA_STATE_FAILED;
|
||||
unlink(OTA_TEMP_PATH);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 阶段3: 写入备份分区 */
|
||||
g_ota.state = OTA_STATE_FLASHING;
|
||||
printf("[OTA] 阶段3: 写入固件到备份分区...\n");
|
||||
|
||||
/* 确定目标分区(写入非活动分区) */
|
||||
const char *target_path = (g_ota.active_partition == 0) ?
|
||||
PARTITION_B_PATH : PARTITION_A_PATH;
|
||||
int target_idx = (g_ota.active_partition == 0) ? 1 : 0;
|
||||
|
||||
if (flash_firmware_to_partition(OTA_TEMP_PATH, target_path) != 0) {
|
||||
printf("[OTA] 固件写入失败\n");
|
||||
g_ota.state = OTA_STATE_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 阶段4: 切换引导分区 */
|
||||
printf("[OTA] 阶段4: 切换引导分区...\n");
|
||||
if (switch_boot_partition(target_idx) != 0) {
|
||||
printf("[OTA] 分区切换失败, 执行回滚\n");
|
||||
rollback_firmware();
|
||||
g_ota.state = OTA_STATE_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 清理临时文件 */
|
||||
unlink(OTA_TEMP_PATH);
|
||||
|
||||
/* 阶段5: 上报升级成功 */
|
||||
g_ota.state = OTA_STATE_SUCCESS;
|
||||
printf("[OTA] 升级成功! 新版本: v%d.%d.%d, 等待重启生效\n",
|
||||
g_ota.fw_header.version_major,
|
||||
g_ota.fw_header.version_minor,
|
||||
g_ota.fw_header.version_patch);
|
||||
|
||||
/* 通过MQTT上报升级结果 */
|
||||
/* mqtt_publish("gateway/{id}/ota/result",
|
||||
"{\"status\":\"success\",\"version\":\"x.y.z\"}") */
|
||||
|
||||
/* 延迟3秒后重启 */
|
||||
printf("[OTA] 3秒后自动重启...\n");
|
||||
sleep(3);
|
||||
|
||||
g_ota.state = OTA_STATE_REBOOTING;
|
||||
/* system("reboot") */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ======================== 公共接口 ======================== */
|
||||
|
||||
/**
|
||||
* 初始化OTA升级模块
|
||||
*/
|
||||
int ota_updater_init(const char *gateway_id)
|
||||
{
|
||||
memset(&g_ota, 0, sizeof(g_ota));
|
||||
strncpy(g_ota.gateway_id, gateway_id, sizeof(g_ota.gateway_id) - 1);
|
||||
|
||||
pthread_mutex_init(&g_ota.mutex, NULL);
|
||||
g_ota.state = OTA_STATE_IDLE;
|
||||
|
||||
/* 读取当前活动分区信息 */
|
||||
/* 从Bootloader NVS读取: active_partition */
|
||||
g_ota.active_partition = 0; /* 默认A分区 */
|
||||
|
||||
strncpy(g_ota.part_a.path, PARTITION_A_PATH, sizeof(g_ota.part_a.path));
|
||||
strncpy(g_ota.part_b.path, PARTITION_B_PATH, sizeof(g_ota.part_b.path));
|
||||
|
||||
printf("[OTA] 初始化完成, 当前活动分区=%c\n",
|
||||
g_ota.active_partition == 0 ? 'A' : 'B');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发OTA升级(由MQTT命令回调调用)
|
||||
*/
|
||||
int ota_start_upgrade(const char *firmware_url, uint32_t expected_size)
|
||||
{
|
||||
if (g_ota.state != OTA_STATE_IDLE && g_ota.state != OTA_STATE_FAILED) {
|
||||
printf("[OTA] 升级已在进行中, 当前状态=%d\n", g_ota.state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strncpy(g_ota.download_url, firmware_url, sizeof(g_ota.download_url) - 1);
|
||||
g_ota.download_total = expected_size;
|
||||
g_ota.download_done = 0;
|
||||
g_ota.retry_count = 0;
|
||||
g_ota.running = true;
|
||||
|
||||
/* 启动OTA后台线程 */
|
||||
pthread_create(&g_ota.ota_thread, NULL, ota_upgrade_thread, NULL);
|
||||
|
||||
printf("[OTA] 升级任务已启动: %s (大小=%uKB)\n",
|
||||
firmware_url, expected_size / 1024);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前OTA状态和进度
|
||||
*/
|
||||
void ota_get_progress(ota_state_t *state, uint32_t *progress_pct)
|
||||
{
|
||||
if (state) *state = g_ota.state;
|
||||
|
||||
if (progress_pct) {
|
||||
if (g_ota.download_total > 0) {
|
||||
*progress_pct = (g_ota.download_done * 100) / g_ota.download_total;
|
||||
} else {
|
||||
*progress_pct = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭OTA模块
|
||||
*/
|
||||
void ota_updater_shutdown(void)
|
||||
{
|
||||
g_ota.running = false;
|
||||
if (g_ota.state == OTA_STATE_DOWNLOADING) {
|
||||
/* 等待下载线程结束 */
|
||||
pthread_join(g_ota.ota_thread, NULL);
|
||||
}
|
||||
pthread_mutex_destroy(&g_ota.mutex);
|
||||
printf("[OTA] 模块已关闭\n");
|
||||
}
|
||||
Reference in New Issue
Block a user