/* * test_iot_loop_ver.c — V1.10 验证: 0x4A 版本解析 + 版本缓存 + MQTT 异步回包时序 * * 覆盖: * 1. lup_parse_version: 0x4A 响应帧解析 (字段/错误路径) * 2. lup_cache_from_info / iot_loop_ver_str / iot_loop_hw_ver_str: 缓存写入与格式化 * 3. iot_verq_poll 异步回包时序: * - 命令 pending + RESPONSE_READY → code=0 回包 + 缓存刷新 + 通道释放 * - 命令 pending + TIMEOUT → code=5 回包 * - 后台刷新: dirty=1 + IDLE → 发起 0x4A (不设 pending) * - 后台响应消费: !dirty + RESPONSE_READY → 更新缓存 + 释放 * - 残留超时清理: TIMEOUT → 归位 IDLE * * 编译: gcc -o test_iot_loop_ver test_iot_loop_ver.c && ./test_iot_loop_ver */ #include #include #include #include /* ---- mock: 最小依赖 ---- */ #define LUP_MAGIC 0x7F #define LUP_CMD_GET_VERSION 0x4A #define LUP_MAX_PKG_LEN 70 #define IOT_MQTT_TOPIC_MAX_LEN 64 typedef struct { uint8_t status; uint8_t hard_main, hard_sub, hard_ssub; uint8_t soft_main, soft_sub, soft_ssub; char version_str[32]; } LUP_VersionInfo; typedef struct { uint8_t valid; uint8_t hard_main, hard_sub, hard_ssub; uint8_t soft_main, soft_sub, soft_ssub; } LoopVerCache; typedef enum { LUP_STATE_IDLE = 0, LUP_STATE_WAIT_RESPONSE, LUP_STATE_RESPONSE_READY, LUP_STATE_TIMEOUT } LUP_CmdState; typedef struct { uint8_t pending_cmd; uint32_t send_tick; uint32_t timeout_ms; LUP_CmdState state; uint8_t resp_buf[LUP_MAX_PKG_LEN]; uint16_t resp_len; } LUP_CmdTracker; /* ---- mock 全局 ---- */ static LUP_CmdTracker g_lup_cmd; static LoopVerCache g_lup_ver_cache; static char g_iot_dev_serial[16] = "A1B2C3D4E5F6"; /* ---- mock 时间 ---- */ static uint32_t g_mock_ms = 100000; static uint32_t mstick(void) { return g_mock_ms; } static uint32_t dev_time_now(void) { return 1719000008UL; } /* ---- mock 发布捕获 ---- */ static char g_pub_topic[64]; static char g_pub_payload[512]; static uint16_t g_pub_len; static int g_pub_count; static int g_getver_calls; static int iot_mqtt_publish(const char *topic, const char *payload, uint16_t payload_len, uint8_t qos) { (void)qos; g_pub_count++; snprintf(g_pub_topic, sizeof(g_pub_topic), "%s", topic ? topic : ""); if (payload_len < sizeof(g_pub_payload)) { memcpy(g_pub_payload, payload, payload_len); g_pub_payload[payload_len] = '\0'; } else { memcpy(g_pub_payload, payload, sizeof(g_pub_payload) - 1); g_pub_payload[sizeof(g_pub_payload) - 1] = '\0'; } g_pub_len = payload_len; return 0; } /* mock lup_send_get_version: 模拟 lup_cmd_begin 的状态推进 */ static void lup_send_get_version(void) { g_getver_calls++; g_lup_cmd.pending_cmd = LUP_CMD_GET_VERSION; g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE; g_lup_cmd.resp_len = 0; memset(g_lup_cmd.resp_buf, 0, sizeof(g_lup_cmd.resp_buf)); } /* mock lup_cmd_done: 释放命令通道 */ static void lup_cmd_done(void) { g_lup_cmd.pending_cmd = 0; g_lup_cmd.state = LUP_STATE_IDLE; g_lup_cmd.resp_len = 0; } #define PRINT(...) printf(__VA_ARGS__) /* ---- 被测静态状态 (从 iot_mqtt_srv.c 提取的变量, 单测转为可访问全局) ---- */ static uint8_t _iot_verq_pending; static uint32_t _iot_verq_msg_id; static uint32_t _iot_verq_deadline; static uint8_t _iot_ver_cache_dirty; /* ================= 嵌入被测函数 (从真实源码提取) ================= */ int lup_parse_version(const uint8_t *pkg, uint16_t len, LUP_VersionInfo *info) { if (len < 10) return -1; // 1+3+7+2 = 13 min // pkg layout: [7F][Addr=0][LEN=8][CMD=0x4A][Status...7bytes][XOR][SUM] if (pkg[0] != LUP_MAGIC || pkg[3] != LUP_CMD_GET_VERSION) return -2; if (pkg[2] < 7) return -3; // LEN should be >= 7 const uint8_t *val = pkg + 4; // skip magic + header(3) info->status = val[0]; info->hard_main = val[1]; info->hard_sub = val[2]; info->hard_ssub = val[3]; info->soft_main = val[4]; info->soft_sub = val[5]; info->soft_ssub = val[6]; return 0; } void lup_cache_from_info(const LUP_VersionInfo *info) { if (!info) return; g_lup_ver_cache.valid = 1; g_lup_ver_cache.hard_main = info->hard_main; g_lup_ver_cache.hard_sub = info->hard_sub; g_lup_ver_cache.hard_ssub = info->hard_ssub; g_lup_ver_cache.soft_main = info->soft_main; g_lup_ver_cache.soft_sub = info->soft_sub; g_lup_ver_cache.soft_ssub = info->soft_ssub; } static void iot_loop_ver_str(char *out, uint16_t out_len) { if (!g_lup_ver_cache.valid || !out || out_len == 0) { if (out && out_len) out[0] = '\0'; return; } snprintf(out, out_len, "%d.%d.%d", g_lup_ver_cache.soft_main, g_lup_ver_cache.soft_sub, g_lup_ver_cache.soft_ssub); } static void iot_loop_hw_ver_str(char *out, uint16_t out_len) { if (!g_lup_ver_cache.valid || !out || out_len == 0) { if (out && out_len) out[0] = '\0'; return; } snprintf(out, out_len, "%d.%d.%d", g_lup_ver_cache.hard_main, g_lup_ver_cache.hard_sub, g_lup_ver_cache.hard_ssub); } static void iot_verq_publish(const char *payload, uint16_t len) { char topic[IOT_MQTT_TOPIC_MAX_LEN]; snprintf(topic, sizeof(topic), "dld960/%s/dev", g_iot_dev_serial); iot_mqtt_publish(topic, payload, len, 1); } static void iot_verq_poll(void) { if (_iot_verq_pending) { /* 命令查询在途: 等 0x4A 响应或超时 */ if (g_lup_cmd.state == LUP_STATE_RESPONSE_READY) { LUP_VersionInfo info; char resp[320]; char ver_str[16], hw_str[16], vs_str[64]; int ok = 0; memset(&info, 0, sizeof(info)); if (lup_parse_version(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &info) == 0) { lup_cache_from_info(&info); /* 同步刷新缓存 */ snprintf(ver_str, sizeof(ver_str), "%d.%d.%d", info.soft_main, info.soft_sub, info.soft_ssub); snprintf(hw_str, sizeof(hw_str), "%d.%d.%d", info.hard_main, info.hard_sub, info.hard_ssub); snprintf(vs_str, sizeof(vs_str), "V%s (HW:%s)", ver_str, hw_str); snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"loop_version_query\",\"ts\":%lu,\"code\":0," "\"msg\":\"success\"," "\"data\":{\"loop_ver\":\"%s\",\"loop_hw_ver\":\"%s\",\"version_str\":\"%s\"}}", _iot_verq_msg_id, dev_time_now(), ver_str, hw_str, vs_str); ok = 1; } else { char lv[16], lhv[16]; iot_loop_ver_str(lv, sizeof(lv)); iot_loop_hw_ver_str(lhv, sizeof(lhv)); snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"loop_version_query\",\"ts\":%lu,\"code\":5," "\"msg\":\"parse error\"," "\"data\":{\"loop_ver\":\"%s\",\"loop_hw_ver\":\"%s\",\"version_str\":\"\"}}", _iot_verq_msg_id, dev_time_now(), lv, lhv); } lup_cmd_done(); _iot_verq_pending = 0; iot_verq_publish(resp, strlen(resp)); PRINT("IOT: loop_version_query resp %s\n", ok ? "ok" : "parse error"); } else if (g_lup_cmd.state == LUP_STATE_TIMEOUT || mstick() > _iot_verq_deadline) { char resp[320]; char lv[16], lhv[16]; iot_loop_ver_str(lv, sizeof(lv)); iot_loop_hw_ver_str(lhv, sizeof(lhv)); snprintf(resp, sizeof(resp), "{\"msg_id\":%lu,\"cmd\":\"loop_version_query\",\"ts\":%lu,\"code\":5," "\"msg\":\"no response\"," "\"data\":{\"loop_ver\":\"%s\",\"loop_hw_ver\":\"%s\",\"version_str\":\"\"}}", _iot_verq_msg_id, dev_time_now(), lv, lhv); lup_cmd_done(); _iot_verq_pending = 0; iot_verq_publish(resp, strlen(resp)); PRINT("IOT: loop_version_query timeout\n"); } /* 其它 state (WAIT_RESPONSE) 继续等 */ } else { /* 后台缓存刷新: 上电 / OTA done 后置 dirty, 命令通道空闲时发 0x4A */ if (_iot_ver_cache_dirty && g_lup_cmd.state == LUP_STATE_IDLE) { lup_send_get_version(); _iot_ver_cache_dirty = 0; PRINT("IOT: loop version cache refresh query\n"); } else if (!_iot_ver_cache_dirty && g_lup_cmd.state == LUP_STATE_RESPONSE_READY) { /* 后台查询响应 (无命令 pending) → 更新缓存 + 释放通道 */ LUP_VersionInfo info; memset(&info, 0, sizeof(info)); if (lup_parse_version(g_lup_cmd.resp_buf, g_lup_cmd.resp_len, &info) == 0) { lup_cache_from_info(&info); PRINT("IOT: loop version cache refreshed %d.%d.%d (HW:%d.%d.%d)\n", info.soft_main, info.soft_sub, info.soft_ssub, info.hard_main, info.hard_sub, info.hard_ssub); } lup_cmd_done(); } else if (g_lup_cmd.state == LUP_STATE_TIMEOUT) { lup_cmd_done(); /* 清残留超时态 (后台查询失败或被其它命令覆盖) */ } } } /* ================= 测试工具 ================= */ static int g_pass = 0, g_fail = 0; #define CHECK(cond, msg) do { \ if (cond) { g_pass++; printf(" PASS: %s\n", msg); } \ else { g_fail++; printf(" FAIL: %s (line %d)\n", msg, __LINE__); } \ } while (0) static void reset_state(void) { memset(&g_lup_cmd, 0, sizeof(g_lup_cmd)); memset(&g_lup_ver_cache, 0, sizeof(g_lup_ver_cache)); memset(&g_pub_payload, 0, sizeof(g_pub_payload)); g_pub_count = 0; g_pub_len = 0; g_getver_calls = 0; _iot_verq_pending = 0; _iot_verq_msg_id = 0; _iot_verq_deadline = 0; _iot_ver_cache_dirty = 0; g_mock_ms = 100000; } /* 构造 0x4A 响应帧: 7F 00 LEN 4A [status][hard_m][hard_s][hard_ss][soft_m][soft_s][soft_ss] [xor][sum] (checksum 字节 lup_parse_version 不校验, 填 0) */ static void build_version_frame(uint8_t *pkg, uint16_t *len, uint8_t status, uint8_t hm, uint8_t hs, uint8_t hss, uint8_t sm, uint8_t ss, uint8_t sss) { pkg[0] = LUP_MAGIC; pkg[1] = 0x00; pkg[2] = 0x08; pkg[3] = LUP_CMD_GET_VERSION; pkg[4] = status; pkg[5] = hm; pkg[6] = hs; pkg[7] = hss; pkg[8] = sm; pkg[9] = ss; pkg[10] = sss; pkg[11] = 0; pkg[12] = 0; /* XOR/SUM 不校验 */ *len = 13; } static void test_parse_version(void) { printf("[1] lup_parse_version\n"); uint8_t pkg[16]; uint16_t len; LUP_VersionInfo info; build_version_frame(pkg, &len, 0x00, 1,0,0, 1,2,3); CHECK(lup_parse_version(pkg, len, &info) == 0, "valid frame returns 0"); CHECK(info.status == 0x00, "status=0"); CHECK(info.hard_main == 1 && info.hard_sub == 0 && info.hard_ssub == 0, "hard=1.0.0"); CHECK(info.soft_main == 1 && info.soft_sub == 2 && info.soft_ssub == 3, "soft=1.2.3"); /* 错误路径 */ CHECK(lup_parse_version(pkg, 9, &info) == -1, "len<10 -> -1"); pkg[0] = 0x00; CHECK(lup_parse_version(pkg, len, &info) == -2, "bad magic -> -2"); pkg[0] = LUP_MAGIC; pkg[3] = 0x55; CHECK(lup_parse_version(pkg, len, &info) == -2, "bad cmd -> -2"); pkg[3] = LUP_CMD_GET_VERSION; pkg[2] = 0x05; CHECK(lup_parse_version(pkg, len, &info) == -3, "LEN<7 -> -3"); } static void test_cache(void) { printf("[2] 版本缓存\n"); LUP_VersionInfo info; char out[24]; memset(&info, 0, sizeof(info)); info.hard_main = 1; info.hard_sub = 0; info.hard_ssub = 0; info.soft_main = 1; info.soft_sub = 2; info.soft_ssub = 3; lup_cache_from_info(&info); CHECK(g_lup_ver_cache.valid == 1, "cache valid after write"); CHECK(g_lup_ver_cache.soft_main == 1 && g_lup_ver_cache.soft_ssub == 3, "cache soft fields"); iot_loop_ver_str(out, sizeof(out)); CHECK(strcmp(out, "1.2.3") == 0, "loop_ver str = 1.2.3"); iot_loop_hw_ver_str(out, sizeof(out)); CHECK(strcmp(out, "1.0.0") == 0, "loop_hw_ver str = 1.0.0"); /* 无效缓存 → 空串 */ memset(&g_lup_ver_cache, 0, sizeof(g_lup_ver_cache)); iot_loop_ver_str(out, sizeof(out)); CHECK(strcmp(out, "") == 0, "invalid cache -> empty str"); iot_loop_hw_ver_str(out, sizeof(out)); CHECK(strcmp(out, "") == 0, "invalid hw cache -> empty str"); /* NULL/0 长度防护 */ iot_loop_ver_str(NULL, 0); CHECK(1, "NULL guard no crash"); } static void test_verq_success(void) { printf("[3] verq_poll: 命令成功回包\n"); reset_state(); uint8_t pkg[16]; uint16_t len; build_version_frame(pkg, &len, 0x00, 1,0,0, 1,2,3); memcpy(g_lup_cmd.resp_buf, pkg, len); g_lup_cmd.resp_len = len; g_lup_cmd.state = LUP_STATE_RESPONSE_READY; _iot_verq_pending = 1; _iot_verq_msg_id = 407; _iot_verq_deadline = g_mock_ms + 300; iot_verq_poll(); CHECK(g_pub_count == 1, "publish once"); CHECK(strstr(g_pub_payload, "\"msg_id\":407") != NULL, "msg_id echoed"); CHECK(strstr(g_pub_payload, "\"code\":0") != NULL, "code=0"); CHECK(strstr(g_pub_payload, "\"loop_ver\":\"1.2.3\"") != NULL, "loop_ver=1.2.3"); CHECK(strstr(g_pub_payload, "\"loop_hw_ver\":\"1.0.0\"") != NULL, "loop_hw_ver=1.0.0"); CHECK(strstr(g_pub_payload, "\"version_str\":\"V1.2.3 (HW:1.0.0)\"") != NULL, "version_str format"); CHECK(strcmp(g_pub_topic, "dld960/A1B2C3D4E5F6/dev") == 0, "resp topic"); CHECK(_iot_verq_pending == 0, "pending cleared"); CHECK(g_lup_cmd.state == LUP_STATE_IDLE, "cmd channel released"); CHECK(g_lup_ver_cache.valid == 1, "cache refreshed by command resp"); } static void test_verq_timeout(void) { printf("[4] verq_poll: 命令超时\n"); reset_state(); /* 缓存先有效, 超时回包应携带缓存值 */ g_lup_ver_cache.valid = 1; g_lup_ver_cache.soft_main = 9; g_lup_ver_cache.soft_sub = 9; g_lup_ver_cache.soft_ssub = 9; g_lup_ver_cache.hard_main = 8; g_lup_ver_cache.hard_sub = 8; g_lup_ver_cache.hard_ssub = 8; g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE; /* 还挂着 */ _iot_verq_pending = 1; _iot_verq_msg_id = 408; _iot_verq_deadline = g_mock_ms + 300; g_mock_ms += 400; /* 超过 deadline */ iot_verq_poll(); CHECK(g_pub_count == 1, "timeout publish once"); CHECK(strstr(g_pub_payload, "\"code\":5") != NULL, "code=5"); CHECK(strstr(g_pub_payload, "\"msg\":\"no response\"") != NULL, "msg=no response"); CHECK(strstr(g_pub_payload, "\"loop_ver\":\"9.9.9\"") != NULL, "cache value kept in resp"); CHECK(_iot_verq_pending == 0, "pending cleared"); CHECK(g_lup_cmd.state == LUP_STATE_IDLE, "cmd released after timeout"); /* 直接 TIMEOUT 态 */ reset_state(); g_lup_cmd.state = LUP_STATE_TIMEOUT; _iot_verq_pending = 1; _iot_verq_msg_id = 409; _iot_verq_deadline = g_mock_ms + 300; iot_verq_poll(); CHECK(g_pub_count == 1, "TIMEOUT state publish once"); CHECK(strstr(g_pub_payload, "\"code\":5") != NULL, "TIMEOUT state code=5"); } static void test_bg_refresh(void) { printf("[5] verq_poll: 后台缓存刷新\n"); reset_state(); /* 5a. dirty=1 + IDLE → 发起查询, 不设 pending */ _iot_ver_cache_dirty = 1; g_lup_cmd.state = LUP_STATE_IDLE; iot_verq_poll(); CHECK(g_getver_calls == 1, "bg query issued"); CHECK(_iot_ver_cache_dirty == 0, "dirty cleared"); CHECK(_iot_verq_pending == 0, "no command pending set"); CHECK(g_pub_count == 0, "no publish for bg query"); /* 5b. 通道忙 (WAIT_RESPONSE) 时 dirty 不消费 */ { int base = g_getver_calls; _iot_ver_cache_dirty = 1; g_lup_cmd.state = LUP_STATE_WAIT_RESPONSE; iot_verq_poll(); CHECK(g_getver_calls == base, "busy channel -> no query"); CHECK(_iot_ver_cache_dirty == 1, "dirty kept while busy"); } /* 5c. 后台响应消费: !dirty + RESPONSE_READY → 更新缓存 + 释放, 不回包 */ reset_state(); uint8_t pkg[16]; uint16_t len; build_version_frame(pkg, &len, 0x00, 2,0,0, 3,4,5); memcpy(g_lup_cmd.resp_buf, pkg, len); g_lup_cmd.resp_len = len; g_lup_cmd.state = LUP_STATE_RESPONSE_READY; _iot_ver_cache_dirty = 0; iot_verq_poll(); CHECK(g_pub_count == 0, "bg resp no publish"); CHECK(g_lup_ver_cache.valid == 1, "bg resp cache valid"); CHECK(g_lup_ver_cache.soft_main == 3 && g_lup_ver_cache.soft_ssub == 5, "bg resp soft=3.4.5"); CHECK(g_lup_cmd.state == LUP_STATE_IDLE, "bg resp channel released"); /* 5d. 残留超时清理 */ reset_state(); g_lup_cmd.state = LUP_STATE_TIMEOUT; iot_verq_poll(); CHECK(g_lup_cmd.state == LUP_STATE_IDLE, "stale timeout cleaned"); } int main(void) { printf("=== test_iot_loop_ver: V1.10 版本缓存 + 异步回包 ===\n"); reset_state(); test_parse_version(); test_cache(); test_verq_success(); test_verq_timeout(); test_bg_refresh(); printf("\nRESULT: %d passed, %d failed\n", g_pass, g_fail); return g_fail ? 1 : 0; }