Files
STM32F4-Base/test/host/ftp_auth/ftp_auth_test.c
T
wandering1 924ac3d428 test(host): 断言字面量统一纯 ASCII,30 文件 1535 行
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 21:00:17 +08:00

204 lines
8.6 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.
/*
* 模块名称:FTP 鉴权状态机(lftpd_auth)主机侧单元测试
* 模块功能:在 PC 上对 Drivers/BSP/NET/lftpd/lftpd_auth.c 做离线验证
* (用户名匹配 / 口令校验 / 失败计数 / 限时锁定 / tick 回绕)。
*
* 被测对象:真实源文件 lftpd_auth.c + 其依赖 sha256.c(均以 #include 原样
* 纳入,保证测的是生产代码而非副本;两模块零平台依赖,无需桩——
* tick 由参数注入正是本模块的纯函数设计意图)。
*
* 为什么要主机侧单测(而不是只用台架):
* 锁定 60 s / 解锁 / 成功清零 / tick 32 位回绕是"防口令爆破"契约的核心,
* 板端每验一条要真等 60 s 且回绕要跑满 49.7 天;主机侧注入合成 tick
* 毫秒级穷举。用户名侧信道路径(错用户名也走哈希)同属纯逻辑。
*
* 覆盖点:
* TC-FTPAUTH-001 user_match:相等 / 大小写不敏感 / 不等 / NULL
* TC-FTPAUTH-002 正确用户名+正确口令 → OK,计数 0
* TC-FTPAUTH-003 错口令 → FAIL,计数 1
* TC-FTPAUTH-004 错用户名+对口令 → FAIL(哈希路径已走,时间拉平)
* TC-FTPAUTH-005 连续 5 次失败 → 第 6 次(锁定期内)LOCKED,对口令也拒
* TC-FTPAUTH-006 锁定期满(+60000 tick)→ 对口令 OK,计数清零
* TC-FTPAUTH-007 成功清零计数:3 败 + 1 成 → 再 4 败不触发锁定(第 5 败才锁)
* TC-FTPAUTH-008 tick 32 位回绕:锁定期内回绕点仍 LOCKED,期满回绕点放行
* TC-FTPAUTH-009 配置 pwd_hash 非法 hex → 恒 FAIL(fail-closed)
* TC-FTPAUTH-010 空口令 / NULL 口令 → FAIL;NULL creds/state → FAIL
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-11
*/
#include <stdio.h>
#include <string.h>
/* 生产源文件原样纳入(顺序敏感:先 sha256 后 lftpd_auth) */
#include "sha256.c"
#include "lftpd_auth.c"
/* ===================== 断言框架 ===================== */
static int s_total;
static int s_passed;
static int s_failed;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" PASS %s\n", name); } \
else { s_failed++; printf(" FAIL %s (line %d)\n", name, __LINE__); } \
} while (0)
/* 与 ftp_cfg.h FTP_CFG_DEF_HASH 同源钉值(SHA256("adminadmin")) */
#define DEF_HASH "d82494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892"
static const ftp_auth_creds_t s_creds = { "admin", DEF_HASH, 1u };
/* ===================== TC-FTPAUTH-001:user_match ===================== */
static void tc_user_match(void)
{
CHECK(ftp_auth_user_match("admin", "admin") == 1, "TC-FTPAUTH-001a equal strings match");
CHECK(ftp_auth_user_match("admin", "ADMIN") == 1, "TC-FTPAUTH-001b case-insensitive");
CHECK(ftp_auth_user_match("admin", "root") == 0, "TC-FTPAUTH-001c mismatch rejected");
CHECK(ftp_auth_user_match("admin", "admin2") == 0, "TC-FTPAUTH-001d prefix does not count as match");
CHECK(ftp_auth_user_match("admin", NULL) == 0, "TC-FTPAUTH-001e NULL rejected");
}
/* ===================== TC-FTPAUTH-002..004:基本判定 ===================== */
static void tc_basic(void)
{
ftp_auth_state_t st;
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, 1000u) == FTP_AUTH_OK,
"TC-FTPAUTH-002 right user + right password -> OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-002b counter is 0 after success");
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-003 wrong password -> FAIL");
CHECK(st.fail_count == 1u, "TC-FTPAUTH-003b fail count = 1");
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 0u, "admin", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-004 wrong user + right password -> FAIL (hash path taken)");
}
/* ===================== TC-FTPAUTH-005..006:锁定与解锁 ===================== */
static void tc_lock(void)
{
ftp_auth_state_t st;
uint32_t t = 10000u;
(void)memset(&st, 0, sizeof(st));
for (int i = 0; i < 5; i++) {
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, t) == FTP_AUTH_FAIL,
"TC-FTPAUTH-005 attempts 1..5 -> FAIL");
}
CHECK(st.fail_count == 5u, "TC-FTPAUTH-005b counter reaches FAIL_MAX");
/* 锁定期内:对口令也 LOCKED,且不再计数 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, t + 1u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-005c right password during lock window -> LOCKED");
CHECK(st.fail_count == 5u, "TC-FTPAUTH-005d LOCKED does not change the counter");
/* 59999 tick:仍在窗内(差值 < 60000) */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st,
st.lock_until_tick - 1u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-005e last tick inside window still LOCKED");
/* 期满:恰好到达 lock_until → 放行 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st,
st.lock_until_tick) == FTP_AUTH_OK,
"TC-FTPAUTH-006 right password after lock expiry -> OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-006b counter cleared after unlock then success");
}
/* ===================== TC-FTPAUTH-007:成功清零计数 ===================== */
static void tc_reset(void)
{
ftp_auth_state_t st;
uint32_t t = 5000u;
(void)memset(&st, 0, sizeof(st));
for (int i = 0; i < 3; i++) {
(void)ftp_auth_check(&s_creds, 1u, "wrong", &st, t);
}
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, t) == FTP_AUTH_OK,
"TC-FTPAUTH-007a success after 3 failures -> OK");
CHECK(st.fail_count == 0u, "TC-FTPAUTH-007b success clears the counter");
for (int i = 0; i < 4; i++) {
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, t) == FTP_AUTH_FAIL,
"TC-FTPAUTH-007c after clear, 4 more failures still FAIL (not locked)");
}
CHECK(ftp_auth_check(&s_creds, 1u, "wrong", &st, t) == FTP_AUTH_FAIL,
"TC-FTPAUTH-007d 5th failure FAILs and arms the lock");
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, t + 1u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-007e LOCKED after 5 consecutive failures");
}
/* ===================== TC-FTPAUTH-008:tick 32 位回绕 ===================== */
static void tc_wraparound(void)
{
ftp_auth_state_t st;
/* 手工构造:锁定截止在 0xFFFFFFF0(距 32 位翻转仅 16 tick) */
(void)memset(&st, 0, sizeof(st));
st.fail_count = 5u;
st.lock_until_tick = 0xFFFFFFF0u;
/* 回绕前 240 tick:仍在窗内 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, 0xFFFFFF00u) == FTP_AUTH_LOCKED,
"TC-FTPAUTH-008a inside window before wraparound -> LOCKED");
/* 回绕后 32 tick:now=0x10,(int32_t)(0x10-0xFFFFFFF0)=+32 → 期满 */
CHECK(ftp_auth_check(&s_creds, 1u, "admin", &st, 0x00000010u) == FTP_AUTH_OK,
"TC-FTPAUTH-008b after wraparound window elapsed -> OK (signed diff resists wraparound)");
}
/* ===================== TC-FTPAUTH-009..010:fail-closed 边界 ===================== */
static void tc_fail_closed(void)
{
ftp_auth_state_t st;
ftp_auth_creds_t bad_creds = { "admin", "zz2494f05d6917ba02f7aaa29689ccb444bb73f20380876cb05d1f37537b7892", 1u };
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&bad_creds, 1u, "admin", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-009 invalid hex config -> always FAIL (nobody can log in, fail-closed)");
(void)memset(&st, 0, sizeof(st));
CHECK(ftp_auth_check(&s_creds, 1u, "", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010a empty password -> FAIL");
CHECK(ftp_auth_check(&s_creds, 1u, NULL, &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010b NULL password -> FAIL");
CHECK(ftp_auth_check(NULL, 1u, "admin", &st, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010c NULL creds -> FAIL");
CHECK(ftp_auth_check(&s_creds, 1u, "admin", NULL, 1000u) == FTP_AUTH_FAIL,
"TC-FTPAUTH-010d NULL state -> FAIL");
}
/* ===================== main ===================== */
int main(void)
{
printf("=== FTP Auth Tests ===\n");
printf("-- user match --\n");
tc_user_match();
printf("-- basic check --\n");
tc_basic();
printf("-- lock / unlock --\n");
tc_lock();
printf("-- success reset --\n");
tc_reset();
printf("-- tick wraparound --\n");
tc_wraparound();
printf("-- fail-closed --\n");
tc_fail_closed();
printf("\n=== FTP Auth Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}