Files
vd_960/vd960DBN/tests/test_event_report.c
T
wangfq e3eaa111fe fix(mqtt): 修复 loop_data>512B 溢出发垃圾包致 broker RST 重连风暴 (现场P0)
根因(非平台推测的环形缓冲/event_report队列):
  mqtt_publish 的 mqttBuf=512B < loop_data(4通道)604B
  → MQTTSerialize_publish 返回 -2 不写 buf
  → len 为 uint32_t, -2 变 42.9亿
  → WCHNET 把清零缓冲+越界相邻全局(temp_guide=report_config→report_c)
    当 520B 垃圾包发出 → broker 见非法类型0x00 RST → 重连风暴

修复(net_srv.c):
  - MAX_MQTTBUF_LEN 512→1024 (容纳 604B loop_data)
  - mqtt_publish: len uint32→int + 守卫 if(len<=0)return (序列化失败绝不发残缓冲)
  - keepalive 9→60 (报告建议, 9s过激)

event_report 协议违规修复(iot_mqtt_srv.c):
  - 重连重发保持原 msg_id/ts (V1.04 §5.3-2), 不再作废换号致平台去重失效

验证: tests/test_mqtt_publish_overflow.c 复现旧垃圾包+验证守卫/大缓冲;
      tests/test_event_report.c T8 断言重连同 msg_id. 均全过.
遗留: ts=上电秒数非Unix时间戳, 待定方案(设备无RTC/SNTP)

Refs: docs/incidents/2026-07-15-DC045A49718F-protocol-error.md
2026-07-15 14:58:03 +08:00

254 lines
12 KiB
C

