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

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

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

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

374 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:假 CH395F 芯片(NET 域主机侧单测共享件)
* 模块功能:给 NET 域的主机侧单测一个**可脚本化的假芯片**。真实 ch395f.h 拉 HAL 与
* SPI 寄存器定义,PC 上编不动;而 net_listener / net_pending / net_events
* 的行为恰恰完全由"芯片此刻报什么状态"决定,必须有假件才能测。
*
* ⚠ 仅供 test/host/** 使用,不参与 MCU 构建。
*
* 为什么值得共享一份(而不是每模块自带桩):三份假芯片各自漂移时,"假件与被测代码
* 同时错"这类问题再也不会报出来。各模块的 stub/ch395f.h 只做一件事——include 本文件,
* 于是签名与常量只有一份定义。
*
* 常量与类型必须与 Drivers/BSP/CH395F/ch395f.h 逐字一致(2026-10-23 从真实头抄录):
* CH395F_ERR_SUCCESS 0x00 / CH395F_PROTO_TYPE_TCP 0x03 / CH395F_SOCKET_CLOSED 0x00
* CH395F_TCP_CLOSED 0x00 / CH395F_TCP_LISTEN 0x01;ch395f_sock_state_t 为
* { uint8_t open; uint8_t tcp_state; } 两字节。
*
* 必须复刻的两条芯片怪癖(不复刻就等于把被测代码的坑假件掉):
* 1. ch395f_get_sock_int_status() 是**整体读即清**、芯片没有按位清除手段。本假件
* 返回该 socket 的挂起位图并同步清零。AGENTS 模块约定里 B3 的实证坑("顺手
* 全量清一遍"把别的在用 Socket 的门焊死)靠这条才照得出来。
* 2. *_cached 系列返回的是**缓存值**而不是现场值:open 状态与 TCP 状态分开、且
* "监听还活着"的判据是 (open != CLOSED && tcp_state == LISTEN)。测试据此能构造
* "缓存陈旧"的形态。
*
* 脚本化模型(沿用本仓既有假件风格:nand_port 的真头+假 diskio、lftpd_io 的可注入假堆):
* - 每个读函数一条按调用序号取的返回队列,队列耗尽后回落到**最后一个值**(便于
* 写"一直可读""一直关不掉"这类持续状态);
* - 每个写/控制函数一条调用记录(次数 + 最近一次入参),另给逐函数调用计数,
* 让"这一轮根本不该碰芯片"成为可断言的事;
* - g_chip.log[] 记录**调用顺序**(短名),用于断言"先清接收、再关、再读即清"这类
* 时序契约;
* - ch395f_fake_reset() 一次全清。
*
* 创建日期:2026-10-23
*/
#ifndef CH395F_FAKE_H
#define CH395F_FAKE_H
#include <stdint.h>
/* —— 与真实 ch395f.h 逐字一致的常量(值抄自真实头,勿"顺手改")—— */
#define CH395F_ERR_SUCCESS 0x00U
#define CH395F_PROTO_TYPE_TCP 0x03U
#define CH395F_SOCKET_CLOSED 0x00U
#define CH395F_SOCKET_OPEN 0x05U /* 真头 ch395f.h:220 = 0x05U(05H SOCKET_OPEN,docs/芯片手册/CH395F.md:527)*/
#define CH395F_TCP_CLOSED 0x00U
#define CH395F_TCP_LISTEN 0x01U
#define CH395F_TCP_ESTABLISHED 0x04U /* 真头 ch395f.h:229 = 0x04U(04H TCP_ESTABLISHED,docs/芯片手册/CH395F.md:539)*/
/* 发送/接收缓冲尺寸宏链:**与真实 ch395f.h 同构转抄**(2026-10-23 逐条核对)。
* net_pending.h 里的 NET_SEND_CHUNK_MAX 由这两个宏推导,是"一次最多往芯片写多少"
* 的真实上界,假件不能自己发明数字(否则分块测试就是在测假件的常量):
* RAM_BLOCK_SIZE 512 / SEND_BLOCKS 2 → SEND_BUF 1024
* RECV_BLOCKS 4 → RECV_BUF 2048 → SPI_DMA_BUF 2052 → SPI_DMA_MAX_PAYLOAD 2048
* ⇒ NET_SEND_CHUNK_MAX = min(1024, 2048) = 1024 */
#define CH395F_RAM_BLOCK_SIZE 512U
#define CH395F_SEND_BLOCKS 2U
#define CH395F_SEND_BUF_SIZE_BYTES (CH395F_SEND_BLOCKS * CH395F_RAM_BLOCK_SIZE)
#define CH395F_RECV_BLOCKS 4U
#define CH395F_RECV_BUF_SIZE_BYTES (CH395F_RECV_BLOCKS * CH395F_RAM_BLOCK_SIZE)
#define CH395F_SPI_DMA_BUF_SIZE (CH395F_RECV_BUF_SIZE_BYTES + 4U)
#define CH395F_SPI_DMA_MAX_PAYLOAD (CH395F_SPI_DMA_BUF_SIZE - 4U)
#define CH395F_FAKE_SOCK_MAX 8 /* 与 NET_MAX_SOCKETS 同量级,测试自用 */
#define CH395F_FAKE_QUEUE_LEN 24
#define CH395F_FAKE_LOG_LEN 96
#define CH395F_FAKE_GLOB_QUEUE 24
/* 写内容捕获容量(测试自用,真实头无此宏 → 不参与假芯片常量对齐门):
* 256 够覆盖"一个小数据报全文"与"分块发送首块的前 256 字节"两类断言。 */
#define CH395F_FAKE_WRITE_CAP 256
/* ===== 扩展区(2026-10-24,net_socket 主机单测批次;**纯新增**,上面既有宏/字段/函数
* 一字未动,net_listener / net_events / net_pending 三套的语义不受影响)=====
*
* 追加的是 net_socket.c 这一层会调、而前三个模块不调的芯片原语(初始化下发类 +
* 全局中断 + sock_reset 复位机 + cmd_done 判据)。签名/常量同样**逐字抄自真实头**
* Drivers/BSP/CH395F/ch395f.h,改真头即编译期/断言期暴露漂移。
*
* 必须复刻的第三条芯片怪癖(前两套测不到、net_socket.c 却是主路):
* 3. GET_GLOB_INT_STATUS_ALL(0x19,2 字节)**整体读即清**,且它是全局寄存器
* (含全部 Socket 事件位)。AGENTS 模块约定 B3:唯一读者只能是 net_poll。
* 假件按调用序号返回预置值,并单独计数,让"别处偷偷读全局"成为可断言的事。
* 4. sock_reset 复位机(E4 定案四步:读空 RX → CLOSE → 排空事件(quiescent) →
* 等 byte0=00H,含 LISTEN 态二段 CLOSE 补发)。这里做**契约等价**实现:
* 阶段推进与最短等待(GRACEFUL 100ms / QUARANTINE 400ms)照抄驱动,
* 但不复制驱动的超时数值细节(那属于驱动自己的单测面)。
*/
#define CH395F_ERR_BUSY 0x10U /* 真头 ch395f.h:170 */
#define CH395F_ERR_MEM 0x11U /* 真头 ch395f.h:171 */
#define CH395F_ERR_TIMEOUT 0x13U /* 真头 ch395f.h:173 */
#define CH395F_ERR_VAL 0x19U /* 真头 ch395f.h:179 */
#define CH395F_ERR_USE 0x1BU /* 真头 ch395f.h:181(open 已被占用)*/
#define CH395F_PROTO_TYPE_UDP 0x02U /* 真头 ch395f.h:213 */
#define CH395F_FUN_PARA_SOCKET_CLOSE (1UL << 3) /* 真头 ch395f.h:205 */
#define CH395F_TCP_MSS 1024U /* 真头 ch395f.h:124 */
#define CH395F_PHY_DISCONN 0x01U /* 真头 ch395f.h:191 */
#define CH395F_GINT_STAT_PHY_CHANGE 0x04U /* 真头 ch395f.h:245 */
#define CH395F_SOCK_STATE_NONE 0xFFU /* 真头 ch395f.h:762 */
#define CH395F_SINT_STAT_SENBUF_FREE 0x01U /* 真头 ch395f.h:263 */
#define CH395F_SINT_STAT_SEND_OK 0x02U /* 真头 ch395f.h:264 */
#define CH395F_SINT_STAT_RECV_OK 0x04U /* 真头 ch395f.h:265 */
#define CH395F_SINT_STAT_CONNECT 0x08U /* 真头 ch395f.h:266 */
#define CH395F_SINT_STAT_DISCONNECT 0x10U /* 真头 ch395f.h:267 */
#define CH395F_SINT_STAT_SOCK_TIMEOUT 0x40U /* 真头 ch395f.h:269 */
typedef enum {
CH395F_STATUS_OK = 0, /* 真头 ch395f.h:275 */
CH395F_STATUS_ERROR = -1,
CH395F_STATUS_TIMEOUT = -2,
CH395F_STATUS_NOT_DETECTED = -3
} ch395f_status_t;
typedef enum {
CH395F_RESET_GRACEFUL = 0, /* 真头 :803:最短等待 100ms */
CH395F_RESET_QUARANTINE /* 真头 :804:最短等待 400ms */
} ch395f_reset_mode_t;
typedef enum {
CH395F_DONE_OPEN = 0, /* 真头 :826:byte0 变 05H */
CH395F_DONE_LISTEN, /* 真头 :827:byte0=05H 且 TCP=01H */
CH395F_DONE_CLOSE /* 真头 :828:byte0=00H */
} ch395f_done_what_t;
/* 复位机阶段(与驱动同名状态的**契约等价**简化,不代表驱动内部实现) */
#define CH395F_FAKE_RST_IDLE 0
#define CH395F_FAKE_RST_DRAINED 1 /* 已读空 RX 并发过 CLOSE */
#define CH395F_FAKE_RST_MINWAIT 2 /* 等最短沉降窗(100/400ms) */
#define CH395F_FAKE_RST_QUIET 3 /* 排空本 Socket 事件(连续 2 轮无位) */
#define CH395F_FAKE_RST_WAITCLS 4 /* 等 byte0=00H(含二段 CLOSE 补发) */
#define CH395F_FAKE_RST_QUIESCENT 2U
typedef struct {
uint8_t open;
uint8_t tcp_state;
} ch395f_sock_state_t;
/* —— 可预置的芯片侧世界 —— */
typedef struct {
uint8_t cached_open[CH395F_FAKE_SOCK_MAX]; /* sock_state_cached().open */
uint8_t cached_tcp[CH395F_FAKE_SOCK_MAX]; /* sock_state_cached().tcp_state */
uint8_t status_byte0[CH395F_FAKE_SOCK_MAX]; /* get_socket_status 的 sock 状态 */
uint8_t status_byte1[CH395F_FAKE_SOCK_MAX]; /* get_socket_status 的 TCP 状态 */
uint8_t int_status[CH395F_FAKE_SOCK_MAX]; /* 读即清的挂起位图 */
uint16_t recv_len_q[CH395F_FAKE_SOCK_MAX][CH395F_FAKE_QUEUE_LEN];
int recv_len_n[CH395F_FAKE_SOCK_MAX];
int recv_len_i[CH395F_FAKE_SOCK_MAX];
uint8_t remote[CH395F_FAKE_SOCK_MAX][6]; /* get_remot_ipp 的 6 字节:ip[4]+port(小端) */
uint16_t link_change; /* link_change_count(链路变更计数) */
uint8_t link_up; /* link_up_cached */
uint8_t open_rc[CH395F_FAKE_SOCK_MAX]; /* open_socket 返回码 */
uint8_t listen_rc[CH395F_FAKE_SOCK_MAX]; /* tcp_listen 返回码 */
} ch395f_fake_world_t;
typedef struct {
/* 调用计数("不该碰芯片"的断言面) */
int open_calls;
int close_calls;
int listen_calls;
int int_status_calls;
int get_status_calls;
int read_calls;
int write_calls;
int set_proto_calls;
int set_port_calls;
int cached_calls;
/* 最近一次入参 */
uint8_t last_sock;
uint16_t last_read_len;
uint16_t last_port;
uint8_t last_proto;
uint8_t last_keepalive;
int keepalive_calls;
/* 调用顺序日志(短名首字母):
* 'c'=close 'i'=int_status(读即清) 's'=get_socket_status 'r'=read_recv_buf
* 'R'=get_recv_len 'o'=open_socket 'l'=tcp_listen 'p'=set_sour_port
* 'P'=set_proto_type 'w'=write_send_buf
* 扩展区新增(net_socket 批次):'G'=读全局中断 'v'=sock_events(读即清)
* 'X'=sock_reset_begin 'x'=sock_reset_poll 'z'=cmd_done 'T'=tcp_connect
* 'I'=set_ip_addr 'N'=set_gwip 'K'=set_mask 'M'=set_mac 'D'=set_dhcp
* 'F'=set_fun_para 'Q'=set_tcp_mss 'b'=set_send_buf 'u'=set_recv_buf
* 'e'=set_des_ip 'q'=set_des_port 'g'=get_phy_status 'E'=check_exist
* 'Z'=reset 'n'=init */
char log[CH395F_FAKE_LOG_LEN];
int log_n;
} ch395f_fake_obs_t;
extern ch395f_fake_world_t g_fake;
extern ch395f_fake_obs_t g_chip;
void ch395f_fake_reset(void); /* 全清并把所有 socket 设为"已关闭" */
void ch395f_fake_push_recv_len(uint8_t sock, uint16_t v);
void ch395f_fake_set_listening(uint8_t sock);
void ch395f_fake_set_peer(uint8_t sock, uint8_t b0, uint8_t b1, uint8_t b2,
uint8_t b3, uint16_t peer_port); /* 预置对端六元组 */ /* 缓存:open!=CLOSED 且 tcp=LISTEN */
/* —— 假芯片实现(签名与真实 ch395f.h 一字不改)—— */
ch395f_sock_state_t ch395f_sock_state_cached(uint8_t sn);
ch395f_sock_state_t ch395f_get_sock_state(uint8_t sn);
void ch395f_get_socket_status(uint8_t sock, uint8_t *status);
uint8_t ch395f_get_sock_int_status(uint8_t sock);
uint16_t ch395f_get_recv_len(uint8_t sock);
void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len);
void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len);
uint8_t ch395f_open_socket(uint8_t sock);
uint8_t ch395f_close_socket(uint8_t sock);
uint8_t ch395f_tcp_listen(uint8_t sock);
void ch395f_set_proto_type(uint8_t sock, uint8_t proto);
void ch395f_set_sour_port(uint8_t sock, uint16_t port);
void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf);
void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable);
uint8_t ch395f_link_up_cached(void);
uint16_t ch395f_link_change_count(void);
/* ===== 扩展区第二部分:新增状态世界 + 新增假芯片原型 =====
* ⚠ 刻意**不往既有 ch395f_fake_world_t / ch395f_fake_obs_t 里加字段**,而是另开一份
* ch395f_fake_ext_t / ch395f_fake_ext_obs_t:既有三套单测的复位与断言面一字不动,
* 新增状态由 ch395f_fake_reset() 调 ch395f_fake_ext_reset() 一并清零(场景间不残留)。
*/
typedef struct {
/* —— 初始化/参数下发面(net_init 主路)—— */
int exist_rc; /* ch395f_check_exist 返回(ch395f_status_t)*/
int reset_rc; /* ch395f_reset 返回 */
int init_rc; /* ch395f_init 返回 */
uint8_t version; /* ch395f_get_version(版本门控 0x46 的用点位)*/
uint8_t dhcp_enable; /* ch395f_set_dhcp */
uint8_t ping_enable; /* ch395f_ping_enable */
uint8_t ip[4]; /* ch395f_set_ip_addr */
uint8_t gw[4]; /* ch395f_set_gwip_addr */
uint8_t mask[4]; /* ch395f_set_mask_addr */
uint8_t mac[6]; /* ch395f_set_mac_addr */
uint32_t fun_para; /* ch395f_set_fun_para 最近一次实发值 */
uint16_t mss; /* ch395f_set_tcp_mss */
uint8_t retrans_cnt;
uint16_t retrans_period;
uint32_t ka_idle;
uint32_t ka_intvl;
uint8_t ka_cnt;
uint8_t send_blk_start[CH395F_FAKE_SOCK_MAX]; /* ch395f_set_send_buf */
uint8_t send_blk_cnt[CH395F_FAKE_SOCK_MAX];
uint8_t recv_blk_start[CH395F_FAKE_SOCK_MAX]; /* ch395f_set_recv_buf */
uint8_t recv_blk_cnt[CH395F_FAKE_SOCK_MAX];
uint8_t des_ip[CH395F_FAKE_SOCK_MAX][4]; /* ch395f_set_des_ip */
uint16_t des_port[CH395F_FAKE_SOCK_MAX]; /* ch395f_set_des_port */
uint8_t connect_rc[CH395F_FAKE_SOCK_MAX]; /* ch395f_tcp_connect 返回 */
/* —— 全局中断(0x19,整体读即清;唯一读者应为 net_poll)—— */
uint16_t glob_q[CH395F_FAKE_GLOB_QUEUE];
int glob_n;
int glob_i;
uint8_t phy_status; /* ch395f_get_phy_status(喂 glob_event_feed)*/
/* —— sock_reset 复位机(契约等价,见文件头注释 4)—— */
uint8_t rst_state[CH395F_FAKE_SOCK_MAX];
uint8_t rst_mode[CH395F_FAKE_SOCK_MAX];
uint32_t rst_t0[CH395F_FAKE_SOCK_MAX];
uint8_t rst_quiet[CH395F_FAKE_SOCK_MAX]; /* 连续无事件轮数 */
/* 1=复位机**不**替芯片把 cached_open 落回 CLOSED(造"永远关不掉"的芯片形态,
* 用来量上层的复位超时收口)。默认 0 → 与既有行为一致。 */
uint8_t rst_keep_open[CH395F_FAKE_SOCK_MAX];
/* GET_RECV_BUF 的**脚本化内容**(默认长度 0 → 与既有"全零交付"逐字相同)。
* UDP 报文头(长度/源端口/源 IP)只有内容可脚本化才能被断言:net_recvfrom 要
* 从芯片头里拆出对端地址,全零内容下"拆对了"与"拆反了"看起来一模一样。 */
uint8_t recv_bytes[128];
uint16_t recv_bytes_n;
/* SINT 的**电平型**来源(可脚本化):读即清之后立刻把这个掩码放回 int_status。
* 默认 0 → 与既有假件行为逐字相同(读一次就没了)。真实芯片的 RECV_OK /
* SENBUF_FREE 本就是"条件位":只要条件还在(RX 仍非空 / 发送缓冲仍空),下一轮
* 就还会再报一次。net_socket.c 的多块发送要的就是这个稳态,没有它就只能
* 造"写完一块就再也等不到空闲"的假死形态。 */
uint8_t sint_sticky[CH395F_FAKE_SOCK_MAX];
/* —— 2026-11-03 UDP 队列化改造追加(默认全 0 → 既有行为逐字不变)—— */
/* GET_RECV_BUF 的**流式读**开关:UDP 一个数据报 = 8 字节信息头 + 载荷,
* 生产代码是**两次**读(先剥头再读载荷),而 set_recv_bytes 每次都从预置
* 内容第 0 字节交付——那样"剥掉头"与"把包头当载荷"在断言里长得一样。
* 本开关让交付内容带游标:第二次读拿到的是第 8 字节往后的载荷。 */
uint8_t recv_stream_en;
uint16_t recv_pos; /* 游标:已交付字节数(截断排空要断言它) */
/* WRITE_SEND_BUF 的**内容捕获**开关:假芯片原本忽略 p_data(既有用例只数次数),
* 但 UDP 发送要钉"写进芯片的是不是调用方那份字节、分块边界对不对"。
* 默认 0 → 既有"内容不可断言"的行为一字不变。 */
uint8_t write_cap_en;
uint8_t write_cap[CH395F_FAKE_WRITE_CAP]; /* 按写入顺序拼接的字节(够 2 块 1KB 的前 256B) */
uint16_t write_cap_n; /* 已捕获字节数(超出容量后只计数不存) */
uint32_t write_bytes; /* 芯片收到的总字节数(含未捕获部分) */
} ch395f_fake_ext_t;
typedef struct {
int exist_calls;
int reset_calls;
int init_calls;
int set_ip_calls;
int set_gw_calls;
int set_mask_calls;
int set_mac_calls;
int set_dhcp_calls;
int ping_calls;
int set_fun_para_calls;
int set_mss_calls;
int set_retrans_calls;
int set_ka_calls;
int set_send_buf_calls;
int set_recv_buf_calls;
int set_des_ip_calls;
int set_des_port_calls;
int connect_calls;
int glob_calls; /* 读全局中断次数(B3:除 net_poll 外应为 0)*/
int glob_bits; /* 最近一次读到的 16 位全局位图 */
int sock_events_calls; /* ch395f_sock_events(只碰本 Socket 的护栏原语)*/
int rst_begin_calls;
int rst_begin_reject; /* begin 被拒次数(复位机已在运行)*/
int rst_poll_calls;
int rst_done[CH395F_FAKE_SOCK_MAX]; /* 复位机完成计数 */
int rst_fail[CH395F_FAKE_SOCK_MAX]; /* 复位机失败计数 */
int cmd_done_calls;
uint8_t last_cmd_done_what;
int rst_close_calls; /* 复位机内部发出的 CLOSE 命令(含二段补发)*/
int rst_rx_discard_calls; /* 复位机 RX 排空事务次数 */
} ch395f_fake_ext_obs_t;
extern ch395f_fake_ext_t g_fx;
extern ch395f_fake_ext_obs_t g_fxchip;
/* 由 ch395f_fake_reset() 自动调用;单独暴露是为了让测试能只清扩展面 */
void ch395f_fake_ext_reset(void);
void ch395f_fake_push_glob(uint16_t v); /* 预置全局中断返回序列 */
void ch395f_fake_set_sint_sticky(uint8_t sock, uint8_t mask);
/* 预置 GET_RECV_BUF 交付的字节(n=0 撤销预置);p 可为 NULL(表示填 n 个 0xA5) */
void ch395f_fake_set_recv_bytes(const uint8_t *p, uint16_t n);
/* 同上并**开启流式读**(游标归零):UDP 用例要的是"头 8 字节 + 载荷"两次读
* 各自拿到正确片段,而不是每次都从第 0 字节重交。 */
void ch395f_fake_set_recv_stream(const uint8_t *p, uint16_t n);
/* 开启 WRITE_SEND_BUF 内容捕获(g_fx.write_cap / write_cap_n / write_bytes):
* 默认关 → 既有"内容不可断言"的行为一字不变。 */
void ch395f_fake_enable_write_capture(void);
/* 顺序断言辅助:清空调用日志 / 找某个短名第一次出现的位置(-1=没调过)。
* 刻意提供"清零日志",否则一次长轮询(如 sock_reset 的几十轮 'x')会把 96 格
* 环形外的顺序信息挤掉,让后续场景的顺序断言失真。 */
void ch395f_fake_clear_log(void);
int ch395f_fake_log_find(char c);
/* —— 新增假芯片实现(签名与真实 ch395f.h 一字不改)—— */
ch395f_status_t ch395f_check_exist(void);
ch395f_status_t ch395f_reset(void);
ch395f_status_t ch395f_init(void);
uint8_t ch395f_get_version(void);
void ch395f_set_fun_para(uint32_t para);
void ch395f_set_tcp_mss(uint16_t mss);
void ch395f_set_retrans_count(uint8_t cnt);
void ch395f_set_retrans_period(uint16_t period);
void ch395f_set_keepalive_idle(uint32_t time);
void ch395f_set_keepalive_intvl(uint32_t time);
void ch395f_set_keepalive_cnt(uint8_t cnt);
void ch395f_set_dhcp(uint8_t enable);
void ch395f_ping_enable(uint8_t enable);
void ch395f_set_ip_addr(uint8_t *p_ip);
void ch395f_set_gwip_addr(uint8_t *p_gwip);
void ch395f_set_mask_addr(uint8_t *p_mask);
void ch395f_set_mac_addr(uint8_t *p_mac);
void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count);
void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count);
void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip);
void ch395f_set_des_port(uint8_t sock, uint16_t port);
uint8_t ch395f_tcp_connect(uint8_t sock);
uint8_t ch395f_get_phy_status(void);
uint16_t ch395f_get_glob_int_status_all(void);
void ch395f_glob_event_feed(uint16_t glob_bits);
uint8_t ch395f_sock_events(uint8_t sn);
int ch395f_sock_reset_begin(uint8_t sn, ch395f_reset_mode_t mode);
int ch395f_sock_reset_poll(uint8_t sn);
int ch395f_cmd_done(uint8_t sn, ch395f_done_what_t what);
#endif /* CH395F_FAKE_H */