2. 升级包版本管理策略设计

任务目标

设计一套OTA升级包的版本管理策略,解决以下核心问题:升级包命名规则、版本号比较逻辑、版本回滚判定,以及服务端与设备端如何协同确认“哪个包该被下载”。本案例聚焦于版本管理中最关键的三个环节:语义化版本解析、升级包元信息结构、以及设备端版本决策流程。

版本管理策略设计

命名规则:采用语义化版本号 MAJOR.MINOR.PATCH(如 2.1.0),升级包文件名格式为 product_model_MAJOR_MINOR_PATCH.bin(如 sensor_v2_2_1_0.bin)。

升级包元信息:每个升级包在服务端manifest中记录以下字段:version(目标版本号)、min_supported_version(可升级的最低版本)、size(包大小)、checksum(SHA-256)、release_notes(变更说明)。

版本决策规则:设备端仅当 目标版本 > 当前版本 且 当前版本 ≥ min_supported_version 时才允许下载升级。若升级失败,设备回滚到当前版本并记录失败原因,不自动重试同一版本超过3次。

关键完整代码

// version_manager.h
#ifndef VERSION_MANAGER_H
#define VERSION_MANAGER_H

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#define MAX_VERSION_LEN 16
#define MAX_MODEL_LEN 32
#define MAX_URL_LEN 256

typedef struct {
    char model[MAX_MODEL_LEN];
    uint16_t major;
    uint16_t minor;
    uint16_t patch;
} VersionInfo;

typedef struct {
    char version[MAX_VERSION_LEN];          // 目标版本号,如 "2.1.0"
    char min_supported[MAX_VERSION_LEN];    // 最低可升级版本
    uint32_t size;
    char checksum[65];                      // SHA-256 hex
    char url[MAX_URL_LEN];
} PackageMeta;

// 解析 "x.y.z" 到 VersionInfo,成功返回0
int parse_version(const char* str, VersionInfo* out);

// 比较版本:a>b返回1,a==b返回0,a<b返回-1
int compare_version(const VersionInfo* a, const VersionInfo* b);

// 生成升级包文件名(不含路径),如 "sensor_v2_2_1_0.bin"
void generate_package_name(const char* model, const VersionInfo* v, char* out, size_t out_size);

// 设备端决策:是否允许升级到 target
bool should_upgrade(const VersionInfo* current, const PackageMeta* target);

#endif // VERSION_MANAGER_H
// version_manager.c
#include "version_manager.h"
#include <stdio.h>

int parse_version(const char* str, VersionInfo* out) {
    if (!str || !out) return -1;
    unsigned mj, mn, pt;
    if (sscanf(str, "%u.%u.%u", &mj, &mn, &pt) != 3) return -1;
    if (mj > 65535 || mn > 65535 || pt > 65535) return -1;
    out->major = (uint16_t)mj;
    out->minor = (uint16_t)mn;
    out->patch = (uint16_t)pt;
    return 0;
}

int compare_version(const VersionInfo* a, const VersionInfo* b) {
    if (a->major != b->major) return a->major > b->major ? 1 : -1;
    if (a->minor != b->minor) return a->minor > b->minor ? 1 : -1;
    if (a->patch != b->patch) return a->patch > b->patch ? 1 : -1;
    return 0;
}

void generate_package_name(const char* model, const VersionInfo* v, char* out, size_t out_size) {
    snprintf(out, out_size, "%s_v%d_%d_%d.bin", model, v->major, v->minor, v->patch);
}

bool should_upgrade(const VersionInfo* current, const PackageMeta* target) {
    VersionInfo target_v, min_v;
    if (parse_version(target->version, &target_v) != 0) return false;
    if (parse_version(target->min_supported, &min_v) != 0) return false;

    // 目标必须高于当前
    if (compare_version(&target_v, current) <= 0) return false;
    // 当前版本必须不低于最低支持版本
    if (compare_version(current, &min_v) < 0) return false;
    return true;
}
// 使用示例(main.c 片段)
#include <stdio.h>
#include "version_manager.h"

int main() {
    VersionInfo current = {0};
    parse_version("1.2.0", &current);

    PackageMeta pkg = {
        .version = "2.1.0",
        .min_supported = "1.0.0",
        .size = 4096,
        .checksum = "ab12...",
        .url = "https://ota.example.com/sensor_v2_2_1_0.bin"
    };

    if (should_upgrade(&current, &pkg)) {
        char fname[64];
        VersionInfo tv;
        parse_version(pkg.version, &tv);
        generate_package_name("sensor", &tv, fname, sizeof(fname));
        printf("允许升级,下载包名: %s\n", fname);
    } else {
        printf("不允许升级\n");
    }
    return 0;
}

编译与运行

gcc -Wall -o version_demo main.c version_manager.c
./version_demo

验证方法

运行后预期输出(由代码逻辑唯一确定):

允许升级,下载包名: sensor_v2_2_1_0.bin

手动验证边界条件:将 current 改为 2.1.0(应拒绝,目标不高于当前);将 min_supported 改为 1.3.0(应拒绝,当前1.2.0低于最低支持)。修改后重新编译运行,观察输出与预期一致。

关键说明

  • 版本号比较采用逐段数值比较,不依赖字符串排序,避免 "10.0.0" < "9.0.0" 这类字典序错误。
  • 回滚策略在设备端固件中实现:升级失败时从备份分区恢复,并将当前版本号回退到升级前值,同时将失败计数写入持久化存储;同一目标版本失败3次后不再自动尝试,需人工介入。
  • 服务端manifest需按版本号降序排列,设备端拉取后遍历找到第一个 should_upgrade 返回true的包即为最优目标。