/******************************************************************************
 * 模块名称：CH395F Network Test Task
 * 模块功能：CH395F 全阶段测试入口，统一在独立 FreeRTOS 任务中运行。
 *          测试分两组：
 *            Phase 1~5（硬件层）— 阻塞式独占执行（各自有内部循环/等待），netTask 未运行
 *            Phase 4~10（NET 层）— 事件驱动，TCP 监听循环中运行（需 PC 客户端连接）
 *          所有测试结果通过 TEST_CHECK/TEST_REPORT 宏输出到串口。
 * 作者：王建锋
 * 创建日期：2026-07-22
 ******************************************************************************/

#include <string.h>
#include "ch395f_test_task.h"
#include "cmsis_os.h"
#include "net_task.h"
#include "net_socket.h"
#include "ch395f_test.h"
#include "ch395f.h"

#define DBG_TAG "[TEST_TASK]"
#include "dbg_log.h"

/* netTask 初始化完成标志（由 net_task.c 在 net_stack_bring_up 成功后置 1）。
 * 仅 NET 层模式使用 —— 硬件层模式下 netTask 不创建，由本任务自行初始化。 */
extern volatile uint8_t g_net_ready;

/*
 * 全局测试统计数据，由 TEST_CHECK/TEST_REPORT 宏自动管理。
 * 每次 TEST_CHECK 自增 total 并根据条件增 passed/failed；
 * TEST_REPORT 汇总输出，不会自动清零 —— 每个 Phase 内自行 memset 重置。
 */
test_stats_t g_test_stats;

/*
 * 收发缓冲区 —— 全局静态，不在任务栈上。
 * s_tx_buf 仅在 Phase 7（大文件传输）和 Phase 9（边界条件）中使用，
 * 其他 Phase 不需要发送固定模式数据，故条件编译节省 RAM。
 */
#define TEST_BUF_SIZE       65536
#if defined(ENABLE_PHASE6_TESTS)  || defined(ENABLE_PHASE7_TESTS) || \
    defined(ENABLE_PHASE8_TESTS)  || defined(ENABLE_PHASE9_TESTS) || \
    defined(ENABLE_PHASE10_TESTS)
static uint8_t s_rx_buf[TEST_BUF_SIZE];
#endif
#if defined(ENABLE_PHASE7_TESTS) || defined(ENABLE_PHASE9_TESTS)
static uint8_t s_tx_buf[TEST_BUF_SIZE];
#endif

/* ===================================================================
 * Phase 1 — 寄存器与 SPI 命令路径验证
 *
 * 测试目的：确认 CH395F 与 MCU 之间的 SPI 通信链路正常，所有底层命令
 *          能够正确发送并得到预期回复。这是其他所有 Phase 的前提条件。
 *
 * 共 9 项测试（P1-01 ~ P1-09），逐项执行，汇总报告。
 * =================================================================== */

/* 解析点分十进制 IP 字符串为数组（避免引入 sscanf 库） */
static void test_parse_ip(const char *str, uint8_t *ip_arr)
{
    uint32_t val;
    int i;

    for (i = 0; i < 4; i++) {
        val = 0;
        while (*str >= '0' && *str <= '9') {
            val = val * 10 + (*str - '0');
            str++;
        }
        ip_arr[i] = (uint8_t)val;
        if (*str == '.') {
            str++;
        }
    }
}

