0f7b5a87fb
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号) 一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、 采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。 门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏 undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8 reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF; id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。 test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字, 是本批唯一改变构建产物的文件集合。 为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged 部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、 不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。 实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01% (基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 / 告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。 基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处 引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使 识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。 ⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二 根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有), 今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。 固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0; App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889 断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿; check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带 BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。 .cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法: Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。 同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%, RW_CCM 57,376 B / 87.9%,阈值 92%。
622 lines
32 KiB
C
622 lines
32 KiB
C
/*
|
||
* 分片 5:建流三件套(listen / accept / connect)的锁定版决策 + net_init 下发面
|
||
* + UDP 数据报路径 + netTask 直调入口
|
||
*
|
||
* 为什么值得单独一片:这三条是「真机极难按需复现」的决策分支——open 被芯片拒
|
||
* (USE)、LISTEN 命令失败、TCP_CONNECT 被命令级拒绝、CONNECT 事件未收到的补 accept、
|
||
* 对端「发完就 FIN」的 closed-with-readable,在台架上要凑齐对端行为+时序+总线状态
|
||
* 才见得到一次;而每一条的错误形态都是「静默毁掉监听」或「数据被吞」(TRAP-NET-09/
|
||
* -12/-18/-20 与 E14 复核实验的原文结论)。
|
||
*
|
||
* 创建日期:2026-10-24
|
||
*/
|
||
|
||
/* 断线注入点:第二次轮询时把状态打回 CLOSED(模拟 net_poll 处理 DISCONNECT 之后的形态)。
|
||
* 用假内核现成的「每轮 net_poll 回调」注入并发事件,而不是自己发明时间或事件 API。 */
|
||
static int s_ns_poll_seen;
|
||
static void ns_drop_conn_on_second_poll(void)
|
||
{
|
||
s_ns_poll_seen++;
|
||
if (s_ns_poll_seen == 2) {
|
||
s_net_socks[0].state = NET_SOCK_STATE_CLOSED;
|
||
s_net_socks[0].session_dead = 1U;
|
||
}
|
||
}
|
||
|
||
/* TC-NETSOCK-400 net_init 的下发面与门控(一次调用钉十条契约) */
|
||
static void tc_400_net_init_downlink(void)
|
||
{
|
||
uint8_t mac_zero[6] = {0, 0, 0, 0, 0, 0};
|
||
uint8_t mac_real[6] = {0x02, 0x11, 0x22, 0x33, 0x44, 0x55};
|
||
int i;
|
||
|
||
/* 1) 芯片不在位:直接失败,且不得继续下发任何参数 */
|
||
reset_all();
|
||
g_fx.exist_rc = (int)CH395F_STATUS_NOT_DETECTED;
|
||
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
|
||
"TC-NETSOCK-400a chip not present -> -1");
|
||
CHECK(g_fxchip.reset_calls == 0 && g_fxchip.init_calls == 0,
|
||
"TC-NETSOCK-400b when absent it must not reset or initialise (do not drag the error into the stack)");
|
||
CHECK(g_ns_error_count == 1, "TC-NETSOCK-400c an absent chip must leave an [ERR] line (else only a dead port is left to observe)");
|
||
|
||
/* 2) 协议栈初始化失败 */
|
||
reset_all();
|
||
g_fx.init_rc = (int)CH395F_STATUS_ERROR;
|
||
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
|
||
"TC-NETSOCK-400d ch395f_init failure -> -1");
|
||
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e after an init failure PING must not be enabled (half-configured state)");
|
||
|
||
/* 3) 版本门控:低于 0x46 必须**拒置** bit3(SOCKET_CLOSE),退回芯片自动关断 */
|
||
reset_all();
|
||
g_fx.version = 0x45U;
|
||
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
|
||
"TC-NETSOCK-400f a too-old version is not fatal: bring-up continues");
|
||
CHECK(g_fx.fun_para == 0U && g_ns_error_count == 1,
|
||
"TC-NETSOCK-400g ver<0x46 -> FUN_PARA actually sends 0 (bit3 refused) plus [ERR], never silently degraded");
|
||
|
||
/* 4) 版本够新:FUN_PARA=0x08;bit1(TCP_SERVER 多连接) 与 bit0(保留) 都必须为 0 */
|
||
reset_all();
|
||
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
|
||
"TC-NETSOCK-400h normal initialisation succeeds");
|
||
/* bit3=SOCKET_CLOSE 要置;bit1=TCP_SERVER 多连接(本项目单连接模型)与
|
||
* bit0=保留位都必须为 0——旧实现把 0x09 当「多 Socket 模式」下发,2026-10-11
|
||
* 定案为 0x08(bit0 是保留位,证据链 TRAP-NET-15)。所以掩码要考 bit0|bit1,
|
||
* 拿 0x09 考会把我们正想要的 bit3 一起算进去(第一版就这么写错了)。 */
|
||
CHECK(g_fx.fun_para == CH395F_FUN_PARA_SOCKET_CLOSE &&
|
||
(g_fx.fun_para & 0x03UL) == 0UL,
|
||
"TC-NETSOCK-400i FUN_PARA sets bit3 only, bit0/bit1 are 0 (0x09 was a historical mistake, TRAP-NET-15)");
|
||
CHECK(g_fx.mss == (uint16_t)CH395F_TCP_MSS && g_fx.retrans_cnt == 8U &&
|
||
g_fx.retrans_period == 200U,
|
||
"TC-NETSOCK-400j MSS and retransmit parameters come from net_config.h truth (8 tries / 200ms, P0-a)");
|
||
CHECK(g_fx.ka_idle == 60000U && g_fx.ka_intvl == 5000U && g_fx.ka_cnt == 3U,
|
||
"TC-NETSOCK-400k KeepAlive 60s/5s/3 and it must be issued before ch395f_init");
|
||
CHECK(g_fx.mss != 0U || g_fx.ka_idle != 0U ? 1 : 0, "TC-NETSOCK-400k2 parameters really were issued");
|
||
CHECK(g_chip.log_n == 0 ||
|
||
ch395f_fake_log_find('n') > ch395f_fake_log_find('Q'),
|
||
"TC-NETSOCK-400l SET-class commands (MSS/KeepAlive) come before INIT_CH395 (manual hard requirement)");
|
||
|
||
/* 5) IP/掩码/网关三样都按点分顺位下发,并缓存本机 IP 供 getsockname 用 */
|
||
CHECK(g_fx.ip[0] == 192U && g_fx.ip[1] == 168U && g_fx.ip[2] == 1U &&
|
||
g_fx.ip[3] == 100U && g_fx.gw[3] == 1U && g_fx.mask[0] == 255U,
|
||
"TC-NETSOCK-400m IP, gateway and mask follow the parse_ip_string order (segment 0 lands at [0])");
|
||
CHECK(g_fxchip.set_dhcp_calls == 0 && g_fx.dhcp_enable == 0U,
|
||
"TC-NETSOCK-400n with a static IP given, DHCP is never enabled");
|
||
|
||
/* 6) ip=NULL → 走 DHCP,不下发静态三样 */
|
||
reset_all();
|
||
(void)net_init(NULL, NULL, NULL, NULL);
|
||
CHECK(g_fxchip.set_dhcp_calls == 1 && g_fx.dhcp_enable == 1U,
|
||
"TC-NETSOCK-400o ip=NULL -> enables DHCP");
|
||
CHECK(g_fxchip.set_ip_calls == 0 && g_fxchip.set_gw_calls == 0 &&
|
||
g_fxchip.set_mask_calls == 0, "TC-NETSOCK-400p the DHCP form issues no static address at all");
|
||
|
||
/* 7) MAC:全零=用芯片内置,不下发;非零必须下发(写 EEPROM,须在 INIT 之前) */
|
||
reset_all();
|
||
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_zero);
|
||
CHECK(g_fxchip.set_mac_calls == 0, "TC-NETSOCK-400q all-zero MAC -> keep the chip built-in one, do not issue it");
|
||
reset_all();
|
||
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_real);
|
||
CHECK(g_fxchip.set_mac_calls == 1 && g_fx.mac[0] == 0x02U && g_fx.mac[5] == 0x55U,
|
||
"TC-NETSOCK-400r a non-zero MAC is issued (device online identity stays consistent with the UI)");
|
||
CHECK(ch395f_fake_log_find('M') < ch395f_fake_log_find('n') ||
|
||
ch395f_fake_log_find('M') < 0,
|
||
"TC-NETSOCK-400s SET_MAC comes BEFORE INIT_CH395 (TRAP-NET-19: reversed order does not take effect this power cycle)");
|
||
|
||
/* 8) 收发缓冲块布局:6 块/Socket,先配数据 Socket 再配监听 Socket 0(TRAP-NET-01) */
|
||
reset_all();
|
||
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
|
||
CHECK(g_fxchip.set_recv_buf_calls == NET_MAX_SOCKETS &&
|
||
g_fxchip.set_send_buf_calls == NET_MAX_SOCKETS,
|
||
"TC-NETSOCK-400t all 8 sockets are explicitly reallocated (power-on defaults only cover 0-3)");
|
||
for (i = 0; i < NET_MAX_SOCKETS; i++) {
|
||
if (g_fx.recv_blk_start[i] != (uint8_t)(i * 6) ||
|
||
g_fx.recv_blk_cnt[i] != (uint8_t)CH395F_RECV_BLOCKS ||
|
||
g_fx.send_blk_start[i] != (uint8_t)(i * 6 + (int)CH395F_RECV_BLOCKS) ||
|
||
g_fx.send_blk_cnt[i] != (uint8_t)CH395F_SEND_BLOCKS) {
|
||
break;
|
||
}
|
||
}
|
||
CHECK(i == NET_MAX_SOCKETS,
|
||
"TC-NETSOCK-400u layout recv=i*6 / send=recv+4, socket 7 uses exactly blocks 0..47 (manual 5.45)");
|
||
CHECK(g_fx.ping_enable == 1U && g_fxchip.ping_calls == 1, "TC-NETSOCK-400v PING response enabled");
|
||
|
||
/* 9) net_init 的第一件事是取消挂起并通知调用方(顺序错了 = 调用方永久挂死) */
|
||
reset_all();
|
||
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
|
||
CHECK(g_peers.pending_cancel_all_calls == 1 &&
|
||
g_peers.send_cancel_all_calls == 1,
|
||
"TC-NETSOCK-400w both pending tables are cancelled before a rebuild (recv and send each one, missing one strands a task)");
|
||
CHECK(g_peers.last_pend_cancel_err == NET_ERR_CONNRESET &&
|
||
g_peers.last_snd_cancel_err == NET_ERR_CONNRESET,
|
||
"TC-NETSOCK-400x cancellation reason = CONNRESET");
|
||
CHECK(g_peers.pending_capture_calls == 1,
|
||
"TC-NETSOCK-400y registers the executing task (the pending self-wait guard judges who is executing from it)");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-410 net_listen_locked:open 重试、清事件时序、失败对称关断 */
|
||
static void tc_410_listen_locked(void)
|
||
{
|
||
struct net_sockaddr_in in;
|
||
int fd;
|
||
|
||
/* 非 TCP 流 → INVAL */
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
|
||
"TC-NETSOCK-410a UDP cannot listen");
|
||
|
||
/* 状态不对(还没 bind 也没到 CREATED)→ INVAL */
|
||
bring_up();
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
|
||
"TC-NETSOCK-410b listening again on an established fd -> INVAL (never demote a data channel to a listener)");
|
||
|
||
/* 成功路径:BOUND → LISTENING,并启用 auto_relisten */
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
(void)memset(&in, 0, sizeof(in));
|
||
in.sin_port = net_htons(2404);
|
||
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-410c bind precondition");
|
||
ch395f_fake_clear_log();
|
||
CHECK(net_listen_locked(fd) == 0, "TC-NETSOCK-410d listen succeeds");
|
||
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING &&
|
||
s_net_socks[fd].auto_relisten == 1U && s_net_socks[fd].lost_conn_seen == 0U,
|
||
"TC-NETSOCK-410e enters LISTENING with auto_relisten=1 (BSD listener stays resident) and debounce cleared");
|
||
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 2404U,
|
||
"TC-NETSOCK-410f source port comes from the control block local_port (the port must not drift)");
|
||
CHECK(ch395f_fake_log_find('i') >= 0 &&
|
||
ch395f_fake_log_find('i') < ch395f_fake_log_find('l'),
|
||
"TC-NETSOCK-410g clearing this socket events comes BEFORE TCP_LISTEN (E14: reversed swallows the new CONNECT)");
|
||
CHECK(g_fxchip.glob_calls == 0,
|
||
"TC-NETSOCK-410h listen must never read the global interrupt register (exactly what the B3 audit removed)");
|
||
|
||
/* open 一直失败:重试 3 次,每次都走确定性排渣,最后 BUSY */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
g_fake.open_rc[fd] = CH395F_ERR_USE;
|
||
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
|
||
"TC-NETSOCK-410i open still failing after 3 tries -> BUSY");
|
||
CHECK(g_chip.open_calls == 3 && g_fxchip.rst_begin_calls == 3,
|
||
"TC-NETSOCK-410j three opens plus three sock_reset (TRAP-NET-09 retry semantics kept, not a blind 500ms wait)");
|
||
CHECK(g_chip.listen_calls == 0, "TC-NETSOCK-410k with no successful open, never issue the LISTEN command");
|
||
|
||
/* LISTEN 命令失败:open 已成的 Socket 必须关回去(U12 对称,否则卡 OPEN 无监听) */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
g_fake.listen_rc[fd] = CH395F_ERR_VAL;
|
||
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
|
||
"TC-NETSOCK-410l TCP_LISTEN failure -> BUSY");
|
||
CHECK(g_chip.close_calls == 1 && g_chip.open_calls == 1,
|
||
"TC-NETSOCK-410m a failed LISTEN must close the opened socket (else every later open returns USE)");
|
||
CHECK(s_net_socks[fd].state != NET_SOCK_STATE_LISTENING,
|
||
"TC-NETSOCK-410n failure must not falsely report LISTENING");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-411 公开 net_listen:越界 / 队列 / 结果恢复 */
|
||
static void tc_411_listen_public(void)
|
||
{
|
||
struct net_sockaddr_in in;
|
||
int fd;
|
||
|
||
bring_up();
|
||
CHECK(net_listen(-1) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-411a out of range -> BADF");
|
||
CHECK(net_listen(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-411b upper bound out of range -> BADF");
|
||
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
(void)memset(&in, 0, sizeof(in));
|
||
in.sin_port = net_htons(7003);
|
||
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-411c bind");
|
||
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
|
||
CHECK(net_listen(fd) == 0, "TC-NETSOCK-411d end-to-end listen succeeds (enqueue, dispatch, notify value 0)");
|
||
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING,
|
||
"TC-NETSOCK-411e the public entry really drove the chip-side listener");
|
||
CHECK(g_ns_notify_last_val == 0U,
|
||
"TC-NETSOCK-411f a successful listen notify value is 0 (the 0x70000000 sentinel must not leak)");
|
||
|
||
/* 已监听的 fd 再 listen:netTask 里 INVAL,经通知值回传 */
|
||
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
|
||
"TC-NETSOCK-411g listening twice -> -1 with errno INVAL (exact code, not a generic -1)");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-420 accept:单连接模型的四种形态 */
|
||
static void tc_420_accept_shapes(void)
|
||
{
|
||
struct net_sockaddr_in in;
|
||
struct net_sockaddr *sa = (struct net_sockaddr *)∈
|
||
int addrlen;
|
||
int fd;
|
||
|
||
/* 1) 已有可接受连接:返回的就是监听 fd 自身(单连接),并回填对端 */
|
||
bring_up();
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
addrlen = (int)sizeof(in);
|
||
(void)memset(&in, 0, sizeof(in));
|
||
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
|
||
CHECK(net_accept(fd, sa, &addrlen) == fd, "TC-NETSOCK-420a single connection: accept returns the listening fd itself");
|
||
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 51999U &&
|
||
addrlen == (int)sizeof(in),
|
||
"TC-NETSOCK-420b fills the peer address and length (lftpd prints who connected from this)");
|
||
CHECK(g_peers.lost_probe_calls == 0,
|
||
"TC-NETSOCK-420c non-LISTENING never calls the missed-accept probe (the probe is listener-only self-rescue)");
|
||
|
||
/* 2) LISTENING 且探针命中 → 接受(CONNECT 事件未收到的自愈路径) */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
|
||
s_net_socks[fd].local_port = 2404U;
|
||
s_net_socks[fd].remote_port = 55555U;
|
||
g_peers.lost_probe_hit = 1;
|
||
addrlen = 0;
|
||
CHECK(net_accept(fd, NULL, NULL) == fd,
|
||
"TC-NETSOCK-420d the probe moved the listener to ESTABLISHED -> accept succeeds at once (relisten settle root fix)");
|
||
CHECK(g_peers.lost_probe_calls == 1,
|
||
"TC-NETSOCK-420e only LISTENING calls the probe (once per accept round, with debounce)");
|
||
|
||
/* 3) LISTENING 且无连接 → WOULDBLOCK,且不动监听重建 */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
|
||
addrlen = (int)sizeof(in);
|
||
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
|
||
"TC-NETSOCK-420f no connection -> WOULDBLOCK (never blocks netTask)");
|
||
CHECK(g_peers.listener_req_calls == 0,
|
||
"TC-NETSOCK-420g while the listener is alive, never register a rebuild (that is the U12 accident shape)");
|
||
|
||
/* 4) CLOSED + auto_relisten + RX 有残留 → 接受"已关但可读",先排空再 EOF */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
|
||
s_net_socks[fd].auto_relisten = 1U;
|
||
s_net_socks[fd].local_port = 21U;
|
||
s_net_socks[fd].remote_port = 44444U;
|
||
ch395f_fake_push_recv_len((uint8_t)fd, 1027U);
|
||
addrlen = (int)sizeof(in);
|
||
CHECK(net_accept(fd, sa, &addrlen) == fd,
|
||
"TC-NETSOCK-420h closed-with-readable: accept this connection to drain it (relisten settle, reversed yields 425)");
|
||
CHECK(g_peers.listener_req_calls == 0,
|
||
"TC-NETSOCK-420i with leftover data present, do NOT register a rebuild (relisting first destroys arrived data)");
|
||
CHECK(g_chip.get_status_calls == 0,
|
||
"TC-NETSOCK-420j the drain decision only reads GET_RECV_LEN, no status query");
|
||
|
||
/* 5) CLOSED + auto_relisten + RX 空 → 登记 REQ_ACCEPT 并 WOULDBLOCK */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
|
||
s_net_socks[fd].auto_relisten = 1U;
|
||
s_net_socks[fd].local_port = 21U;
|
||
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
|
||
"TC-NETSOCK-420k CLOSED with no leftover -> WOULDBLOCK");
|
||
CHECK(g_peers.listener_req_calls == 1 &&
|
||
g_peers.last_req_reason == (int)NET_LISTENER_REQ_ACCEPT &&
|
||
g_peers.last_req_fd == fd,
|
||
"TC-NETSOCK-420l only a consumer hitting empty registers an immediate rebuild (listener lifecycle P2: removing it widens the CLOSED window)");
|
||
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0,
|
||
"TC-NETSOCK-420m the registration path costs zero chip transactions (rebuild lives only in net_listener_tick)");
|
||
|
||
/* 6) 非监听型 CLOSED(客户端)→ 不擅自登记 */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
|
||
s_net_socks[fd].auto_relisten = 0U;
|
||
CHECK(net_accept(fd, sa, &addrlen) == -1 && g_peers.listener_req_calls == 0,
|
||
"TC-NETSOCK-420n CLOSED on a client fd registers no listener rebuild");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-421 accept 的公开入口边界 */
|
||
static void tc_421_accept_public(void)
|
||
{
|
||
struct net_sockaddr_in in;
|
||
struct net_sockaddr *sa = (struct net_sockaddr *)∈
|
||
int addrlen;
|
||
int fd;
|
||
|
||
bring_up();
|
||
CHECK(net_accept(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-421a out of range -> BADF");
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
addrlen = 0;
|
||
CHECK(net_accept(fd, sa, NULL) == fd,
|
||
"TC-NETSOCK-421b accept still works with addrlen NULL (address fill skipped, no crash)");
|
||
addrlen = (int)sizeof(in);
|
||
CHECK(net_accept(fd, NULL, &addrlen) == fd,
|
||
"TC-NETSOCK-421c addr NULL: returns just the fd and fills no address");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-430 connect:地址簿记、open 重试、命令被拒回滚 */
|
||
static void tc_430_connect(void)
|
||
{
|
||
struct net_sockaddr_in in;
|
||
int fd;
|
||
|
||
bring_up();
|
||
CHECK(net_connect(-1, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-430a out of range -> BADF");
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
CHECK(net_connect(fd, NULL, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL,
|
||
"TC-NETSOCK-430b addr=NULL -> INVAL (arguments blocked before enqueue)");
|
||
CHECK(net_connect(fd, (struct net_sockaddr *)&in, 1) == -1 &&
|
||
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430c addrlen too small -> INVAL");
|
||
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-430d invalid arguments create no message (netTask is not occupied)");
|
||
|
||
/* 成功:地址簿记 + 大端顺位 + 状态 CONNECTING */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
(void)memset(&in, 0, sizeof(in));
|
||
in.sin_family = NET_AF_INET;
|
||
in.sin_port = net_htons(8888);
|
||
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
|
||
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
|
||
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
|
||
"TC-NETSOCK-430e connect command issued (asynchronous, the interrupt decides success)");
|
||
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING,
|
||
"TC-NETSOCK-430f local state moves to CONNECTING");
|
||
CHECK(s_net_socks[fd].remote_port == 8888U,
|
||
"TC-NETSOCK-430g peer port stored in bookkeeping as host order");
|
||
CHECK(g_fx.des_ip[fd][0] == 192U && g_fx.des_ip[fd][1] == 168U &&
|
||
g_fx.des_ip[fd][2] == 1U && g_fx.des_ip[fd][3] == 200U,
|
||
"TC-NETSOCK-430h destination IP issued as a big-endian array (the order set_des_ip needs, reversed means no link)");
|
||
CHECK(g_chip.set_port_calls == 1 && g_fx.des_port[fd] == 8888U,
|
||
"TC-NETSOCK-430i source port issued once (SET_SOUR_PORT), the peer port goes via SET_DES_PORT=8888");
|
||
CHECK(g_fxchip.sock_events_calls == 1 && g_fxchip.glob_calls == 0,
|
||
"TC-NETSOCK-430j clearing stale interrupts only clears this socket (F-NET-7: the old extra global read welded others shut)");
|
||
CHECK(ch395f_fake_log_find('v') < ch395f_fake_log_find('T') ||
|
||
ch395f_fake_log_find('v') < 0,
|
||
"TC-NETSOCK-430k clearing events precedes TCP_CONNECT (the two-stage pre-clear of TRAP-NET-12/-18)");
|
||
|
||
/* 命令级被拒(非 BUSY):必须回滚,不能停在 CONNECTING 干等 */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
g_fx.connect_rc[fd] = CH395F_ERR_VAL;
|
||
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
|
||
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430l chip refused the command -> -1");
|
||
CHECK(g_chip.close_calls == 1,
|
||
"TC-NETSOCK-430m after a refusal the manual requires reopening from OPEN, so the socket is closed");
|
||
CHECK(s_net_socks[fd].session_dead == 1U &&
|
||
s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
|
||
"TC-NETSOCK-430n a command-level failure also ends the session: sets session_dead and CLOSED (B2, else pending recv waits forever)");
|
||
CHECK(g_ns_error_count >= 1, "TC-NETSOCK-430o a refusal must leave an [ERR] line");
|
||
|
||
/* BUSY 只是命令仍在执行,不得判失败 */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
g_fx.connect_rc[fd] = CH395F_ERR_BUSY;
|
||
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
|
||
"TC-NETSOCK-430p BUSY means the chip is still executing -> keep the old behaviour and let the interrupt decide (do not kill a slow chip)");
|
||
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING && g_chip.close_calls == 0,
|
||
"TC-NETSOCK-430q BUSY neither rolls back nor closes the socket");
|
||
|
||
/* open 三次失败 → BUSY,且不发 CONNECT */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
|
||
g_fake.open_rc[fd] = CH395F_ERR_USE;
|
||
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
|
||
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-430r open failing 3 times -> BUSY");
|
||
CHECK(g_chip.open_calls == 3 && g_fxchip.connect_calls == 0,
|
||
"TC-NETSOCK-430s with no successful open, never issue TCP_CONNECT");
|
||
|
||
/* 非 TCP 流 / 未在用 → INVAL(netTask 侧再判一次) */
|
||
reset_all();
|
||
bring_up();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
|
||
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430t a UDP slot never takes the TCP connect path");
|
||
CHECK(net_connect_locked(7, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
|
||
net_get_errno() == NET_ERR_INVAL,
|
||
"TC-NETSOCK-430u in range but not in use -> INVAL (in_use is checked with the control block; BADF is only for range)");
|
||
CHECK(net_connect_locked(NET_MAX_SOCKETS, (const struct net_sockaddr *)&in,
|
||
(int)sizeof(in)) == -1, "TC-NETSOCK-430v out of range -> BADF");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-440 UDP 数据报:sendto 分块 / recvfrom 剥 8 字节报文头 */
|
||
static void tc_440_udp_datagram(void)
|
||
{
|
||
static char big[2100];
|
||
char buf[64];
|
||
struct net_sockaddr_in in;
|
||
struct net_sockaddr *sa = (struct net_sockaddr *)∈
|
||
int addrlen;
|
||
int fd;
|
||
|
||
bring_up_netctx();
|
||
CHECK(net_sendto(-1, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-440a sendto out of range -> BADF");
|
||
CHECK(net_sendto(0, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-440b not in use -> BADF");
|
||
CHECK(net_recvfrom(0, buf, sizeof(buf), 0, NULL, NULL) == -1 &&
|
||
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-440c recvfrom on an unused fd -> BADF");
|
||
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
CHECK(net_sendto(fd, NULL, 5, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
|
||
"TC-NETSOCK-440d sendto invalid arguments -> INVAL");
|
||
CHECK(net_recvfrom(fd, NULL, 5, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
|
||
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-440e recvfrom with a NULL buf -> INVAL");
|
||
|
||
/* 带目的地址:先设 DesIP/DesPort 再写数据(不带上限的自旋实现,健康链路一次写完) */
|
||
reset_all();
|
||
bring_up_netctx();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
(void)memset(&in, 0, sizeof(in));
|
||
in.sin_port = net_htons(8888);
|
||
in.sin_addr.s_addr = net_inet_addr("192.168.1.9");
|
||
CHECK(net_sendto(fd, "hello", 5, 0, sa, (int)sizeof(in)) == 5,
|
||
"TC-NETSOCK-440f sendto returns the number of bytes sent");
|
||
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
|
||
"TC-NETSOCK-440g the destination address travels with this send (UDP is connectionless, address is per packet)");
|
||
CHECK(g_chip.write_calls == 1 && s_net_socks[fd].send_bytes == 5U,
|
||
"TC-NETSOCK-440h small packet written in one go plus statistics");
|
||
|
||
/* 大报分块(仍受 NET_SEND_CHUNK_MAX 上界约束) */
|
||
reset_all();
|
||
bring_up_netctx();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
g_ns_int_pin_set = 0;
|
||
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
|
||
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
|
||
CHECK(net_sendto(fd, big, 2100, 0, NULL, 0) == 2100, "TC-NETSOCK-440i all 2100 bytes delivered");
|
||
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-440j UDP also chunks at the 1024 bound (the FIFO overrun constraint matches TCP)");
|
||
|
||
/* recvfrom:前 8 字节是芯片 UDP 头(长度/srcPort/srcIP),要剥掉再交出去 */
|
||
reset_all();
|
||
bring_up_netctx();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
ch395f_fake_push_recv_len((uint8_t)fd, 8U + 20U);
|
||
addrlen = (int)sizeof(in);
|
||
(void)memset(&in, 0, sizeof(in));
|
||
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen) == 20,
|
||
"TC-NETSOCK-440k returns 20: the 8-byte report header was stripped, the header is not handed over as data");
|
||
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
|
||
"TC-NETSOCK-440l fills the source address structure and length");
|
||
|
||
/* 无数据 + 非阻塞 → WOULDBLOCK(阻塞形态是自旋无预算,本套刻意不测,见交付说明) */
|
||
reset_all();
|
||
bring_up_netctx();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
|
||
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440m non-blocking with no data -> WOULDBLOCK");
|
||
s_net_socks[fd].blocking = 0U;
|
||
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == -1 &&
|
||
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440n the non-blocking attribute follows the same decision");
|
||
/* 短于 8 字节的残包:不剥头,整包交出去(钉当前实现的边界,不猜"应该怎么算") */
|
||
reset_all();
|
||
bring_up_netctx();
|
||
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
|
||
ch395f_fake_push_recv_len((uint8_t)fd, 5U);
|
||
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == 5,
|
||
"TC-NETSOCK-440o with recv_len<8 no header is stripped, the whole packet is returned (current implementation)");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-450 netTask 直调入口的 NULL 守卫与委托 */
|
||
static void tc_450_direct_entries(void)
|
||
{
|
||
bring_up();
|
||
CHECK(net_send_sock(NULL, "x", 1) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-450a net_send_sock(NULL) -> BADF");
|
||
CHECK(net_recv_sock(NULL, (void *)"x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-450b net_recv_sock(NULL) -> BADF");
|
||
CHECK(net_close_sock(NULL) == -1 && net_get_errno() == NET_ERR_BADF,
|
||
"TC-NETSOCK-450c net_close_sock(NULL) -> BADF");
|
||
|
||
/* netTask 内直调 send:挂起登记会被"执行者自等待"守卫拒 → 返回 -1(BUSY) */
|
||
reset_all();
|
||
bring_up();
|
||
{
|
||
int fd = mk_established(NET_SOCK_STREAM);
|
||
|
||
s_net_socks[fd].send_ready = 0U;
|
||
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
|
||
g_peers.send_register_rc = -1; /* 复刻守卫:netTask 自己发的必被拒 */
|
||
CHECK(net_send_sock(&s_net_socks[fd], "hello", 5) == -1,
|
||
"TC-NETSOCK-450d inside netTask a stalled buffer never registers pending (nobody can complete it) -> -1");
|
||
CHECK(g_peers.send_reg_calls == 1,
|
||
"TC-NETSOCK-450e the registration call really happened (the refusal came from the register side, not from us not trying)");
|
||
}
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-460 复位收口超时:芯片永远关不掉时上层必须有界退出(不得无限占用 netTask) */
|
||
static void tc_460_reset_sync_timeout_bound(void)
|
||
{
|
||
uint32_t t_before;
|
||
int fd;
|
||
|
||
bring_up();
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
|
||
g_fx.rst_keep_open[fd] = 1U; /* 复位机怎么努力芯片都不落回 CLOSED */
|
||
t_before = g_ns_tick;
|
||
|
||
CHECK(net_close(fd) == 0,
|
||
"TC-NETSOCK-460a even when the chip cannot be closed, close proceeds (fd-side bookkeeping must still close out)");
|
||
CHECK((g_ns_tick - t_before) <= 700U,
|
||
"TC-NETSOCK-460b the reset wait has a hard 600ms ceiling (without one a bad chip drags netTask down)");
|
||
CHECK(g_ns_error_count >= 1,
|
||
"TC-NETSOCK-460c a reset timeout must leave [ERR]: that line is the only way to ever notice it");
|
||
CHECK(s_net_socks[fd].in_use == 0U,
|
||
"TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)");
|
||
reset_all();
|
||
|
||
/* 探针窗口心跳缺口修复:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回
|
||
* CLOSED 也与本调用无关(排渣与等待归 net_listener_tick 的隔离机) */
|
||
bring_up();
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
s_net_socks[fd].auto_relisten = 1U;
|
||
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
|
||
g_fx.rst_keep_open[fd] = 1U;
|
||
t_before = g_ns_tick;
|
||
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 20U &&
|
||
g_ns_error_count == 0 && g_peers.listener_settle_calls == 1,
|
||
"TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (heartbeat gap fix)");
|
||
reset_all();
|
||
|
||
/* 非监听 client fd 保留旧超时线(同步、600ms 封顶、留痕) */
|
||
bring_up();
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
|
||
g_fx.rst_keep_open[fd] = 1U;
|
||
t_before = g_ns_tick;
|
||
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 700U &&
|
||
g_ns_error_count >= 1,
|
||
"TC-NETSOCK-460f client-fd drop keeps the same bounded close-out as net_close and logs it too");
|
||
reset_all();
|
||
}
|
||
|
||
/* TC-NETSOCK-470 发送途中的并发事件:断线要按短写交出已发量,不能假装全成功 */
|
||
static void tc_470_send_disconnect_midway(void)
|
||
{
|
||
static char big[2100];
|
||
int fd;
|
||
int rc;
|
||
|
||
bring_up();
|
||
fd = mk_established(NET_SOCK_STREAM);
|
||
/* 第一块写得进去,第二块等待期间对端断线(net_poll 处理 DISCONNECT 把状态打回 CLOSED) */
|
||
g_ns_int_pin_set = 0;
|
||
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
|
||
g_fake.int_status[fd] = CH395F_SINT_STAT_DISCONNECT;
|
||
g_ns_on_poll = ns_drop_conn_on_second_poll;
|
||
s_ns_poll_seen = 0;
|
||
|
||
rc = net_send(fd, big, 2100, 0);
|
||
|
||
CHECK(rc >= 0 && rc < 2100,
|
||
"TC-NETSOCK-470a a mid-send disconnect only truncates: returns bytes already sent (a short write), not -1 and not a false 2100");
|
||
CHECK((int)s_net_socks[fd].send_bytes == rc,
|
||
"TC-NETSOCK-470b byte statistic matches what was actually delivered (what the peer got cannot be clawed back)");
|
||
CHECK(g_ns_error_count >= 1,
|
||
"TC-NETSOCK-470c a short write must log [ERR] (a silent truncation once made the peer see zero bytes from a UI push)");
|
||
reset_all();
|
||
}
|
||
|
||
static void ns_run_s5(void)
|
||
{
|
||
tc_400_net_init_downlink();
|
||
tc_410_listen_locked();
|
||
tc_411_listen_public();
|
||
tc_420_accept_shapes();
|
||
tc_421_accept_public();
|
||
tc_430_connect();
|
||
tc_440_udp_datagram();
|
||
tc_450_direct_entries();
|
||
tc_460_reset_sync_timeout_bound();
|
||
tc_470_send_disconnect_midway();
|
||
}
|