2. 固件版本管理模块实现

任务目标

实现IoT终端固件版本管理模块,包含版本号解析与比较、版本列表维护、版本状态跟踪,并支持版本回滚与灰度发布策略。本案例聚焦于终端侧版本管理核心逻辑,不涉及OTA传输协议与服务器端实现。

核心设计

版本号采用major.minor.patch三段式结构。版本状态机包含:ACTIVE(当前运行)、STANDBY(待回滚备份)、GRAY(灰度中)、ROLLBACK(回滚目标)。灰度发布通过维护一个灰度比例阈值,当终端标识哈希落入阈值区间时允许升级。

完整实现代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#define MAX_VERSION_LEN 32
#define MAX_VERSIONS 8
#define GRAY_THRESHOLD_DEFAULT 20  // 默认灰度比例20%

typedef enum {
    VERSION_ACTIVE = 0,
    VERSION_STANDBY,
    VERSION_GRAY,
    VERSION_ROLLBACK
} version_state_t;

typedef struct {
    char version[MAX_VERSION_LEN];   // 版本号字符串
    uint32_t major;
    uint32_t minor;
    uint32_t patch;
    version_state_t state;
    uint32_t gray_threshold;         // 灰度阈值百分比 0-100
} firmware_version_t;

typedef struct {
    firmware_version_t versions[MAX_VERSIONS];
    int count;
    char active_version[MAX_VERSION_LEN];  // 当前激活版本
} version_manager_t;

// 解析版本号字符串为三段数字
static int parse_version(const char* ver, uint32_t* maj, uint32_t* min, uint32_t* pat) {
    if (sscanf(ver, "%u.%u.%u", maj, min, pat) != 3) return -1;
    return 0;
}

// 比较两个版本号: a>b返回1, a==b返回0, a<b返回-1
static int compare_versions(const char* a, const char* b) {
    uint32_t amaj, amin, apat, bmaj, bmin, bpat;
    if (parse_version(a, &amaj, &amin, &apat) != 0) return -2;
    if (parse_version(b, &bmaj, &bmin, &bpat) != 0) return -2;
    if (amaj != bmaj) return amaj > bmaj ? 1 : -1;
    if (amin != bmin) return amin > bmin ? 1 : -1;
    if (apat != bpat) return apat > bpat ? 1 : -1;
    return 0;
}

// 初始化版本管理器
void vm_init(version_manager_t* vm) {
    memset(vm, 0, sizeof(version_manager_t));
    vm->count = 0;
    strcpy(vm->active_version, "0.0.0");
}

// 添加新版本到列表
int vm_add_version(version_manager_t* vm, const char* ver, uint32_t gray_threshold) {
    if (vm->count >= MAX_VERSIONS) return -1;
    if (parse_version(ver, NULL, NULL, NULL) != 0) return -2;
    
    // 检查重复
    for (int i = 0; i < vm->count; i++) {
        if (strcmp(vm->versions[i].version, ver) == 0) return -3;
    }
    
    firmware_version_t* fv = &vm->versions[vm->count];
    strncpy(fv->version, ver, MAX_VERSION_LEN - 1);
    fv->version[MAX_VERSION_LEN - 1] = '\0';
    parse_version(ver, &fv->major, &fv->minor, &fv->patch);
    fv->state = VERSION_STANDBY;
    fv->gray_threshold = gray_threshold;
    vm->count++;
    return 0;
}

// 激活指定版本(用于升级或回滚)
int vm_activate(version_manager_t* vm, const char* ver) {
    for (int i = 0; i < vm->count; i++) {
        if (strcmp(vm->versions[i].version, ver) == 0) {
            // 将当前ACTIVE版本转为STANDBY
            for (int j = 0; j < vm->count; j++) {
                if (vm->versions[j].state == VERSION_ACTIVE) {
                    vm->versions[j].state = VERSION_STANDBY;
                }
            }
            vm->versions[i].state = VERSION_ACTIVE;
            strcpy(vm->active_version, ver);
            return 0;
        }
    }
    return -1;  // 版本不存在
}

// 回滚到指定版本(自动将当前ACTIVE标记为ROLLBACK来源)
int vm_rollback(version_manager_t* vm, const char* target_ver) {
    // 目标版本必须存在于列表中
    int found = 0;
    for (int i = 0; i < vm->count; i++) {
        if (strcmp(vm->versions[i].version, target_ver) == 0) {
            found = 1;
            break;
        }
    }
    if (!found) return -1;
    
    // 执行回滚:将当前ACTIVE转为ROLLBACK状态,目标版本转为ACTIVE
    for (int i = 0; i < vm->count; i++) {
        if (vm->versions[i].state == VERSION_ACTIVE) {
            vm->versions[i].state = VERSION_ROLLBACK;
        }
    }
    return vm_activate(vm, target_ver);
}