#ifdef ENABLE_PHASE1_TESTS
static void phase1_run(void) {
    uint8_t ip_info[20];

    /* ---- 前置清全局中断（读即清除） ----
     * net_stack_bring_up() 在 set_phy(100M_FULL) 强制重协商 + 等待链路建立
     * 期间，芯片会锁存 GINT_STAT_PHY_CHANGE（0x0004）标志；硬件层模式下无
     * netTask 轮询消费它，若不清除将导致 P1-06 的"原始状态 0x0000"检查误报。
     * 此处读取一次丢弃，保证 P1-06 反映的是测试期间的纯净状态。 */
    (void)ch395f_get_glob_int_status_all();

    DBG_INFO("=== CH395F Phase 1 Tests ===");
    DBG_INFO("--- 9 test items (P1-01 ~ P1-09) ---");

    /* ---- P1-01: ch395f_check_exist() ----
     * 原理：SPI 写入命令 0x06 (CMD_CHECK_EXIST) 后跟测试字节 0x57。
     *       CH395F 硬件将测试字节按位取反后返回，期望收到 ~0x57 = 0xA8。
     * 通过：函数返回 CH395F_ERR_SUCCESS（0x00）= SPI 回显取反匹配。
     * 失败：返回 CH395F_STATUS_NOT_DETECTED（0xFF）= SPI 通信异常或芯片未响应。
     */
    {
        ch395f_status_t ret = ch395f_check_exist();
        DBG_INFO("  [P1-01] ch395f_check_exist()");
        DBG_INFO("          SPI write: CMD=0x06, DATA=0x57");
        DBG_INFO("          expect: ~0x57 = 0xA8");
        DBG_INFO("          actual: 0x%02X", (uint8_t)ret);
        TEST_CHECK(ret == CH395F_ERR_SUCCESS,
                   "P1-01 check_exist = 0x%02X (expect 0x00=~0x57=0xA8 match)", (uint8_t)ret);
    }

    /* ---- P1-02: ch395f_get_version() ----
     * 原理：SPI 写入命令 0x27 (CMD_GET_IC_VER)，读取芯片版本字节。
     * 通过：版本值 == 0x4A（CH395F 硬件版本）。
     * 失败：版本值 ≠ 0x4A（芯片型号不匹配、未正确初始化或损坏）。
     * 注意：不同批次的 CH395F 版本号可能不同，若更换芯片需更新此判断值。
     */
    {
        uint8_t ver = (uint8_t)ch395f_get_version();
        DBG_INFO("  [P1-02] ch395f_get_version()");
        DBG_INFO("          expect: 0x4A");
        DBG_INFO("          actual: 0x%02X", ver);
        TEST_CHECK(ver == 0x4A,
                   "P1-02 version = 0x%02X (expect 0x4A)", ver);
    }

    /* ---- P1-03: 设置 IP → 回读验证 ----
     * 原理：ch395f_set_ip_addr() 将 IP 写入 CH395F 内部寄存器，
     *       然后通过 ch395f_get_ip_inf() 回读完整网络信息（IP+GW+MASK）。
     *       前 4 字节为 IP 地址。
     * 通过：回读 IP[0..3] == 192.168.1.100（与写入值一致）。
     * 失败：任意字节不匹配（SPI 写入/回读路径异常或寄存器未更新）。
     */
    {
        uint8_t set_ip[4] = {192, 168, 1, 100};
        ch395f_set_ip_addr(set_ip);
        memset(ip_info, 0, sizeof(ip_info));
        ch395f_get_ip_inf(ip_info);
        int match = (ip_info[0] == 192 && ip_info[1] == 168 &&
                     ip_info[2] == 1   && ip_info[3] == 100);
        DBG_INFO("  [P1-03] ch395f_set_ip_addr(192.168.1.100)");
        DBG_INFO("          expect: IP[0..3] = 192.168.1.100");
        DBG_INFO("          actual: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
        TEST_CHECK(match, "P1-03 IP = %d.%d.%d.%d (expect 192.168.1.100)",
                   ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
    }

    /* ---- P1-04: 设置网关 → 回读验证 ----
     * 原理：同 P1-03，通过 ch395f_set_gwip_addr() 写入，ch395f_get_ip_inf()
     *       回读的第 5-8 字节为网关地址。
     * 通过：回读 GW[0..3] == 192.168.1.1。
     * 失败：任意字节不匹配。
     */
    {
        uint8_t set_gw[4] = {192, 168, 1, 1};
        ch395f_set_gwip_addr(set_gw);
        memset(ip_info, 0, sizeof(ip_info));
        ch395f_get_ip_inf(ip_info);
        int match = (ip_info[4] == 192 && ip_info[5] == 168 &&
                     ip_info[6] == 1   && ip_info[7] == 1);
        DBG_INFO("  [P1-04] ch395f_set_gwip_addr(192.168.1.1)");
        DBG_INFO("          expect: GW[0..3] = 192.168.1.1");
        DBG_INFO("          actual: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
        TEST_CHECK(match, "P1-04 GW = %d.%d.%d.%d (expect 192.168.1.1)",
                   ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
    }

    /* ---- P1-05: 设置掩码 → 回读验证 ----
     * 原理：同 P1-03，ch395f_set_mask_addr() 写入，ch395f_get_ip_inf()
     *       回读的第 9-12 字节为子网掩码。
     * 通过：回读 MASK[0..3] == 255.255.255.0。
     * 失败：任意字节不匹配。
     */
    {
        uint8_t set_mask[4] = {255, 255, 255, 0};
        ch395f_set_mask_addr(set_mask);
        memset(ip_info, 0, sizeof(ip_info));
        ch395f_get_ip_inf(ip_info);
        int match = (ip_info[8] == 255 && ip_info[9] == 255 &&
                     ip_info[10] == 255  && ip_info[11] == 0);
        DBG_INFO("  [P1-05] ch395f_set_mask_addr(255.255.255.0)");
        DBG_INFO("          expect: MASK[0..3] = 255.255.255.0");
        DBG_INFO("          actual: %d.%d.%d.%d", ip_info[8], ip_info[9], ip_info[10], ip_info[11]);
        TEST_CHECK(match, "P1-05 MASK = %d.%d.%d.%d (expect 255.255.255.0)",
                   ip_info[8], ip_info[9], ip_info[10], ip_info[11]);
    }

    /* ---- P1-06: ch395f_get_glob_int_status_all()（16 位版） ----
     * 原理：读取全局中断状态寄存器（2 字节版本，含所有 Socket 中断位）。
     *       在无任何网络事件的空闲状态下，应为 0x0000。
     * 通过：返回 0x0000。
     * 失败：返回非 0x0000（有残留中断，如前序操作异常触发但未清除）。
     * 注意：此寄存器为读后清零，故先读 16 位版再读 1 字节版。
     */
    {
        uint16_t gint_all = ch395f_get_glob_int_status_all();
        DBG_INFO("  [P1-06] ch395f_get_glob_int_status_all()");
        DBG_INFO("          expect: 0x0000");
        DBG_INFO("          actual: 0x%04X", gint_all);
        TEST_CHECK(gint_all == 0x0000,
                   "P1-06 glob_int_status_all = 0x%04X (expect 0x0000)", gint_all);
    }

    /* ---- P1-07: 打开→关闭 Socket 0 → 验证 CMD_STATUS ----
     * 原理：连续执行 open_socket(0) 和 close_socket(0)，然后读取
     *       命令状态寄存器，确认 Socket 状态机正常流转。
     * 通过：CMD_STATUS == 0x00（CH395F_ERR_SUCCESS），表示两次命令均成功执行。
     * 失败：CMD_STATUS 非 0x00（Socket 硬件状态异常或命令序列错误）。
     */
    {
        uint8_t open_ret = ch395f_open_socket(0);
        uint8_t close_ret = ch395f_close_socket(0);
        uint8_t cmd_st = ch395f_get_cmd_status();
        DBG_INFO("  [P1-07] ch395f_open_socket(0) -> ch395f_close_socket(0)");
        DBG_INFO("          expect: CMD_STATUS == 0x00 (CH395F_ERR_SUCCESS)");
        DBG_INFO("          actual: open=%d, close=%d, CMD_STATUS=0x%02X",
                 open_ret, close_ret, cmd_st);
        TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS,
                   "P1-07 open=%d close=%d cmd=0x%02X (expect 0x00)",
                   open_ret, close_ret, cmd_st);
    }

    /* ---- P1-08: 设置 ARP 参数 ----
     * 原理：ch395f_set_arp(period, retry) 配置 ARP 请求周期和重试次数。
     *       period = 10（×100ms = 1s）、retry = 5（最多 5 次重试）。
     * 通过：CMD_STATUS == 0x00（参数写入成功）。
     * 失败：CMD_STATUS 非 0x00（命令未被执行 —— 参数无效或 SPI 异常）。
     */
    {
        ch395f_set_arp(10, 5);
        uint8_t cmd_st = ch395f_get_cmd_status();
        DBG_INFO("  [P1-08] ch395f_set_arp(period=10, retry=5)");
        DBG_INFO("          expect: CMD_STATUS == 0x00");
        DBG_INFO("          actual: CMD_STATUS = 0x%02X", cmd_st);
        TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS,
                   "P1-08 set_arp CMD_STATUS = 0x%02X (expect 0x00)", cmd_st);
    }

    /* ---- P1-09: 设置 TTL ----
     * 原理：ch395f_set_ttl(sock, ttl) 设置指定 Socket 的 IP 报 TTL 值。
     *       sock = 0（Socket 0）、ttl = 64（默认值）。
     * 通过：CMD_STATUS == 0x00。
     * 失败：CMD_STATUS 非 0x00。
     */
    {
        ch395f_set_ttl(0, 64);
        uint8_t cmd_st = ch395f_get_cmd_status();
        DBG_INFO("  [P1-09] ch395f_set_ttl(sock=0, ttl=64)");
        DBG_INFO("          expect: CMD_STATUS == 0x00");
        DBG_INFO("          actual: CMD_STATUS = 0x%02X", cmd_st);
        TEST_CHECK(cmd_st == CH395F_ERR_SUCCESS,
                   "P1-09 set_ttl CMD_STATUS = 0x%02X (expect 0x00)", cmd_st);
    }

    TEST_REPORT("Phase 1");
}
#endif

/* ===================================================================
 * Phase 2 — TCP Client（CH395F 作为客户端主动连接 PC）
 *
 * 测试目的：验证 CH395F 作为 TCP Client 发起连接、发送数据、接收回显、
 *          关闭连接、重连的完整生命周期。重复 3 轮以验证连接稳定性。
 *
 * 前置条件：PC 上运行 TCP 回显服务器（python test/ch395f_socket_test.py tcp_server --port 8081）
 * 通过：3/3 轮回显成功。
 * 失败：任意轮连接失败、回显超时或数据不一致。
 * =================================================================== */

#ifdef ENABLE_PHASE2_TESTS
#ifndef PHASE2_REMOTE_IP
#define PHASE2_REMOTE_IP        "192.168.1.2"
#endif
#ifndef PHASE2_REMOTE_PORT
#define PHASE2_REMOTE_PORT      8081
#endif
#ifndef PHASE2_LOCAL_PORT
#define PHASE2_LOCAL_PORT       50000
#endif
#ifndef PHASE2_LOOP_COUNT
#define PHASE2_LOOP_COUNT       3
#endif
#ifndef PHASE2_TIMEOUT_MS
#define PHASE2_TIMEOUT_MS       10000
#endif
#ifndef PHASE2_TC203_TIMEOUT_MS
#define PHASE2_TC203_TIMEOUT_MS 30000   /* TC-203 连接超时窗（指南规定 30s） */
#endif

/* TCP Client 单次会话：对应 TC-NET-201 步 2~7 与 TC-NET-202 每轮 6 步。
 * 调用前须已设置协议类型 / 目标 IP / 目标端口（TC-NET-201 步 1）。
 * 每轮使用不同本地端口 local_port 规避 TIME_WAIT。
 * 返回 1 表示全流程通过，0 表示中途失败（已记录 TEST_CHECK）。
 */
/* TC-NET-201（硬件层）：单次 TCP Client 连接与数据交换，步骤与文档 7 步严格对应 */
static void phase2_tc201(void)
{
    uint8_t sock = 0;       /* 本用例操作的 Socket 索引，固定 0（单连接模式） */
    uint8_t ip_arr[4];      /* 目标 IP 四字节数组，由 test_parse_ip() 解析 PHASE2_REMOTE_IP 填入 */
    uint8_t tx[64];         /* 发送缓冲：64 字节递增模式数据 0x00..0x3F */
    uint8_t rx[300];        /* 接收缓冲：存放 PC 回显数据，300 > 64 预留安全余量 */
    uint8_t r;              /* ch395f_* 命令返回值（0x00 = CH395F_ERR_SUCCESS） */
    uint8_t ok;             /* 当前步骤结果标志：1 = 成功跳出等待循环，0 = 超时失败 */
    uint16_t p;             /* 逐字节比对循环索引（rx[p] vs tx[p]） */
    uint16_t n;             /* 实际参与比对的字节数 = MIN(rl, 64) */
    uint16_t rl;            /* 本次实际读取长度 = MIN(收到长度, sizeof(rx))，防缓冲区溢出 */
    uint32_t tick;          /* HAL_GetTick() 快照；用无符号减法测超时窗口（回绕安全） */
    int16_t k;              /* 通用循环索引（如构造 tx 递增模式） */
    int16_t match;          /* 回显校验结果：1 = 全部字节一致，0 = 发现不符（提前 break） */

    test_parse_ip(PHASE2_REMOTE_IP, ip_arr);

    /* 步1：配置协议类型 / 目标 IP / 目标端口 / 本地端口 */
    ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
    ch395f_set_des_ip(sock, ip_arr);
    ch395f_set_des_port(sock, PHASE2_REMOTE_PORT);
    ch395f_set_sour_port(sock, (uint16_t)PHASE2_LOCAL_PORT);
    TEST_CHECK(ch395f_get_cmd_status() == CH395F_ERR_SUCCESS,
               "TC201 step1 cfg proto/ip/port");
    if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) return;

    /* 构造 64 字节递增模式数据 0x00..0x3F */
    for (k = 0; k < 64; k++) {
        tx[k] = (uint8_t)k;
    }

    /* 步2：OPEN Socket 0 */
    r = ch395f_open_socket(sock);
    TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC201 step2 open");
    if (r != CH395F_ERR_SUCCESS) return;

    /* 步3：CONNECT */
    r = ch395f_tcp_connect(sock);
    TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC201 step3 connect");
    if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }

    /* 步4：等待 CONNECT 中断
     *
     * CH395F 的中断上报是【两级】结构：
     *
     *   第 1 级 —— 全局中断状态 GET_GLOB_INT_STATUS_ALL（16 位，读即清除）
     *     只回答"哪个 Socket 发生了事件"，不回答具体是什么事件。
     *     16 位布局（见 ch395f.h GINT_STAT 宏定义）：
     *       bit0      UNREACH     (0x0001)  目标不可达
     *       bit1      IP_CONFLI   (0x0002)  IP 冲突
     *       bit2      PHY_CHANGE  (0x0004)  链路状态变化
     *       bit3      DHCP        (0x0008)  DHCP 事件
     *       bit4~7    Socket0~3   (0x0010/0x20/0x40/0x80)
     *       bit8~11   Socket4~7   (0x0100~0x0800)
     *       bit15     DHCPV6      (0x8000)
     *
     *   第 2 级 —— Socket 中断状态 CMD_GET_INT_STATUS_SN（8 位，读即清除）
     *     才包含具体事件类型：SENBUF_FREE / SEND_OK / RECV_OK /
     *     CONNECT / DISCONNECT / DISCARD / SOCK_TIMEOUT。
     *     必须在第 1 级提示"本 Socket 有事件"后再读，否则读了也是空。
     *
     * 【掩码 (1U << (sock + 4)) 的由来】
     *   全局状态的 bit0~3 被 4 个全局事件占用，因此 Socket N 的事件标志
     *   固定从 bit4 开始排列 —— Socket N 对应第 (N + 4) 位：
     *     sock=0 → 1<<4  = 0x0010（CH395F_GINT_STAT_SOCK0）
     *     sock=1 → 1<<5  = 0x0020（CH395F_GINT_STAT_SOCK1）
     *     ...
     *     sock=7 → 1<<11 = 0x0800（CH395F_GINT_STAT_SOCK7）
     *   `int_st & (1U << (sock + 4))` 非 0
     *   ⇔ 芯片报告"Socket [sock] 有待处理事件"，
     *   此时才值得再花一次 SPI 事务去读第 2 级确认具体事件。
     */
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) {
        /* 第 1 级：读全局状态。注意本次读取本身会清掉全部全局标志
         * 并拉高 INT#（读即清除），所以每轮循环都在重新采样。 */
        uint16_t int_st = ch395f_get_glob_int_status_all();

        /* 本 Socket 在全局寄存器中有事件挂起？ */
        if (int_st & (1U << (sock + 4))) {
            /* 第 2 级：读本 Socket 的具体事件类型（同样读即清除） */
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & CH395F_SINT_STAT_CONNECT) {
                /* bit3 = 0x08：TCP 三次握手完成，进入 ESTABLISHED */
                ok = 1;
                break;
            }
            /* 是别的事件（理论上此阶段不应出现），已被读清、直接忽略，
             * 继续下一轮轮询等 CONNECT */
        }
        HAL_Delay(10);   /* 10ms 轮询周期，直至 CONNECT 或超时退出 */
    }
    TEST_CHECK(ok, "TC201 step4 connected");
    if (!ok) { 
        ch395f_close_socket(sock); 
        return; 
    }
    HAL_Delay(100);

    /* 步5：发送 64 字节，并等待 SENBUF_FREE 中断确认发送缓冲释放 */
    ch395f_write_send_buf(sock, tx, 64);
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < 2000) {
        uint16_t int_st = ch395f_get_glob_int_status_all();
        if (int_st & (1U << (sock + 4))) {
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & CH395F_SINT_STAT_SENBUF_FREE) { 
                ok = 1; 
                break; 
            }
        }
        HAL_Delay(10);
    }
    TEST_CHECK(ok, "TC201 step5 sendbuf_free");
    if (!ok) { 
        ch395f_close_socket(sock); 
        return; 
    }

    /* 步6：等待回显并逐字节验证（递增模式 0x00..0x3F）
     *
     * 背景：步 5 已把 tx[64] 发给 PC，PC 端 tcp_server 收到后原样回显。
     * 本步骤最多等 10 秒，每轮轮询做两件事：
     *   ① 查芯片接收缓冲有没有回显数据到达（优先处理）；
     *   ② 查有没有 DISCONNECT / TIMEOUT 事件（连接断了就别傻等了）。
     *
     * 关键 API 语义区别：
     *   ch395f_get_recv_len()  —— 只查询积压字节数，不取走数据（可反复调用）
     *   ch395f_read_recv_buf() —— 真正从芯片 RX FIFO 取出数据（取走即消耗）
     */
    tick = HAL_GetTick();
    ok = 0;                          /* 尚未收到任何回显数据 */
    while (HAL_GetTick() - tick < 10000) {

        /* ---- ① 数据到达检查 ----
         * rlen：芯片 RX 缓冲区当前积压的字节数（纯查询、不消耗）。
         *       rlen > 0 说明 PC 回显已到达，进入读取 + 校验流程。 */
        uint16_t rlen = ch395f_get_recv_len(sock);
        if (rlen > 0) {

            /* 截断保护：一次最多读 sizeof(rx)=300 字节，
             * 防止 rlen 异常偏大时 read_recv_buf 写越界 rx[] */
            rl = (rlen > (uint16_t)sizeof(rx)) ? (uint16_t)sizeof(rx) : rlen;

            /* 清零将要填充的区域（防御性写法，避免残留脏字节干扰比对） */
            memset(rx, 0, rl);

            /* 从芯片 RX FIFO 取出回显 —— 此刻起这些数据已被消费 */
            ch395f_read_recv_buf(sock, rx, rl);

            /* 参与比对的长度：预期回显 64 字节；若 TCP 分片导致本次
             * 只到了一部分（rl < 64），则只比对已到的部分 */
            n = (rl < 64) ? rl : 64;

            /* 判定初值：必须收满 64 字节才算通过。若 rl < 64（分片/短读），
             * match 直接为 0 → "echo verify" 判 FAIL */
            match = (rl >= 64);

            /* 与发送的递增模式逐字节比对：tx[k]=k，任一不符立即置 FAIL 退出 */
            for (p = 0; p < n; p++) {
                if (rx[p] != tx[p]) { 
                    match = 0; 
                    break; 
                }
            }
            TEST_CHECK(match, "TC201 step6 echo verify");

            ok = 1;      /* 标记"收到过数据"，循环外的 echo rcvd 检查不再重复报错 */
            break;       /* 校验结论已得出，无论 PASS/FAIL 都跳出 */
        }

        /* ---- ② 连接异常检查（还没等到数据才走到这里）----
         * 两级中断：全局状态确认本 Socket 有事件 → 再读具体事件类型。
         * 注意先查数据再查断开：断开事件可能和最后一批数据同时到达，
         * 数据优先保证"临终回显"不被漏掉。 */
        uint16_t int_st = ch395f_get_glob_int_status_all();
        if (int_st & (1U << (sock + 4))) {
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) {
                break;   /* 对端断开 / 超时：继续等也不会有回显了。
                            ok 保持 0，跳出后由下方统一判 FAIL */
            }
        }

        /* 每 50ms 一轮。若本轮只读了部分数据（rlen > 300 的极端情况），
         * 剩余字节下一轮 get_recv_len 会继续报告，不会丢 */
        HAL_Delay(50);
    }
    /* 到这里只有两种情况：
     *   ok==1：收到过数据，结果已由循环内 "echo verify" 记录；
     *   ok==0：10s 内什么都没收到 / 中途断开 —— 在此补一条 FAIL。 */
    if (!ok) {
        TEST_CHECK(0, "TC201 step6 echo rcvd");
    }

    /* 步7：关闭 Socket 并等待状态回到 CLOSED（CH395F_SOCKET_CLOSED = 0x00） */
    ch395f_close_socket(sock);
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < 5000) {
        uint8_t st[2];
        ch395f_get_socket_status(sock, st);
        if (st[0] == CH395F_SOCKET_CLOSED) { 
            ok = 1; 
            break; 
        }
        HAL_Delay(50);
    }
    TEST_CHECK(ok, "TC201 step7 closed");
    HAL_Delay(200);
}

