/* * ota_srv.c 隔离单测 (gcc, 无 WCH 环境) * * 策略: mock CONFIG.h/cmcng.h + mock 外设 (NOR flash 模拟 / UART2 发送记录 / * 可推进时钟 / offlog 记录) → #include 真实 ../APP/ota_srv.c (同编译单元, * static 函数可见) → 断言会话状态机 + 刷写协议帧 + 超时重试。 * * 编译: * gcc -I tests/ota_mock -I APP/include -I APP -o /tmp/test_ota_srv \ * tests/test_ota_srv.c * 运行: /tmp/test_ota_srv */ #include #include #include #include #include "cmcng.h" /* mock: Flag_Counter / PRINT / mstick / UART2_SendString */ /* ============================================================================ * mock 外设 * ============================================================================*/ Flag_Counter g_flag_counter_key = {0, 0, 0}; Flag_Counter g_flag_counter_ota = {0, 0, 0}; static uint32_t _mock_ms = 0; uint32_t mstick(void) { return _mock_ms; } static void mock_advance(uint32_t ms) { _mock_ms += ms; } /* UART2 发送记录: 保存最近一帧 */ static uint8_t _tx_buf[512]; static uint16_t _tx_len = 0; static uint16_t _tx_count = 0; void UART2_SendString(uint8_t *buf, uint16_t len) { _tx_len = (len <= sizeof(_tx_buf)) ? len : (uint16_t)sizeof(_tx_buf); memcpy(_tx_buf, buf, _tx_len); _tx_count++; } static void tx_reset(void) { _tx_len = 0; _tx_count = 0; } /* ---- mock NOR flash (W25Q32 语义: 写=AND, 擦=0xFF) ---- */ #define MOCK_FLASH_SIZE (4 * 1024 * 1024) static uint8_t flash[MOCK_FLASH_SIZE]; static void flash_init(void) { memset(flash, 0xFF, sizeof(flash)); } void SPI_Flash_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t size) { assert(ReadAddr + size <= MOCK_FLASH_SIZE); memcpy(pBuffer, &flash[ReadAddr], size); } void SPI_Flash_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t size) { uint16_t i; assert(WriteAddr + size <= MOCK_FLASH_SIZE); for (i = 0; i < size; i++) flash[WriteAddr + i] &= pBuffer[i]; } void SPI_Flash_Erase_Sector(uint32_t Dst_Addr) { assert(Dst_Addr * 4096UL + 4096UL <= MOCK_FLASH_SIZE); memset(&flash[Dst_Addr * 4096UL], 0xFF, 4096); } /* 与 storage.c 语义一致: 目标区非 0xFF → 擦扇区 → 读-改-写保留同扇区其他数据 */ void SPI_Flash_Write(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t size) { uint32_t secpos, secoff, secremain; uint16_t i; while (size > 0) { secpos = WriteAddr / 4096; secoff = WriteAddr % 4096; secremain = 4096 - secoff; if (size <= secremain) secremain = size; /* 目标区是否全 0xFF */ { uint8_t need_erase = 0; for (i = 0; i < secremain; i++) { if (flash[secpos * 4096 + secoff + i] != 0xFF) { need_erase = 1; break; } } if (need_erase) { uint8_t sector[4096]; memcpy(sector, &flash[secpos * 4096], 4096); for (i = 0; i < secremain; i++) sector[secoff + i] = pBuffer[i]; memset(&flash[secpos * 4096], 0xFF, 4096); for (i = 0; i < 4096; i++) flash[secpos * 4096 + i] &= sector[i]; } else { for (i = 0; i < secremain; i++) flash[WriteAddr + i] &= pBuffer[i]; } } pBuffer += secremain; WriteAddr += (uint32_t)secremain; size -= (uint16_t)secremain; } } uint8_t SPI_Flash_ReadJEDEC_ID(void) { return 0x16; } /* W25Q32 */ /* ---- mock offlog (记录最近 OTA 事件) ---- */ static uint32_t _ota_start_cnt = 0, _ota_result_cnt = 0; static uint32_t _last_ota_result = 0; void offlog_evt(uint8_t type, const uint8_t *payload, uint8_t len) { (void)type; (void)payload; (void)len; } void offlog_ota_start(uint8_t slot, uint32_t size) { _ota_start_cnt++; (void)slot; (void)size; } void offlog_ota_result(uint32_t code) { _ota_result_cnt++; _last_ota_result = code; } void offlog_boot(uint32_t r) { (void)r; } /* ---- mock loop_uart_proto ---- */ void lup_frame_reset(void) { } /* ---- mock iot_mqtt_srv ---- */ static uint8_t _mock_any_car = 0; static uint32_t _ota_err_report = 0, _ota_err_cnt = 0; uint8_t iot_any_car(void) { return _mock_any_car; } void iot_evt_report_ota_error(uint32_t err) { _ota_err_report = err; _ota_err_cnt++; } /* ============================================================================ * 嵌入真实 ota_srv.c (同一编译单元, static 可见) * ============================================================================*/ #include "../APP/ota_srv.c" /* ============================================================================ * 测试 * ============================================================================*/ #define CHK(cond) do { if (!(cond)) { \ printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); return 1; } } while (0) /* 构造测试镜像 (5120B = 20 片) */ static void make_image(uint8_t *buf, uint32_t size) { uint32_t i; for (i = 0; i < size; i++) buf[i] = (uint8_t)(i * 7 + 3); } static int test_crc32(void) { /* CRC-32/ISO-HDLC 标准向量 */ CHK(ota_crc32((const uint8_t *)"123456789", 9) == 0xCBF43926UL); CHK(ota_crc32(NULL, 0) == 0x00000000UL); printf("PASS test_crc32\n"); return 0; } static int test_hex_decode(void) { uint8_t out[4]; CHK(ota_hex_decode("0102aBff", out, 4) == 0); CHK(out[0] == 0x01 && out[1] == 0x02 && out[2] == 0xAB && out[3] == 0xFF); CHK(ota_hex_decode("01z2", out, 2) != 0); /* 非法字符 */ CHK(ota_hex_decode("010", out, 2) != 0); /* 长度不足 */ printf("PASS test_hex_decode\n"); return 0; } static int test_json_extract_hex(void) { char out[520]; /* 无空格 (紧凑格式) */ CHK(ota_json_extract_hex( "{\"msg_id\":402,\"cmd\":\"ota_data\",\"ts\":1,\"data\":{\"target\":\"loop\",\"offset\":0,\"crc32\":1,\"data\":\"0102aBff\"}}", out, sizeof(out)) == 8); CHK(strcmp(out, "0102aBff") == 0); /* 带空格 (json.dumps 默认) — 2026-08-20 事故场景 */ CHK(ota_json_extract_hex( "{\"msg_id\": 402, \"cmd\": \"ota_data\", \"ts\": 1, \"data\": {\"target\": \"loop\", \"offset\": 0, \"crc32\": 1, \"data\": \"0102aBff\"}}", out, sizeof(out)) == 8); CHK(strcmp(out, "0102aBff") == 0); /* 512 hex 满片 */ { char json[700]; char hex[513]; int i, n; for (i = 0; i < 256; i++) sprintf(hex + i * 2, "%02x", i); snprintf(json, sizeof(json), "{\"msg_id\":1,\"cmd\":\"ota_data\",\"data\":{\"data\":\"%s\"}}", hex); n = ota_json_extract_hex(json, out, sizeof(out)); CHK(n == 512); CHK(strcmp(out, hex) == 0); } /* 找不到 data 字段 */ CHK(ota_json_extract_hex("{\"cmd\":\"ota_status\"}", out, sizeof(out)) == 0); CHK(out[0] == '\0'); printf("PASS test_json_extract_hex\n"); return 0; } static int test_meta_restore(void) { flash_init(); tx_reset(); ota_init(); CHK(_meta.state == OTA_STATE_IDLE); /* 写一份 downloading 元数据 → 重新 init → 恢复续传状态 */ _meta.magic = OTA_MAGIC; _meta.state = OTA_STATE_DOWNLOADING; _meta.size = 512; _meta.received = 256; ota_meta_write(&_meta); ota_init(); CHK(_meta.state == OTA_STATE_DOWNLOADING); CHK(_meta.received == 256); printf("PASS test_meta_restore\n"); return 0; } static int test_session_flow(void) { uint8_t img[5120]; uint32_t i; make_image(img, sizeof(img)); flash_init(); tx_reset(); ota_init(); /* begin */ CHK(ota_cmd_begin(sizeof(img), ota_crc32(img, sizeof(img)), "1.1.0", 0) == 0); CHK(_meta.state == OTA_STATE_DOWNLOADING); CHK(_meta.received == 0); /* 20 片下发 */ for (i = 0; i < sizeof(img); i += 256) { uint32_t clen = (sizeof(img) - i > 256) ? 256 : (sizeof(img) - i); uint8_t chunk[256]; char hex[513]; uint32_t j; memcpy(chunk, &img[i], clen); for (j = 0; j < clen; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(i, ota_crc32(chunk, clen), hex) == 0); } CHK(_meta.received == sizeof(img)); /* end → ready */ CHK(ota_cmd_end(ota_crc32(img, sizeof(img))) == 0); CHK(_meta.state == OTA_STATE_READY); /* 暂存区与镜像一致 (读回比对) */ { uint8_t back[5120]; SPI_Flash_Read(back, OTA_SLOT_A_BASE, sizeof(back)); CHK(memcmp(back, img, sizeof(img)) == 0); } printf("PASS test_session_flow\n"); return 0; } static int test_begin_resume(void) { uint8_t img[5120]; uint32_t i; make_image(img, sizeof(img)); flash_init(); ota_init(); /* 下载 16 片 (4096B, 触发元数据节流 flush) 后"断电重启" */ CHK(ota_cmd_begin(sizeof(img), ota_crc32(img, sizeof(img)), "", 0) == 0); for (i = 0; i < 16 * 256; i += 256) { uint8_t chunk[256]; char hex[513]; uint32_t j; memcpy(chunk, &img[i], 256); for (j = 0; j < 256; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(i, ota_crc32(chunk, 256), hex) == 0); } CHK(_meta.received == 16 * 256); /* 4096B, 已 flush */ /* 重启 */ ota_init(); CHK(_meta.state == OTA_STATE_DOWNLOADING); CHK(_meta.received == 16 * 256); /* 节流点精确续传 */ /* 重新 begin → 续传点 */ CHK(ota_cmd_begin(sizeof(img), ota_crc32(img, sizeof(img)), "", 0) == 0); CHK(_meta.received == 16 * 256); /* 继续下载剩余 → ready */ for (i = 16 * 256; i < sizeof(img); i += 256) { uint32_t clen = (sizeof(img) - i > 256) ? 256 : (sizeof(img) - i); uint8_t chunk[256]; char hex[513]; uint32_t j; memcpy(chunk, &img[i], clen); for (j = 0; j < clen; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(i, ota_crc32(chunk, clen), hex) == 0); } CHK(ota_cmd_end(ota_crc32(img, sizeof(img))) == 0); CHK(_meta.state == OTA_STATE_READY); printf("PASS test_begin_resume\n"); return 0; } static int test_dup_and_gap(void) { uint8_t img[1024]; uint8_t chunk[256]; char hex[513]; uint32_t j; make_image(img, sizeof(img)); flash_init(); ota_init(); CHK(ota_cmd_begin(sizeof(img), ota_crc32(img, sizeof(img)), "", 0) == 0); memcpy(chunk, &img[0], 256); for (j = 0; j < 256; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(0, ota_crc32(chunk, 256), hex) == 0); /* 重复片幂等 */ CHK(ota_cmd_data(0, ota_crc32(chunk, 256), hex) == 0); CHK(_meta.received == 256); /* 乱序缺片 (跳 256 直接发 512) → 拒绝 */ memcpy(chunk, &img[512], 256); for (j = 0; j < 256; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(512, ota_crc32(chunk, 256), hex) == 1); CHK(_meta.received == 256); /* 单片 CRC 错 → 拒绝 */ memcpy(chunk, &img[256], 256); for (j = 0; j < 256; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(256, 0xDEADBEEFUL, hex) == 1); CHK(_meta.received == 256); printf("PASS test_dup_and_gap\n"); return 0; } /* 模拟 bootloader: 根据收到的 A7 块回 data ACK */ static void flash_step_until_ack(uint32_t *blocks_done, uint32_t total_blocks) { /* ota_poll 推进一帧发送 + 喂 ACK, 直到收到末块 */ uint32_t guard = 0; while (*blocks_done < total_blocks && guard++ < 10000) { ota_poll(); /* 发 A7 块 */ CHK(_tx_len >= 7 && _tx_buf[0] == 0x9F); CHK(_tx_buf[4] == 0xA7); /* 构造 data ACK: 9F SubL SubH 02 A7 00 CHECK */ { uint8_t ack[7]; uint8_t sum = 0; ack[0] = 0x9F; ack[1] = _tx_buf[1]; ack[2] = _tx_buf[2]; ack[3] = 0x02; ack[4] = 0xA7; ack[5] = 0x00; sum = (uint8_t)(ack[1] + ack[2] + ack[3] + ack[4] + ack[5]); ack[6] = sum; ota_flash_feed_ack(ack, 7); } (*blocks_done)++; } CHK(*blocks_done == total_blocks); } static int test_flash_flow(void) { uint8_t img[5120]; make_image(img, sizeof(img)); flash_init(); tx_reset(); _ota_err_cnt = 0; ota_init(); /* 下载 → ready */ CHK(ota_cmd_begin(sizeof(img), ota_crc32(img, sizeof(img)), "", 0) == 0); { uint32_t i; for (i = 0; i < sizeof(img); i += 256) { uint32_t clen = (sizeof(img) - i > 256) ? 256 : (sizeof(img) - i); uint8_t chunk[256]; char hex[513]; uint32_t j; memcpy(chunk, &img[i], clen); for (j = 0; j < clen; j++) sprintf(hex + j * 2, "%02x", chunk[j]); CHK(ota_cmd_data(i, ota_crc32(chunk, clen), hex) == 0); } } CHK(ota_cmd_end(ota_crc32(img, sizeof(img))) == 0); CHK(_meta.state == OTA_STATE_READY); /* flash: 无车 → 启动 */ _mock_any_car = 0; tx_reset(); CHK(ota_cmd_flash(0, 0) == 0); CHK(_meta.state == OTA_STATE_FLASHING); CHK(g_ota_flash_active == 1); CHK(g_flag_counter_ota.flag == 1); CHK(_ota_start_cnt >= 1); /* offlog 升级开始已写 */ /* START → 发 A5 启动帧 */ ota_poll(); CHK(_tx_len == 6 && _tx_buf[0] == 0x9F && _tx_buf[4] == 0xA5 && _tx_buf[5] == 0xA7); /* pre_ok → ADDR */ { uint8_t pre_ok[7] = {0x9F, 0x01, 0x00, 0x02, 0xA5, 0x00, 0xA8}; ota_flash_feed_ack(pre_ok, 7); } ota_poll(); /* A6 地址帧: 9F 01 00 05 A6 08 00 34 00 CHECK */ CHK(_tx_len == 10 && _tx_buf[4] == 0xA6); CHK(_tx_buf[5] == 0x08 && _tx_buf[6] == 0x00 && _tx_buf[7] == 0x34 && _tx_buf[8] == 0x00); /* 校验和: SubL+SubH+LEN+CMD+ADDR = 01+00+05+A6+08+00+34+00 */ CHK(_tx_buf[9] == (uint8_t)(0x01 + 0x00 + 0x05 + 0xA6 + 0x08 + 0x00 + 0x34 + 0x00)); /* addr_ok → DATA 阶段 (总块数 = ceil(5120/248) = 21) */ { uint8_t addr_ok[7] = {0x9F, 0x01, 0x00, 0x02, 0xA6, 0x00, 0xA9}; ota_flash_feed_ack(addr_ok, 7); } CHK(_fs_sub == 21); /* 首块序号 = 总块数 */ /* 逐块 A7 + ACK 直到完成 */ flash_step_until_ack(&(uint32_t){0}, 21); /* 完成后: 恢复 0x7F 模式, 元数据 ready (镜像保留可重刷), 结果已记 */ CHK(_meta.state == OTA_STATE_READY); CHK(g_ota_flash_active == 0); CHK(g_flag_counter_ota.flag == 0); CHK(_ota_result_cnt >= 1); CHK(_last_ota_result == 0); CHK(_ota_err_cnt == 0); /* 无失败告警 */ printf("PASS test_flash_flow (21 blocks)\n"); return 0; } static int test_flash_reject_car(void) { flash_init(); ota_init(); /* 直接构造 ready 态 */ _meta.state = OTA_STATE_READY; _meta.size = 512; _mock_any_car = 1; CHK(ota_cmd_flash(0, 0) == 3); /* 有车 → 拒绝 */ CHK(g_ota_flash_active == 0); _mock_any_car = 1; CHK(ota_cmd_flash(0, 1) == 0); /* force → 跳过 */ CHK(g_ota_flash_active == 1); printf("PASS test_flash_reject_car\n"); return 0; } static int test_flash_ready_timeout(void) { flash_init(); tx_reset(); _ota_err_cnt = 0; ota_init(); _meta.state = OTA_STATE_READY; _meta.size = 512; _mock_any_car = 0; CHK(ota_cmd_flash(0, 0) == 0); ota_poll(); /* 发 A5 (首发) */ /* 不喂 ACK: 首发后 3 次超时重发 + 第 4 个超时周期 → 失败 轮次: 超时1(→START) A5(2) 超时2(→START) A5(3) 超时3(→START) A5(4) 超时4(→fail) */ { int k; for (k = 0; k < 7; k++) { mock_advance(1001); ota_poll(); } } CHK(_meta.state == OTA_STATE_FLASH_FAILED); CHK(_meta.last_result == OTA_ERR_READY_TIMEOUT); CHK(_ota_err_cnt == 1); /* event_report ota_error 告警 */ CHK(_ota_err_report == OTA_ERR_READY_TIMEOUT); CHK(g_ota_flash_active == 0); CHK(g_flag_counter_ota.flag == 0); CHK(_ota_result_cnt >= 1); CHK(_last_ota_result == OTA_ERR_READY_TIMEOUT); printf("PASS test_flash_ready_timeout\n"); return 0; } int main(void) { int fails = 0; fails += test_crc32(); fails += test_hex_decode(); fails += test_json_extract_hex(); fails += test_meta_restore(); fails += test_session_flow(); fails += test_begin_resume(); fails += test_dup_and_gap(); fails += test_flash_flow(); fails += test_flash_reject_car(); fails += test_flash_ready_timeout(); if (fails == 0) { printf("\nALL PASS\n"); return 0; } printf("\n%d TEST(S) FAILED\n", fails); return 1; }