feat(protect): B7 定值持久化 + 全局软压板 + L5-1 竞态收口(W1 代码腿完成)
- 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 / 零乱码
This commit is contained in:
@@ -84,6 +84,10 @@ static int f_yk(uint32_t ioa, uint8_t dpc, uint8_t sel)
|
||||
s_f_yk_ioa = ioa;
|
||||
s_f_yk_dpc = dpc;
|
||||
s_f_yk_sel = sel;
|
||||
if (ioa == 0x600Bu) {
|
||||
/* YK12 全局软压板(B7):转真实板端源(prot_set_set_sealed 原子路径) */
|
||||
return s_src_yk_exec(ioa, dpc, sel);
|
||||
}
|
||||
return (ioa == 0x600Au) ? 0 : -1;
|
||||
}
|
||||
static int f_set_read(uint32_t addr, float *out)
|
||||
@@ -610,6 +614,48 @@ static void tc_dc(void)
|
||||
"TC-013 无出口硬件 YK 选择段 PN");
|
||||
}
|
||||
|
||||
/* TC-024(B7):YK12 全局软压板两段式转真(经真实板端源 + 原子封存) */
|
||||
static void tc_dc_yk12(void)
|
||||
{
|
||||
uint32_t now = 1000u;
|
||||
uint8_t a[16];
|
||||
uint8_t body[1];
|
||||
uint16_t n;
|
||||
cap_t c;
|
||||
|
||||
reset_all(now);
|
||||
handshake(now);
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 软压板默认投入");
|
||||
body[0] = 0x81u; /* S=1, DPC=OFF:选择段 */
|
||||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(0, &c) == 0) && (c.cot == IEC104_COT_ACTCON)
|
||||
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-024 YK12 选择段 actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 选择段不动作");
|
||||
body[0] = 0x01u; /* 执行 OFF */
|
||||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
|
||||
"TC-024 YK12 执行段 actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 0u, "TC-024 执行段软压板转真退出");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-024 set_sealed 原子封存 → selfcheck 通过");
|
||||
CHECK(prot_set_dirty_peek() == 1u, "TC-024 置脏待持久化");
|
||||
body[0] = 0x82u; /* 再投入:选择 ON */
|
||||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
body[0] = 0x02u; /* 执行 ON */
|
||||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
|
||||
"TC-024 投入执行 actcon");
|
||||
CHECK(prot_set_get_ctl(PS_GLOBAL_EN) == 1u, "TC-024 软压板转真投入");
|
||||
body[0] = 0x80u; /* DPC=00 非法:选择段即 PN */
|
||||
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Bu, body, 1u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
|
||||
"TC-024 非法 DPC 选择段 PN");
|
||||
}
|
||||
|
||||
static void tc_se(void)
|
||||
{
|
||||
uint32_t now = 1000u;
|
||||
@@ -635,6 +681,7 @@ static void tc_se(void)
|
||||
"TC-014 定值执行 actcon");
|
||||
CHECK(prot_set_get_f(PS_OC1_CUR) == 5.0f, "TC-014 定值已写入(真实 prot_set)");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-014 执行后 seal → selfcheck 通过");
|
||||
CHECK(prot_set_dirty_peek() == 1u, "TC-014 执行段置脏(B7 待落盘)");
|
||||
iec104_put_float(body, 100000.0f); /* 越上限 */
|
||||
body[4] = IEC104_QDC_S;
|
||||
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
|
||||
@@ -666,6 +713,10 @@ static void tc_rd(void)
|
||||
CHECK((cap_i(2, &c) == 0) && (c.type == 102u)
|
||||
&& (c.cot == IEC104_COT_UNK_IOA) && ((c.oa & 0x80u) != 0u),
|
||||
"TC-015 未知 IOA COT14+EVI");
|
||||
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0u, NULL, 0u);
|
||||
feed_i(a, n, now);
|
||||
CHECK((cap_i(3, &c) == 0) && (c.cot == IEC104_COT_UNK_IOA),
|
||||
"TC-015 本地定值项(comm_addr=0)不可寻址(B7)");
|
||||
}
|
||||
|
||||
static void tc_cs(void)
|
||||
@@ -852,6 +903,7 @@ int main(void)
|
||||
tc_t2();
|
||||
tc_t3_t1();
|
||||
tc_dc();
|
||||
tc_dc_yk12();
|
||||
tc_se();
|
||||
tc_rd();
|
||||
tc_cs();
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
* 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
|
||||
@@ -138,6 +144,100 @@ 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) {
|
||||
@@ -149,6 +249,9 @@ int main(void) {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user