Files
wandering1 7efa58d9a1
CI / 静态门禁 (pre-commit) (push) Failing after 25s
CI / 主机侧单测 (MSVC) (push) Successful in 48s
network: 台架 IP 迁到 192.168.31.100,并把重复写死的值收进单一真源
分两段(用户裁定 2026-11-02):先做地址迁移,再把同类字面量收敛成
单一真源,以后再改地址只动一处。

一、IP 迁移 192.168.1.100 → 192.168.31.100(gw 同步 → .31.1)
- 工厂默认:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_IP/_GW
- 注释/文档示例、主机与板级测试、台架 py 脚本、两份 sim_config.ini
- 字节序教学点同步换算 hex:0x6401A8C0 → 0x641FA8C0(内存
  C0 A8 1F 64),旧版示例引文与历史 Trap 记录按冻结纪律不动
- UI 黄金基准:网口设置 4 态 + shot_info 共 5 张 .bin 由
  check_content_pages.py --update 重刷(渲染值 IP 变了,属预期变更)
- 保留判定:客户点表引用(IEC104/继电保护规划)、芯片手册摘录、
  日期冻结的变更记录行、已退役台架档案 MDK-ARM/retracted_bench_*

二、单一真源收口(A+B+C+D,用户批准全做)
- 新建 test/host/bench_defaults.py:台架 py 默认值唯一定义点
  (DEVICE_IP/DEVICE_GW/PC_IP/NET_BCAST),22 个脚本改为引用;
  MDK-ARM 两探针与 scripts/board_ui_nav.py 经 sys.path shim 引入
- test/board/net_test_task.h 新增 TEST_PC_PEER_IP,板测三处
  PHASE*_REMOTE_IP 改引该宏(保留 -D 覆盖);Phase1 断言改
  memcmp 数组比较,数组各只出现一次
- simulator/tests/check_content_pages.py 从 net_cfg.c 正则解析
  NET_CFG_DEF_* 推导期望值(解析失败即报错、不静默兜底),
  脚本内台架 IP 字面量清零
- 装饰性注释去具体值(net_cfg.h/net_socket.h/net_socket.c/
  net_socket_sim.c);字节序教学 hex 对刻意保留为唯一写值点
- 刻意不动:主机单测夹具 IP(逐文件钉住的任意值是正确测试
  实践)、文档正文 IP(那是文档内容本身)

顺带:scripts/gen_task_table.py 规范串清掉「,B5」——编号清退提交
72b4b094 改了受管区没重跑生成器,task-table 门禁因此一直红

验证:产品 @build 0/0;CH395F 套件开关构建 0/0 后还原产品态;
6 套主机单测 rc=0;content_pages(含宏推导路径)/菜单 8 态/
推流位比对全 PASS;bom-drift 99 无漂移;doc-links/coding-standard/
cppcheck 绿;py_compile 23 文件 + --help 冒烟(含 shim 绝对路径)
2026-11-02 14:46:42 +08:00

