/* * 分片 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 发送卡住:两条出口都要留痕(2026-11-03 队列化改造后重写) * 改造前这里钉的是"netTask 内自旋到 1s 停滞超时后短写"——那 1 秒全部花在 netTask * 里,正是本次要消掉的形态。改造后 UDP 与 TCP 共用 net_send_locked 的预算机: * ① 执行任务上下文:无人能完成挂起 → 拒绝自等 → -1(BUSY),且不写忙缓冲; * ② 调用方上下文:超预算转挂起(登记进 net_send_pending,与 TCP 同一张表), * 登记里带的是**已写进芯片的字节数**——续传从断点起,已交付的字节绝不重发 * (重发半截数据报 = 对端收到两个包,比丢包更难查)。 */ static void tc_506_sendto_stall_and_short(void) { static char big[2100]; int fd; int rc; /* ① 执行任务上下文、发送缓冲一开始就不空闲 */ bring_up_netctx(); fd = udp_bind_ctx(8888); s_net_socks[fd].send_ready = 0U; g_ns_int_pin_set = 0; ch395f_fake_push_glob((uint16_t)(1U << (fd + 4))); /* 每轮 500ms:停在自旋预算(2ms)与停滞超时(1000ms)之间 → 走"转挂起被拒"这条口 */ g_ns_poll_tick_inc = 500U; rc = net_sendto(fd, big, 2100, 0, NULL, 0); CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-506a a stall inside netTask now gives up with BUSY instead of spinning a full second and short-writing"); CHECK(g_chip.write_calls == 0, "TC-NETSOCK-506b it never wrote into a busy buffer (no half datagram left in the chip FIFO)"); CHECK(g_peers.send_reg_calls == 1, "TC-NETSOCK-506c the hand-over was really attempted (the refusal comes from the register side, not from us not trying to hand over)"); reset_all(); /* ② 调用方上下文:第一块写得进、之后芯片不再报空闲 → 第二块转挂起 */ bring_up(); fd = udp_bind_ctx(8888); 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_send_locked(&s_net_socks[fd], big, 2100, (TaskHandle_t)&g_ns_task_caller); CHECK(rc == NET_PENDING_REGISTERED, "TC-NETSOCK-506d a UDP send past the spin budget converts to pending exactly like TCP (no in-place busy wait)"); CHECK(g_peers.send_reg_calls == 1 && g_peers.last_snd_sent == 1024, "TC-NETSOCK-506e the pending entry carries the bytes already written (1024), so the resume starts at the break point"); CHECK(g_chip.write_calls == 1, "TC-NETSOCK-506f exactly one chunk reached the chip before handing over"); 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.31.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.31.100") == 0, "TC-NETSOCK-550c local address closes parse-store-print (old code printed 100.31.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.31.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.31.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.31.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,31,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(); /* 2026-11-03 队列化改造:剥头只发生在**服务器模式**,而服务器模式由 bind 建立 * (DesIP=0xFFFFFFFF)。改造前这段直接 net_socket 就能收,是因为它无条件剥头—— * 那正是本组要修的缺陷(客户端模式没有头,照剥就吃掉载荷前 8 字节)。 */ fd = udp_bind_ctx(8888); 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 上下文契约(2026-11-03 队列化改造**反转**了本组的原命题) * 改造前:非执行任务调用 net_sendto / net_recvfrom 一律 NET_ERR_NOT_IN_NETTASK(-12) * ——那是 2026-10-24"先把危险面关掉"的临时守卫(SPI 竞争 TRAP-NET-14)。 * 改造后:任意任务都能调,芯片事务全部串行到 netTask 执行;守卫随之退役。 * 三条一起才成立,缺一条都不算把改造钉住: * ① 非执行任务调用**成功**,且芯片事务发生在泵(=netTask 上下文)里,不在调用方 * ② 执行任务内直接执行,不投自己(板级用例照旧可在自己任务里裸调) * ③ 参数/状态错误优先于上下文;-12 这个码在 UDP 路径上不再出现 */ static void tc_560_udp_context_guard(void) { char buf[8]; int fd; /* ① 非执行任务(bring_up 已把身份还原成 caller):改造前被拒,改造后必须成 */ bring_up(); fd = udp_bind_ctx(8888); CHECK(fd >= 0, "UDP-560a0 bind from a caller task succeeds (queue path, no direct SPI)"); g_chip.write_calls = 0; g_chip.read_calls = 0; g_fxchip.set_des_ip_calls = 0; g_ns_put_calls = 0; g_ns_notify_calls = 0; CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == 3, "UDP-560a non-executing task's sendto now succeeds (it used to be refused with -12)"); CHECK(g_chip.write_calls == 1 && g_ns_put_calls >= 1 && g_ns_notify_calls >= 1, "UDP-560b the write happened while the pump ran as netTask: the caller enqueued, blocked, and was notified (serialised, not forbidden)"); CHECK(net_get_errno() != NET_ERR_NOT_IN_NETTASK, "UDP-560c the retired guard does not come back: -12 is never returned on a success path"); ch395f_fake_push_recv_len((uint8_t)fd, 8U + 3U); s_net_socks[fd].readable = 1U; CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == 3, "UDP-560d non-executing task's recvfrom goes the same route and returns the payload"); CHECK(g_chip.read_calls >= 2 && g_ns_block_waits >= 1, "UDP-560e the caller slept on its own notification while netTask did the SPI reads (the old spin-in-caller shape is gone)"); /* ③ 参数检查排在上下文与状态之前(守卫退役不许顺手把错误优先级改掉) */ CHECK(net_sendto(-1, "abc", 3, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF, "UDP-560f out-of-range fd still reports BADF first"); CHECK(net_sendto(fd, NULL, 3, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_INVAL, "UDP-560g NULL buf still reports INVAL"); CHECK(net_recvfrom(fd, NULL, 3, NET_MSG_DONTWAIT, NULL, NULL) == -1 && net_get_errno() == NET_ERR_INVAL, "UDP-560h recvfrom keeps the same precedence"); reset_all(); /* ② 执行任务上下文:直接执行,不投自己(板级 testTask 形态) */ bring_up_netctx(); fd = udp_bind_ctx(8888); g_ns_put_calls = 0; CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == 3, "UDP-560i netTask context sends directly"); CHECK(g_ns_put_calls == 0, "UDP-560j the executing task never enqueues to itself (a self-post would have to wait for its own pump)"); reset_all(); /* ④ 队列未绑定(net_init 之前的早期上下文):与其他队列化接口同一口径 NET_ERR, * 不是 BADF 也不是 -12——无从投递就是无从投递 */ reset_all(); g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == NET_ERR && s_net_udp[fd].armed == 0U, "UDP-560k before net_init (no queue bound) sendto returns NET_ERR and leaves no slot armed"); CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == NET_ERR, "UDP-560l the same for recvfrom (the armed out-param slot is handed back on that exit)"); reset_all(); /* ⑤ 执行任务内的**阻塞** UDP 收包仍被拒(改造没有打开自等死锁的门): * 完成它的 tick 就跑在同一个任务里 */ bring_up_netctx(); fd = udp_bind_ctx(8888); CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BUSY, "UDP-560m a blocking UDP receive inside netTask is refused with BUSY (self-wait = deadlock)"); CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK, "UDP-560n the same call with NET_MSG_DONTWAIT is the legal netTask form (WOULDBLOCK, no wait)"); 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(); }