3. 固件镜像头部定义
任务目标
为Bootloader差分OTA机制定义统一的固件镜像头部结构,包含魔数、版本号、镜像长度、CRC32校验值等关键字段,并实现对应的头部解析函数,使Bootloader能够识别合法镜像并提取升级所需元数据。
镜像头部结构定义
头部采用固定长度64字节设计,字段按小端序存储。魔数用于快速识别固件合法性,版本号采用主版本+次版本组合,CRC32覆盖整个镜像文件(含头部自身)。头部结构体定义如下:
// firmware_header.h
#ifndef FIRMWARE_HEADER_H
#define FIRMWARE_HEADER_H
#include <stdint.h>
#define FW_HEADER_MAGIC 0x4649524D // "FIRM"
#define FW_HEADER_SIZE 64
#define FW_VERSION_MAJOR_MIN 1
#define FW_VERSION_MINOR_MIN 0
#pragma pack(push, 1)
typedef struct {
uint32_t magic; // 魔数,固定为0x4649524D
uint16_t version_major; // 主版本号
uint16_t version_minor; // 次版本号
uint32_t image_length; // 固件总长度(含头部)
uint32_t crc32; // 整个镜像的CRC32校验值
uint32_t reserved[3]; // 保留字段,置0
uint8_t reserved2[32]; // 保留区域,置0
} firmware_header_t;
#pragma pack(pop)
// 解析结果状态码
#define FW_PARSE_OK 0
#define FW_ERR_MAGIC -1
#define FW_ERR_VERSION -2
#define FW_ERR_LENGTH -3
#define FW_ERR_CRC -4
// 解析头部并校验
int firmware_header_parse(const uint8_t *image, uint32_t image_size,
firmware_header_t *out_header);
// 计算CRC32(标准多项式0xEDB88320)
uint32_t crc32_compute(const uint8_t *data, uint32_t len);
#endif
头部解析实现
// firmware_header.c
#include "firmware_header.h"
#include <string.h>
// CRC32查表法实现
static uint32_t crc32_table[256];
static uint8_t crc_table_ready = 0;
static void crc32_init_table(void) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t c = i;
for (int j = 0; j < 8; j++) {
c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
}
crc32_table[i] = c;
}
crc_table_ready = 1;
}
uint32_t crc32_compute(const uint8_t *data, uint32_t len) {
if (!crc_table_ready) crc32_init_table();
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < len; i++) {
crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
}
return crc ^ 0xFFFFFFFF;
}
int firmware_header_parse(const uint8_t *image, uint32_t image_size,
firmware_header_t *out_header) {
if (!image || !out_header || image_size < FW_HEADER_SIZE) {
return FW_ERR_LENGTH;
}
// 拷贝头部数据(防止对齐问题)
memcpy(out_header, image, FW_HEADER_SIZE);
// 1. 校验魔数
if (out_header->magic != FW_HEADER_MAGIC) {
return FW_ERR_MAGIC;
}
// 2. 校验版本号范围
if (out_header->version_major < FW_VERSION_MAJOR_MIN ||
out_header->version_minor < FW_VERSION_MINOR_MIN) {
return FW_ERR_VERSION;
}
// 3. 校验镜像长度合法性
if (out_header->image_length != image_size ||
out_header->image_length < FW_HEADER_SIZE) {
return FW_ERR_LENGTH;
}
// 4. 校验CRC32(覆盖整个镜像)
uint32_t calc_crc = crc32_compute(image, image_size);
if (calc_crc != out_header->crc32) {
return FW_ERR_CRC;
}
return FW_PARSE_OK;
}
最小验证程序
// test_header.c
#include <stdio.h>
#include <string.h>
#include "firmware_header.h"
int main(void) {
// 构造一个合法的测试镜像
uint8_t image[256];
memset(image, 0, sizeof(image));
firmware_header_t hdr;
hdr.magic = FW_HEADER_MAGIC;
hdr.version_major = 1;
hdr.version_minor = 2;
hdr.image_length = sizeof(image);
hdr.crc32 = 0; // 先置0,后面计算
memset(hdr.reserved, 0, sizeof(hdr.reserved));
memset(hdr.reserved2, 0, sizeof(hdr.reserved2));
memcpy(image, &hdr, FW_HEADER_SIZE);
// 填充一些镜像数据(模拟固件内容)
for (int i = FW_HEADER_SIZE; i < (int)sizeof(image); i++) {
image[i] = (uint8_t)(i * 3);
}
// 计算并写入CRC
uint32_t crc = crc32_compute(image, sizeof(image));
memcpy(image + 8, &crc, 4); // crc32字段偏移为8
// 解析验证
firmware_header_t parsed;
int ret = firmware_header_parse(image, sizeof(image), &parsed);
if (ret == FW_PARSE_OK) {
printf("解析成功: 版本=%u.%u, 长度=%u, CRC=0x%08X\n",
parsed.version_major, parsed.version_minor,
parsed.image_length, parsed.crc32);
} else {
printf("解析失败, 错误码=%d\n", ret);
}
// 测试错误魔数场景
image[0] = 0x00;
ret = firmware_header_parse(image, sizeof(image), &parsed);
printf("错误魔数测试: 返回码=%d (预期=%d)\n", ret, FW_ERR_MAGIC);
return 0;
}
编译与运行
# 编译(需支持C99或以上标准)
gcc -Wall -Wextra -o test_header firmware_header.c test_header.c
# 运行验证
./test_header
验证方法与通过标准
- 正常路径:程序输出“解析成功: 版本=1.2, 长度=256, CRC=0xXXXXXXXX”,且CRC值与实际计算一致,说明头部解析和CRC校验逻辑正确。
- 异常路径:错误魔数测试返回码为-1(FW_ERR_MAGIC),证明非法镜像能被正确拒绝。
- 边界测试:可自行修改image_size小于64字节,应返回FW_ERR_LENGTH;修改头部版本号为0,应返回FW_ERR_VERSION。
关键说明
- 头部采用
#pragma pack(1)确保结构体在内存中紧密排列,无填充字节,保证跨平台解析一致性。 - CRC32计算覆盖整个镜像(含头部),Bootloader在升级前必须对完整镜像做此校验,防止传输损坏或非法篡改。
- 保留字段预留32字节,可用于后续扩展(如签名信息、硬件兼容性标记等),当前实现将其置0并在解析时不作额外检查。
- 实际Bootloader环境中,解析后的头部信息应传递给差分合并模块,用于确认目标版本与当前运行版本的关系,决定是否执行回滚操作。