# 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 卷），**匿名登录**（`USER anonymous` / `PASS` 任意）。
- 仅支持 **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.1.100**（掩码 255.255.255.0）；PC 可设 192.168.1.x（如 192.168.1.77）。
- 启动时 `netTask` 完成 `net_init()` + PHY 稳定（约 >2s），`ftpTask` 另有 `osDelay(7000)` 等待后才 `listen`；若 `open_socket` 失败会 `vTaskDelay(3000)` 重试（最多 3 次）。**客户端应在上电约 7s 后、串口出现 `[FTP] waiting for connection...` 再连接。**

### 2.2 软件

- Python 3.x（脚本位于 `test/`）。
- 可选 GUI 客户端：FileZilla / MobaXterm / lftp。

---

## 3. 编译与运行（固件侧）

- **默认构建即可提供 FTP Server**，无需切换测试套件。
- 任何改动后必须执行 `@build`（即 `MDK-ARM/build.bat`），确认 **0 Error(s) 0 Warning(s)** 再烧录（遵循 AGENTS.md 铁律）。
- 若需跑**自动化 FTP 文件传输用例**：在 `test/test_config.h` 选择 `TEST_SUITE_CH395F`，并在 `test/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.1.100`）、`--port`（默认 21），在 `test/` 目录运行。

### 4.1 冒烟测试：`ftp_test.py`

- 用途：验证连接、登录、`PWD`、`PASV`、`LIST` 基本链路。
- 命令：`python ./test/ftp_test.py --host 192.168.1.100`
- 预期：
  - 连接成功并读到 `220` 欢迎；
  - `USER anonymous` / `PASS` 返回 `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/ftp_up_dl_test.py --host 192.168.1.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/ftp_speed_test.py --host 192.168.1.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` / 注释。

---

## 5. GUI 客户端手测（可选）

- **FileZilla**：主机 `192.168.1.100`，端口 21，用户 `anonymous`，密码任意；可拖拽上传/下载、浏览目录。
- 要点：使用二进制模式传输；文件名遵守 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`。
- [ ] `ftp_test.py` 冒烟 PASS（连接 / 登录 / PASV / LIST）。
- [ ] `ftp_up_dl_test.py` 上传下载比对 `MATCH`。
- [ ] `ftp_speed_test.py` 大文件 `MATCH` + 记录速度基线。
- [ ] （可选）GUI 客户端手动上传/下载成功。
- [ ] （可选）阶段 8 自动化 `TC-NET-801/802/803` PASS。

---

## 8. 常见失败与排查（基于已知陷阱）

### 8.1 连接被拒 / 长时间无欢迎

- 确认 PC 与固件同网段、IP 正确；上电后等待约 7s（PHY 稳定 + `ftpTask` 延迟）再连。
- 若反复 `open_socket` 失败：见 **TRAP-09**（PHY 未稳定；固件已做 3 次重试 + 7s 延迟，仍失败检查网线/交换机）。

### 8.2 PASV 失败 / 数据通道连不上

- 确认 PC 防火墙未阻断高端口（PASV 动态端口）。
- **数据通道必须先 `connect` 再发命令**（如 `LIST`/`STOR`），顺序反了会失败（脚本已遵循）。
- **TRAP-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 / 速度抖动

- **TRAP-13** / 设计约束：CH395F `WRITE_SEND_BUF` 芯片侧吞吐限制，单帧 >1KB 才稳定；TCP 背压会"堵一会儿"，属正常流控，不丢数据。
- 接收侧 `recv_len` 首包可能较大、次包可能为 0，**必须循环读直到连接关闭**（脚本已处理）。
- 若持续 RST：检查 **SPI 分频 = 8**（PCB 设计限制）、`net_poll()` 周期（10ms）是否正常、中断是否统一在 `net_poll()` 中经 `GET_GLOB_INT_STATUS` 处理。

### 8.5 路径问题

- **TRAP-12**：FatFS 根路径 `/` 前缀由 `to_fatfs_path()` 统一处理；客户端用绝对路径（如 `/UPLOAD.DAT`）即可。

### 8.6 单客户端限制

- FTP 仅接受 1 个并发客户端；上一个会话未 `QUIT` 前，新连接会被拒。每次测完务必 `QUIT` / 断开。

### 8.7 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`。
- **编译验证**：`@build` 必须 0/0 再烧录（AGENTS.md 铁律）。
- **套件切换**：`test/test_config.h` 改 `TEST_SUITE_CH395F` / `TEST_SUITE_GD5F` / `TEST_SUITE_STORAGE`（三选一，互斥）。

---

## 10. 参考文档

- `docs/CH395F_Test_Guide.md` —— 网络测试总说明（含阶段 8 文件传输、FTP 陷阱清单）
- `docs/NET_Socket_FTP_Support_Plan.md` —— FTP / PASV Socket 改造方案
- `docs/CH395F_Trap_Records.md` —— CH395F 已知陷阱
- `docs/BSD_Socket_API_使用指南.md` —— BSD Socket API 用法
