Files
STM32F4-Base/test/host/ftp_regression.py
T
wandering1 d1e6bb4ba7 W5波+N-19收口:FTP鉴权落地、Trap25三连定案修复(N-30)、N-20/N-23/N-26
N-19 FTP 鉴权:
- lftpd_auth(USER/PASS 状态机 + 连续5次失败锁60s)+ SHA-256
  (user||pwd) 口令哈希;config.ini [ftp] 段 user/pwd_hash/write,
  出厂 admin/admin 在用有启动告警;lftpd_log.c 删除并入 lftpd_auth
- ftp_cfg 配置读写;台架 ftp_auth_bench.py 13/13

N-30 / Trap 25(CH395F_Trap_Records.md 四次定案全记录):
- 修复1 closed-with-data accept:客户端发完正常 FIN 后芯片按
  FUN_PARA(0x09) §5.52② 保留 Socket+RX 缓冲,旧代码把 CLOSED 一律
  relisten 自毁缓冲 → STOR 必挂 425;改为接受"已关但可读"连接排空
- 修复2 丢 CONNECT 中断探针 net_lost_connect_probe + last_peer_port
  幽灵甄别(拆链窗口握手完成但中断丢失,双信源去抖补 accept)
- 修复3 send_list/send_nlst 批量单发:背靠背 ~60B 小段毒化芯片 TX
  (芯片报 SEND_OK 但对端全丢),整份目录一次性写入
- 证伪存档:固定沉降/Socket 轮换冷却/listen 门控(台架 v7 矩阵)
- 台架工具 test/board/ch395f_reuse_bench.{c,h} + host 脚本存档
  (不注册编译;实验分支 n30-chip-bench 保留)

N-20:Error_Handler 死循环降级(记录+复位路径)
N-23:scripts/check_registration.py 双端注册反向校验(pre-commit)
N-26:GD5F NAND 直 DMA 调用方缓冲窗口断言

验证:BUILD_OK 0/0;ftp_regression 连跑 4 轮 11/11(修复前
LIST#4/#7 100% 挂、STOR#2 曾 100% 425);ftp_auth_bench 13/13;
@test 启动标记 PASS;ci_local L0+L0.5 全过;doc-links 通过。
登记簿 N-30 已销项。
2026-10-11 18:50:24 +08:00

96 lines
4.0 KiB
Python
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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""FTP 数据连接回归测试(D20 验证 + 日后回归)
背景(2026-09-22 台架实测 D20):同一 FTP 会话内**第二个数据连接必失败**
(425 Can't open data connection)。根因是单连接 Socket 复用时的"陈旧 DISCONNECT"
误判:上一条数据连接的 FIN/ACK 在 Socket 已复用为新连接之后才上报,新连接被
net 层当作"刚建立就断开"释放。修复见 net_socket.c handle_disconnect_event 的
陈旧事件甄别(回读芯片 TCP 状态,仍 ESTABLISHED 则丢弃该中断)。
本脚本在一个会话里连续做多次数据连接(LIST/NLST/STOR/RETR),并做大文件往返
内容校验;任一环节失败即退出码 1。
用法:
& '.venv\\Scripts\\python.exe' test/host/ftp_regression.py --host 192.168.1.100
"""
import argparse
import io
import sys
import time
from ftplib import FTP
SMALL = b"D20-small-" + b"A" * 1017 # 1024B
BIG = bytes(((i * 7 + 3) & 0xFF) for i in range(32768)) # 32KB 模式数据
def main(argv=None):
ap = argparse.ArgumentParser(description="FTP data-connection regression (D20)")
ap.add_argument("--host", default="192.168.1.100", help="device IP")
ap.add_argument("--port", type=int, default=21, help="FTP control port")
ap.add_argument("--timeout", type=int, default=20, help="socket timeout (s)")
# N-19(2026-10-11):FTP 已强制鉴权,匿名登录被 530 拒;默认口令 admin/admin
# (出厂值,见 ftp_cfg.h),改过 [ftp] pwd_hash 后用这两个参数覆盖
ap.add_argument("--user", default="admin", help="FTP user ([ftp] user)")
ap.add_argument("--pwd", default="admin", help="FTP password")
args = ap.parse_args(argv)
results = []
def step(name, fn):
"""执行一步并记录结果;不抛异常,便于一次跑完全部步骤。"""
t0 = time.time()
try:
extra = fn()
dt = time.time() - t0
results.append((name, True, dt))
print(" %-16s OK %6.2fs %s" % (name, dt, extra if extra is not None else ""))
return True
except Exception as e: # noqa: BLE001
dt = time.time() - t0
results.append((name, False, dt))
print(" %-16s FAIL %6.2fs %s %s" % (name, dt, type(e).__name__, e))
return False
f = FTP()
print("[ftp] connecting %s:%d ..." % (args.host, args.port))
f.connect(args.host, args.port, timeout=args.timeout)
f.login(args.user, args.pwd)
print("[ftp] login ok: %s" % f.getwelcome())
# 同一会话内连续 8 次数据连接(含两次大文件)—— D20 的失败点是第 2 次
step("LIST #1", lambda: "%d lines" % len(f.nlst()))
step("STOR #2 1KB", lambda: f.storbinary("STOR d20s.tmp", io.BytesIO(SMALL)) or "226")
got = bytearray()
step("RETR #3 1KB", lambda: (f.retrbinary("RETR d20s.tmp", got.extend), "226")[1])
if bytes(got) != SMALL:
print(" !! RETR #3 content mismatch (%d bytes)" % len(got))
results.append(("RETR#3-content", False, 0.0))
else:
results.append(("RETR#3-content", True, 0.0))
step("LIST #4", lambda: "%d lines" % len(f.nlst()))
step("STOR #5 32KB", lambda: f.storbinary("STOR d20b.tmp", io.BytesIO(BIG)) or "226")
big = bytearray()
step("RETR #6 32KB", lambda: (f.retrbinary("RETR d20b.tmp", big.extend), "226")[1])
if bytes(big) != BIG:
print(" !! RETR #6 content mismatch (%d/%d bytes)" % (len(big), len(BIG)))
results.append(("RETR#6-content", False, 0.0))
else:
results.append(("RETR#6-content", True, 0.0))
step("LIST #7", lambda: "%d lines" % len(f.nlst()))
step("cleanup", lambda: (f.delete("d20s.tmp"), f.delete("d20b.tmp"), "deleted")[2])
step("QUIT", lambda: f.quit() or "221")
ok = sum(1 for _, good, _ in results if good)
print("[ftp] %d/%d steps passed" % (ok, len(results)))
return 0 if ok == len(results) else 1
if __name__ == "__main__":
sys.exit(main())