/* * 分片 6:边界残支(公开入口的越界/队满、直调入口的参数支、等待环里的其余出口) * * 这一片不为「覆盖率好看」而写:每条都对应一条**能被改坏**的分支——越界检查挪到 * 入队之后,调用方就会在 netTask 里等到一个没人处理的 BADF;短写留痕被删掉, * 上层就会像 2026-09-21 那次 UI 推流一样「对端永久等待剩余字节」而日志全白。 * 唯一一条刻意**不是**真实时序的场景是 TC-NETSOCK-530(一次轮询之间被压了 1s), * 它在真机上的对应形态是 netTask 被更高优先级任务压住——用大步进时基表达, * 并在注释里写明它考的是「停滞超时先于自旋预算生效」这条判定顺序。 * * 创建日期:2026-10-24 */ /* TC-NETSOCK-500 六个公开入口的越界形态:BADF 必须在**入队之前**由调用方侧挡下 */ static void tc_500_public_bounds(void) { struct net_sockaddr_in in; char buf[8]; int addrlen; bring_up(); (void)memset(&in, 0, sizeof(in)); addrlen = (int)sizeof(in); CHECK(net_send(NET_MAX_SOCKETS, buf, 4, 0) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500a net_send out of range -> BADF"); CHECK(net_recv(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500b net_recv out of range -> BADF"); CHECK(net_recv_to(NET_MAX_SOCKETS, buf, 4, 10U) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500c net_recv_to out of range -> BADF"); CHECK(net_close(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500d net_close out of range -> BADF"); CHECK(net_drop_conn(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500e net_drop_conn out of range -> BADF"); CHECK(net_accept(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500f net_accept out of range -> BADF"); CHECK(net_connect(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500g net_connect out of range -> BADF"); CHECK(net_sendto(NET_MAX_SOCKETS, buf, 4, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500h net_sendto out of range -> BADF"); CHECK(net_recvfrom(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500i net_recvfrom out of range -> BADF"); CHECK(g_ns_put_calls == 0, "TC-NETSOCK-500j every out-of-range shape enqueues nothing (blocking after the enqueue hands netTask an ownerless request)"); reset_all(); } /* TC-NETSOCK-501 队满时每个公开入口都要报 BUSY(不能有的报、有的静默) */ static void tc_501_all_entries_busy_on_full_queue(void) { struct net_sockaddr_in in; int fd; bring_up(); fd = mk_established(NET_SOCK_STREAM); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; ns_rtos_q_fill((int)NS_Q_CAP); CHECK(net_accept(fd, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501a net_accept on a full queue -> BUSY"); ns_rtos_q_fill((int)NS_Q_CAP); (void)memset(&in, 0, sizeof(in)); CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501b net_connect on a full queue -> BUSY"); ns_rtos_q_fill((int)NS_Q_CAP); CHECK(net_recv_to(fd, (void *)"x", 1, 20U) == -1 && net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501c net_recv_to on a full queue -> BUSY"); ns_rtos_q_fill((int)NS_Q_CAP); CHECK(net_recv(fd, (void *)"x", 1, NET_MSG_DONTWAIT) == -1 && net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501d net_recv on a full queue -> BUSY"); ns_rtos_q_fill((int)NS_Q_CAP); CHECK(net_send(fd, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501e net_send on a full queue -> BUSY"); CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-501f the full-queue path sends no notification (the caller leaves with the return value)"); reset_all(); } /* TC-NETSOCK-502 fd 表用尽:NOMEM 且不影响已建立的会话 */ static void tc_502_socket_table_full(void) { int i; int fd; bring_up(); for (i = 0; i < NET_MAX_SOCKETS; i++) { fd = net_socket(NET_AF_INET, NET_SOCK_STREAM); if (fd < 0) { break; } } CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-502a all 8 slots are allocatable"); CHECK(net_socket(NET_AF_INET, NET_SOCK_STREAM) == -1 && net_get_errno() == NET_ERR_NOMEM, "TC-NETSOCK-502b the ninth -> NOMEM (no out-of-range table write)"); s_net_socks[3].state = NET_SOCK_STATE_ESTABLISHED; CHECK(net_get_sock(3) != NULL && net_listener_armed(3) == 1, "TC-NETSOCK-502c a full table does not disturb live sessions (alloc failure only returns NOMEM)"); reset_all(); } /* TC-NETSOCK-503 net_wait_result 的两段式拾取:先零等待,已有通知就不必再睡 */ static void tc_503_zero_wait_pickup(void) { int fd; int rc; bring_up(); fd = mk_established(NET_SOCK_STREAM); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; /* 预置一条**已挂起**的通知(真机上=上一条消息的回执还没被读走), * 公开入口应先零等待拾取它,而不是再睡一次(睡过头就是 实测踩过的坑) */ ns_rtos_post_notify((TaskHandle_t)&g_ns_task_caller, (uint32_t)(int)4); g_ns_block_waits = 0; rc = net_send(fd, "abcd", 4, 0); CHECK(rc == 4, "TC-NETSOCK-503a the zero-wait peek takes the notify value -> returns the result immediately"); CHECK(g_ns_block_waits == 0, "TC-NETSOCK-503b with a notification already pending it never enters the blocking branch"); CHECK(g_peers.send_reg_calls == 0 && g_chip.write_calls == 0, "TC-NETSOCK-503c this call never touched the chip (the value peeked was the previous receipt, a shape we already hit in practice)"); reset_all(); } /* TC-NETSOCK-504 直调入口的参数支(这些分支从公开入口不可达,只能直接钉) */ static void tc_504_locked_entry_branches(void) { struct net_sockaddr_in in; int fd; bring_up(); (void)memset(&in, 0, sizeof(in)); in.sin_port = net_htons(80); in.sin_addr.s_addr = net_inet_addr("10.1.1.1"); fd = net_socket(NET_AF_INET, NET_SOCK_STREAM); CHECK(net_connect_locked(fd, NULL, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504a connect_locked with addr=NULL -> INVAL"); CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, 1) == -1 && net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504b connect_locked with a short addrlen -> INVAL"); s_net_socks[fd].in_use = 1U; CHECK(net_send_locked(&s_net_socks[fd], "x", 1, NULL) == -1 && net_get_errno() == NET_ERR_NOTCONN, "TC-NETSOCK-504c direct send on a not-established block -> NOTCONN"); CHECK(net_recv_locked(&s_net_socks[fd], (void *)"x", 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-504d direct recv, no data, non-blocking -> WOULDBLOCK"); CHECK(net_recv_locked(&s_net_socks[fd], NULL, 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 && net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504e direct recv with a NULL buf -> INVAL"); reset_all(); } /* TC-NETSOCK-505 发送途中缓冲再也不腾出来:内层等待也要转挂起,并且末块不许登记 */ static void tc_505_inner_wait_and_last_chunk(void) { static char big[2100]; int fd; int rc; /* 第一块写得进、之后芯片不再报空闲 → 第二块的**内层**等待转挂起 */ bring_up(); fd = mk_established(NET_SOCK_STREAM); g_ns_int_pin_set = 0; ch395f_fake_push_glob((uint16_t)(1U << (fd + 4))); g_fake.int_status[fd] = CH395F_SINT_STAT_SENBUF_FREE; /* 只给一次空闲 */ rc = net_send_locked(&s_net_socks[fd], big, 2100, (TaskHandle_t)&g_ns_task_caller); CHECK(rc == NET_PENDING_REGISTERED, "TC-NETSOCK-505a the inner wait (after writing one chunk) also converts to pending past budget, no in-place busy wait"); CHECK(g_chip.write_calls == 2 && g_peers.last_snd_sent == 2048, "TC-NETSOCK-505b registered progress = bytes written into the chip (1024+1024=2048), delivered bytes are never resent"); reset_all(); /* 末块:全部写完但芯片不回执 → 按完成返回,**不许**登记挂起 * (2026-10-14 台架回归 TC-NET-902 正是登记了却无人完成 → 永久挂死) */ bring_up(); fd = mk_established(NET_SOCK_STREAM); rc = net_send_locked(&s_net_socks[fd], big, 1024, (TaskHandle_t)&g_ns_task_caller); CHECK(rc == 1024, "TC-NETSOCK-505c a single chunk finishing exactly -> returns 1024 (complete)"); CHECK(g_peers.send_reg_calls == 0, "TC-NETSOCK-505d the last-chunk wait must not register pending (the sent>=len guard has to hold)"); reset_all(); } /* TC-NETSOCK-506 UDP sendto:等待腾出与短写留痕(与 TCP 同一套语义的另一份实现) */ static void tc_506_sendto_stall_and_short(void) { static char big[2100]; int fd; int rc; bring_up_netctx(); fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); s_net_socks[fd].send_ready = 0U; g_ns_int_pin_set = 0; ch395f_fake_push_glob((uint16_t)(1U << (fd + 4))); /* 每轮只走 1ms:先命中 NET_SEND_TIMEOUT_MS(1s) 之前不会有别出口 → 短写 */ g_ns_poll_tick_inc = 500U; rc = net_sendto(fd, big, 2100, 0, NULL, 0); CHECK(rc < 2100 && rc >= 0, "TC-NETSOCK-506a sendto stalled returns only what went out (short-write semantics match TCP)"); CHECK(g_ns_error_count >= 2, "TC-NETSOCK-506b both the stall [ERR] and the short-write [ERR] must be there (missing one degrades into a silent truncation)"); 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))); g_fake.int_status[fd] = CH395F_SINT_STAT_SENBUF_FREE; /* 只给一次 */ g_ns_poll_tick_inc = 500U; rc = net_sendto(fd, big, 2100, 0, NULL, 0); CHECK(rc >= 1024 && rc < 2100, "TC-NETSOCK-506c first chunk delivered and the second chunk wait timed out -> a value between 1024 and 2099"); reset_all(); } /* TC-NETSOCK-530 停滞超时优先于自旋预算:考判定顺序,不考真实时长 */ static void tc_530_stall_timeout_before_spin(void) { static char buf[64]; int fd; int rc; /* 一次轮询之间被压了 2s(真机形态:netTask 被更高优先级任务压住), * 停滞超时(NET_SEND_TIMEOUT_MS=1s)必须**先于**自旋预算(2ms)命中: * 生产代码里两条判定的先后顺序就是这条断言的内容。 */ bring_up(); fd = mk_established(NET_SOCK_STREAM); s_net_socks[fd].send_ready = 0U; g_ns_poll_tick_inc = 2000U; rc = net_send_locked(&s_net_socks[fd], buf, (int)sizeof(buf), (TaskHandle_t)&g_ns_task_caller); CHECK(rc == 0, "TC-NETSOCK-530a the stall timeout returns the bytes sent so far, 0 (a short write, not -1)"); CHECK(g_peers.send_reg_calls == 0, "TC-NETSOCK-530b the stall timeout wins over the spin budget: no pending registration (a swapped order would register)"); CHECK(net_get_errno() == NET_ERR_TIMEDOUT, "TC-NETSOCK-530c errno = TIMEDOUT (lets the caller tell a stall from a disconnect)"); reset_all(); } /* TC-NETSOCK-535 已经写出去一块之后的超时收口(内层等待的另一条出口) */ static void tc_535_inner_timeout_after_chunk(void) { static char big[2100]; int fd; int rc; /* 第一块写得进(send_ready 初值 1),第二块的**内层**等待直接撞上停滞超时 * (判定顺序:先超时后自旋,所以这里收口在超时支而不是登记挂起) */ bring_up(); fd = mk_established(NET_SOCK_STREAM); g_ns_poll_tick_inc = 2000U; rc = net_send_locked(&s_net_socks[fd], big, 2100, (TaskHandle_t)&g_ns_task_caller); CHECK(rc == 1024, "TC-NETSOCK-535a the already delivered first chunk still counts (1024), the rest is a short write"); CHECK(g_peers.send_reg_calls == 0, "TC-NETSOCK-535b the stall timeout closes out BEFORE any pending registration (swapping the order parks a pending that never completes)"); CHECK(g_chip.write_calls == 1, "TC-NETSOCK-535c the chip was written exactly once: a delivered chunk must never be resent"); reset_all(); } /* TC-NETSOCK-536 net_recv_to 的三条回传分支(-2 / -1+errno / >=0) */ static void tc_536_recv_to_three_exits(void) { char buf[16]; int fd; /* >=0:正常拿到数据 */ bring_up(); fd = mk_established(NET_SOCK_STREAM); s_net_socks[fd].readable = 1U; ch395f_fake_push_recv_len((uint8_t)fd, 12U); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 100U) == 12, "TC-NETSOCK-536a data available -> returns the actual byte count (the >=0 branch)"); /* <0 且非 TIMEDOUT:分派侧 BADF → 恢复 errno 后返回 -1 */ reset_all(); bring_up(); (void)net_socket(NET_AF_INET, NET_SOCK_STREAM); /* 占掉 fd0,用未分配的 fd1 */ g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; CHECK(net_recv_to(1, buf, (int)sizeof(buf), 100U) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-536b a real error -> -1 with errno restored from the notify value (strictly distinct from -2)"); /* ==TIMEDOUT:已在 TC-NETSOCK-224 钉过 -2;这里补一条「-2 不改写 errno」 */ reset_all(); bring_up(); fd = mk_established(NET_SOCK_STREAM); (void)net_set_errno(NET_OK); ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 30U) == -2 && net_get_errno() == NET_OK, "TC-NETSOCK-536c timeout is judged by the return code only and does NOT rewrite the errno slot (this branch sets none)"); reset_all(); } /* TC-NETSOCK-537 fire_event:注册了回调才会被叫到(本层唯一的回调触发点在 SEND_OK) */ static void tc_537_event_callback_path(void) { int fd; bring_up(); fd = mk_established(NET_SOCK_STREAM); /* handle_send_event 是兄弟模块(net_events)的职责,本层只负责把中断交给它; * 回调是否触发属 net_events 的断言面(其单测 TC-NETEV 已覆盖),此处只钉 * 「net_poll 不因回调未注册而跳过事件」。 */ s_net_socks[fd].event_cb = NULL; g_ns_int_pin_set = 0; ch395f_fake_push_glob((uint16_t)(1U << (fd + 4))); g_fake.int_status[fd] = CH395F_SINT_STAT_SEND_OK; CHECK(net_poll() == 1, "TC-NETSOCK-537 events are counted even with no callback registered (a null pointer never skips dispatch)"); reset_all(); } /* TC-NETSOCK-540 close / drop_conn 的失败结果也要经通知值恢复精确 errno */ static void tc_540_negative_results(void) { net_msg_t msg; int fd; /* close 的负结果支:net_close_locked 自身恒返 0(复位没成也放行,见 TC-NETSOCK-320h), * 所以 CLOSE 唯一的负值来自**分派侧**的 net_get_sock 判空 → 通知值 -2 → * 调用方在自己的槽位里恢复成 BADF。范围合法但未分配的 fd 正好走这条。 */ bring_up(); fd = 3; /* 范围内、未分配 */ g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-540a close on an unallocated fd -> -1 with errno restored to BADF from the notify value, not a generic NET_ERR"); CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-540a2 drop_conn takes the same negative-value restoration branch"); reset_all(); /* drop_conn 同理:用未分配 fd 走分派侧 BADF 后再由调用方恢复 */ bring_up(); (void)memset(&msg, 0, sizeof(msg)); msg.type = NET_MSG_DROP_CONN; msg.sockfd = 1; msg.caller = (TaskHandle_t)&g_ns_task_net; (void)ns_rtos_q_put_raw(&msg); net_process_messages(); CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-540b drop_conn BADF from the dispatch side travels back on the notify value"); /* RECV 的 BADF 支(分派侧 net_get_sock 判空) */ reset_all(); bring_up(); (void)memset(&msg, 0, sizeof(msg)); msg.type = NET_MSG_RECV; msg.sockfd = 4; msg.buf = (void *)"x"; msg.len = 1; msg.caller = (TaskHandle_t)&g_ns_task_net; (void)ns_rtos_q_put_raw(&msg); net_process_messages(); CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-540c recv BADF from the dispatch side likewise travels back on the notify value"); reset_all(); } /* TC-NETSOCK-545 未绑队列时的 sendto/recvfrom/getpeername 等纯本地入口 */ static void tc_545_local_entries_without_queue(void) { struct net_sockaddr_in in; int addrlen = (int)sizeof(in); reset_all(); CHECK(net_getsockname(0, (struct net_sockaddr *)&in, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-545a even uninitialised it safely reports BADF (no crash)"); CHECK(net_getpeername(0, (struct net_sockaddr *)&in, &addrlen) == -1, "TC-NETSOCK-545b getpeername behaves the same"); CHECK(net_inet_addr("1.2.3.4") != 0U, "TC-NETSOCK-545c pure helpers do not depend on initialisation"); reset_all(); } /* ============= TC-NETSOCK-550 s_addr 同族一致性(字节序统一后的收口断言) ============= * * 这次字节序统一的实质不是「给 inet_addr 换个数值」,而是「全仓只剩一套 s_addr 约定」。 * 所以这组断言全部写在**跨函数的接缝**上:解析→存储→芯片下发→回读→打印, * 每一环都拿同一句点分十进制做基准。任何一处又引入第二套顺位,这里必红。 */ /* TC-NETSOCK-550 解析 → 存储 → 打印 闭合(getsockname / getpeername 同族可比对) */ static void tc_550_family_roundtrip(void) { struct net_sockaddr_in in; struct net_sockaddr *sa = (struct net_sockaddr *)∈ char txt[16]; int addrlen; int fd; bring_up(); /* 1) getsockname 的 s_addr 与 inet_addr 解析结果**直接相等**(旧实现差一次换端) */ (void)mk_established(NET_SOCK_STREAM); /* getsockname 要 in_use 的描述符 */ addrlen = (int)sizeof(in); (void)memset(&in, 0, sizeof(in)); CHECK(net_getsockname(0, sa, &addrlen) == 0, "TC-NETSOCK-550a getsockname precondition ok"); CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.1.100"), "TC-NETSOCK-550b getsockname s_addr is the same family as inet_addr (direct equality)"); CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.1.100") == 0, "TC-NETSOCK-550c local address closes parse-store-print (old code printed 100.1.168.192)"); /* 2) connect 的对端簿记同样闭合(lftpd:864 与 104 显示走的就是这条链) */ 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(2404U); in.sin_addr.s_addr = net_inet_addr("192.168.1.200"); g_ns_cur_task = (TaskHandle_t)&g_ns_task_net; CHECK(net_connect_locked(fd, sa, (int)sizeof(in)) == 0, "TC-NETSOCK-550d connect precondition ok"); addrlen = (int)sizeof(in); (void)memset(&in, 0, sizeof(in)); CHECK(net_getpeername(fd, sa, &addrlen) == 0, "TC-NETSOCK-550e getpeername ok (reads event cache, zero SPI)"); CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.1.200"), "TC-NETSOCK-550f getpeername peer equals the s_addr fed into net_connect"); CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.1.200") == 0, "TC-NETSOCK-550g peer address prints right (lftpd connection-received log depends on it)"); CHECK(s_net_socks[fd].remote_ip_arr[0] == 192U && s_net_socks[fd].remote_ip_arr[3] == 200U, "TC-NETSOCK-550h chip des_ip array stays wire order [192,168,1,200]: on-wire behaviour unchanged"); reset_all(); } /* TC-NETSOCK-555 UDP 报文头里的源地址拆装(同族的第四个生产者:net_recvfrom) */ static void tc_555_udp_src_addr(void) { /* CH395F UDP 头(§9.2.4):[0-1] 总长(LE) [2-3] 源端口(LE) [4-7] 源 IP(线序) */ static const uint8_t hdr[8] = { 11U, 0U, /* 总长 = 8 头 + 3 数据 */ 0x1FU, 0xCBU, /* 源端口 51999(小端 1F CB) */ 239U, 1U, 2U, 3U /* 源 IP 线序:239.1.2.3 */ }; struct net_sockaddr_in in; struct net_sockaddr *sa = (struct net_sockaddr *)∈ char buf[16]; char txt[16]; int addrlen; int fd; bring_up_netctx(); fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); ch395f_fake_push_recv_len((uint8_t)fd, (uint16_t)(8U + 3U)); ch395f_fake_set_recv_bytes(hdr, (uint16_t)sizeof(hdr)); addrlen = (int)sizeof(in); (void)memset(&in, 0, sizeof(in)); CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &addrlen) == 3, "TC-NETSOCK-555a strips the 8-byte chip header and delivers only the 3-byte payload"); CHECK(in.sin_addr.s_addr == net_inet_addr("239.1.2.3"), "TC-NETSOCK-555b source IP unpack matches the inet_addr family"); CHECK(in.sin_port == net_htons(51999U), "TC-NETSOCK-555c source port read chip little-endian then stored in network order"); CHECK(addrlen == (int)sizeof(in) && net_ntohs(in.sin_port) == 51999U, "TC-NETSOCK-555d addrlen filled and port round-trips to 51999"); CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "239.1.2.3") == 0, "TC-NETSOCK-555e multicast source address prints correctly"); reset_all(); } /* TC-NETSOCK-560 UDP 上下文守卫:非执行任务不得直调芯片 * 三条一起才成立,缺一条都不算把危险面关掉: * ① 执行任务内照常成功(守卫不是"把 UDP 判死") * ② 非执行任务被拒且**一次芯片事务都没有**(拒了却已经抢过 SPI = 白拒) * ③ 正常错误路径不被守卫吞掉,且**走消息队列的接口完全不受影响** * (那些接口生来就是给非 netTask 用的,误伤它们才是大事故) */ static void tc_560_udp_context_guard(void) { char buf[8]; int fd; /* ① 执行任务上下文:与修复前逐字相同 */ bring_up_netctx(); fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == 3, "TC-NETSOCK-560a net_sendto unchanged inside the executing task"); reset_all(); /* ② 非执行任务:拒绝 + 零芯片事务(bring_up 里 net_init 是以 netTask 身份跑的, * 此刻 g_ns_cur_task 已还原成 caller,正是"别的任务") */ bring_up(); fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); g_chip.write_calls = 0; g_chip.read_calls = 0; g_fxchip.set_des_ip_calls = 0; g_fxchip.glob_calls = 0; g_ns_poll_calls = 0; g_ns_error_count = 0; CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_NOT_IN_NETTASK, "TC-NETSOCK-560b non-executing task gets NET_ERR_NOT_IN_NETTASK (exact code, not -1)"); CHECK(g_chip.write_calls == 0 && g_fxchip.set_des_ip_calls == 0 && g_ns_poll_calls == 0, "TC-NETSOCK-560c clean refusal: zero writes, zero set_des_ip, zero net_poll rounds"); CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 && net_get_errno() == NET_ERR_NOT_IN_NETTASK, "TC-NETSOCK-560d net_recvfrom guarded the same way"); CHECK(g_chip.read_calls == 0 && g_fxchip.glob_calls == 0 && g_ns_poll_calls == 0, "TC-NETSOCK-560e recvfrom touched no chip and no global interrupt register either"); CHECK(g_ns_error_count == 2, "TC-NETSOCK-560f each refusal logs one ERR line (silence hides the wrong task)"); /* ③ 正常错误路径优先:守卫排在参数校验之后 */ CHECK(net_sendto(-1, "abc", 3, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-560g out-of-range fd still reports BADF first"); CHECK(net_sendto(fd, NULL, 3, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-560h NULL buf still reports INVAL, the guard does not swallow it"); CHECK(net_recvfrom(fd, NULL, 3, NET_MSG_DONTWAIT, NULL, NULL) == -1 && net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-560i recvfrom keeps the same precedence"); /* ④ 走消息队列的接口不受守卫影响:同一个非 netTask 上下文照常能把活干完 */ CHECK(net_close(fd) == 0, "TC-NETSOCK-560j net_close from a non-executing task still works (queue path untouched)"); /* 执行完的证据用"收到回执 + 槽位释放",不看 close_calls:芯片此刻已是 * CLOSED,走的是快路径(不再补发 CLOSE,见 TC-NETSOCK-320a/b),拿它当证据 * 会把一条正确的快路径判成失败 */ CHECK(s_net_socks[fd].in_use == 0U && g_ns_notify_calls >= 1, "TC-NETSOCK-560k queue path really completed (receipt seen and slot released)"); reset_all(); /* ⑤ 未捕获执行任务(net_init 没跑):守卫不抢先,仍由参数/状态检查给确定错误 */ reset_all(); g_ns_pump = net_process_messages; CHECK(net_sendto(0, "abc", 3, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-560l before net_init sendto reports BADF, no invented context error"); /* 未捕获执行任务、但 socket 确实在用时,守卫**选择放行**:宁可不做判定,也不给 * 一个无从判定的场景编造错误码。放行后走的是完全正常的执行路径(这里 DONTWAIT * 无数据 → WOULDBLOCK),证明守卫不是"没初始化就一律拒绝"的粗暴实现。 * 刻意用 recvfrom 而不是 sendto:未初始化时 net_poll 直接短路返回、时间基准 * 不前进,sendto 的等待环永远等不到超时(真机上不可达:net_socket 之后还有 * NET_READY 屏障,但这条差异值得记一笔,见交付报告)。 */ reset_all(); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; /* net_init 没跑:判据恒 NULL */ fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); g_fxchip.glob_calls = 0; CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-560m guard stays open when the executing task was never captured"); CHECK(net_get_errno() != NET_ERR_NOT_IN_NETTASK && g_fxchip.glob_calls == 0, "TC-NETSOCK-560n no context error invented and no global register read"); reset_all(); } static void ns_run_s6(void) { tc_500_public_bounds(); tc_501_all_entries_busy_on_full_queue(); tc_502_socket_table_full(); tc_503_zero_wait_pickup(); tc_504_locked_entry_branches(); tc_505_inner_wait_and_last_chunk(); tc_506_sendto_stall_and_short(); tc_530_stall_timeout_before_spin(); tc_535_inner_timeout_after_chunk(); tc_536_recv_to_three_exits(); tc_537_event_callback_path(); tc_540_negative_results(); tc_545_local_entries_without_queue(); tc_550_family_roundtrip(); tc_555_udp_src_addr(); tc_560_udp_context_guard(); }