/* TC-NET-202（硬件层）：关闭重连 x PHASE2_LOOP_COUNT 轮，每轮 6 步 */
static void phase2_tc202_once(uint8_t sock, uint16_t local_port)
{
    uint8_t tx[64];
    uint8_t rx[300];
    uint8_t r;
    uint8_t ok;
    uint16_t p;
    uint16_t n;
    uint16_t rl;
    uint32_t tick;
    int k;
    int match;

    for (k = 0; k < 64; k++) {
        tx[k] = (uint8_t)k;
    }

    /* 步1：设置本地端口并 OPEN Socket 0 */
    ch395f_set_sour_port(sock, local_port);
    r = ch395f_open_socket(sock);
    TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC202 step1 open");
    if (r != CH395F_ERR_SUCCESS) return;

    /* 步2：CONNECT */
    r = ch395f_tcp_connect(sock);
    TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC202 step2 connect");
    if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }

    /* 步3：等待 CONNECT 中断 */
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < PHASE2_TIMEOUT_MS) {
        uint16_t int_st = ch395f_get_glob_int_status_all();
        if (int_st & (1U << (sock + 4))) {
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & CH395F_SINT_STAT_CONNECT) { ok = 1; break; }
        }
        HAL_Delay(10);
    }
    TEST_CHECK(ok, "TC202 step3 connected");
    if (!ok) { ch395f_close_socket(sock); return; }
    HAL_Delay(100);

    /* 步4：发送 64 字节 + SENBUF_FREE */
    ch395f_write_send_buf(sock, tx, 64);
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < 2000) {
        uint16_t int_st = ch395f_get_glob_int_status_all();
        if (int_st & (1U << (sock + 4))) {
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & CH395F_SINT_STAT_SENBUF_FREE) { ok = 1; break; }
        }
        HAL_Delay(10);
    }
    TEST_CHECK(ok, "TC202 step4 sendbuf_free");
    if (!ok) { ch395f_close_socket(sock); return; }

    /* 步5：等待回显并验证 */
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < 10000) {
        uint16_t rlen = ch395f_get_recv_len(sock);
        if (rlen > 0) {
            rl = (rlen > (uint16_t)sizeof(rx)) ? (uint16_t)sizeof(rx) : rlen;
            memset(rx, 0, rl);
            ch395f_read_recv_buf(sock, rx, rl);
            n = (rl < 64) ? rl : 64;
            match = (rl >= 64);
            for (p = 0; p < n; p++) {
                if (rx[p] != tx[p]) { match = 0; break; }
            }
            TEST_CHECK(match, "TC202 step5 echo verify");
            ok = 1;
            break;
        }
        uint16_t int_st = ch395f_get_glob_int_status_all();
        if (int_st & (1U << (sock + 4))) {
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) break;
        }
        HAL_Delay(50);
    }
    if (!ok) TEST_CHECK(0, "TC202 step5 echo rcvd");

    /* 步6：关闭 + 等待 CLOSED */
    ch395f_close_socket(sock);
    tick = HAL_GetTick();
    ok = 0;
    while (HAL_GetTick() - tick < 5000) {
        uint8_t st[2];
        ch395f_get_socket_status(sock, st);
        if (st[0] == CH395F_SOCKET_CLOSED) { ok = 1; break; }
        HAL_Delay(50);
    }
    TEST_CHECK(ok, "TC202 step6 closed");
    HAL_Delay(200);
}

static void phase2_tc202(void)
{
    uint8_t sock = 0;
    uint8_t ip_arr[4];
    int i;

    test_parse_ip(PHASE2_REMOTE_IP, ip_arr);

    /* 协议 / 目标端口在重连间保持不变，仅每轮更换本地端口 */
    ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
    ch395f_set_des_ip(sock, ip_arr);
    ch395f_set_des_port(sock, PHASE2_REMOTE_PORT);

    for (i = 0; i < PHASE2_LOOP_COUNT; i++) {
        phase2_tc202_once(sock, (uint16_t)(PHASE2_LOCAL_PORT + i));
    }
}

/* TC-NET-203（硬件层）：TCP Client 连接超时（负向测试，P1）
 *
 * 前置环境：PC 未在 192.168.1.2:8081 提供服务（与 TC-201/202 互斥，
 * 需先关闭 PC 端 tcp_server）。固件自行辨别环境错误：
 * 若等待期间误收到 CONNECT，说明 PC 服务未关，判 FAIL 并给出指引。
 *
 * 负向通过路径（本质都是"未建立连接"，按事件+耗时细分原因）：
 *   SOCK_TIMEOUT(快, <3s)  —— 实测主路径：对端在线但端口关闭，RST 拒绝，
 *                             芯片毫秒级上报，并非等待重传耗尽
 *   SOCK_TIMEOUT(慢, ≥3s)  —— 对端无响应（掉电/防火墙丢包），重传耗尽后报超时
 *   DISCONNECT             —— 预留兼容路径（SYN_SENT 阶段实测基本不出现）
 *   UNREACH(全局位)        —— ARP 失败、对端不在线（仅作备注，不影响判定）
 *
 * 已知陷阱（Trap 14）：上一会话 close 的 DISCONNECT 会滞后锁存在
 * SINT_SN（wait-CLOSED 的 STATUS 查询不清中断）。防御双保险：
 *   ① 步 2 前预清两级中断；
 *   ② 判负向后用 GET_SOCKET_STATUS 反核实，ESTABLISHED 即改判环境错误。
 */