206 lines
15 KiB
Markdown
Raw Permalink 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.
# FTP 功能测试指导
> 适用对象:STM32F4-Base 上基于 `lftpd` 的 FTP Server 功能验证。
> 配套源码:`Drivers/BSP/NET/lftpd/`、`App/task/net_task.c`、`Src/freertos.c`(StartFtpTask)。
> 关联文档:`CH395F_Test_Guide.md`(网络测试总说明·阶段 8)、`NET_Socket_FTP_Support_Plan.md`(PASV 改造方案)、`CH395F_Trap_Records.md`。
---
## 1. 概述
- FTP Server(`lftpd`)在**默认固件构建中即常驻运行**:`Src/freertos.c` 的 `StartFtpTask`(默认 `__weak` 实现)直接调用 `lftpd_start("/", 21, &ftp)`,与测试套件选择无关。
- 监听端口 **21**,根目录 `/`(即 NAND 上的 FatFS 卷)。**需要账号鉴权,设备没有匿名通道**:账号取
`/config.ini` 的 `[ftp]` 段(`user` / `pwd_hash` = SHA-256(user‖password) / `write`),出厂默认
**admin/admin**(用默认口令时启动日志有告警);**连续 5 次失败锁定 60 秒**。完整口径见
`../../子系统设计/网络配置说明.md`。
- 仅支持 **1 个并发客户端**(控制通道 Socket0 + PASV 数据通道动态 Socket2,backlog=1)。
- 测试分两类:
- **PC 端手测(推荐,覆盖真实 FTP 行为)**:用 `test/` 下 Python 脚本或 GUI 客户端。
- **固件自动化(TEST_SUITE_CH395F 阶段 8)**:TC-NET-801/802/803 文件传输。
---
## 2. 测试环境
### 2.1 硬件与网络
- CH395F 网口经以太网连接到 PC 或同一台交换机。
- PC 网卡与固件 IP **同网段**。固件默认静态 IP **192.168.31.100**(掩码 255.255.255.0);PC 可设 192.168.31.x(如 192.168.31.77)。
- **启动时序(2026-09-21 现行实现,取代旧文档的 `osDelay(7000)`)**:`netTask` 完成 `net_init()` + PHY 稳定后置 `SYS_EVT_NET_READY`;`defaultTask` 挂载卷 + 加载配置后置 `SYS_EVT_STORAGE_READY`;`ftpTask` 以 `osEventFlagsWait(两位, osFlagsWaitAll | osFlagsNoClear)` 等**两位同时就绪**再 `lftpd_start`。串口出现 `[FTP] waiting for connection...` 即可连接(实测:上电后约 0.5~1s,取决于存储挂载耗时)。
- ⚠ **就绪位等待必须带 `osFlagsNoClear`**(2026-09-21 实证,详见 §8.6):`osEventFlagsWait` 默认**消费**所等待的位,若被其它等待者清掉,`ftpTask` 的 `WaitAll(两位)` 永不成立 → FTP 静默不监听。
- **单客户端**:监听 socket 与数据通道复用,同一时刻只服务 1 个会话;上一个会话未 `QUIT` 就断开时,服务端约 30s 后才丢弃旧会话并重新 listen,期间新连接被拒(`WinError 10061`)。脚本已内置连接重试;**不要用裸 TCP 工具探测 21 端口**(探测本身会占掉唯一会话,见 §8.7)。
### 2.2 软件
- Python 3.x(脚本位于 `test/`)。
- 可选 GUI 客户端:FileZilla / MobaXterm / lftp。
---
## 3. 编译与运行(固件侧)
- **默认构建即可提供 FTP Server**,无需切换测试套件。
- 任何改动后必须执行 `/keil-build`(即 `keil-build` skill:`.dsh/skills/keil-build/scripts/keil_build.py`),确认 **0 Error(s) 0 Warning(s)** 再烧录(遵循 AGENTS.md 铁律)。
- 若需跑**自动化 FTP 文件传输用例**:在 `test/board/test_config.h` 选择 `TEST_SUITE_CH395F`,并在 `test/board/ch395f_test.h` 打开 `ENABLE_PHASE8_TESTS`(文件传输阶段),重新编译烧录。
- 烧录后打开串口(115200bps)观察:
- `net_init` 完成、PHY link up;
- `[FTP] waiting for connection...`(约 3 次重试足够 PHY 稳定);
- 客户端连接后进入 FTP 会话(控制通道 220 欢迎)。
---
## 4. PC 端脚本手测(主要手段)
所有脚本支持 `--host`(默认 `192.168.31.100`)、`--port`(默认 21)、`--user` / `--password`
(默认出厂账号 `admin` / `admin`;设备口令改过就要显式传,**没有匿名通道**),在 `test/` 目录运行;`ftp_up_dl_test.py` / `ftp_speed_test.py` 内置**连接重试**(单客户端:旧会话未释放时自动等待重试,默认 6 次 × 3s)。
推荐入口:`& '.venv\Scripts\python.exe' test/host/ftp_up_dl_test.py`(功能闭环:登录/TYPE I/PASV/STOR/LIST/RETR/比对/DELE/QUIT)与 `& '.venv\Scripts\python.exe' test/host/ftp_speed_test.py --size 128`(大文件 + 吞吐);后者同时是 **ftpTask 栈水位测量**的负载(见 `docs/内存管理/RAM使用分析与优化方案.md` §4.0.6)。
### 4.1 冒烟测试:`ftp_test.py`
- 用途:验证连接、登录、`PWD`、`PASV`、`LIST` 基本链路。
- 命令:`python ./test/host/ftp_test.py --host 192.168.31.100`
- 预期:
- 连接成功并读到 `220` 欢迎;
- `USER admin` 返回 `331`(需要口令)、`PASS admin` 返回 `230`;
- `PASV` 返回 `227` 并解析出数据端口(或回退 `EPSV`);
- `LIST` 返回目录列表;
- `QUIT` 正常关闭。
- 判据:全程无 `[NO RESPONSE]` / `CONNECT FAILED`,且 LIST 返回 `226` 即 PASS。**空卷下 LIST 数据通道为 0 字节属正常**(新格式化卷无文件),不必要求有目录列表输出。
### 4.2 上传 / 下载 / 比对:`ftp_up_dl_test.py`
- 用途:验证 `STOR`/`RETR`/`DELE` 与数据一致性(`TYPE I` 二进制 + `PASV`)。
- 命令:`python ./test/host/ftp_up_dl_test.py --host 192.168.31.100`
- 流程:登录 → `TYPE I` → `STOR up_test.dat` → `LIST` 校验存在 → `RETR up_test.dat` → 逐字节比对 → `DELE` 清理 → `QUIT`。
- 预期:`*** MATCH! Upload/Download success. ***`,且 LIST 中出现 `up_test.dat`。
- 判据:上传/下载字节数相等且内容逐字节一致。
### 4.3 速度与大数据量:`ftp_speed_test.py`
- 用途:测量上传/下载吞吐,作回归基线,同时验证大文件不丢字节。
- 命令:`python ./test/host/ftp_speed_test.py --host 192.168.31.100 --size 512`(默认 512KB,`--size` 可调)。
- 预期:上传/下载均返回 `226`,回读 `MATCH!`,打印 KB/s。
- 注意:脚本使用 8.3 文件名 `SPEEDTST.DAT`(**FTP 仅支持 8.3 短文件名**,见 §8.3);以回读比对(MATCH)为最终判据,中间打印的 Sent 字段仅供参考。
- 判据:size 一致 + MATCH + 速度稳定(受 SPI2 分频 8 限制,通常约 300~450 KB/s,参见 `CH395F_Test_Guide.md` 阶段 5 吞吐基线)。
### 4.4 抓包辅助:`ftp_capture.py`
- 用途:连接/传输过程中抓包,分析 PASV 端口、命令/数据通道交互,用于疑难排查。
- 用法参见脚本内 `--help` / 注释。
### 4.5 大目录完整性:`ftp_list_fill_test.py`(2026-10-22 起常驻)
- **为什么单独立一个脚本**:lftpd 的 `send_list` 曾把目录装进一块 static 1536 B 缓冲,
装满就 break 并回 −1,`cmd_list` 据此回 **550** —— 目录一大,客户端拿到的是**静默截断**
的清单外加一个错误码,而清单看起来完全像"目录里就这些"。台架实测 30 条目的根目录只回
24 条目(1512 B),6 个文件凭空消失。修完之后,**目录清空的设备上这颗雷再也暴露不出来**,
所以需要一个自己把目录撑过一块的回归。机理与修复见 `../../问题记录/CH395F_Trap_Records.md`
的 LIST 吞包定案(定案三)增补。
- 命令:`& '.venv\Scripts\python.exe' test/host/ftp_list_fill_test.py`(`--files 26` 默认)
- 做什么:造 26 个 8.3 短名小文件(`ZZFTnn.TXT`)→ `LIST` → 三条判据 → 逐个 `DELE` 还原
- 判据:① 26 个 STOR 全 226;② LIST 全文里每个名字都在(少一个 = 截断回归);③ LIST 的
**完成回包是 226 而不是 550**(用 ftplib 的 `retrlines`,非 2xx 完成会直接抛异常)。
- 实测:`收到 56 行 / 3544 字节`(跨 3 块,满块续发被真正走到)→ 三条全 PASS。
- 注意:会在设备上写删 26 个文件,需要 `[ftp] write=1`(出厂默认);失败时按 WARN 提示手清残留。
---
## 5. GUI 客户端手测(可选)
- **FileZilla**:主机 `192.168.31.100`,端口 21,用户 `admin`、密码 `admin`(**匿名会被 530 拒**);可拖拽上传/下载、浏览目录。
- 要点:使用二进制模式传输;文件名遵守 8.3;**一次只连一个客户端**。
---
## 6. 自动化测试(TEST_SUITE_CH395F 阶段 8)
- 入口:`ENABLE_PHASE8_TESTS` → 文件传输阶段(100KB 文件)。
- 用例:
- `TC-NET-801`:100KB 文件上发(MCU→PC);
- `TC-NET-802`:100KB 文件下发(PC→MCU);
- `TC-NET-803`:round-trip(可选)。
- 详见 `docs/测试说明/板级测试/CH395F_Test_Guide.md` 阶段 8。
---
## 7. 测试清单(Checklist)
- [ ] 默认构建烧录,串口见 `[FTP] waiting for connection`(缺此行 → 见 §8.6)。
- [ ] `ftp_test.py` 冒烟 PASS(连接 / 登录 / PASV / LIST)。
- [ ] `ftp_up_dl_test.py` 上传下载比对 `MATCH`(10 项全 PASS)。
- [ ] `ftp_speed_test.py` 大文件 `MATCH` + 记录速度基线;同时用 `scripts/stack_report.py` 复核 ftpTask 栈余量(内存方案 §4.0.6)。
- [ ] (可选)GUI 客户端手动上传/下载成功。
- [ ] (可选)阶段 8 自动化 `TC-NET-801/802/803` PASS。
---
## 8. 常见失败与排查(基于已知陷阱)
### 8.1 连接被拒 / 长时间无欢迎
- 确认 PC 与固件同网段、IP 正确;上电后等待约 7s(PHY 稳定 + `ftpTask` 延迟)再连。
- 若反复 `open_socket` 失败:见 **TRAP-NET-09**(PHY 未稳定;固件已做 3 次重试 + 7s 延迟,仍失败检查网线/交换机)。
### 8.2 PASV 失败 / 数据通道连不上
- 确认 PC 防火墙未阻断高端口(PASV 动态端口)。
- **数据通道必须先 `connect` 再发命令**(如 `LIST`/`STOR`),顺序反了会失败(脚本已遵循)。
- **TRAP-NET-11**:`net_accept_locked` 在 `ESTABLISHED` 状态导致 PASV 失败——需校验 `ESTABLISHED`。
### 8.3 STOR 报 FR_INVALID_NAME (err=6)
- **8.3 文件名限制**:`upload_test.dat`(11+3)超过 8.3 → `FR_INVALID_NAME`。改用短名如 `UPLOAD.DAT` / `SPEEDTST.DAT`。这是 FatFS LFN/8.3 约束,非驱动 bug。
### 8.4 大文件传输中途 RST / 速度抖动
- **设计约束**:CH395F `WRITE_SEND_BUF` 芯片侧吞吐限制,单帧 >1KB 才稳定;TCP 背压会"堵一会儿",属正常流控,不丢数据。
- 接收侧 `recv_len` 首包可能较大、次包可能为 0,**必须循环读直到连接关闭**(脚本已处理)。
- 若持续 RST:检查 **SPI 分频 = 8**(PCB 设计限制)、`net_poll()` 周期(10ms)是否正常、中断是否统一在 `net_poll()` 中经 `GET_GLOB_INT_STATUS` 处理。
### 8.5 路径问题
- **FatFS 路径口径**:根路径 `/` 前缀由 `to_fatfs_path()` 统一处理(详见 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1);客户端用绝对路径(如 `/UPLOAD.DAT`)即可。
### 8.6 FTP 从不监听(21 端口一直拒绝)
- 现象:串口无 `[FTP] waiting for connection...`、无 `[LFT_INET]` 报错,`[STACK]` 行里 ftpTask 峰值极低(实测 156 B,仅为任务入口),21 端口始终 `connection refused`。
- 根因(2026-09-21 定位并修复):`osEventFlagsWait` 默认**消费**所等待的位,而 `SYS_EVT_NET_READY` / `SYS_EVT_STORAGE_READY` 是**状态位**。`netTask` 等 `STORAGE_READY` 时清掉 bit1,`uiTask` / `timeSyncTask` 等 `NET_READY` 时清掉 bit0 → `ftpTask` 的 `WaitAll(bit0|bit1)` 条件永不成立,任务永久阻塞在内核等待队列(无日志、无报错、静默失效)。
- 修复:**所有就绪位等待加 `osFlagsNoClear`**(`Src/freertos.c` 的 ftpTask、`App/task/ui_task.c`、`App/sync/time_sync.c`、`App/task/net_task.c`、`test/board/ch395f_test_task.c`);命令位(`SYS_EVT_CFG_SAVE` / `SYS_EVT_NET_RECONFIG`)保持消费型等待不变。
- 验证:修后串口出现 `[FTP] waiting for connection...`、21 端口可连;ftpTask 的 `[STACK]` 峰值握手后升到 ~2.5KB、128KB 传输后升到 ~3.4KB(该值曾触发余量告警,已在 2026-09-21 把 lftpd 大局部变量静态化后降到 **1184 B / 4096 B,余量 71%**——见内存方案 §4.1.4/§4.0.6)。
### 8.7 单客户端限制:端口"通"但随后连接被拒(旧会话未释放)
- **FTP 仅接受 1 个并发客户端**:上一个会话未 `QUIT` 前,新连接会被拒;每次测完务必 `QUIT` / 断开。
- 现象:用 `Test-NetConnection` / `nc` 探测 21 端口显示可达,紧接着 FTP 脚本却 `10061 connection refused`。
- 根因:探测本身完成了一次 TCP 握手 → 占用了 CH395F 唯一的会话 socket;服务端进入控制通道等待,直到读超时(约 30s)才打印 `[ERR] [FTP] error reading next command` 并回到 `waiting for connection...`。
- 处理:**别用裸 TCP 工具探测 21 端口**;用 FTP 脚本(已内置重试:默认 6 次 × 3s)。若已被占用,等约 30s 或复位设备。
### 8.8 吞吐远低于基线(本次实测 ~50~60 KB/s @128KB)
- 本次 128KB STOR/RETR 实测上/下行约 49 / 60 KB/s,明显低于阶段 5 记录的 300~450 KB/s(`CH395F_Test_Guide.md`)。
- 已知差异:FTP 路径每次读 512B 经 FatFs→FTL→NAND 写/读,而阶段 5 基线是裸 socket 吞吐;小文件(128KB)还叠加了会话建立/关闭开销。
- 待办:如需吞吐结论,用 `ftp_speed_test.py --size 512`~`2048` 复测并与阶段 5 基线对齐;必要时再查 `WRITE_SEND_BUF` 分帧与 `net_poll` 周期。
### 8.9 LIST/STOR/RETR 全部 550:卷未挂载 / 无 FAT 文件系统
- 根因:FTP 服务的是 NAND 上的 FatFS 卷。固件在 `StartDefaultTask` 启动期执行 `f_mount(&fs, "", 1)`(会触发 `disk_initialize` → `nand_ftl_init`),使卷可用。
- **首启自动建卷**:若 `f_mount` 返回 `FR_NO_FILESYSTEM`(如坏块重建/恢复测试后卷被清空),固件会自动 `f_mkfs`(FM_FAT32、无分区表,参数与存储测试一致)再挂载。该过程上电会多花数秒(格式化),串口可见 `[FS] no FAT volume, formatting NAND...` → `[FS] FATFS mounted`。
- 若串口见 `[FS] f_mount failed (fr=...)` 且非首次(已建卷仍失败):检查 `f_mkfs` 是否报错(`[FS] f_mkfs failed`),或 NAND 是否存在物理/ECC 错误;必要时用 `TEST_SUITE_STORAGE` 跑一次确认磁盘健康。
---
## 9. 诊断手段(沿用前期测试经验)
- **串口日志 + `DBG_*`**:在 `lftpd.c` / `net_socket` 关键路径加 `DBG_ERROR/INFO` 打印(注意编码安全铁律——只用 Edit 改中文源文件,禁止用 `Get/Set-Content` 整文件重写)。
- **抓包**:`ftp_capture.py` 或 Wireshark 过滤 `tcp.port==21`。
- **编译验证**:`/keil-build` 必须 0/0 再烧录(AGENTS.md 铁律)。
- **套件切换**:`test/board/test_config.h` 改 `TEST_SUITE_CH395F` / `TEST_SUITE_GD5F` / `TEST_SUITE_STORAGE`(三选一,互斥)。
---
## 10. 参考文档
- `docs/测试说明/板级测试/CH395F_Test_Guide.md` —— 网络测试总说明(含阶段 8 文件传输、FTP 陷阱清单)
- `[计划]` FTP / PASV Socket 改造方案(尚未创建)
- `docs/问题记录/CH395F_Trap_Records.md` —— CH395F 已知陷阱
- `docs/网络/BSD_Socket_API_使用指南.md` —— BSD Socket API 用法