// 灰度判断:根据设备ID哈希决定是否允许升级到指定版本
int vm_gray_check(version_manager_t* vm, const char* target_ver, uint32_t device_id) {
    for (int i = 0; i < vm->count; i++) {
        if (strcmp(vm->versions[i].version, target_ver) == 0) {
            uint32_t hash = device_id * 2654435761u;  // Knuth乘法哈希
            uint32_t percent = hash % 100;
            return percent < vm->versions[i].gray_threshold ? 1 : 0;
        }
    }
    return -1;  // 版本不存在
}

// 打印当前版本列表状态
void vm_print_status(version_manager_t* vm) {
    printf("=== 固件版本列表 (共%d个版本) ===\n", vm->count);
    printf("当前激活版本: %s\n\n", vm->active_version);
    for (int i = 0; i < vm->count; i++) {
        firmware_version_t* fv = &vm->versions[i];
        const char* state_str;
        switch (fv->state) {
            case VERSION_ACTIVE: state_str = "ACTIVE"; break;
            case VERSION_STANDBY: state_str = "STANDBY"; break;
            case VERSION_GRAY: state_str = "GRAY"; break;
            case VERSION_ROLLBACK: state_str = "ROLLBACK"; break;
            default: state_str = "UNKNOWN"; break;
        }
        printf("  %-12s | %s | 灰度阈值: %u%%\n", 
               fv->version, state_str, fv->gray_threshold);
    }
    printf("===============================\n");
}

// 演示入口
int main() {
    version_manager_t vm;
    vm_init(&vm);
    
    // 添加版本
    vm_add_version(&vm, "1.0.0", 100);   // 初始版本,全量
    vm_add_version(&vm, "1.1.0", 20);    // 灰度版本,20%设备
    vm_add_version(&vm, "1.2.0", 100);   // 正式版本
    
    // 激活初始版本
    vm_activate(&vm, "1.0.0");
    vm_print_status(&vm);
    
    // 模拟灰度发布:设备ID 1001尝试升级到1.1.0
    printf("\n[灰度发布测试] 设备1001尝试升级到1.1.0...\n");
    int allowed = vm_gray_check(&vm, "1.1.0", 1001);
    printf("设备1001灰度判定: %s\n", allowed == 1 ? "允许升级" : "暂不升级");
    
    // 模拟另一台设备
    allowed = vm_gray_check(&vm, "1.1.0", 2002);
    printf("设备2002灰度判定: %s\n", allowed == 1 ? "允许升级" : "暂不升级");
    
    // 执行升级到1.1.0(假设灰度通过)
    printf("\n[执行升级] 激活1.1.0...\n");
    vm_activate(&vm, "1.1.0");
    vm_print_status(&vm);
    
    // 模拟回滚到1.0.0
    printf("\n[回滚操作] 回滚到1.0.0...\n");
    vm_rollback(&vm, "1.0.0");
    vm_print_status(&vm);
    
    return 0;
}

编译与运行

gcc -o version_mgr version_mgr.c -Wall
./version_mgr

验证方法与通过条件

程序执行后应依次输出:

  • 初始状态:1.0.0为ACTIVE,其余为STANDBY
  • 灰度判定:设备1001与2002的判定结果由哈希计算决定(可能一真一假或同真同假,取决于具体ID)
  • 升级后:1.1.0变为ACTIVE,原1.0.0转为STANDBY
  • 回滚后:1.0.0重新变为ACTIVE,原1.1.0标记为ROLLBACK

通过条件:版本状态转换符合预期,灰度判定逻辑可复现(同一设备ID多次运行结果一致),回滚后原版本正确恢复为ACTIVE且旧版本状态被标记为ROLLBACK。

关键说明

  • 灰度阈值gray_threshold表示允许升级的设备百分比(0-100),在vm_add_version时设定
  • 设备ID哈希采用Knuth乘法哈希,保证分布均匀且确定性
  • 回滚操作将当前ACTIVE版本标记为ROLLBACK状态,便于追踪回滚历史
  • 实际工程中,vm_activate成功后应触发固件搬运与重启切换,本案例仅管理版本状态