static void phase2_tc203(void)
{
    uint8_t sock = 0;       /* 操作 Socket 0（单连接模式） */
    uint8_t ip_arr[4];      /* 目标 IP 四字节数组 */
    uint16_t sour_port = (uint16_t)(PHASE2_LOCAL_PORT + 16);  /* 避开 201/202 用过的端口防 TIME_WAIT */
    uint32_t tick;
    uint32_t elapsed;       /* 步3 从 CONNECT 到收到负向事件的耗时（ms），用于细分原因 */
    uint8_t r;
    uint8_t neg_si;         /* 步3 命中的 socket 事件位（SOCK_TIMEOUT/DISCONNECT） */
    uint8_t ev;             /* 步3 结果：0=无事件 1=收到负向事件(过) 2=误收到CONNECT(环境错误) */

    DBG_INFO("--- TC-NET-203: connect timeout (PC 8081 MUST be OFF) ---");

    test_parse_ip(PHASE2_REMOTE_IP, ip_arr);

    /* 步1：配置协议/目标 IP/目标端口/本地端口，校验 CMD_STATUS */
    ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
    ch395f_set_des_ip(sock, ip_arr);
    ch395f_set_des_port(sock, (uint16_t)PHASE2_REMOTE_PORT);
    ch395f_set_sour_port(sock, sour_port);
    TEST_CHECK(ch395f_get_cmd_status() == CH395F_ERR_SUCCESS,
               "TC203 step1 cfg proto/ip/port");
    if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) return;

    /* 预清陈旧事件（Trap 14）：上一会话 close 的 DISCONNECT 可能滞后锁存
     * 在 SINT_SN 中（wait-CLOSED 轮询的 STATUS 是纯查询、不清中断），
     * 若不清除，本用例首次读中断会误吃旧事件导致负向误判。 */
    (void)ch395f_get_glob_int_status_all();
    (void)ch395f_get_sock_int_status(sock);

    /* 步2：OPEN + CONNECT。命令为异步下发，此时均应立即返回成功，
     * 真正的失败结果由步 3 的中断体现 */
    r = ch395f_open_socket(sock);
    TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC203 step2 open");
    if (r != CH395F_ERR_SUCCESS) return;

    r = ch395f_tcp_connect(sock);
    TEST_CHECK(r == CH395F_ERR_SUCCESS, "TC203 step2 connect");
    if (r != CH395F_ERR_SUCCESS) { ch395f_close_socket(sock); return; }

    /* 步3：30s 两级中断轮询，等"连不上"的证据。
     * 先扫全局 UNREACH 位作备注，再查本 socket 位取具体事件。
     *
     * 实测行为（2026-08 上板）：PC 端口关闭时 SYN 被 RST 拒绝，
     * 芯片以【毫秒级 SOCK_TIMEOUT】上报，并非等待 30s 静默重传耗尽；
     * DISCONNECT 仅在已建立连接断开时触发，SYN_SENT 阶段基本不会出现。
     * 因此同时记录耗时用于细分失败原因：
     *   <3s   → 对端主动拒绝（RST，端口关闭）
     *   ≥3s   → 真无应答（对端离线/防火墙丢包，重传耗尽） */
    tick = HAL_GetTick();
    ev = 0;
    neg_si = 0;
    while (HAL_GetTick() - tick < PHASE2_TC203_TIMEOUT_MS) {
        uint16_t int_st = ch395f_get_glob_int_status_all();

        if (int_st & CH395F_GINT_STAT_UNREACH) {
            DBG_INFO("  global UNREACH: ARP fail (peer offline)");
        }

        if (int_st & (1U << (sock + 4))) {
            uint8_t si = ch395f_get_sock_int_status(sock);
            if (si & (CH395F_SINT_STAT_SOCK_TIMEOUT | CH395F_SINT_STAT_DISCONNECT)) {
                neg_si = si;    /* 记下事件位，跳出后再细分原因 */
                ev = 1;
                break;
            }
            if (si & CH395F_SINT_STAT_CONNECT) {
                ev = 2;     /* 连上了 = PC 服务没关，属环境错误而非芯片故障 */
                break;
            }
        }
        HAL_Delay(10);
    }

    elapsed = HAL_GetTick() - tick;

    /* 反核实（Trap 14 第二道防线）：负向事件与实际状态矛盾时以状态为准。
     * 若收到"负向事件"但 socket 实际已 ESTABLISHED，说明刚才读到的是
     * 上一会话遗留的陈旧 DISCONNECT，真实结果是连接成功 —— 环境错误。 */
    if (ev == 1) {
        uint8_t st_chk[2];
        ch395f_get_socket_status(sock, st_chk);
        if (st_chk[0] == CH395F_TCP_ESTABLISHED) {
            DBG_ERROR("  negative event but socket ESTABLISHED -> stale DISCONNECT!");
            ev = 2;
        }
    }

    if (ev == 1) {
        const char *why;
        const char *short_tag;

        if (neg_si & CH395F_SINT_STAT_DISCONNECT) {
            why = "DISCONNECT: peer sent RST (port closed)";
            short_tag = "DISC/RST";
        } else if (elapsed < 3000U) {
            /* 毫秒级超时 = 收到了 RST 快速失败，而非静默无应答 */
            why = "SOCK_TIMEOUT fast: peer actively refused (RST, port closed)";
            short_tag = "T/O-fast(refused)";
        } else {
            why = "SOCK_TIMEOUT slow: no response (offline / firewall drop)";
            short_tag = "T/O-slow(silence)";
        }
        DBG_INFO("  negative event @%lums: %s", elapsed, why);
        TEST_CHECK(1, "TC203 step3 no-connect OK (%lums %s)", elapsed, short_tag);
    } else if (ev == 2) {
        DBG_ERROR("  CONNECT received -> PC 8081 server is still running!");
        TEST_CHECK(0, "TC203 step3 connected?! stop PC server and retry");
    } else {
        TEST_CHECK(0, "TC203 step3 no interrupt in %dms (state machine hang?)",
                   PHASE2_TC203_TIMEOUT_MS);
    }

    /* 步4：无论步 3 结论如何都关闭 Socket，确认状态机可恢复到 CLOSED */
    ch395f_close_socket(sock);
    tick = HAL_GetTick();
    ev = 0;
    while (HAL_GetTick() - tick < 5000) {
        uint8_t st[2];
        ch395f_get_socket_status(sock, st);
        if (st[0] == CH395F_SOCKET_CLOSED) { ev = 1; break; }
        HAL_Delay(50);
    }
    TEST_CHECK(ev == 1, "TC203 step4 closed");

    HAL_Delay(200);
}

static void phase2_run(void)
{
    DBG_INFO("=== CH395F Phase 2 Tests (TCP Client) ===");

    DBG_INFO("--- TC-NET-201: connect & 64B exchange ---");
    phase2_tc201();

    DBG_INFO("--- TC-NET-202: close/reconnect x%d ---", PHASE2_LOOP_COUNT);
    phase2_tc202();

    DBG_INFO("--- TC-NET-203: negative test, stop PC 8081 server first ---");
    phase2_tc203();

    TEST_REPORT("Phase 2");
}


#endif

/* ===================================================================
 * Phase 3 — UDP Echo（CH395F 作为 UDP Server）
 *
 * 测试目的：验证 CH395F UDP 协议栈的收发能力，包括：
 *          1. UDP Socket 打开和关闭
 *          2. 从任意源接收 UDP 数据报
 *          3. 解析 UDP 包头的源 IP/端口
 *          4. 向对应源地址回显数据
 *          5. SENDBUF_FREE 中断等待（确保发送缓冲可用）
 *
 * 前置条件：PC 运行 udp_client 发包到 192.168.1.100:60000
 * 会话结构：累计 PHASE3_TOTAL_ROUNDS(60) 个有效数据包后关闭 Socket
 *          第 1~30 轮 = TC-301（PC 发 64B）；第 31~60 轮 = TC-302（1B/64B/200B）
 *          "HELLO"(5B) 为 PC 就绪探针：照常回显但不计入轮次
 * 通过：60/60 有效轮回显成功。
 * 失败：任意轮回显超时或解析错误。
 * =================================================================== */

#ifdef ENABLE_PHASE3_TESTS

/* UDP 有效回显轮数：1~30 = TC-301（64B），31~60 = TC-302（变长）。
 * "HELLO" 探针只回显不计数，与 test/ch395f_socket_test.py udp_client 约定 */
#ifndef PHASE3_TOTAL_ROUNDS
#define PHASE3_TOTAL_ROUNDS     60
#endif

static void phase3_run(void) {
    uint8_t sock = 0;
    uint8_t rx_buf[300] = {0};
    int counted;

    DBG_INFO("=== CH395F Phase 3 Tests (UDP) ===");

    /*
     * 配置 Socket 0 为 UDP Server：
     * - 协议类型 UDP
     * - 本地端口 60000
     * - 目标 IP/端口初始为广播（0xFF.0xFF.0xFF.0xFF:0），
     *   收到数据后根据包头中的源地址动态设置。
     */
    sock = 0;
    ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP);
    ch395f_set_sour_port(sock, 60000);
    {
        uint8_t bcast[4] = {0xFF, 0xFF, 0xFF, 0xFF};
        ch395f_set_des_ip(sock, bcast);
    }
    ch395f_set_des_port(sock, 0);

    TEST_CHECK(ch395f_open_socket(sock) == CH395F_ERR_SUCCESS,
               "UDP socket %d opened (port=60000)", sock);
    HAL_Delay(200);

    /*
     * 计数式回显循环：累计 PHASE3_TOTAL_ROUNDS 个有效数据包。
     * CH395F UDP 接收数据格式（read_recv_buf 返回）：
     *   [0-1] 保留
     *   [2-3] 源端口（大端）
     *   [4-7] 源 IP
     *   [8+]  载荷数据
     * 回显前需根据源 IP/Port 设置目标地址，写回载荷数据。
     * 每次 write_send_buf 后需等待 SENDBUF_FREE 中断，确保数据已
     * 从 MCU 传输到 CH395F 内部发送缓冲，避免后续接收覆盖。
     *
     * "HELLO"(5B) 为 PC 就绪探针：照常回显但不计入轮次，
     * 使脚本探针阶段不占用用例预算（与 udp_client 脚本约定）。
     */
    counted = 0;
    while (counted < PHASE3_TOTAL_ROUNDS) {
        uint32_t tick = HAL_GetTick();
        int echo_ok = 0;
        int is_probe = 0;
        while (HAL_GetTick() - tick < 30000) {
            uint16_t rlen = ch395f_get_recv_len(sock);
            if (rlen > 8) {
                memset(rx_buf, 0, sizeof(rx_buf));
                ch395f_read_recv_buf(sock, rx_buf, rlen);

                uint16_t payload_len = rlen - 8;
                uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8);
                uint8_t *src_ip = &rx_buf[4];
                uint8_t *payload = &rx_buf[8];

                is_probe = (payload_len == 5 &&
                            memcmp(payload, "HELLO", 5) == 0);

                ch395f_set_des_ip(sock, src_ip);
                ch395f_set_des_port(sock, src_port);
                ch395f_write_send_buf(sock, payload, payload_len);

                uint32_t sw = HAL_GetTick();
                while (HAL_GetTick() - sw < 2000) {
                    uint16_t gs = ch395f_get_glob_int_status_all();
                    if (gs & (1U << (sock + 4))) {
                        if (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE)
                            break;
                    }
                    HAL_Delay(10);
                }
                echo_ok = 1;
                break;
            }
            HAL_Delay(50);
        }

        if (!echo_ok) {
            TEST_CHECK(0, "UDP echo #%d timeout", counted + 1);
            break;
        }
        if (!is_probe) {
            counted++;
            TEST_CHECK(1, "UDP echo #%d", counted);
        }
    }

    ch395f_close_socket(sock);
    TEST_CHECK(1, "UDP socket closed");
    TEST_REPORT("Phase 3");
}
#endif

/* ===================================================================
 * Phase 4 — DHCP 自动获取 IP
 *
 * 测试目的：验证 CH395F DHCP 客户端功能，包括：
 *          1. DHCP DISCOVER → OFFER → REQUEST → ACK 完整握手
 *          2. DHCP 成功后 IP 地址非零
 *          3. 广播宣告（UDP 广播本机 IP）
 *          4. 接收并回显 HELLO 消息
 *
 * 注意：DHCP 成功后会改变 CH395F 的 IP 配置，需与之前静态 IP 测试隔离。
 * 前置条件：PC 上运行 DHCP Server（管理员权限）。
 * 通过：DHCP SUCCESS + IP 非零 + HELLO 回显成功。
 * 失败：DHCP 超时或 HELLO 无响应。
 * =================================================================== */

