Files
STM32F4-Base/test/host/soe/soe_test.c
T
wandering1 1d96fc9f87 feat(soe): B9 动作记录 + 时标 SOE(L4-4/L4-5)+ 104 M_SP_TB_1 自发
- 新增 App/log/soe_rec.{c,h}:64B 定长 SOE 记录纯编解码(CP56Time2a
  时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS
  依赖,主机 L0.5 覆盖(32 断言,含与 iec104 CP56 解码器交叉互证)
- 新增 App/log/soe_store.{c,h}:protectTask 边沿投递(取时标 + 组记录 +
  SPSC 待发环/消息队列,零文件系统);defaultTask 经 syslog_drain 尾部
  排空落 /log/soe.log(卷锁 try / FA_WRITE|FA_OPEN_APPEND / 校验字节数 /
  容量满 unlink 重建,全套照 run.log 实证纪律;无卷损坏归类路径)
- protect_task.c:元件动作/返回、软压板投退、采样闭锁/恢复六类边沿
  → soe_store_post;post 内禁 DBG_*(protect 静态最坏链 872 B 不变,
  改前/改后 stack_report 九任务逐字节一致),丢弃留痕挪 drain 侧
- iec104:单点 YX 自发改 M_SP_TB_1(30) 带 CP56 时标(总召/周期保持
  M_SP_NA_1,单测两种都断言);link cfg 新增 epoch_ms 时标源回调
  (NULL/无效 → IV 位如实置位);serv 绑 sys_clock(秒域时基,
  ms 域 = 秒×1000,不造假亚秒)
- 主机单测:test/host/soe/(32 断言)+ iec104 94→97(TB 时标/IV);
  iec104_master.py 解析 type 30(B6/B7 流不回归);decode_soe.py
  台架解码断言工具(seq 单调/校验和/UTC 交叉)
- 注册:uvprojx + check_registration SIM_EXEMPT 点名(MCU-only 胶水)
- 文档(我方属主区):`App/log/README.md` 增 soe 节(64B 记录/单写者/
  post 禁 DBG/UTC 交叉坑);`docs/子系统设计/IEC104上行设计说明.md`
  §1 范围行 + §3.2 时标注 + §7 台架行 + §8 第 2 条销项 + 变更记录行

台架腿(bench 锁 B9,release --bench-pass):run_boot_test PASS;
--b7-pre 21/21、--b7-post 13/13(B6/B7 回归 + 确定性动作边沿);
自发 YX 上送 M_SP_TB_1 带 ts(11:00:51 动作 / 11:00:53 复归);
FTP 下载 /log/soe.log 30 条记录 decode PASS(seq 0..28 单调、
ELM_ASSERT 14 条、UTC 交叉一致);定值/压板已恢复默认。

台账 +1088 B(SPSC 环 512 + 静态 FIL 528 + 计数器)随合并由 Lead
回填(ram_baseline --write + check_mem_numbers --write)。

gate: only mem-budget/mem-numbers pending lead ledger backfill
(pre-commit --all-files: 29 Passed / 2 Failed / 0 Skipped;
hygiene self-check OK: no BOM/CRLF/mojibake/trailws/non-ASCII literal)

sync 追平 v2-on-ui(B8 freq + N33 抬栈)后复跑:@build 0/0
(Code=209492 RW=1104 ZI=154184);stack_report 九任务全 PASS、
protectTask 872 B/57% 不变;ci_local L0.5 15 runner 951/951
(freq 46 / soe 32 / iec104 97 / prot_set 90 / prot_elm 47)。
顺带修 protect_task.c 陈旧注释:定值巡检 31 项 → 40 项(B8 后)。
2026-10-12 19:38:13 +08:00

214 lines
8.2 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.
/*
* 模块名称:SOE 记录编解码主机侧单元测试(B9,L0.5)
* 模块功能:离线验证 App/log/soe_rec.c——64B 定长记录构建/解析往返、
* 校验和硬门槛(撕裂/半写拒绝)、版本门槛、CP56Time2a 时标
* 编码正确性(与 iec104_asdu.c 的 CP56 解码交叉验证:同一算法族
* 两个实现互证)、IV 语义、钳位、保留区清零。
*
* 被测对象:真实源文件(#include 原样纳入):
* soe_rec.c(B9 记录编解码)
* iec104_asdu.c(交叉验证用 CP56 解码器,纯逻辑)
*
* 覆盖点:
* TC-SOE-001 构建/解析字段往返(type/elm/flags/seq/epoch/value)
* TC-SOE-002 校验和硬门槛:单字节翻转 → -3;版本篡改 → -2
* TC-SOE-003 CP56 编码正确性:已知 epoch → 期望字节序列;
* 与 iec104_cp56_to_epoch_ms 往返互证(含闰日 2024-02-29)
* TC-SOE-004 IV 语义:flags IV → CP56 byte2 bit7 置位 + epoch 字段 0
* TC-SOE-005 钳位:epoch < 2000 → 2000-01-01;epoch > 2127 上界 → 2127-12-31
* TC-SOE-006 结构不变量:size=64、保留区 [23..62] 恒 0、sum=sum8([0..62])
* TC-SOE-007 序号域:0 / 0xFFFFFFFF 边界往返(uint32 回绕语义)
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-12(B9)
*/
#include <stdint.h>
#include <stdio.h>
#include <string.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 "soe_rec.c"
#include "iec104_asdu.c"
/* ---------------- TC-SOE-001 字段往返 ---------------- */
static void tc_roundtrip(void)
{
uint8_t rec[SOE_REC_SIZE];
soe_rec_t out;
int rc;
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 2u, 0u, 12345u,
1760234567u, 0xDEADBEEFu);
rc = soe_rec_parse(rec, &out);
CHECK(rc == 0, "TC-001 parse ok");
CHECK(out.ver == SOE_REC_VER, "TC-001 ver");
CHECK(out.type == (uint8_t)SOE_T_ELM_ASSERT, "TC-001 type");
CHECK(out.elm_id == 2u, "TC-001 elm_id");
CHECK(out.flags == 0u, "TC-001 flags");
CHECK(out.seq == 12345u, "TC-001 seq");
CHECK(out.epoch_s == 1760234567u, "TC-001 epoch");
CHECK(out.value == 0xDEADBEEFu, "TC-001 value");
CHECK(out.cp56[6] == (uint8_t)((2025u - 2000u) & 0x7Fu), "TC-001 cp56 year=2025");
}
/* ---------------- TC-SOE-002 校验和/版本硬门槛 ---------------- */
static void tc_gates(void)
{
uint8_t rec[SOE_REC_SIZE];
soe_rec_t out;
uint32_t i;
int bad = 0;
soe_rec_build(rec, (uint8_t)SOE_T_ELM_RETURN, 0u, 0u, 7u, 1760234567u, 3u);
/* 每个数据字节单独翻转:解析必须全部拒绝(sum 覆盖 [0..62]) */
for (i = 0u; i < SOE_OFF_SUM; i++) {
uint8_t save = rec[i];
rec[i] ^= 0x01u;
if (soe_rec_parse(rec, &out) == 0) {
bad = 1;
}
rec[i] = save;
}
CHECK(bad == 0, "TC-002 any byte flip rejected");
CHECK(soe_rec_parse(rec, &out) == 0, "TC-002 intact parses ok");
rec[SOE_OFF_VER] = (uint8_t)(SOE_REC_VER + 1u); /* 版本篡改:sum 仍按新
* 字节算会自洽,但版本
* 门槛先拒 */
CHECK(soe_rec_parse(rec, &out) == -2, "TC-002 bad version -> -2");
CHECK(soe_rec_parse(NULL, &out) == -1, "TC-002 null args -> -1");
}
/* ---------------- TC-SOE-003 CP56 正确性 + 交叉往返 ---------------- */
static void tc_cp56(void)
{
uint8_t rec[SOE_REC_SIZE];
soe_rec_t out;
uint64_t back;
int rc;
/* 2026-10-12 12:34:56 UTC = 1791808496(手算:2026-10-12 = epoch 1791806400
*(当日 00:00 UTC)+ 12*3600+34*60+56 = 45296 → 1791851696? 以解码器为准,
* 这里用"往返互证 + 显式字段"双保险,不依赖手算) */
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 1u, 1791808496u, 0u);
(void)soe_rec_parse(rec, &out);
/* 显式字段:12:34:56 → ms 域 = 56*1000(秒域时基如实编码,亚秒 0) */
CHECK(((uint32_t)out.cp56[0] | ((uint32_t)out.cp56[1] << 8)) == 56000u,
"TC-003 ms field = sec*1000 (56000)");
rc = iec104_cp56_to_epoch_ms(out.cp56, &back);
CHECK(rc == 0, "TC-003 cross decode ok");
CHECK(back == 1791808496000ULL, "TC-003 cross roundtrip exact");
/* 闰日 2024-02-29 00:00:00 UTC = 1709164800 */
soe_rec_build(rec, (uint8_t)SOE_T_SAMPLE_BLOCK, 0u, 0u, 2u, 1709164800u, 0u);
(void)soe_rec_parse(rec, &out);
CHECK(out.cp56[4] == 29u && out.cp56[5] == 2u && out.cp56[6] == 24u,
"TC-003 leap day fields");
rc = iec104_cp56_to_epoch_ms(out.cp56, &back);
CHECK((rc == 0) && (back == 1709164800000ULL), "TC-003 leap roundtrip");
/* 2000-01-01 00:00:00 边界 */
soe_rec_build(rec, (uint8_t)SOE_T_PLATE_IN, 0u, 0u, 3u, 946684800u, 0u);
(void)soe_rec_parse(rec, &out);
rc = iec104_cp56_to_epoch_ms(out.cp56, &back);
CHECK((rc == 0) && (back == 946684800000ULL), "TC-003 year-2000 boundary");
}
/* ---------------- TC-SOE-004 IV 语义 ---------------- */
static void tc_iv(void)
{
uint8_t rec[SOE_REC_SIZE];
soe_rec_t out;
uint64_t back;
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 1u, SOE_FLG_IV, 9u, 0u, 1u);
(void)soe_rec_parse(rec, &out);
CHECK((out.cp56[2] & 0x80u) != 0u, "TC-004 cp56 IV bit set");
CHECK(out.epoch_s == 0u, "TC-004 epoch field zeroed");
CHECK(iec104_cp56_to_epoch_ms(out.cp56, &back) == -2,
"TC-004 cross decoder rejects IV");
}
/* ---------------- TC-SOE-005 钳位 ---------------- */
static void tc_clamp(void)
{
uint8_t rec[SOE_REC_SIZE];
soe_rec_t out;
uint64_t back;
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 4u, 100u, 0u);
(void)soe_rec_parse(rec, &out);
CHECK((out.cp56[4] == 1u) && (out.cp56[5] == 1u) && (out.cp56[6] == 0u),
"TC-005 clamp low -> 2000-01-01");
(void)iec104_cp56_to_epoch_ms(out.cp56, &back);
CHECK(back == 946684800000ULL, "TC-005 clamp low exact");
/* epoch_s 为 uint32 → 最大 2106-02-07(0xFFFFFFFF),恒在 CP56 域内:
* 上界钳位是防御性死分支(epoch 域扩展时生效),此处断言精确往返 */
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 5u, 0xFFFFFFFFu, 0u);
(void)soe_rec_parse(rec, &out);
CHECK(out.cp56[6] == 106u, "TC-005 uint32 max -> year 2106 (no clamp needed)");
(void)iec104_cp56_to_epoch_ms(out.cp56, &back);
CHECK(back == 4294967295000ULL, "TC-005 uint32 max exact roundtrip");
}
/* ---------------- TC-SOE-006 结构不变量 ---------------- */
static void tc_layout(void)
{
uint8_t rec[SOE_REC_SIZE];
uint32_t i;
uint8_t s = 0u;
int zero_ok = 1;
CHECK(SOE_REC_SIZE == 64u, "TC-006 record size 64");
soe_rec_build(rec, (uint8_t)SOE_T_SAMPLE_RESUME, SOE_ELM_NONE, 0u,
0u, 946684800u, 0u);
for (i = 23u; i <= 62u; i++) {
if (rec[i] != 0u) {
zero_ok = 0;
}
}
CHECK(zero_ok, "TC-006 reserved [23..62] zero");
for (i = 0u; i < SOE_OFF_SUM; i++) {
s = (uint8_t)(s + rec[i]);
}
CHECK(rec[SOE_OFF_SUM] == s, "TC-006 sum8 covers [0..62]");
CHECK(rec[SOE_OFF_ELM] == SOE_ELM_NONE, "TC-006 elm none");
}
/* ---------------- TC-SOE-007 序号边界 ---------------- */
static void tc_seq_edges(void)
{
uint8_t rec[SOE_REC_SIZE];
soe_rec_t out;
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 0u, 946684800u, 0u);
CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0u), "TC-007 seq 0");
soe_rec_build(rec, (uint8_t)SOE_T_ELM_ASSERT, 0u, 0u, 0xFFFFFFFFu,
946684800u, 0u);
CHECK((soe_rec_parse(rec, &out) == 0) && (out.seq == 0xFFFFFFFFu),
"TC-007 seq 0xFFFFFFFF");
}
int main(void)
{
printf("=== SOE Record Codec Host Tests ===\n");
tc_roundtrip();
tc_gates();
tc_cp56();
tc_iv();
tc_clamp();
tc_layout();
tc_seq_edges();
printf("=== SOE Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}