Files
vd_960/vd960DBN/tests/test_ota_srv.c
T
wangfq c305ca651f fix(vd960DBN): OTA 下载失败 — hex 提取兼容 json.dumps 空格
现象: DBNMQTTool 第一片 ota_data code=1 crc/gap, received=0, 重试全败

根因: 工具 json.dumps 默认带空格 ("data": "hex"), 设备端 strstr 找
"data":" 无空格格式 → 提取失败 → hexbuf 空 → 解码失败 code=1
(simple_parse_json 容错空格所以其他命令正常, 只有手工 strstr 踩坑)

修复:
- ota_srv.c/h: 新增 ota_json_extract_hex() 兼容有无空格 (扫描 data 键跳过空白)
- iot_mqtt_srv.c: ota_data 改用该函数, 删 strstr 手工提取 + hp 残留
- 单测 +test_json_extract_hex: 无空格/带空格(事故场景)/512hex/找不到 4 场景

验证: 单测 11/11 PASS; 语法 0 错误; 待板级重测下载
2026-08-20 14:13:06 +08:00

488 lines
17 KiB
C

/*
* 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 <stdio.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#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;
}