Files
STM32F4-Base/test/host/net_socket/ns_peers.c
T
super_agent_bot fff961d803
CI / 静态门禁 (pre-commit) (push) Failing after 49s
CI / 主机侧单测 (MSVC) (push) Failing after 30s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73)
前置来自 #57 SNTP 调研:旧 net_sendto / net_recvfrom 由调用方直调
SPI 且无限自旋收包,既违约「netTask 是芯片唯一访问者」,又把 IWDG
预算吃在自旋里。

改造面
- 新增 Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译):
  bind/set_peer/sendto/recvfrom 四条执行体 + 挂起收包表 + tick;
  net_msg_type_t 加四型,公开 API 一律「入队 → 睡在通知上」。
- 源地址出参不搭 net_msg_t(按值拷贝,AGENTS 禁加回传字段):改住
  net_udp.c 的 per-fd 槽,netTask 就地写回调用方内存,通知值只带
  结果码;每条出口都清槽,泄漏 = 该 fd 永久 BUSY。
- 发送复用 net_send_locked,不写第二份分块逻辑;「会话是否已死」
  收成一条判据 net_send_state_down()(TCP 看 ESTABLISHED、UDP 看
  UDP_OPEN),原先 4 处各写一遍。
- 顺带修掉真缺陷(新条 TRAP-NET-22):旧 sendto 把 DesIP 写成具体
  IP,芯片静默切 UDP Client,而旧 recvfrom 无条件剥 8 字节 → SNTP
  这类 48 B 应答丢载荷前 8 字节并交出垃圾源地址。加 udp_mode(塞进
  原有 padding,sizeof 不变)、bind 恒 SERVER、显式对端 sendto 切
  CLIENT、set_peer(fd, NULL) 轻量回 SERVER,仅 SERVER 且 avail >= 8
  才剥头。
- 截断定案:一次调用交付一个数据报,buf 装不下的尾巴用 64 B 栈上
  scratch 排空(不用 clear_recv_buf——它连后面已到达的包一起丢)。
- 取消点接齐:net_init 重建 cancel_all、net_socket 分配 fd clear、
  net_close settle_close + clear、重 bind 前先结算等包者,
  net_process_messages 第 5 个 tick 位挂 net_udp_tick。
- 收紧两处契约(原实现无生产调用者):UDP 未 bind 一律 -5;
  net_recv / _nb / _to 拒 DGRAM(绕过剥头就是静默吃 8 字节);
  NET_ERR_NOT_IN_NETTASK(-12) 退役为历史常量并写明原因。

验证
- 主机单测 test/host/net_socket 全套 616/616 绿(改造前 470):新
  分片 ns_s7_udp.c 20 组,改写 440 / 555 / 560 组;覆盖率
  net_udp.c 234/236(99.2%)、net_socket.c 956/969(98.7%),
  scripts/test_coverage_baseline.json 随本提交登记新文件。
- 测试抓到 4 个被测码真 bug:自等守卫误用调用侧谓词、芯片长度被读
  两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。
- Keil ARMCC 0 Error(s) 0 Warning(s);net_udp.o Code 1,358 / ZI 224
  (8 fd × 28 B,其余模块零增长),全仓 RW_IRAM1 72.2%;内存台账
  受管区按门禁提示用 --write / --update 机器回填,未手改数字。
- pre-commit 全量钩子(含 doc-links、coding-standard、mem-budget、
  generated-fresh、bsp-err、registration、nested-comment)全绿。

限度
- 本轮无板子:真机回归 5 条(两端互通、set_peer(NULL) 能否恢复收
  广播、截断排空后下一包完整性、挂起 recv 期间 IWDG 余量、bind 写
  DesIP 与 open 先后的真芯片效果)已写进覆盖率口径「台架必验」。
- scripts/stack_report.py 退出码 1 是 10-26 起挂在账上的 iec104Task
  栈债(margin 1,016 < 1,024,见 docs/代码规范/pre-commit使用说明.md:
  122);本轮只让 net_bind 的 UDP 分支多一层静态帧(1,544 → 1,600),
  netTask margin 1,888 与 timeSyncTask 1,152 不受影响。抬栈要过
  mem-budget 台账,不在本分支自行处理。
