1fe163ba7b
- prot_set:新增 PS_GLOBAL_EN(枚举尾追加);值区双缓冲 + 单字交换, prot_set_set_sealed 原子写+封存;selfcheck 改 seqlock 式复核 (提交撞见 = 本轮跳过)→ 收口 B5 登记的 µs 级误锁窗口 - 持久化:新增 App/cfg/prot_set_store.c/h,/prot_set.bin blob (magic+版本+计数+值区+sum32 正/反码对,144 B)走 cfg_store_write_atomic; uvprojx 注册 + 模拟器豁免点名(check_registration 同步) - 写路径:C_SE/YK12 执行段置脏 → iec104Task 投递 SYS_EVT_PROT_SET_SAVE (事件位 bit7)→ defaultTask(配置引擎属主)落盘,写失败脏回置 = 天然 重试;装载 = STORAGE_READY 屏障前整表原子 parse(损坏保出厂默认 + 自检留痕 prot set corrupt;文件缺失 = 首启静默) - YK12 转真:两段式(选择段校验 DPC / 执行段 set_sealed),protectTask 下一拍读退出态 → 复位元件 + 停判据(global plate OUT/IN 边沿日志); 本地定值项 comm_addr=0 显式不可寻址(防 IOA=0 误命中首个本地项) - 单测:prot_set 63 断言(+33:软压板/原子写/脏握手/blob 往返/损坏拒绝/ 伪造自洽越界对抗样本)、iec104 94 断言(+7:YK12 两段转真/置脏/本地项 C_RD EVI) - 文档:IEC104 设计说明 §3.3/§3.4/§7、保护规划 §3.5 L5-1/§7 P4/变更记录、 App/algo 与 App/cfg README、W1 任务卡 B7→🟦(台架腿 🔴 待窗口,随 B6 同窗) - 验证:@build 0 错 0 警(Code=205500,ZI +280 = 双缓冲+blob 缓冲); ci_local 全绿(L0 27 钩子 + L0.5 13 套件 826 断言);ram_baseline 台账 受管区与内存快照已同步;19 个改动文件无 BOM / LF / 零乱码
260 lines
11 KiB
C
260 lines
11 KiB
C
/*
|
||
* 模块名称:保护定值模型(P1)主机侧单元测试
|
||
* 模块功能:在 PC 上对 App/algo/prot_set.c 做离线验证。
|
||
*
|
||
* 被测对象:真实源文件(#include 原样纳入)。纯逻辑,无需桩。
|
||
*
|
||
* 覆盖点(docs/算法/继电保护功能复现规划.md §5.2 / §3.4 L5-1):
|
||
* TC-PS-001 init:默认值装载、越界位图 0、selfcheck 通过、未闭锁
|
||
* TC-PS-002 set:范围内写入生效;越界拒写且原值不动
|
||
* TC-PS-003 校验和封存语义:set 后未 seal → selfcheck 失败并闭锁;
|
||
* seal → 通过并解锁(L5-1 核心闭环)
|
||
* TC-PS-004 load_raw:合法整表装载并封存;含越界项 → 整表不装载 +
|
||
* bad_mask 指认越界项(定值一致性:宁可全旧不可半新)
|
||
* TC-PS-005 Q10 往返精度(±1 LSB)
|
||
* TC-PS-006 元数据表自检:min<=max、投退项 0..1、非零通讯地址不重复
|
||
* TC-PS-007 越界编号守卫(get/set)
|
||
* TC-PS-008 全局软压板 PS_GLOBAL_EN(B7):默认投入、set_sealed 原子写
|
||
* 不破坏 selfcheck、脏标志三件套(peek/take/mark)
|
||
* TC-PS-009 持久化 blob 往返:serialize → init 清场 → parse 恢复
|
||
* (位精确 + 封存自洽 + parse 不置脏)
|
||
* TC-PS-010 损坏 blob 整表拒绝(all-or-nothing):值区位翻转/坏 magic/
|
||
* 截断/超长/坏版本/伪造自洽越界 → 全部拒绝且活跃值区一字不动
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 创建日期:2026-10-11
|
||
*/
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
|
||
static int s_total = 0, s_passed = 0, s_failed = 0;
|
||
|
||
#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)
|
||
|
||
#include "prot_set.c"
|
||
|
||
/* TC-PS-001 */
|
||
static void tc_init(void) {
|
||
prot_set_init();
|
||
CHECK(prot_set_bad_mask() == 0u, "TC-PS-001 init: no bad items");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-PS-001 init: selfcheck pass");
|
||
CHECK(prot_set_locked() == 0u, "TC-PS-001 init: not locked");
|
||
CHECK(prot_set_get_f(PS_OC1_CUR) > 9.99f && prot_set_get_f(PS_OC1_CUR) < 10.01f,
|
||
"TC-PS-001 default OC1 current = 10.0 A");
|
||
CHECK(prot_set_get_ctl(PS_OC3_TRIP) == 0u,
|
||
"TC-PS-001 default OC3 = alarm (trip off)");
|
||
}
|
||
|
||
/* TC-PS-002 */
|
||
static void tc_set_range(void) {
|
||
prot_set_init();
|
||
CHECK(prot_set_set(PS_OC2_CUR, PROT_SET_Q10(7.5)) == 0,
|
||
"TC-PS-002 in-range set accepted");
|
||
CHECK(prot_set_get_f(PS_OC2_CUR) > 7.49f && prot_set_get_f(PS_OC2_CUR) < 7.51f,
|
||
"TC-PS-002 value readable after set");
|
||
CHECK(prot_set_set(PS_OC2_CUR, PROT_SET_Q10(101.0)) == -1,
|
||
"TC-PS-002 over-max set rejected");
|
||
CHECK(prot_set_get_f(PS_OC2_CUR) > 7.49f && prot_set_get_f(PS_OC2_CUR) < 7.51f,
|
||
"TC-PS-002 rejected set leaves value unchanged");
|
||
CHECK(prot_set_set(PS_OC1_EN, PROT_SET_Q10(2)) == -1,
|
||
"TC-PS-002 ctl item rejects value 2");
|
||
}
|
||
|
||
/* TC-PS-003 */
|
||
static void tc_seal_selfcheck(void) {
|
||
prot_set_init();
|
||
CHECK(prot_set_set(PS_OC1_CUR, PROT_SET_Q10(8.0)) == 0, "TC-PS-003 set ok");
|
||
CHECK(prot_set_selfcheck() == -1,
|
||
"TC-PS-003 set-without-seal => selfcheck FAIL (tamper detect)");
|
||
CHECK(prot_set_locked() == 1u, "TC-PS-003 selfcheck failure latches lock");
|
||
prot_set_seal();
|
||
CHECK(prot_set_selfcheck() == 0, "TC-PS-003 seal => selfcheck pass");
|
||
CHECK(prot_set_locked() == 0u, "TC-PS-003 seal clears lock");
|
||
}
|
||
|
||
/* TC-PS-004 */
|
||
static void tc_load_raw(void) {
|
||
int32_t tbl[PS_ID_COUNT];
|
||
uint32_t i;
|
||
|
||
prot_set_init();
|
||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||
tbl[i] = prot_set_get((prot_set_id_t)i);
|
||
}
|
||
tbl[PS_OL_CUR] = PROT_SET_Q10(1.5);
|
||
CHECK(prot_set_load_raw(tbl) == 0, "TC-PS-004 valid table loads");
|
||
CHECK(prot_set_get_f(PS_OL_CUR) > 1.49f && prot_set_get_f(PS_OL_CUR) < 1.51f,
|
||
"TC-PS-004 loaded value visible");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-PS-004 load_raw auto-seals");
|
||
|
||
tbl[PS_OC3_DLY] = PROT_SET_Q10(60001.0); /* 越上限 */
|
||
tbl[PS_OL_CUR] = PROT_SET_Q10(2.5); /* 合法改动 */
|
||
CHECK(prot_set_load_raw(tbl) == -1, "TC-PS-004 bad table rejected");
|
||
CHECK(prot_set_bad_mask() == (1uL << PS_OC3_DLY),
|
||
"TC-PS-004 bad_mask points at the offending item");
|
||
CHECK(prot_set_get_f(PS_OL_CUR) > 1.49f && prot_set_get_f(PS_OL_CUR) < 1.51f,
|
||
"TC-PS-004 rejected load keeps OLD table (all-or-nothing)");
|
||
CHECK(prot_set_load_raw(0) == -1, "TC-PS-004 NULL table rejected");
|
||
}
|
||
|
||
/* TC-PS-005 */
|
||
static void tc_q10_roundtrip(void) {
|
||
float v = 12.345f;
|
||
int32_t q;
|
||
|
||
prot_set_init();
|
||
q = PROT_SET_Q10(v);
|
||
CHECK(prot_set_set(PS_OC1_CUR, q) == 0, "TC-PS-005 set ok");
|
||
{
|
||
float back = prot_set_get_f(PS_OC1_CUR);
|
||
float err = back - v;
|
||
if (err < 0.0f) { err = -err; }
|
||
CHECK(err < 0.001f, "TC-PS-005 Q10 roundtrip error < 1 LSB");
|
||
}
|
||
}
|
||
|
||
/* TC-PS-006 */
|
||
static void tc_meta_sanity(void) {
|
||
uint32_t i, j;
|
||
int ok_range = 1, ok_ctl = 1, ok_addr = 1;
|
||
|
||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||
const prot_set_meta_t *m = &g_prot_set_meta[i];
|
||
if (m->min > m->max) { ok_range = 0; }
|
||
if ((m->def < m->min) || (m->def > m->max)) { ok_range = 0; }
|
||
if ((m->is_ctl != 0u) &&
|
||
((m->min != 0) || (m->max != PROT_SET_Q10(1)))) { ok_ctl = 0; }
|
||
if (m->comm_addr == 0u) { continue; }
|
||
for (j = i + 1u; j < (uint32_t)PS_ID_COUNT; j++) {
|
||
if (g_prot_set_meta[j].comm_addr == m->comm_addr) { ok_addr = 0; }
|
||
}
|
||
}
|
||
CHECK(ok_range, "TC-PS-006 meta: min<=def<=max for all items");
|
||
CHECK(ok_ctl, "TC-PS-006 meta: ctl items are 0..1");
|
||
CHECK(ok_addr, "TC-PS-006 meta: comm addresses unique");
|
||
CHECK((uint32_t)PS_ID_COUNT <= 32u, "TC-PS-006 item count fits bad_mask bitmap");
|
||
}
|
||
|
||
/* TC-PS-007 */
|
||
static void tc_id_guard(void) {
|
||
prot_set_init();
|
||
CHECK(prot_set_get(PS_ID_COUNT) == 0, "TC-PS-007 get out-of-range id -> 0");
|
||
CHECK(prot_set_set(PS_ID_COUNT, 0) == -1, "TC-PS-007 set out-of-range id -> -1");
|
||
CHECK(prot_set_set_sealed(PS_ID_COUNT, 0) == -1, "TC-PS-007 set_sealed id guard");
|
||
}
|
||
|
||
/* TC-PS-008(B7:全局软压板 + 原子写 + 脏标志) */
|
||
static void tc_global_plate_dirty(void) {
|
||
prot_set_init();
|
||
CHECK(prot_set_dirty_peek() == 0u, "TC-PS-008 init not dirty");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-PS-008 global plate default IN");
|
||
CHECK(prot_set_set(PS_GLOBAL_EN, PROT_SET_Q10(2)) == -1, "TC-PS-008 plate rejects 2");
|
||
CHECK(prot_set_set_sealed(PS_GLOBAL_EN, 0) == 0, "TC-PS-008 plate OUT via set_sealed");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-PS-008 plate OUT readable");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-PS-008 set_sealed leaves selfcheck PASS (no unsealed window)");
|
||
CHECK(prot_set_locked() == 0u, "TC-PS-008 set_sealed no lock");
|
||
CHECK(prot_set_dirty_peek() == 1u, "TC-PS-008 set_sealed marks dirty");
|
||
CHECK(prot_set_dirty_take() == 1u, "TC-PS-008 dirty take consumes");
|
||
CHECK(prot_set_dirty_peek() == 0u, "TC-PS-008 dirty cleared after take");
|
||
prot_set_dirty_mark();
|
||
CHECK(prot_set_dirty_peek() == 1u, "TC-PS-008 dirty mark re-arms (write-fail retry)");
|
||
CHECK(prot_set_set_sealed(PS_GLOBAL_EN, PROT_SET_Q10(2)) == -1, "TC-PS-008 set_sealed range check");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-PS-008 rejected set_sealed keeps value");
|
||
}
|
||
|
||
/* TC-PS-009(B7:blob 往返) */
|
||
static void tc_store_roundtrip(void) {
|
||
uint8_t blob[PROT_SET_BLOB_SZ];
|
||
uint32_t len = 0u;
|
||
int32_t cur;
|
||
|
||
prot_set_init();
|
||
(void)prot_set_set_sealed(PS_OC1_CUR, PROT_SET_Q10(12.5));
|
||
(void)prot_set_set_sealed(PS_GLOBAL_EN, 0);
|
||
(void)prot_set_dirty_take();
|
||
cur = prot_set_get(PS_OC1_CUR);
|
||
CHECK(prot_set_store_serialize(blob, sizeof(blob), &len) == 0, "TC-PS-009 serialize ok");
|
||
CHECK(len == (uint32_t)PROT_SET_BLOB_SZ, "TC-PS-009 blob exact size");
|
||
prot_set_init();
|
||
CHECK(prot_set_get(PS_OC1_CUR) != cur, "TC-PS-009 init wiped to defaults");
|
||
CHECK(prot_set_store_parse(blob, len) == 0, "TC-PS-009 parse ok");
|
||
CHECK(prot_set_get(PS_OC1_CUR) == cur, "TC-PS-009 value round-trip bit-exact");
|
||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-PS-009 plate state round-trip");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-PS-009 parsed table sealed");
|
||
CHECK(prot_set_dirty_peek() == 0u, "TC-PS-009 parse does not mark dirty");
|
||
}
|
||
|
||
/* 伪造辅助:按 blob 值区重算尾部 sum/sumn(构造"自洽但越界"的对抗样本) */
|
||
static void s_forge_tail(uint8_t *b, uint32_t len) {
|
||
uint32_t sum = 0u;
|
||
uint32_t inv;
|
||
uint32_t i;
|
||
|
||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||
sum += s_get32(b + PROT_SET_BLOB_HDR_SZ + 4u * i);
|
||
}
|
||
inv = ~sum;
|
||
s_put32(b + len - 8u, sum);
|
||
s_put32(b + len - 4u, inv);
|
||
}
|
||
|
||
/* TC-PS-010(B7:损坏 blob 整表拒绝,all-or-nothing) */
|
||
static void tc_store_corrupt(void) {
|
||
uint8_t blob[PROT_SET_BLOB_SZ];
|
||
uint32_t len = 0u;
|
||
int32_t keep;
|
||
|
||
prot_set_init();
|
||
(void)prot_set_set_sealed(PS_OC2_CUR, PROT_SET_Q10(6.0));
|
||
keep = prot_set_get(PS_OC2_CUR);
|
||
CHECK(prot_set_store_serialize(blob, sizeof(blob), &len) == 0, "TC-PS-010 serialize ok");
|
||
|
||
blob[PROT_SET_BLOB_HDR_SZ] ^= 0x01u; /* 值区一位翻转:sum 对不符 */
|
||
CHECK(prot_set_store_parse(blob, len) == -1, "TC-PS-010 flipped value byte rejected (sum pair)");
|
||
CHECK(prot_set_get(PS_OC2_CUR) == keep, "TC-PS-010 reject leaves values untouched");
|
||
|
||
(void)prot_set_store_serialize(blob, sizeof(blob), &len);
|
||
blob[0] ^= 0xFFu; /* magic 破坏 */
|
||
CHECK(prot_set_store_parse(blob, len) == -1, "TC-PS-010 bad magic rejected");
|
||
|
||
(void)prot_set_store_serialize(blob, sizeof(blob), &len);
|
||
CHECK(prot_set_store_parse(blob, len - 4u) == -1, "TC-PS-010 truncated blob rejected");
|
||
CHECK(prot_set_store_parse(blob, len + 4u) == -1, "TC-PS-010 oversized blob rejected");
|
||
|
||
(void)prot_set_store_serialize(blob, sizeof(blob), &len);
|
||
blob[5] ^= 0x02u; /* version 高字节:1 -> 0x200 */
|
||
CHECK(prot_set_store_parse(blob, len) == -1, "TC-PS-010 version mismatch rejected");
|
||
|
||
/* 对抗样本:越限定值 + 重算 sum 对(校验全过、范围必拦) */
|
||
(void)prot_set_store_serialize(blob, sizeof(blob), &len);
|
||
s_put32(blob + PROT_SET_BLOB_HDR_SZ + 4u * (uint32_t)PS_OC1_CUR,
|
||
(uint32_t)(g_prot_set_meta[PS_OC1_CUR].max + 1));
|
||
s_forge_tail(blob, len);
|
||
CHECK(prot_set_store_parse(blob, len) == -1, "TC-PS-010 forged consistent out-of-range rejected");
|
||
CHECK(prot_set_bad_mask() == (1uL << PS_OC1_CUR), "TC-PS-010 bad_mask points at item");
|
||
CHECK(prot_set_get(PS_OC1_CUR) == PROT_SET_Q10(10.0), "TC-PS-010 defaults still intact");
|
||
CHECK(prot_set_selfcheck() == 0, "TC-PS-010 active table still self-consistent after all rejects");
|
||
}
|
||
|
||
int main(void) {
|
||
printf("=== Prot Set Tests ===\n");
|
||
tc_init();
|
||
tc_set_range();
|
||
tc_seal_selfcheck();
|
||
tc_load_raw();
|
||
tc_q10_roundtrip();
|
||
tc_meta_sanity();
|
||
tc_id_guard();
|
||
tc_global_plate_dirty();
|
||
tc_store_roundtrip();
|
||
tc_store_corrupt();
|
||
|
||
printf("\n=== Prot Set Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|