#ifdef ENABLE_PHASE4_TESTS
#ifndef PHASE4_ANNOUNCE_PORT
#define PHASE4_ANNOUNCE_PORT    60001
#endif
#ifndef PHASE4_ANNOUNCE_SOCK
#define PHASE4_ANNOUNCE_SOCK    6
#endif
#ifndef PHASE4_HELLO_WAIT_MS
#define PHASE4_HELLO_WAIT_MS    30000   /* HELLO 回显窗：PC 在 ACK 后固定 sleep(3)s，
                                         * 窗口须覆盖 PC 侧启动延迟（旧值 5s 重叠仅 ~2s） */
#endif

static void phase4_run(void) {
    uint8_t ip_info[20] = {0};
    int same = 0;

    DBG_INFO("=== CH395F Phase 4 Tests (DHCP) ===");

    /*
     * 启用 DHCP：ch395f_set_dhcp(1) 使 CH395F 进入 DHCP 自动协商。
     * 然后轮询 DHCP 状态寄存器：0x00 = 成功，非 0x00 = 进行中/失败。
     * 最多等待 30 秒。
     */
    ch395f_set_dhcp(1);
    TEST_CHECK(1, "DHCP enabled, waiting for IP...");

    HAL_Delay(500);
    uint32_t tick = HAL_GetTick();
    uint8_t dhcp_ok = 0;
    while (HAL_GetTick() - tick < 30000) {
        ch395f_get_glob_int_status_all();
        if (ch395f_get_dhcp_status() == 0x00) { dhcp_ok = 1; break; }
        HAL_Delay(500);
    }
    TEST_CHECK(dhcp_ok, "DHCP completed within 30s");
    if (!dhcp_ok) goto phase4_end;

    /*
     * IP 寄存器提交滞后（2026-08-24 上板实测）：DHCP 状态已置成功、
     * 芯片已按新租约通信，但此刻 get_ip_inf 仍回读旧静态配置——
     * 串口打印与宣告广播会带出过期 IP。
     * 对策：轮询直至连续 3 次（间隔 300ms）读到相同非零值才采信。
     */
    {
        uint8_t last[20] = {0};
        tick = HAL_GetTick();
        while (HAL_GetTick() - tick < 8000) {
            memset(ip_info, 0, sizeof(ip_info));
            ch395f_get_ip_inf(ip_info);
            if ((ip_info[0] || ip_info[1] || ip_info[2] || ip_info[3]) &&
                memcmp(last, ip_info, sizeof(ip_info)) == 0) {
                if (++same >= 3) break;
            } else {
                same = 0;
            }
            if (memcmp(last, ip_info, sizeof(ip_info)) != 0) {
                DBG_INFO("  DHCP IP report: %d.%d.%d.%d",
                         ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
                memcpy(last, ip_info, sizeof(ip_info));
            }
            HAL_Delay(300);
        }
        memcpy(ip_info, last, sizeof(ip_info));
    }
    TEST_CHECK(same >= 3, "IP registers settled (3 consecutive reads)");

    DBG_INFO("  IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
    TEST_CHECK(ip_info[0]!=0||ip_info[1]!=0||ip_info[2]!=0||ip_info[3]!=0,
               "IP non-zero");

    /*
     * DHCP 成功后广播宣告本机 IP：通过 Socket 6（额外 UDP Socket）
     * 向 255.255.255.255:60001 发送 4 字节 IP 地址。
     * 然后等待 PC 回复 HELLO（5 字节），收到后原样回显。
     */
    {
        uint8_t anno_sock = PHASE4_ANNOUNCE_SOCK;
        uint8_t bcast[4] = {0xFF,0xFF,0xFF,0xFF};
        uint8_t rx_buf[64] = {0};

        ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP);
        ch395f_set_send_buf(anno_sock, 24, 2);
        ch395f_set_recv_buf(anno_sock, 26, 2);
        ch395f_set_sour_port(anno_sock, 60000);
        ch395f_set_des_ip(anno_sock, bcast);
        ch395f_set_des_port(anno_sock, 0);

        if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) {
            ch395f_set_des_ip(anno_sock, bcast);
            ch395f_set_des_port(anno_sock, PHASE4_ANNOUNCE_PORT);
            ch395f_write_send_buf(anno_sock, ip_info, 4);
            uint32_t sw = HAL_GetTick();
            while (HAL_GetTick() - sw < 2000) {
                uint16_t gs = ch395f_get_glob_int_status_all();
                if (gs & (1U << (anno_sock + 4))) {
                    if (ch395f_get_sock_int_status(anno_sock) & CH395F_SINT_STAT_SENBUF_FREE) break;
                }
                HAL_Delay(10);
            }
            TEST_CHECK(1, "DHCP announcement sent");

            ch395f_set_des_ip(anno_sock, bcast);
            ch395f_set_des_port(anno_sock, 0);

            int hello_ok = 0;
            uint32_t wait = HAL_GetTick();
            while (HAL_GetTick() - wait < PHASE4_HELLO_WAIT_MS) {
                ch395f_get_glob_int_status_all();
                uint16_t rlen = ch395f_get_recv_len(anno_sock);
                if (rlen > 8) {
                    memset(rx_buf,0,sizeof(rx_buf));
                    ch395f_read_recv_buf(anno_sock, rx_buf, rlen);
                    uint16_t plen = rlen - 8;
                    uint8_t *payload = &rx_buf[8];
                    if (plen == 5 && memcmp(payload, "HELLO", 5) == 0) {
                        uint8_t *src_ip = &rx_buf[4];
                        uint16_t src_port = (uint16_t)rx_buf[2] | ((uint16_t)rx_buf[3] << 8);
                        ch395f_set_des_ip(anno_sock, src_ip);
                        ch395f_set_des_port(anno_sock, src_port);
                        ch395f_write_send_buf(anno_sock, payload, plen);
                        TEST_CHECK(1, "HELLO echo OK");
                        hello_ok = 1;
                        break;
                    }
                }
                HAL_Delay(50);
            }
            TEST_CHECK(hello_ok, "HELLO echo within %dms", PHASE4_HELLO_WAIT_MS);
            ch395f_close_socket(anno_sock);
        }
    }

phase4_end:
    TEST_REPORT("Phase 4");
}
#endif

/* ===================================================================
 * Phase 5 — 大文件传输（硬件层裸命令）
 *
 * 测试目的：不依赖 net_socket/netTask，直接用 ch395f_* 命令验证
 *          TCP 批量数据搬运能力：
 *          1. 裸命令建立 TCP Client 连接（open/connect/等 CONNECT）
 *          2. 顺序双测向：先连续发送 256KB（PC 只收不回声，SENBUF_FREE 流控），
 *             再连续接收 256KB（PC 按相同 LCG 模式回放，滚动校验），内存 O(CHUNK)
 *          3. 全量累加和 TX/RX 对总账 + 双向独立吞吐基线
 *
 * PC 配合：python test/ch395f_socket_test.py tcp_bulk --port 8081 --bulk 262144 --chunk 4096
 *          （先收满 256KB 不回声，再按 LCG 模式回放 256KB）
 * 通过：全部块校验一致，且 SUM(TX)==SUM(RX)。
 * 失败：连接超时 / SENBUF_FREE 超时 / RX 停滞 / 数据不一致。
 * =================================================================== */

#ifdef ENABLE_PHASE5_TESTS

/* 回传参数（TC-NET-502 合规测试时按档位修改重编译） */
#ifndef PHASE5_REMOTE_IP
#define PHASE5_REMOTE_IP        "192.168.1.2"
#endif
#ifndef PHASE5_REMOTE_PORT
#define PHASE5_REMOTE_PORT      8081    /* PC tcp_server 监听端口 */
#endif
#ifndef PHASE5_LOCAL_PORT
#define PHASE5_LOCAL_PORT       50010
#endif
#ifndef PHASE5_TOTAL_BYTES
#define PHASE5_TOTAL_BYTES      (256*1024)   /* 回传总量 */
#endif
#ifndef PHASE5_CHUNK_SIZE
#define PHASE5_CHUNK_SIZE       4093    /* 单块 = socket 默认发送缓冲，DP-04 边界 */
#endif
#ifndef PHASE5_CONN_TIMEOUT_MS
#define PHASE5_CONN_TIMEOUT_MS  15000
#endif
#define PHASE5_TX_TIMEOUT_MS       2000   /* 单块 SENBUF_FREE 等待上限 */
#define PHASE5_RX_STALL_TIMEOUT_MS 5000   /* 发送完成后 RX 无进展上限 */

/* 32 位累加和：足以发现丢字节/错位，比 CRC32 便宜 */
static uint32_t phase5_sum32(const uint8_t *p, uint32_t n) {
    uint32_t s = 0;
    while (n--) s += *p++;
    return s;
}

/* 按块索引确定性生成模式数据（LCG）：发送与回读两侧可独立复现，逐字节比对 */
static void phase5_gen_block(uint8_t *buf, uint32_t idx) {
    uint32_t seed = 0xC2D2E6F9u ^ (idx * 2654435761u);
    uint32_t k;

    for (k = 0; k < PHASE5_CHUNK_SIZE; k++) {
        seed = seed * 1664525u + 1013904223u;
        buf[k] = (uint8_t)(seed >> 24);
    }
}