/* 隔离单测: iot_mqtt_srv.c event_report 模块 (协议 V1.04)
* 覆盖: 沿检测 / 队列 / ACK出队 / 5s重发×3 / 挂起与解除 / 重连补报 / 包长上限 / 溢出
* 被测代码与固件同构 (mock: mstick, mqtt_publish, PRINT, LUP 类型)
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
/* ---------- mocks ---------- */
#define PRINT(...) do{}while(0)
#define LUP_COIL_COUNT 4
typedef struct { uint8_t car_state, loop_state, misc_type; struct { uint32_t passtime_ms; } misc; } LUP_CoilSensor;
typedef struct { uint8_t coil_count; LUP_CoilSensor coils[LUP_COIL_COUNT]; } LUP_SensorReport;
typedef enum { IOT_STATE_DISCONNECTED=0, IOT_STATE_READY=5 } IotMqttState;
static IotMqttState g_iot_state = IOT_STATE_READY;
static uint32_t _mock_ms = 0;
static uint32_t mstick(void) { return _mock_ms; }
static struct { uint8_t topic_pub[64]; } g_iot_topic = {"dld960/AA/dev"};
/* 捕获发布 */
static char last_payload[600]; static int pub_count = 0;
static void mqtt_publish(char *topic, char *payload, int qos) {
(void)topic; (void)qos;
strncpy(last_payload, payload, sizeof(last_payload)-1);
pub_count++;
}
/* ---------- 被测模块 (与固件同构) ---------- */
#define IOT_EVT_QUEUE_DEPTH 16
#define IOT_EVT_MAX_PER_PKT 6
#define IOT_EVT_ACK_TIMEOUT_MS 5000
#define IOT_EVT_MAX_RETRY 3
enum { IOT_EVT_CAR_ENTER = 0, IOT_EVT_CAR_LEAVE, IOT_EVT_LOOP_CUT, IOT_EVT_LOOP_RESTORE };
typedef struct { uint8_t type, ch; uint32_t value; } IotEvent;
static IotEvent _evt_queue[IOT_EVT_QUEUE_DEPTH];
static uint8_t _evt_head, _evt_count;
static uint8_t _evt_prev_car[LUP_COIL_COUNT], _evt_prev_loop[LUP_COIL_COUNT];
static uint32_t _evt_cut_ms[LUP_COIL_COUNT];
static uint8_t _evt_prev_valid;
static uint32_t _evt_msg_id, _evt_pend_id, _evt_pend_ts;
static uint8_t _evt_pend_n;
static uint32_t _evt_sent_ms;
static uint8_t _evt_retry, _evt_gaveup;
static void iot_evt_enqueue(uint8_t type, uint8_t ch, uint32_t value) {
if (_evt_count >= IOT_EVT_QUEUE_DEPTH) {
_evt_head = (_evt_head + 1) % IOT_EVT_QUEUE_DEPTH;
_evt_count--;
if (_evt_pend_id) { _evt_pend_id = 0; if (_evt_pend_n) _evt_pend_n = 0; }
}
{ IotEvent *e = &_evt_queue[(_evt_head + _evt_count) % IOT_EVT_QUEUE_DEPTH];
e->type = type; e->ch = ch; e->value = value; _evt_count++; }
_evt_gaveup = 0;
}
static void iot_evt_feed(const LUP_SensorReport *sr) {
uint8_t i;
if (!_evt_prev_valid) {
for (i = 0; i < LUP_COIL_COUNT; i++) {
_evt_prev_car[i] = (i < sr->coil_count) ? sr->coils[i].car_state : 0;
_evt_prev_loop[i] = (i < sr->coil_count) ? sr->coils[i].loop_state : 0;
}
_evt_prev_valid = 1; return;
}
for (i = 0; i < sr->coil_count && i < LUP_COIL_COUNT; i++) {
const LUP_CoilSensor *cs = &sr->coils[i];
if (!_evt_prev_loop[i] && !cs->loop_state) {
if (!_evt_prev_car[i] && cs->car_state) iot_evt_enqueue(IOT_EVT_CAR_ENTER, i+1, 0);
else if (_evt_prev_car[i] && !cs->car_state) {
uint32_t v = (cs->misc_type == 0) ? cs->misc.passtime_ms : 0;
iot_evt_enqueue(IOT_EVT_CAR_LEAVE, i+1, v);
}
}
if (!_evt_prev_loop[i] && cs->loop_state) {
_evt_cut_ms[i] = mstick(); iot_evt_enqueue(IOT_EVT_LOOP_CUT, i+1, 0);
} else if (_evt_prev_loop[i] && !cs->loop_state) {
iot_evt_enqueue(IOT_EVT_LOOP_RESTORE, i+1, (mstick() - _evt_cut_ms[i]) / 50);
}
_evt_prev_car[i] = cs->car_state; _evt_prev_loop[i] = cs->loop_state;
}
}
static void iot_evt_send(uint32_t id, uint32_t ts, uint8_t n) {
static const char *evt_names[] = {"car_enter","car_leave","loop_cut","loop_restore"};
static char payload[512];
int w, rem = sizeof(payload); char *p = payload; uint8_t i;
w = snprintf(p, rem, "{\"msg_id\":%u,\"cmd\":\"event_report\",\"ts\":%u,"
"\"data\":{\"events\":[", id, ts);
if (w < 0 || w >= rem) return;
p += w; rem -= w;
for (i = 0; i < n; i++) {
const IotEvent *e = &_evt_queue[(_evt_head + i) % IOT_EVT_QUEUE_DEPTH];
w = snprintf(p, rem, "%s{\"type\":\"%s\",\"ch\":%d,\"value\":%u}",
(i>0)?",":"", evt_names[e->type & 0x03], e->ch, e->value);
if (w < 0 || w >= rem) return;
p += w; rem -= w;
}
snprintf(p, rem, "]}}");
mqtt_publish((char *)g_iot_topic.topic_pub, payload, 1);
}
void iot_evt_handle_ack(uint32_t msg_id, int code) {
if (_evt_pend_id == 0 || msg_id != _evt_pend_id) return;
if (code != 0) return;
_evt_head = (_evt_head + _evt_pend_n) % IOT_EVT_QUEUE_DEPTH;
_evt_count -= _evt_pend_n;
_evt_pend_id = 0; _evt_pend_n = 0; _evt_retry = 0;
}
static void iot_evt_process(void) {
static uint8_t _was_ready = 0;
uint8_t ready = (g_iot_state == IOT_STATE_READY);
uint32_t now = mstick();
if (ready && !_was_ready) {
_evt_gaveup = 0;
if (_evt_pend_id) { /* 重连: 同 msg_id/ts 立即重发 */
_evt_retry = 0;
iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n);
_evt_sent_ms = now; _was_ready = ready; return;
}
}
_was_ready = ready;
if (!ready) return;
if (_evt_pend_id) {
if (now - _evt_sent_ms < IOT_EVT_ACK_TIMEOUT_MS) return;
if (_evt_retry >= IOT_EVT_MAX_RETRY) { _evt_pend_id = 0; _evt_gaveup = 1; return; }
_evt_retry++;
iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n);
_evt_sent_ms = now;
return;
}
if (_evt_count == 0 || _evt_gaveup) return;
_evt_pend_n = (_evt_count > IOT_EVT_MAX_PER_PKT) ? IOT_EVT_MAX_PER_PKT : _evt_count;
_evt_pend_id = ++_evt_msg_id;
_evt_pend_ts = now / 1000;
_evt_retry = 0;
iot_evt_send(_evt_pend_id, _evt_pend_ts, _evt_pend_n);
_evt_sent_ms = now;
}
/* ---------- 测试 ---------- */
static int fails = 0;
#define CHECK(cond, msg) do{ if(!(cond)){ printf("FAIL: %s\n", msg); fails++; } }while(0)
static LUP_SensorReport mkframe(uint8_t c1,uint8_t c2,uint8_t c3,uint8_t c4,
uint8_t l1,uint8_t l2,uint8_t l3,uint8_t l4, uint32_t pass) {
LUP_SensorReport sr; memset(&sr,0,sizeof(sr)); sr.coil_count=4;
uint8_t cars[4]={c1,c2,c3,c4}, loops[4]={l1,l2,l3,l4};
for(int i=0;i<4;i++){ sr.coils[i].car_state=cars[i]; sr.coils[i].loop_state=loops[i];
sr.coils[i].misc_type=0; sr.coils[i].misc.passtime_ms=pass; }
return sr;
}
int main(void) {
LUP_SensorReport sr;
/* T1: 首帧已有车 → 不产生事件 (只建快照) */
sr = mkframe(1,0,0,0, 0,0,0,0, 0);
iot_evt_feed(&sr);
CHECK(_evt_count == 0, "T1 首帧不出事件");
/* T2: ch2 进车沿 */
sr = mkframe(1,1,0,0, 0,0,0,0, 0);
iot_evt_feed(&sr);
CHECK(_evt_count == 1 && _evt_queue[0].type == IOT_EVT_CAR_ENTER
&& _evt_queue[0].ch == 2, "T2 car_enter ch2");
/* T3: 发送 → 5s 无应答重发×3 → 挂起 */
_mock_ms = 1000;
iot_evt_process(); /* 首发 msg_id=1 */
CHECK(pub_count == 1 && _evt_pend_id == 1, "T3 首发");
CHECK(strstr(last_payload, "\"msg_id\":1") && strstr(last_payload, "car_enter"),
"T3 payload 含 msg_id=1 + car_enter");
uint32_t first_ts = _evt_pend_ts;
for (int r = 1; r <= 3; r++) {
_mock_ms += 5100; iot_evt_process(); /* 重发 r */
CHECK(pub_count == 1 + r, "T3 重发次数");
CHECK(_evt_pend_ts == first_ts, "T3 重发 ts 不刷新");
CHECK(strstr(last_payload, "\"msg_id\":1") != NULL, "T3 重发同 msg_id");
}
_mock_ms += 5100; iot_evt_process(); /* 第4次超时 → 挂起 */
CHECK(pub_count == 4 && _evt_pend_id == 0 && _evt_gaveup == 1 && _evt_count == 1,
"T3 重试耗尽挂起, 事件保留");
_mock_ms += 5100; iot_evt_process();
CHECK(pub_count == 4, "T3 挂起期间不再发送");
/* T4: ch2 出车沿 (新事件) → 解除挂起, 合并重报 (含旧 car_enter) */
sr = mkframe(1,0,0,0, 0,0,0,0, 350);
iot_evt_feed(&sr);
CHECK(_evt_count == 2, "T4 car_leave 入队");
CHECK(_evt_queue[1].value == 350, "T4 car_leave 通过时间=350");
_mock_ms += 100; iot_evt_process(); /* 新包 msg_id=2, 含2条 */
CHECK(pub_count == 5 && _evt_pend_id == 2 && _evt_pend_n == 2, "T4 合并重报");
CHECK(strstr(last_payload, "car_enter") && strstr(last_payload, "car_leave"),
"T4 新包含新旧事件");
/* T5: ACK code!=0 → 不出队; ACK code=0 → 出队 */
iot_evt_handle_ack(2, 5);
CHECK(_evt_pend_id == 2 && _evt_count == 2, "T5 code!=0 不出队");
iot_evt_handle_ack(999, 0);
CHECK(_evt_pend_id == 2, "T5 错 msg_id 忽略");
iot_evt_handle_ack(2, 0);
CHECK(_evt_pend_id == 0 && _evt_count == 0, "T5 ACK 出队");
/* T6: 线圈断开→恢复, value=断开时长(50ms 单位); 断开期间屏蔽 car 沿 */
_mock_ms = 90000;
sr = mkframe(0,0,0,0, 0,0,0,0, 0); /* 归一化: 清 T4 残留的 prev_car(ch1=1) */
iot_evt_feed(&sr); /* 产生 ch1 car_leave, 随即排干 */
while (_evt_count) { iot_evt_process(); iot_evt_handle_ack(_evt_pend_id, 0); _mock_ms += 100; }
_mock_ms = 100000;
sr = mkframe(0,0,0,0, 0,0,1,0, 0); /* ch3 断开 */
iot_evt_feed(&sr);
CHECK(_evt_count == 1 && _evt_queue[_evt_head].type == IOT_EVT_LOOP_CUT, "T6 loop_cut");
_mock_ms = 103000; /* 3s 后 */
sr = mkframe(0,0,1,0, 0,0,0,0, 0); /* ch3 恢复且 car=1: 断开→正常帧不判 car 沿 */
iot_evt_feed(&sr);
CHECK(_evt_count == 2, "T6 恢复帧只出 loop_restore, car 沿被屏蔽");
{ IotEvent *e = &_evt_queue[(_evt_head+1)%IOT_EVT_QUEUE_DEPTH];
CHECK(e->type == IOT_EVT_LOOP_RESTORE && e->value == 60, "T6 断开时长 3000ms/50=60"); }
iot_evt_process(); iot_evt_handle_ack(_evt_pend_id, 0); /* 清场 */
/* T7: 单包上限 6 条 + 包长 < 500B */
for (int k = 0; k < 10; k++) {
sr = mkframe(1,0,0,0, 0,0,0,0, 0); iot_evt_feed(&sr); /* enter */
sr = mkframe(0,0,0,0, 0,0,0,0, 4294967295u); iot_evt_feed(&sr); /* leave, value 最大 */
}
CHECK(_evt_count == 16, "T7 队列封顶16 (20条丢4)");
iot_evt_process();
CHECK(_evt_pend_n == 6, "T7 单包6条");
CHECK(strlen(last_payload) < 500, "T7 包长<500B");
printf(" T7 包长实测: %zu B (6条, value含4294967295)\n", strlen(last_payload));
iot_evt_handle_ack(_evt_pend_id, 0);
CHECK(_evt_count == 10, "T7 ACK 后出队6条");
/* T8: 断线重连 → 未决包用【同 msg_id/ts】重发 (V1.04 §5.3-2, 平台去重不失效) */
iot_evt_process(); /* 发下一包 */
uint32_t pend_before = _evt_pend_id;
uint32_t ts_before = _evt_pend_ts;
CHECK(pend_before != 0, "T8 有未决包");
g_iot_state = IOT_STATE_DISCONNECTED;
iot_evt_process(); /* 断线 (未 ACK) */
g_iot_state = IOT_STATE_READY;
_mock_ms += 100;
int pub_before = pub_count;
iot_evt_process(); /* 重连沿: 同 msg_id 立即重发 */
CHECK(_evt_pend_id == pend_before, "T8 重连后保持同 msg_id (不换号)");
CHECK(_evt_pend_ts == ts_before, "T8 重连后保持原 ts");
CHECK(pub_count == pub_before + 1, "T8 重连触发一次重发");
{ char idbuf[32]; snprintf(idbuf, sizeof(idbuf), "\"msg_id\":%u", pend_before);
CHECK(strstr(last_payload, idbuf) != NULL, "T8 重发报文含原 msg_id"); }
/* 全部确认清空 */
while (_evt_count) { iot_evt_handle_ack(_evt_pend_id, 0); _mock_ms += 100; iot_evt_process(); }
CHECK(_evt_count == 0, "T8 清空");
printf(fails ? "\n== %d FAIL ==\n" : "\n== ALL PASS ==\n", fails);
return fails;
}