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
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/* 回归环: 复现 mqtt_publish 缓冲不足时发出垃圾包, 验证修复
|
||||
* 忠实复刻 MQTTSerialize_publish 契约 (真源码 MQTTSerializePublish.c L64-68):
|
||||
* 若 MQTTPacket_len(rem) > buflen → 返回 -2 且不写 buf
|
||||
* 相邻内存布局: mqttBuf[N] 之后紧邻 temp_guide[]="report_config"
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static int mqtt_packet_len(int rem) { int n=1,r=rem; while(r>=128){r/=128;n++;} return 1+n+rem; }
|
||||
static int mqtt_pub_serialize(unsigned char *buf, int buflen, int qos,
|
||||
const char *topic, const char *payload, int paylen) {
|
||||
int rem = 2 + (int)strlen(topic) + (qos>0?2:0) + paylen;
|
||||
if (mqtt_packet_len(rem) > buflen) return -2; /* BUFFER_TOO_SHORT, 不写 buf */
|
||||
int total = mqtt_packet_len(rem);
|
||||
buf[0] = 0x30 | (qos<<1); /* PUBLISH 头 */
|
||||
memset(buf+1, 0xAB, total-1); /* 标记有效数据(非0) */
|
||||
return total;
|
||||
}
|
||||
|
||||
/* 模拟内存: mqttBuf 紧邻 temp_guide */
|
||||
static unsigned char mem[2048];
|
||||
#define MQTTBUF_OFF 0
|
||||
static void layout_reset(int mqttbuf_len) {
|
||||
memset(mem, 0, sizeof(mem));
|
||||
strcpy((char*)&mem[MQTTBUF_OFF + mqttbuf_len], "report_config"); /* 相邻全局 */
|
||||
}
|
||||
|
||||
/* 旧实现: uint32_t len, 无守卫 */
|
||||
static int old_publish(int mqttbuf_len, const char *payload, int *sent_len, int *type0) {
|
||||
unsigned char *mqttBuf = &mem[MQTTBUF_OFF];
|
||||
memset(mqttBuf, 0, mqttbuf_len); /* clear_mqtt_buf */
|
||||
uint32_t len = (uint32_t)mqtt_pub_serialize(mqttBuf, mqttbuf_len, 0,
|
||||
"dld960/DC045A49718F/dev", payload, strlen(payload));
|
||||
/* WCHNET_SocketSend 发 len 字节 (受 MSS≈576 截断) */
|
||||
int send = (len > 576) ? 520 : (int)len; /* 天文数字→按段截到~520 */
|
||||
*sent_len = send;
|
||||
*type0 = (send>0 && mqttBuf[0]==0x00); /* 首字节=0 → 非法 MQTT 类型 */
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
/* 新实现: int len + 守卫 + 大缓冲 */
|
||||
static int new_publish(int mqttbuf_len, const char *payload, int *sent_len, int *type0) {
|
||||
unsigned char *mqttBuf = &mem[MQTTBUF_OFF];
|
||||
memset(mqttBuf, 0, mqttbuf_len);
|
||||
int len = mqtt_pub_serialize(mqttBuf, mqttbuf_len, 0,
|
||||
"dld960/DC045A49718F/dev", payload, strlen(payload));
|
||||
if (len <= 0) { *sent_len = 0; *type0 = 0; return len; } /* 守卫: 绝不发残缓冲 */
|
||||
uint32_t slen = (uint32_t)len;
|
||||
*sent_len = (int)slen;
|
||||
*type0 = (mqttBuf[0]==0x00);
|
||||
return len;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* 构造 604B 的 loop_data payload (4通道, 与现场同量级) */
|
||||
char loop[700]; int n=0;
|
||||
n += sprintf(loop+n, "{\"msg_id\":8,\"cmd\":\"loop_data\",\"ts\":373,\"data\":{\"channels\":[");
|
||||
for (int c=1;c<=4;c++) n += sprintf(loop+n,
|
||||
"%s{\"ch\":%d,\"level\":\"mid_high\",\"iscar\":false,\"loop_ok\":true,"
|
||||
"\"freq\":61286,\"diff\":-8388608,\"sens\":2,\"cndtn\":0,"
|
||||
"\"misc\":{\"type\":\"time\",\"value\":0}}", c>1?",":"", c);
|
||||
n += sprintf(loop+n, "]}}");
|
||||
printf("loop_data payload = %d B\n", n);
|
||||
|
||||
int fails=0, sent, t0, rc;
|
||||
|
||||
/* --- 旧: 512 缓冲 + loop_data → 发垃圾 --- */
|
||||
layout_reset(512);
|
||||
rc = old_publish(512, loop, &sent, &t0);
|
||||
printf("旧(512): serialize rc=%d, 实发=%dB, 首字节0x00(非法)=%d\n", rc, sent, t0);
|
||||
if (!(rc==-2 && sent>0 && t0==1)) { printf(" 期望复现垃圾包失败\n"); fails++; }
|
||||
else printf(" ✓ 复现: 发出 %dB 全零垃圾包(含相邻report_c), broker必RST\n", sent);
|
||||
|
||||
/* --- 新守卫: 512 缓冲 + loop_data → 不发 --- */
|
||||
layout_reset(512);
|
||||
rc = new_publish(512, loop, &sent, &t0);
|
||||
printf("新守卫(512): rc=%d, 实发=%dB\n", rc, sent);
|
||||
if (!(rc==-2 && sent==0)) { printf(" 守卫未生效\n"); fails++; }
|
||||
else printf(" ✓ 守卫拦截: serialize失败→不发送, 连接不被RST\n");
|
||||
|
||||
/* --- 新: 1024 缓冲 + loop_data → 正常发 --- */
|
||||
layout_reset(1024);
|
||||
rc = new_publish(1024, loop, &sent, &t0);
|
||||
printf("新(1024): rc=%d, 实发=%dB, 首字节合法(非0)=%d\n", rc, sent, !t0);
|
||||
if (!(rc>0 && sent==rc && t0==0)) { printf(" 1024缓冲仍异常\n"); fails++; }
|
||||
else printf(" ✓ 604B报文正常序列化并发送 (TCP自动分段, MQTT不关心段边界)\n");
|
||||
|
||||
/* --- 回归: report_config(216B) 在两版都正常 --- */
|
||||
const char *cfg = "{\"msg_id\":131,\"cmd\":\"report_config\",\"ts\":373,\"code\":0,"
|
||||
"\"msg\":\"success\",\"data\":{\"sensor_type\":12,\"enable\":true}}";
|
||||
layout_reset(1024);
|
||||
rc = new_publish(1024, cfg, &sent, &t0);
|
||||
if (!(rc>0 && t0==0)) { printf(" report_config回归失败\n"); fails++; }
|
||||
else printf("回归: report_config(%dB) 正常\n", (int)strlen(cfg));
|
||||
|
||||
printf(fails?"\n== %d FAIL ==\n":"\n== ALL PASS ==\n", fails);
|
||||
return fails;
|
||||
}
|
||||
Reference in New Issue
Block a user