static void phase5_run(void) {
    static uint8_t tx[PHASE5_CHUNK_SIZE];
    static uint8_t rx[PHASE5_CHUNK_SIZE];
    static uint8_t exp[PHASE5_CHUNK_SIZE];
    uint8_t sock = 0;
    uint8_t ip_arr[4];
    int chunks;
    int bad = 0;
    int conn_ok = 0;
    int blk_tx = 0;
    int blk_rx = 0;
    int can_write = 1;
    int progress_64k = -1;
    uint32_t send_bytes;
    uint32_t sum_tx = 0;
    uint32_t sum_rx = 0;
    uint32_t rx_total = 0;
    uint32_t exp_off = 0;
    uint32_t rx_stall = 0;
    uint32_t tx_sw = 0;
    uint32_t t0;

    DBG_INFO("=== CH395F Phase 5 Tests (HW Bulk Transfer) ===");
    DBG_INFO("  target %s:%d, %d bytes x %d B/chunk",
             PHASE5_REMOTE_IP, PHASE5_REMOTE_PORT,
             PHASE5_TOTAL_BYTES, PHASE5_CHUNK_SIZE);

    /*
     * 步1：裸命令建立 TCP 连接。
     * 先 close 清理上轮残留（结果不关心），再配置并 connect。
     */
    (void)ch395f_close_socket(sock);
    HAL_Delay(50);
    ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
    test_parse_ip(PHASE5_REMOTE_IP, ip_arr);
    ch395f_set_des_ip(sock, ip_arr);
    ch395f_set_des_port(sock, PHASE5_REMOTE_PORT);
    ch395f_set_sour_port(sock, PHASE5_LOCAL_PORT);
    TEST_CHECK(ch395f_open_socket(sock) == CH395F_ERR_SUCCESS &&
               ch395f_tcp_connect(sock) == CH395F_ERR_SUCCESS,
               "step1 open+connect issued");
    if (ch395f_get_cmd_status() != CH395F_ERR_SUCCESS) {
        TEST_REPORT("Phase 5");
        return;
    }

    t0 = HAL_GetTick();
    while (HAL_GetTick() - t0 < PHASE5_CONN_TIMEOUT_MS) {
        uint16_t gs = ch395f_get_glob_int_status_all();
        if ((gs & (1U << (sock + 4))) &&
            (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_CONNECT)) {
            conn_ok = 1;
            break;
        }
        HAL_Delay(10);
    }
    TEST_CHECK(conn_ok, "connected within %dms", PHASE5_CONN_TIMEOUT_MS);
    if (!conn_ok) {
        (void)ch395f_close_socket(sock);
        TEST_REPORT("Phase 5");
        return;
    }

    /*
     * 步2A：连续发送 256KB（单向，PC 只收不回声）。
     *   节奏：写 4096B → 等 SENBUF_FREE（芯片搬走数据后触发）→ 写下一块，
     *   全程不等回显，测得纯 TX 吞吐。
     * 步2B：连续接收 256KB（PC 收到 256KB 后按相同 LCG 模式回放），
     *   对字节流做滚动校验（期望流按块生成 exp[]，字节对位 + sum_rx 累加），
     *   天然处理 TCP 分包/粘包，测得纯 RX 吞吐。
     */
    chunks = PHASE5_TOTAL_BYTES / PHASE5_CHUNK_SIZE;
    send_bytes = (uint32_t)chunks * PHASE5_CHUNK_SIZE;   /* 实际上送字节（4093 档 < TOTAL，余尾不送） */

    /* ---- 步2A：连续发送 ----
     * 目标：把 chunks×PHASE5_CHUNK_SIZE 字节按 LCG 模式数据一次性连续交出，
     *       全程不等回显，测得纯 TX 吞吐。
     *
     * 发送状态机（can_write 标志）：
     *   can_write == 1 : 发送缓冲空闲，可写入下一块
     *   can_write == 0 : 上一块已写入，等待芯片把数据搬出 socket 发送缓冲
     *   （首次进入 can_write 初始为 1，因为连接建立后发送缓冲为空）
     *
     * 芯片写节奏约束（CH395 手册 / Trap 记录）：
     *   每次 WRITE_SEND_BUF 后必须收到 SINT_STAT_SENBUF_FREE 才能再次写入，
     *   否则下次写入会被芯片静默丢弃。SENBUF_FREE 表示芯片已把本次写入的数据
     *   从 socket 发送缓冲搬到内部协议栈/MAC 缓冲（腾出空间），故每写一块
     *   就转 can_write=0 等待该事件——这正是"连续发送"的天然流控节拍，
     *   不会因超过发送缓冲（默认 4096B）而溢出。
     */
    {
        uint32_t tx_t0 = HAL_GetTick();       /* 发送阶段起点：用于计算 TX 吞吐 */
        blk_tx = 0;                           /* 已交出的块计数 */
        can_write = 1;                        /* 初始：连接后发送缓冲为空，可直接写 */
        while (blk_tx < chunks && !bad) {
            if (can_write) {
                /* ---- 发送缓冲空闲：生成并写入一块 ---- */
                phase5_gen_block(tx, (uint32_t)blk_tx);          /* 确定性生成第 blk_tx 块模式（TX/RX 可复现） */
                sum_tx += phase5_sum32(tx, PHASE5_CHUNK_SIZE);   /* 先累加发送侧总账（与 RX 侧对账用） */
                ch395f_write_send_buf(sock, tx, PHASE5_CHUNK_SIZE); /* 4096B 单次写入（= 发送缓冲上限，DP-04 边界） */
                blk_tx++;                     /* 该块已交出 */
                can_write = 0;                /* 进入等待 SENBUF_FREE 状态 */
                tx_sw = HAL_GetTick();        /* 记录等待起点（供超时判定） */
            } else {
                /* ---- 等待上一次写入被芯片搬走（SENBUF_FREE） ----
                 * 两级中断轮询（DP-02）：先读全局中断状态确认本 socket 有事件，
                 * 再读该 socket 的中断状态确认具体为 SENBUF_FREE。
                 * 读全局中断状态即清除中断标志（读即清）。
                 */
                uint16_t gs = ch395f_get_glob_int_status_all();
                if ((gs & (1U << (sock + 4))) &&                 /* socket 中断位（socket 0 → bit 4） */
                    (ch395f_get_sock_int_status(sock) & CH395F_SINT_STAT_SENBUF_FREE)) {
                    can_write = 1;            /* 缓冲已腾空，下一轮写入下一块 */
                } else if (HAL_GetTick() - tx_sw >= PHASE5_TX_TIMEOUT_MS) {
                    /* 超时：芯片迟迟未腾出缓冲 —— 对端未收/链路异常/芯片堵死 */
                    bad++;
                    DBG_ERROR("TX SENBUF_FREE stall @chunk %d", blk_tx);
                    break;
                }
            }
            /* ---- 进度打印（每交出 64KB 打一行） ---- */
            if ((int)(((uint32_t)blk_tx * PHASE5_CHUNK_SIZE) / (64 * 1024)) != progress_64k) {
                progress_64k = (int)(((uint32_t)blk_tx * PHASE5_CHUNK_SIZE) / (64 * 1024));
                DBG_INFO("  TX %d/%d blocks (%lu bytes)", blk_tx, chunks,
                         (uint32_t)blk_tx * PHASE5_CHUNK_SIZE);
            }
        }
        /* 全部块已写入发送缓冲即视为发送完成：最后一块已交给芯片，
         * 无需再等其 SENBUF_FREE，数据会随 TCP 正常发往对端。 */
        TEST_CHECK(bad == 0, "TX phase: %d/%d blocks (%lu bytes)",
                   blk_tx, chunks, (uint32_t)blk_tx * PHASE5_CHUNK_SIZE);
        {
            uint32_t ms = HAL_GetTick() - tx_t0;
            DBG_INFO("  TX throughput: %lu bytes in %lums (%lu KB/s)",
                     (uint32_t)blk_tx * PHASE5_CHUNK_SIZE, ms,
                     ms > 0 ? (uint32_t)(((uint32_t)blk_tx * PHASE5_CHUNK_SIZE) / ms) : 0u);
        }
    }

    /* ---- 步2B：连续接收 ----
     * 目标：PC 收到 256KB 后按相同 LCG 模式回放，MCU 连续排空接收缓冲并对
     *       字节流做滚动校验，测得纯 RX 吞吐。
     *
     * 滚动校验原理：
     *   期望流按块确定性生成，同一时刻只保留当前块的 exp[]（内存 O(CHUNK)）。
     *   exp_off 指向当前期望块内的偏移；回显流可能分包/粘包，故按"字节流"消费：
     *   每次读到 rlen 字节 → 切成若干段（take，每段不超过当前期望块剩余）逐字节
     *   与 exp[exp_off+k] 比对，同时累加 sum_rx；当前块消费完（exp_off==CHUNK）
     *   则回绕并生成下一块期望 —— 与 TCP 的分包/粘包边界完全解耦。
     *
     * 停滞检测（rx_stall）：
     *   记录"最后收到数据"的时刻，>PHASE5_RX_STALL_TIMEOUT_MS 无新数据即失败。
     *   用最后进度而非阶段起点，是为了兼容 PC 端回放开始的延迟与块间间隙。
     */
    if (!bad) {
        uint32_t rx_t0 = HAL_GetTick();       /* 接收阶段起点：用于计算 RX 吞吐 */
        exp_off = 0;                          /* 当前期望块内的消费偏移 */
        blk_rx = 0;                           /* 已完整校验的块数 */
        rx_total = 0;                         /* 已接收字节总数 */
        rx_stall = rx_t0;                     /* 最后收到数据的时刻（停滞检测基准） */
        progress_64k = -1;                    /* 重置进度打印基准（TX 阶段已推进过） */
        phase5_gen_block(exp, 0);             /* 预生成第 0 块期望数据 */
        while (!bad && rx_total < send_bytes) {
            /* ---- 排空接收缓冲：一次可能读出多包数据，全部消费完再进入下一步 ---- */
            for (;;) {
                uint16_t rlen = ch395f_get_recv_len(sock);   /* 查询当前可读字节数 */
                if (rlen == 0) {
                    break;                    /* 缓冲空，跳出排空循环 */
                }
                if (rlen > PHASE5_CHUNK_SIZE) {
                    rlen = PHASE5_CHUNK_SIZE; /* 单次读取上限 = 块大小（DP-04 4KB DMA 边界） */
                }
                ch395f_read_recv_buf(sock, rx, rlen);        /* 一次 SPI 事务读走 rlen 字节 */
                {
                    uint32_t off = 0;         /* 本次读取数据内的消费偏移 */
                    while (off < rlen) {
                        uint32_t take = PHASE5_CHUNK_SIZE - exp_off; /* 当前期望块剩余 */
                        uint32_t k;
                        if (take > (uint32_t)(rlen - off)) {
                            take = rlen - off; /* 截断：本次读取只消费到块边界为止 */
                        }
                        for (k = 0; k < take; k++) {
                            uint8_t b = rx[off + k];
                            if (b != exp[exp_off + k]) {
                                bad++;        /* 逐字节比对失败（定位精确偏移） */
                                DBG_ERROR("rx mismatch @%lu", rx_total + off + k);
                            }
                            sum_rx += b;      /* 同时累加接收侧总账 */
                        }
                        off += take;
                        exp_off += take;
                        rx_total += take;
                        if (exp_off == PHASE5_CHUNK_SIZE) {
                            exp_off = 0;      /* 当前块消费完：回绕并预生成下一块期望 */
                            blk_rx++;
                            if (blk_rx < chunks) {
                                phase5_gen_block(exp, blk_rx);
                            }
                        }
                    }
                }
                rx_stall = HAL_GetTick();     /* 有数据到达：推进停滞检测基准 */
            }
            if (bad) {
                break;
            }
            /* ---- 停滞保护：发送方已回放完或链路异常，迟迟无新数据 ---- */
            if (HAL_GetTick() - rx_stall >= PHASE5_RX_STALL_TIMEOUT_MS) {
                bad++;
                DBG_ERROR("RX stall @%lu/%lu", rx_total, send_bytes);
                break;
            }
            /* ---- 进度打印（每收到 64KB 打一行） ---- */
            if ((int)(rx_total / (64 * 1024)) != progress_64k) {
                progress_64k = (int)(rx_total / (64 * 1024));
                DBG_INFO("  RX %lu/%lu bytes (%d blocks)", rx_total, send_bytes, blk_rx);
            }
            HAL_Delay(2);                     /* 轮询间隔：让出总线/降低 SPI 事务频率 */
        }
        TEST_CHECK(bad == 0, "RX phase: %lu/%lu bytes (%d blocks)", rx_total, send_bytes, blk_rx);
        {
            uint32_t ms = HAL_GetTick() - rx_t0;
            DBG_INFO("  RX throughput: %lu bytes in %lums (%lu KB/s)",
                     rx_total, ms,
                     ms > 0 ? (uint32_t)(rx_total / ms) : 0u);
        }
    }
    TEST_CHECK(sum_tx == sum_rx, "checksum TX==RX (%08x/%08x)", sum_tx, sum_rx);

    /* 步3：关闭，PC 侧应统计到收满 256KB（校验一致）并回放完成 */
    ch395f_close_socket(sock);
    TEST_CHECK(1, "socket closed");
    TEST_REPORT("Phase 5");
}
#endif
/* ===================================================================
 * Phase 6 — NET 层 TCP Echo（基础收发）
 *
 * 测试目的：验证 BSD Socket API（net_socket）的单连接 TCP Echo 通路。
 *          Socket 0 复用监听和数据通道。PC 发任意数据，MCU 原样返回。
 *          这是 Phase 7/8/9/10 的基础。
 *
 * 入口条件：Socket 0 处于 ESTABLISHED（TCP 连接已建立）。
 * 通过：数据收发一致。
 * 失败：send/recv 数量不匹配。
 * =================================================================== */