- 登记簿销项按仓库约定留给主工作树,本分支不动 docs/任务规划/**。
2026-11-03 18:44:01 +08:00

395 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:NET 层兄弟模块桩(net_socket 主机侧单测专用)
* 模块功能:net_socket.c 依赖的三个兄弟模块(net_listener / net_pending / net_events)
* 在这里**按边界契约打桩**,让本套单测只测 net_socket.c 自己那部分决策。
*
* ⚠ 仅供 test/host/net_socket/ 使用,不参与 MCU 构建。
*
* 为什么打桩而不是 #include 真实源(而不是"越真越好"):
* 1. 那三个模块**已各有自己的主机单测**(net_listener 34 条、net_events 34 条、
* net_pending 43 条),本套再跑一遍不增加任何对 net_socket.c 的判别力,
* 只会把"谁的红"变成糊账;
* 2. 本套真正要钉的是**边界契约**——net_socket.c 在什么条件下把什么 reason 登记给
* 监听层、什么条件下把挂起请求登记给 pending 层、以及"登记了就不能再自己发回执"。
* 桩把这些变成可断言的记录,比让真实 tick 在背后默默跑更有判别力;
* 3. 原型一律取**真实头**(net_listener.h / net_pending.h / net_events.h),
* 兄弟模块改签名 → 本套编译期就红。
*
* 桩的取值纪律(不自己发明语义,全部按真实头的函数说明):
* - net_pending_register / net_send_pending_register 返回 NET_PENDING_REGISTERED,
* 与真实实现一致(登记成功=哨兵,供分派跳过即时通知);可注入 -1 测登记被拒;
* - net_pending_tick 支持"下一轮把脚本里的结果当作数据到达,通知登记时的 caller",
* 复刻真实 tick 的"数据到达即通知"契约;
* - 其余全为纯记录(次数 + 最近一次入参),供"不该登记/必须登记"的断言。
*
* 创建日期:2026-10-24
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "net_listener.h"
#include "net_pending.h"
#include "net_events.h"
#include "net_socket.h"
#include "ns_peers.h"
#include "ns_rtos.h"
ns_peers_obs_t g_peers;
/* 桩内部"完成/取消"要发通知:转给假内核,这样测试能断言通知次数与通知值 */
void ns_peers_note_notify(TaskHandle_t to, uint32_t v)
{
ns_rtos_post_notify(to, v);
}
/* 挂起登记簿(只记一条,够本套断言用) */
static int s_pend_active[NET_MAX_SOCKETS];
static void *s_pend_buf[NET_MAX_SOCKETS];
static int s_pend_len[NET_MAX_SOCKETS];
static TaskHandle_t s_pend_caller[NET_MAX_SOCKETS];
static int s_snd_active[NET_MAX_SOCKETS];
static TaskHandle_t s_snd_caller[NET_MAX_SOCKETS];
/* tick 完成脚本:fd -> 结果值(一次生效) */
static int s_pend_done_armed[NET_MAX_SOCKETS];
static int s_pend_done_val[NET_MAX_SOCKETS];
static int s_snd_done_armed[NET_MAX_SOCKETS];
static int s_snd_done_val[NET_MAX_SOCKETS];
void ns_peers_reset(void)
{
(void)memset(&g_peers, 0, sizeof(g_peers));
(void)memset(s_pend_active, 0, sizeof(s_pend_active));
(void)memset(s_pend_buf, 0, sizeof(s_pend_buf));
(void)memset(s_pend_len, 0, sizeof(s_pend_len));
(void)memset(s_pend_caller, 0, sizeof(s_pend_caller));
(void)memset(s_snd_active, 0, sizeof(s_snd_active));
(void)memset(s_snd_caller, 0, sizeof(s_snd_caller));
(void)memset(s_pend_done_armed, 0, sizeof(s_pend_done_armed));
(void)memset(s_pend_done_val, 0, sizeof(s_pend_done_val));
(void)memset(s_snd_done_armed, 0, sizeof(s_snd_done_armed));
(void)memset(s_snd_done_val, 0, sizeof(s_snd_done_val));
g_peers.pend_register_rc = NET_PENDING_REGISTERED;
g_peers.send_register_rc = NET_PENDING_REGISTERED;
}
void ns_peers_arm_pending_done(int sockfd, int result)
{
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_done_armed[sockfd] = 1;
s_pend_done_val[sockfd] = result;
}
}
void ns_peers_arm_send_pending_done(int sockfd, int result)
{
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_snd_done_armed[sockfd] = 1;
s_snd_done_val[sockfd] = result;
}
}
/* ================= net_listener.h ================= */
void net_listener_request(int sockfd, net_listener_req_t reason)
{
g_peers.listener_req_calls++;
g_peers.last_req_fd = sockfd;
g_peers.last_req_reason = (int)reason;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
g_peers.req_armed[sockfd] = 1;
g_peers.req_reason[sockfd] = (int)reason;
}
}
void net_listener_clear(int sockfd)
{
g_peers.listener_clear_calls++;
g_peers.last_clear_fd = sockfd;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
g_peers.req_armed[sockfd] = 0;
}
}
void net_listener_tick(void)
{
g_peers.listener_tick_calls++;
}
void net_listener_settle(int sockfd, net_listener_settle_t level)
{
g_peers.listener_settle_calls++;
g_peers.last_settle_fd = sockfd;
g_peers.last_settle_level = (int)level;
}
/* ================= net_pending.h(recv 侧) ================= */
void net_pending_capture_owner(void)
{
g_peers.pending_capture_calls++;
}
int net_pending_register(int sockfd, void *buf, int len, uint32_t to_ms,
TaskHandle_t caller)
{
g_peers.pending_reg_calls++;
g_peers.last_pend_fd = sockfd;
g_peers.last_pend_to = to_ms;
g_peers.last_pend_caller = caller;
if (g_peers.pend_register_rc != NET_PENDING_REGISTERED) {
return g_peers.pend_register_rc; /* 测"登记被拒"分支 */
}
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_active[sockfd] = 1;
s_pend_buf[sockfd] = buf;
s_pend_len[sockfd] = len;
s_pend_caller[sockfd] = caller;
g_peers.pend_active_now[sockfd] = 1;
}
return NET_PENDING_REGISTERED;
}
void net_pending_tick(net_sock_t *p_socks)
{
int i;
g_peers.pending_tick_calls++;
(void)p_socks;
/* 复刻真实 tick 的主用例:数据到达 → 按登记时的 caller 通知 → 清槽 */
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if ((s_pend_active[i] != 0) && (s_pend_done_armed[i] != 0)) {
s_pend_done_armed[i] = 0;
s_pend_active[i] = 0;
g_peers.pend_active_now[i] = 0;
g_peers.pending_notified++;
ns_peers_note_notify(s_pend_caller[i], (uint32_t)s_pend_done_val[i]);
}
}
}
void net_pending_clear(int sockfd)
{
g_peers.pending_clear_calls++;
g_peers.last_pend_clear_fd = sockfd;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_active[sockfd] = 0;
g_peers.pend_active_now[sockfd] = 0;
}
}
void net_pending_cancel(int sockfd, int err)
{
g_peers.pending_cancel_calls++;
g_peers.last_pend_cancel_err = err;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_active[sockfd] = 0;
g_peers.pend_active_now[sockfd] = 0;
}
}
void net_pending_cancel_all(int err)
{
int i;
g_peers.pending_cancel_all_calls++;
g_peers.last_pend_cancel_err = err;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_pend_active[i] != 0) {
g_peers.pending_notified++;
ns_peers_note_notify(s_pend_caller[i], (uint32_t)err);
}
s_pend_active[i] = 0;
g_peers.pend_active_now[i] = 0;
}
}
void net_pending_settle_close(int sockfd, net_sock_t *p_sock)
{
g_peers.pending_settle_calls++;
g_peers.last_pend_fd = sockfd;
(void)p_sock;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS) && (s_pend_active[sockfd] != 0)) {
/* 真实实现:有数据先送达,无数据按 CONNRESET 取消——两者都**必通知** */
s_pend_active[sockfd] = 0;
g_peers.pend_active_now[sockfd] = 0;
g_peers.pending_notified++;
ns_peers_note_notify(s_pend_caller[sockfd], (uint32_t)NET_ERR_CONNRESET);
}
}
int net_pending_active(int sockfd)
{
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return s_pend_active[sockfd];
}
/* ================= net_pending.h(send 侧) ================= */
/* 会话是否已断(net_send_locked / net_send_pending 的共用判据,真实实现按 type 分流)。
* 桩**必须复刻分流**:写死 TCP 语义的话,UDP 挂起 send 的"会话断了要中止"分支
* 在本套里永远测不到(2026-11-03 UDP 队列化改造新增边界)。 */
int net_send_state_down(const net_sock_t *p_sock)
{
g_peers.state_down_calls++;
if (p_sock == NULL) {
return 1;
}
if (p_sock->type == NET_SOCK_DGRAM) {
return (p_sock->state != NET_SOCK_STATE_UDP_OPEN) ? 1 : 0;
}
return (p_sock->state != NET_SOCK_STATE_ESTABLISHED) ? 1 : 0;
}
int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
TaskHandle_t caller)
{
g_peers.send_reg_calls++;
g_peers.last_snd_fd = sockfd;
g_peers.last_snd_sent = sent;
g_peers.last_snd_len = len;
(void)buf;
if (g_peers.send_register_rc != NET_PENDING_REGISTERED) {
return g_peers.send_register_rc;
}
/* 真实实现的硬前置:sent >= len 视为参数非法拒收(末块边界) */
if (sent >= len) {
return -1;
}
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_snd_active[sockfd] = 1;
s_snd_caller[sockfd] = caller;
}
return NET_PENDING_REGISTERED;
}
void net_send_pending_tick(net_sock_t *p_socks)
{
int i;
g_peers.send_tick_calls++;
(void)p_socks;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if ((s_snd_active[i] != 0) && (s_snd_done_armed[i] != 0)) {
s_snd_done_armed[i] = 0;
s_snd_active[i] = 0;
g_peers.send_notified++;
ns_peers_note_notify(s_snd_caller[i], (uint32_t)s_snd_done_val[i]);
}
}
}
void net_send_pending_clear(int sockfd)
{
g_peers.send_clear_calls++;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_snd_active[sockfd] = 0;
}
}
void net_send_pending_cancel(int sockfd, int err)
{
g_peers.send_cancel_calls++;
g_peers.last_snd_cancel_err = err;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS) && (s_snd_active[sockfd] != 0)) {
s_snd_active[sockfd] = 0;
g_peers.send_notified++;
ns_peers_note_notify(s_snd_caller[sockfd], (uint32_t)err);
}
}
void net_send_pending_cancel_all(int err)
{
int i;
g_peers.send_cancel_all_calls++;
g_peers.last_snd_cancel_err = err;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_snd_active[i] != 0) {
g_peers.send_notified++;
ns_peers_note_notify(s_snd_caller[i], (uint32_t)err);
}
s_snd_active[i] = 0;
}
}
int net_send_pending_active(int sockfd)
{
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return s_snd_active[sockfd];
}
/* ================= net_events.h(事件面与对账) ================= */
/* 事件处理桩:按真实头注释的效果改控制块(CONNECT→ESTABLISHED + 记对端等),
* 让 net_poll 的"中断 → 状态机"分派可断言;同时计数。 */
void handle_connect_event(net_sock_t *p_sock)
{
g_peers.evt_connect_calls++;
if (p_sock != NULL) {
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
p_sock->readable = 0U;
p_sock->released_abnormal = 0U;
p_sock->session_dead = 0U;
}
}
void handle_disconnect_event(net_sock_t *p_sock)
{
g_peers.evt_disconnect_calls++;
if (p_sock != NULL) {
p_sock->last_peer_port = p_sock->remote_port;
p_sock->state = NET_SOCK_STATE_CLOSED;
p_sock->session_dead = 1U;
}
}
void handle_recv_event(net_sock_t *p_sock)
{
g_peers.evt_recv_calls++;
if (p_sock != NULL) {
p_sock->readable = 1U;
}
}
void handle_send_event(net_sock_t *p_sock)
{
g_peers.evt_send_calls++;
if (p_sock != NULL) {
p_sock->send_ready = 1U;
}
}
void handle_timeout_event(net_sock_t *p_sock)
{
g_peers.evt_timeout_calls++;
if (p_sock != NULL) {
p_sock->last_peer_port = p_sock->remote_port;
p_sock->state = NET_SOCK_STATE_CLOSED;
p_sock->released_abnormal = 1U;
p_sock->session_dead = 1U;
}
}
void net_reconcile_tick(void)
{
g_peers.reconcile_calls++;
}
void net_lost_connect_probe(net_sock_t *p_sock)
{
g_peers.lost_probe_calls++;
g_peers.last_probe_state = (p_sock != NULL) ? (int)p_sock->state : -1;
if ((p_sock != NULL) && (g_peers.lost_probe_hit != 0)) {
/* 脚本:探针命中 → 直接转 ESTABLISHED(复刻补 accept 的净效果) */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
p_sock->lost_conn_seen = 0U;
}
}