/* * 模块名称:SOE 记录编解码主机侧纯逻辑单元测试(B9) * 模块功能:离线验证 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 #include #include 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-SOE-001 parse ok"); CHECK(out.ver == SOE_REC_VER, "TC-SOE-001 ver"); CHECK(out.type == (uint8_t)SOE_T_ELM_ASSERT, "TC-SOE-001 type"); CHECK(out.elm_id == 2u, "TC-SOE-001 elm_id"); CHECK(out.flags == 0u, "TC-SOE-001 flags"); CHECK(out.seq == 12345u, "TC-SOE-001 seq"); CHECK(out.epoch_s == 1760234567u, "TC-SOE-001 epoch"); CHECK(out.value == 0xDEADBEEFu, "TC-SOE-001 value"); CHECK(out.cp56[6] == (uint8_t)((2025u - 2000u) & 0x7Fu), "TC-SOE-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-SOE-002 any byte flip rejected"); CHECK(soe_rec_parse(rec, &out) == 0, "TC-SOE-002 intact parses ok"); rec[SOE_OFF_VER] = (uint8_t)(SOE_REC_VER + 1u); /* 版本篡改:sum 仍按新 * 字节算会自洽,但版本 * 门槛先拒 */ CHECK(soe_rec_parse(rec, &out) == -2, "TC-SOE-002 bad version -> -2"); CHECK(soe_rec_parse(NULL, &out) == -1, "TC-SOE-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-SOE-003 ms field = sec*1000 (56000)"); rc = iec104_cp56_to_epoch_ms(out.cp56, &back); CHECK(rc == 0, "TC-SOE-003 cross decode ok"); CHECK(back == 1791808496000ULL, "TC-SOE-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-SOE-003 leap day fields"); rc = iec104_cp56_to_epoch_ms(out.cp56, &back); CHECK((rc == 0) && (back == 1709164800000ULL), "TC-SOE-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-SOE-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-SOE-004 cp56 IV bit set"); CHECK(out.epoch_s == 0u, "TC-SOE-004 epoch field zeroed"); CHECK(iec104_cp56_to_epoch_ms(out.cp56, &back) == -2, "TC-SOE-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-SOE-005 clamp low -> 2000-01-01"); (void)iec104_cp56_to_epoch_ms(out.cp56, &back); CHECK(back == 946684800000ULL, "TC-SOE-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-SOE-005 uint32 max -> year 2106 (no clamp needed)"); (void)iec104_cp56_to_epoch_ms(out.cp56, &back); CHECK(back == 4294967295000ULL, "TC-SOE-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-SOE-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-SOE-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-SOE-006 sum8 covers [0..62]"); CHECK(rec[SOE_OFF_ELM] == SOE_ELM_NONE, "TC-SOE-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-SOE-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-SOE-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; }