#ifdef ENABLE_PHASE6_TESTS
static uint32_t s_phase6_count = 0;

static void phase6_run(int sockfd) {
    /*
     * 使用 NET_MSG_DONTWAIT 非阻塞接收 —— 没有数据时立即返回，
      * 不阻塞任务循环（Phase 7/8/9/10 也共享同一个循环）。
     * 收到数据后立即原样发送回显。
     */
    int n = net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT);
    if (n > 0) {
        int sent = net_send(sockfd, s_rx_buf, n, 0);
        if (sent != n) {
            DBG_ERROR("echo: send %d != recv %d", sent, n);
            return;
        }
        s_phase6_count++;
        if (s_phase6_count <= 10 || (s_phase6_count % 100 == 0)) {
            DBG_INFO("Phase6: echo #%lu (%d bytes)", s_phase6_count, n);
        }
    }
}
#endif

/* ===================================================================
 * Phase 7 — 大文件传输（超过 CH395F 硬件缓冲 4KB）
 *
 * 测试目的：验证超过 Socket 0 接收缓冲（4KB）的数据在 net_send/
 *          net_recv 层的正确处理。net_send_locked 自动将大数据
 *          拆分为 ≤4096B 的块，每块通过一次 DMA 事务写入 CH395F。
 *          接收端同样分多次调用 ch395f_read_recv_buf 读空硬件缓冲。
 *
 * 数据模式：递增字节（s_tx_buf[i] = i & 0xFF），接收端按 offset 逐字节比对。
 * 通过：全部 20480 字节逐字节匹配。
 * 失败：任意字节不一致或收发过程中断。
 * =================================================================== */

#ifdef ENABLE_PHASE7_TESTS
#define PHASE7_FILE_SIZE        20480

static void phase7_fill(uint8_t *buf, uint32_t size) {
    for (uint32_t i = 0; i < size; i++) {
        buf[i] = (uint8_t)(i & 0xFF);
    }
}

static int phase7_verify(uint8_t *buf, uint32_t size, uint32_t offset) {
    for (uint32_t i = 0; i < size; i++) {
        if (buf[i] != (uint8_t)((offset + i) & 0xFF)) {
            DBG_ERROR("Phase7: mismatch at %lu (expect 0x%02X, got 0x%02X)",
                      offset + i, (uint8_t)((offset + i) & 0xFF), buf[i]);
            return 0;
        }
    }
    return 1;
}

static void phase7_run(int sockfd) {
    DBG_INFO("Phase7: starting %d-byte file transfer", PHASE7_FILE_SIZE);

    phase7_fill(s_tx_buf, PHASE7_FILE_SIZE);

    /*
     * 发送阶段：分块发送，每块最多 4096B。
     * net_send 内部通过消息队列转发到 netTask，由 net_send_locked
     * 按 CH395F_SPI_DMA_MAX_PAYLOAD（4096）自动拆分 DMA 事务。
     * 等待 PC 端回显（echod）全部数据。
     */
    uint32_t sent = 0;
    while (sent < PHASE7_FILE_SIZE) {
        int chunk = (PHASE7_FILE_SIZE - sent) > 4096 ? 4096 : (PHASE7_FILE_SIZE - sent);
        int n = net_send(sockfd, s_tx_buf + sent, chunk, 0);
        if (n <= 0) {
            DBG_ERROR("Phase7: send fail at %lu (ret=%d)", sent, n);
            return;
        }
        sent += (uint32_t)n;
        DBG_INFO("Phase7: sent %lu/%d", sent, PHASE7_FILE_SIZE);
    }

    /*
     * 接收阶段：分块接收，逐块验证。
     * PC 回显数据需与发送数据逐字节一致（递增模式）。
     * 验证失败立即返回，不继续接收剩余数据。
     */
    uint32_t recvd = 0;
    while (recvd < PHASE7_FILE_SIZE) {
        int n = net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, 0);
        if (n <= 0) {
            DBG_ERROR("Phase7: recv fail at %lu (ret=%d)", recvd, n);
            return;
        }
        int ok = phase7_verify(s_rx_buf, (uint32_t)n, recvd);
        TEST_CHECK(ok, "Phase7: verify at %lu (%d bytes)", recvd, n);
        if (!ok) return;
        recvd += (uint32_t)n;
    }

    TEST_CHECK(1, "Phase7: %d-byte file transfer complete", PHASE7_FILE_SIZE);
}
#endif

/* ===================================================================
 * Phase 8 — Select/Poll I/O 多路复用
 *
 * 测试目的：验证 net_select() API 在单 Socket 模式下正确工作：
 *          1. Socket 0 有数据到达时 select 返回正数
 *          2. NET_FD_ISSET 正确指示就绪的 Socket
 *          3. 超时（50ms 无数据）时返回 0
 *
 * 入口条件：Socket 0 处于 ESTABLISHED。
 * 通过：select 检测到数据并成功回显。
 * 失败：select 未检测到数据或回显失败。
 * =================================================================== */

#ifdef ENABLE_PHASE8_TESTS
#include "net_select.h"
static uint32_t s_phase8_count = 0;

static void phase8_run(int sockfd) {
    net_fd_set readfds;
    net_timeval tv;

    /*
     * 配置 select：只监测 Socket 0 的可读状态，超时 50ms。
     * NET_FD_ZERO 清空集合，NET_FD_SET 添加 Socket 0。
     * 50ms 超时确保不阻塞任务循环太久。
     */
    NET_FD_ZERO(&readfds);
    NET_FD_SET(sockfd, &readfds);
    tv.tv_sec = 0;
    tv.tv_usec = 50000;

    int nfds = net_select(sockfd + 1, &readfds, NULL, NULL, &tv);
    if (nfds <= 0) return;

    /* Socket 0 可读：非阻塞接收并回显 */
    if (NET_FD_ISSET(sockfd, &readfds)) {
        int n = net_recv(sockfd, s_rx_buf, TEST_BUF_SIZE, NET_MSG_DONTWAIT);
        if (n > 0) {
            int sent = net_send(sockfd, s_rx_buf, n, 0);
            if (sent == n) {
                s_phase8_count++;
                TEST_CHECK(1, "Phase8: select echo #%lu (%d bytes)", s_phase8_count, n);
            }
        }
    }
}
#endif

/* ===================================================================
 * Phase 9 — 边界条件测试
 *
 * 测试目的：验证以下 DMA 和协议栈边界情况：
 *          1. TC-NET-901: 4095B（SPI 最大载荷 -1，单次 DMA 容纳）
 *          2. TC-NET-902: 4096B（= SPI 最大载荷，填满单次 DMA）
 *          3. TC-NET-903: 4097B（超过 SPI 最大载荷，拆 2 次 DMA）
 *          4. TC-NET-904: 零长度发送（返回 -1）
 *          5. TC-NET-905: 20 × 100B 连续快速发送
 *
 * 各用例通过状态机（s_phase9_stage）串行执行，每次 phase9_run 推进一个
 * 步骤（发送→等待→接收→验证→下一阶段）。发送数据为递增模式字节
 * （buf[i] = seed + i），接收端按相同模式验证。
 *
 * 入口条件：Socket 0 处于 ESTABLISHED。
 * 通过：各用例自身判定。
 * =================================================================== */

#ifdef ENABLE_PHASE9_TESTS
static int s_phase9_stage = 0;
static uint32_t s_phase9_recv = 0;

static void phase9_fill(uint8_t *buf, uint32_t size, uint8_t seed) {
    for (uint32_t i = 0; i < size; i++) {
        buf[i] = (uint8_t)(seed + i);
    }
}

static int phase9_verify(uint8_t *buf, uint32_t size, uint8_t seed) {
    for (uint32_t i = 0; i < size; i++) {
        if (buf[i] != (uint8_t)(seed + i)) return 0;
    }
    return 1;
}

static void phase9_run(int sockfd) {
    /* 阶段 0 → 1：发送 4095B */
    if (s_phase9_stage == 0) {
        DBG_INFO("Phase9: 4095B (DMA-1)");
        phase9_fill(s_tx_buf, 4095, 0x10);
        int sent = net_send(sockfd, s_tx_buf, 4095, 0);
        TEST_CHECK(sent == 4095, "Phase9: send 4095B (sent=%d)", sent);
        s_phase9_recv = 0;
        s_phase9_stage = 1;
        return;
    }
    /* 阶段 1：接收并验证 4095B 回显 */
    if (s_phase9_stage == 1) {
        int n = net_recv(sockfd, s_rx_buf + s_phase9_recv, (int)(4095 - s_phase9_recv), 0);
        if (n > 0) {
            s_phase9_recv += (uint32_t)n;
            if (s_phase9_recv >= 4095) {
                TEST_CHECK(phase9_verify(s_rx_buf, 4095, 0x10), "Phase9: verify 4095B");
                s_phase9_stage = 2;
            }
        }
        return;
    }
    /* 阶段 2 → 3：发送 4096B（DMA 边界精确值） */
    if (s_phase9_stage == 2) {
        DBG_INFO("Phase9: 4096B (DMA exact)");
        phase9_fill(s_tx_buf, 4096, 0x20);
        int sent = net_send(sockfd, s_tx_buf, 4096, 0);
        TEST_CHECK(sent == 4096, "Phase9: send 4096B (sent=%d)", sent);
        s_phase9_recv = 0;
        s_phase9_stage = 3;
        return;
    }
    /* 阶段 3：接收并验证 4096B 回显 */
    if (s_phase9_stage == 3) {
        int n = net_recv(sockfd, s_rx_buf + s_phase9_recv, (int)(4096 - s_phase9_recv), 0);
        if (n > 0) {
            s_phase9_recv += (uint32_t)n;
            if (s_phase9_recv >= 4096) {
                TEST_CHECK(phase9_verify(s_rx_buf, 4096, 0x20), "Phase9: verify 4096B");
                s_phase9_stage = 4;
            }
        }
        return;
    }
    /* 阶段 4 → 5：发送 4097B（超过 DMA 上限，自动拆 4096+1） */
    if (s_phase9_stage == 4) {
        DBG_INFO("Phase9: 4097B (DMA+1, 2 SPI xacts)");
        phase9_fill(s_tx_buf, 4097, 0x30);
        int sent = net_send(sockfd, s_tx_buf, 4097, 0);
        TEST_CHECK(sent == 4097, "Phase9: send 4097B (sent=%d)", sent);
        s_phase9_recv = 0;
        s_phase9_stage = 5;
        return;
    }
    /* 阶段 5：接收并验证 4097B 回显 */
    if (s_phase9_stage == 5) {
        int n = net_recv(sockfd, s_rx_buf + s_phase9_recv, (int)(4097 - s_phase9_recv), 0);
        if (n > 0) {
            s_phase9_recv += (uint32_t)n;
            if (s_phase9_recv >= 4097) {
                TEST_CHECK(phase9_verify(s_rx_buf, 4097, 0x30), "Phase9: verify 4097B");
                s_phase9_stage = 6;
            }
        }
        return;
    }
    /* 阶段 6：零长度发送 —— 期望 net_send 返回 -1（NET_ERR_INVAL） */
    if (s_phase9_stage == 6) {
        DBG_INFO("Phase9: zero-length send");
        int sent = net_send(sockfd, s_tx_buf, 0, 0);
        TEST_CHECK(sent < 0, "Phase9: send 0B returns %d (expect <0)", sent);
        s_phase9_stage = 7;
        return;
    }
    /* 阶段 7：20 × 100B 快速发送 */
    if (s_phase9_stage == 7) {
        DBG_INFO("Phase9: 20x rapid sends (100B each)");
        phase9_fill(s_tx_buf, 100, 0x40);
        int all_ok = 1;
        for (int i = 0; i < 20; i++) {
            if (net_send(sockfd, s_tx_buf, 100, 0) != 100) {
                TEST_CHECK(0, "Phase9: rapid #%d fail", i + 1);
                all_ok = 0; break;
            }
            osDelay(1);
        }
        if (all_ok) {
            TEST_CHECK(1, "Phase9: 20x rapid sends OK");
            s_phase9_recv = 0;
            s_phase9_stage = 8;
        } else {
            s_phase9_stage = 0;
        }
        return;
    }
    /* 阶段 8：接收 2000B 回显总和并验证 */
    if (s_phase9_stage == 8) {
        int n = net_recv(sockfd, s_rx_buf + s_phase9_recv, (int)(2000 - s_phase9_recv), 0);
        if (n > 0) {
            s_phase9_recv += (uint32_t)n;
            if (s_phase9_recv >= 2000) {
                TEST_CHECK(1, "Phase9: all echo received");
                TEST_REPORT("Phase 9");
                s_phase9_stage = 0;
            }
        }
        return;
    }
}
#endif

/* ===================================================================
 * Phase 10 — TCP KeepAlive 超时断开
 *
 * 测试目的：验证 CH395F 的 TCP KeepAlive 机制：
 *          1. 连接空闲超过 idle 阈值（60s）后开始发送探测包
 *          2. 探测无响应（默认 3 次 × 5s = 15s）后触发 TIMEOUT
 *          3. Socket 自动关闭并回到 LISTEN
 *
 * 入口条件：Socket 0 处于 ESTABLISHED，PC 连接后静置（不发数据）。
 * 通过：KeepAlive 断开时间 ≥ 60s（从连接建立到 recv 返回 0 的间隔）。
 * 失败：不到 60s 就断开（可能是其他原因导致）或 75s 后仍未断开。
 * =================================================================== */

#ifdef ENABLE_PHASE10_TESTS
static int      s_phase10_armed = 0;
static uint32_t s_phase10_start = 0;

static void phase10_run(int sockfd) {
    /*
     * 第一次调用：记录开始时间并 arm。
     * 后续调用：非阻塞 recv，若返回 0 表示连接已断开（KeepAlive 触发）。
     * 计算耗时与 60s idle 阈值比较。
     */
    if (!s_phase10_armed) {
        s_phase10_armed = 1;
        s_phase10_start = HAL_GetTick();
        DBG_INFO("Phase10: idle wait for KeepAlive timeout (60s)");
        return;
    }

    int n = net_recv(sockfd, s_rx_buf, 64, NET_MSG_DONTWAIT);
    if (n == 0) {
        uint32_t elapsed = HAL_GetTick() - s_phase10_start;
        TEST_CHECK(elapsed >= 60000,
                   "Phase10: KeepAlive triggered after %lums (expect >=60000ms)", elapsed);
        TEST_REPORT("Phase 10");
        s_phase10_armed = 0;
    }
}
#endif

/* ===================================================================
 * 任务入口：StartCh395fTestTask
 *
 * 执行顺序（按构建模式二选一，见 docs/CH395F_Test_Guide.md「构建模式与执行顺序」）：
 *   硬件层模式（阶段 1~5）：netTask 不创建，本任务调用
 *     net_stack_bring_up() 完成协议栈初始化后独占 CH395F 阻塞执行。
 *   NET 层模式（阶段 6~10）：
 *   1. 等待 g_net_ready（netTask 初始化完成标志）
 *   2. 创建 TCP 监听 Socket（单连接模式，端口 8080）
 *   3. 进入事件驱动循环，等待 PC 连接并执行 NET 层测试
 *
 * 单连接模式说明：
 *   Socket 0 复用监听和数据通道。accept 返回的 fd 与监听 fd 相同。
 *   连接断开后需重新 listen。连接恢复检测在循环末尾通过
 *   net_get_sock() 检查状态并自动处理。
 * =================================================================== */

void StartCh395fTestTask(void *argument)
{
    (void)argument;

#if defined(ENABLE_NET_LAYER_TESTS)
    int sockfd = -1;
    int phase_active = 0;
    uint32_t accept_retry = 0;
    struct net_sockaddr_in client_addr;
    int addrlen = sizeof(client_addr);
#endif

    DBG_INFO("Test task started");

#if defined(ENABLE_HW_LAYER_TESTS)
    /* 硬件层模式（阶段 1~5）：netTask 未创建，由本任务完成一次性协议栈
     * 初始化并独占 CH395F —— 无 net_poll() 并发读取中断，无 SPI 并发事务 */
    if (net_stack_bring_up() != 0) {
        DBG_ERROR("net stack bring-up FAIL");
        goto task_end;
    }
#else
    /* NET 层模式（阶段 6~10）：等待 netTask 完成 net_init() 及 CH395F 初始化 */
    while (!g_net_ready) {
        osDelay(100);
    }
    DBG_INFO("netTask ready");
#endif

    /* ==================== 阻塞阶段 ====================
     * Phase 1~5（硬件层）在 TCP 监听之前执行，不依赖外部连接。
     * 各 Phase 有各自的测试逻辑，完成后继续下一 Phase。
     */
#ifdef ENABLE_PHASE1_TESTS
    phase1_run();
#endif
#ifdef ENABLE_PHASE2_TESTS
    phase2_run();
#endif
#ifdef ENABLE_PHASE3_TESTS
    phase3_run();
#endif
#ifdef ENABLE_PHASE4_TESTS
    phase4_run();
#endif
#ifdef ENABLE_PHASE5_TESTS
    phase5_run();
#endif

#ifdef ENABLE_NET_LAYER_TESTS
    /* ==================== 创建 TCP 监听（NET 层模式） ==================== */

    DBG_INFO("Creating test socket...");
    sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
    if (sockfd < 0) {
        DBG_ERROR("Test task: net_socket failed");
        goto task_end;
    }
    DBG_INFO("Test task: socket %d created", sockfd);

    {
        struct net_sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = NET_AF_INET;
        addr.sin_port   = net_htons(8080);
        if (net_bind(sockfd, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
            DBG_ERROR("Test task: net_bind failed");
            goto task_end;
        }
        DBG_INFO("Test task: bound to port 8080");
    }

    if (net_listen(sockfd) < 0) {
        DBG_ERROR("Test task: net_listen failed");
        goto task_end;
    }
    DBG_INFO("Test task: listening on 8080 (single-socket mode)");

    /* ==================== 事件驱动阶段 ====================
     * Phase 6~10（NET 层）在 TCP 监听循环中运行，需要 PC 客户端连接。
     * 没有连接时循环等待；连接建立后执行对应 Phase。
     * 断开后自动重新 listen。
     */
    for (;;) {
        if (!phase_active) {
            int acc = net_accept(sockfd, (struct net_sockaddr *)&client_addr, &addrlen);
            if (acc < 0) {
                if (accept_retry % 100 == 0) {
                    DBG_INFO("Test task: waiting for connection...");
                }
                accept_retry++;
                osDelay(100);
                continue;
            }
            accept_retry = 0;
            phase_active = 1;
            DBG_INFO("Test task: client accepted (fd=%d)", acc);

            /* 连接建立，重置各 Phase 内部状态 */
#ifdef ENABLE_PHASE9_TESTS
            s_phase9_stage = 0;
#endif
#ifdef ENABLE_PHASE10_TESTS
            s_phase10_armed = 0;
#endif
        }

        /* 每 10ms 依次执行各已启用 Phase */
#ifdef ENABLE_PHASE6_TESTS
        phase6_run(sockfd);
#endif
#ifdef ENABLE_PHASE7_TESTS
        phase7_run(sockfd);
        phase_active = 0;   /* Phase 7 一次性完成，发完即止 */
#endif
#ifdef ENABLE_PHASE8_TESTS
        phase8_run(sockfd);
#endif
#ifdef ENABLE_PHASE9_TESTS
        phase9_run(sockfd);
#endif
#ifdef ENABLE_PHASE10_TESTS
        phase10_run(sockfd);
#endif

        /*
         * 连接状态检查：通过 net_get_sock 获取 Socket 控制块，
         * 如果连接已断开（state != ESTABLISHED），关闭并重新 listen。
         * 这是单连接模式的关键 —— Socket 0 必须始终处于 LISTEN 或 ESTABLISHED。
         */
        {
            net_sock_t *sk = net_get_sock(sockfd);
            if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED) {
                DBG_INFO("Test task: connection closed, re-listen...");
                net_close(sockfd);
                osDelay(500);
                net_listen(sockfd);
                phase_active = 0;
            }
        }

        osDelay(10);
    }
#endif /* ENABLE_NET_LAYER_TESTS */

task_end:
    for (;;) {
        osDelay(1000);
    }
}
