Files
STM32F4-Base/test/host/iec104/iec104_test.c
T
wandering1 1fe163ba7b 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 / 零乱码
2026-10-12 15:01:49 +08:00

921 lines
31 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.
/*
* 模块名称:IEC 60870-5-104 被控站协议栈主机侧单元测试
* 模块功能:在 PC 上对 App/protocol/iec104 全栈(asdu + pts + link)做离线验证。
*
* 被测对象:真实源文件(#include 原样纳入):
* iec104_asdu.c + iec104_pts.c + iec104_link.c(B4 协议核心)
* prot_set.c + prot_elm.c + phasor.c + sample_ring.c(板端默认源
* 的链接依赖;定值两段写走**真实 prot_set**)
* 数据源经 iec104_pts_bind_src 注入假源(YC/YX/YK 可控),
* 发送经 send_apdu 回调全量捕获,时基由测试注入。
*
* 覆盖点(docs/子系统设计/IEC104上行设计说明.md §7):
* TC-104-001 CP56Time2a 往返 / 闰年 / IV / 钳位
* TC-104-002 IOA 3B 与短浮点小端往返
* TC-104-003 APCI 重组:半帧 / 粘帧 / 坏起始符 / 坏长度
* TC-104-004 STARTDT 握手 → RUN
* TC-104-005 OPEN 态 I 帧忽略(序号不消费)
* TC-104-006 总召全流程:actcon→4 分片(首召 COT=4)→actterm;二召 COT=2;RBT→COT=4
* TC-104-007 遥信变位 COV(COT=3);无效位不触发
* TC-104-008 遥测死区 COV:死区内不发 / 越死区发
* TC-104-009 k 窗:12 未确认停发;S 确认后恢复
* TC-104-010 w 阈:收满 8 发 S
* TC-104-011 t2 到期发 S
* TC-104-012 t3 空闲发 TESTFR act;未答 t1 超时 → IDLE;con 清挂起不误杀
* TC-104-013 遥控两段式:选择/执行成功;失配 PN;拒绝类选择段即 PN
* TC-104-014 定值两段写(真实 prot_set):越界 PN;执行后 get_f + selfcheck
* TC-104-015 C_RD:YC 命中 / 定值命中 / 未知 IOA COT14+EVI
* TC-104-016 C_CS:clock_sync 收正确 epoch;IV → PN;clock_sync 失败 → PN
* TC-104-017 未知类型 COT12+EVI;错公共地址 COT13+EVI
* TC-104-018 STOPDT:数据停发,TESTFR 仍应答
* TC-104-019 序号错 → IDLE(链路故障)
* TC-104-020 pts:无效通道 IV / 未就绪 NT / 无效遥信 IV / is_dp / 表尺寸
* TC-104-021 C_TS / C_RP actcon
* TC-104-022 周期上送 COT=1(YC 组 + 频率)
* TC-104-023 k 窗满 COV 不丢事件(pend 暂存,窗开后补发)
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-12(B4)
*/
#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 "iec104_asdu.c"
#include "prot_set.c"
#include "sample_ring.c"
#include "phasor.c"
#include "prot_elm.c"
#include "iec104_pts.c"
#include "iec104_link.c"
/* ---------------- 假数据源(定值转发真实 prot_set) ---------------- */
static float s_f_yc[9];
static int s_f_yc_rc[9];
static uint64_t s_f_bits;
static uint64_t s_f_valid;
static uint32_t s_f_yk_ioa;
static uint8_t s_f_yk_dpc;
static uint8_t s_f_yk_sel;
static int f_yc(uint8_t idx, float *out)
{
if (idx >= 9u) {
return -1;
}
if (s_f_yc_rc[idx] != 0) {
return s_f_yc_rc[idx];
}
*out = s_f_yc[idx];
return 0;
}
static uint64_t f_bits(void) { return s_f_bits; }
static uint64_t f_valid(void) { return s_f_valid; }
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)
{
return s_src_set_read(addr, out);
}
static int f_set_write(uint32_t addr, float v, uint8_t sel)
{
return s_src_set_write(addr, v, sel);
}
static const iec104_pts_src_t g_fake = {
f_yc, f_bits, f_valid, f_yk, f_set_read, f_set_write
};
/* ---------------- 发送捕获 + 对时假回调 ---------------- */
#define CAP_MAX 48
#define CAP_SZ 260
static uint8_t s_cap[CAP_MAX][CAP_SZ];
static int s_cap_n;
static uint64_t s_clock_ms;
static int s_clock_rc;
static void cap_send(void *ctx, const uint8_t *apdu, uint16_t len)
{
(void)ctx;
if ((s_cap_n < CAP_MAX) && (len <= CAP_SZ)) {
(void)memcpy(s_cap[s_cap_n], apdu, len);
s_cap_n++;
}
}
static int f_clock(void *ctx, uint64_t ms)
{
(void)ctx;
s_clock_ms = ms;
return s_clock_rc;
}
static iec104_link_t g_lk;
static const iec104_link_cfg_t g_cfg = { 1u, cap_send, f_clock };
/* ---------------- 主站侧模拟(N(R) 单调,照真实主站) ---------------- */
static uint16_t m_ns;
static uint16_t m_nr;
static void m_reset(void)
{
m_ns = 0u;
m_nr = 0u;
s_cap_n = 0;
}
static void feed_u(uint8_t q, uint32_t now)
{
uint8_t b[6];
b[0] = 0x68u; b[1] = 4u; b[2] = q; b[3] = 0u; b[4] = 0u; b[5] = 0u;
iec104_link_on_rx(&g_lk, b, 6u, now);
}
static void feed_i(const uint8_t *asdu, uint16_t len, uint32_t now)
{
uint8_t b[6u + 250u];
uint16_t fs = (uint16_t)(m_ns << 1);
uint16_t fr = (uint16_t)(m_nr << 1); /* N(R) 单调(feed_s 推进) */
b[0] = 0x68u;
b[1] = (uint8_t)(4u + len);
b[2] = (uint8_t)(fs & 0xFFu);
b[3] = (uint8_t)(fs >> 8);
b[4] = (uint8_t)(fr & 0xFFu);
b[5] = (uint8_t)(fr >> 8);
(void)memcpy(&b[6], asdu, len);
iec104_link_on_rx(&g_lk, b, (uint16_t)(6u + len), now);
m_ns = (uint16_t)((m_ns + 1u) % 32768u);
}
static void feed_s(uint16_t nr, uint32_t now)
{
uint8_t b[6];
uint16_t f = (uint16_t)(nr << 1);
b[0] = 0x68u; b[1] = 4u; b[2] = 1u; b[3] = 0u;
b[4] = (uint8_t)(f & 0xFFu);
b[5] = (uint8_t)(f >> 8);
m_nr = nr; /* 主站确认推进(单调) */
iec104_link_on_rx(&g_lk, b, 6u, now);
}
static uint16_t mk_asdu(uint8_t *b, uint8_t type, uint8_t vsq, uint8_t cot,
uint8_t cot_hi, uint8_t with_ioa, uint32_t ioa,
const uint8_t *body, uint16_t blen)
{
uint16_t n = iec104_asdu_put_hdr(b, type, vsq, cot, cot_hi, 1u);
if (with_ioa != 0u) {
iec104_put_ioa(&b[n], ioa);
n += 3u;
}
if (blen > 0u) {
(void)memcpy(&b[n], body, blen);
n += blen;
}
return n;
}
/* ---------------- 复位 / 握手 ---------------- */
static void reset_all(uint32_t now)
{
prot_set_init();
prot_elm_init();
iec104_pts_init();
iec104_pts_bind_src(&g_fake);
(void)memset(s_f_yc, 0, sizeof(s_f_yc));
(void)memset(s_f_yc_rc, 0, sizeof(s_f_yc_rc));
s_f_bits = 0u;
s_f_valid = (1uLL << 14) | (1uLL << 15) | (1uLL << 16) | (1uLL << 17)
| (1uLL << 22) | (1uLL << 30);
s_clock_rc = 0;
s_clock_ms = 0u;
iec104_link_init(&g_lk, &g_cfg, NULL);
iec104_link_open(&g_lk, now);
m_reset();
}
static void handshake(uint32_t now)
{
feed_u(0x07u, now); /* STARTDT act */
}
/* ---------------- 捕获检查 ---------------- */
typedef struct {
uint8_t type, vsq, cot, cot_hi, oa;
uint32_t ca, ioa;
const uint8_t *obj; /* ioa 之后的信息体 */
uint16_t obj_len;
} cap_t;
static int cap_i_count(void)
{
int k, n = 0;
for (k = 0; k < s_cap_n; k++) {
if ((s_cap[k][2] & 0x01u) == 0u) {
n++;
}
}
return n;
}
static int cap_u_count(uint8_t q)
{
int k, n = 0;
for (k = 0; k < s_cap_n; k++) {
if ((s_cap[k][2] & 0x03u) == 0x03u && s_cap[k][2] == q) {
n++;
}
}
return n;
}
static int cap_has_s(uint16_t *nr_out)
{
int k;
for (k = s_cap_n - 1; k >= 0; k--) {
const uint8_t *p = s_cap[k];
if ((p[2] & 0x03u) == 0x01u) {
*nr_out = (uint16_t)(((uint16_t)p[4] | ((uint16_t)p[5] << 8)) >> 1);
return 1;
}
}
return 0;
}
static int cap_i(int n, cap_t *o)
{
int k, seen = 0;
for (k = 0; k < s_cap_n; k++) {
const uint8_t *p = s_cap[k];
uint16_t lf = p[1];
iec104_asdu_hdr_t h;
if ((p[2] & 0x01u) != 0u) {
continue;
}
if (seen == n) {
if (iec104_asdu_parse_hdr(&p[6], (uint16_t)(lf - 4u), &h) != 0) {
return -1;
}
o->type = h.type;
o->vsq = h.vsq;
o->cot = h.cot;
o->cot_hi = h.cot_hi;
o->oa = h.oa;
o->ca = h.ca;
if (h.obj_len >= 3u) {
o->ioa = iec104_get_ioa(h.obj);
o->obj = h.obj + 3u;
o->obj_len = (uint16_t)(h.obj_len - 3u);
} else {
o->ioa = 0u;
o->obj = h.obj;
o->obj_len = 0u;
}
return 0;
}
seen++;
}
return -1;
}
/* ============================ 测试用例 ============================ */
static void tc_cp56(void)
{
uint8_t b[7];
uint64_t e1 = 946684800000ULL; /* 2000-01-01 00:00:00.000 */
uint64_t e2 = 1709188496789ULL; /* 2024-02-29 06:34:56.789(闰日) */
uint64_t back = 0u;
int rc;
iec104_epoch_ms_to_cp56(e1, b);
rc = iec104_cp56_to_epoch_ms(b, &back);
CHECK((rc == 0) && (back == e1), "TC-001 CP56 往返 2000-01-01");
iec104_epoch_ms_to_cp56(e2, b);
back = 0u;
rc = iec104_cp56_to_epoch_ms(b, &back);
CHECK((rc == 0) && (back == e2), "TC-001 CP56 往返闰日 2024-02-29");
b[2] |= 0x80u;
CHECK(iec104_cp56_to_epoch_ms(b, &back) == -2, "TC-001 IV → -2");
iec104_epoch_ms_to_cp56(0u, b); /* 早于 2000 → 钳位 */
back = 0u;
(void)iec104_cp56_to_epoch_ms(b, &back);
CHECK(back == e1, "TC-001 早于 2000 钳位");
}
static void tc_ioa_float(void)
{
uint8_t b[8];
iec104_put_ioa(b, 0x123456u);
CHECK((iec104_get_ioa(b) == 0x123456u) && (b[0] == 0x56u) && (b[1] == 0x34u)
&& (b[2] == 0x12u), "TC-002 IOA 3B 小端");
iec104_put_float(b, 1.0f);
CHECK((b[0] == 0x00u) && (b[1] == 0x00u) && (b[2] == 0x80u)
&& (b[3] == 0x3Fu), "TC-002 float IEEE754 小端");
iec104_put_float(b, -12.5f);
CHECK(iec104_get_float(b) == -12.5f, "TC-002 float 往返");
}
static void tc_apci(void)
{
uint32_t now = 1000u;
reset_all(now);
{
static const uint8_t b[6] = { 0x68u, 4u, 0x07u, 0u, 0u, 0u };
iec104_link_on_rx(&g_lk, b, 3u, now);
CHECK(cap_u_count(0x0Bu) == 0, "TC-003 半帧不触发");
iec104_link_on_rx(&g_lk, &b[3], 3u, now);
CHECK(cap_u_count(0x0Bu) == 1, "TC-003 半帧重组 → con");
}
{
static const uint8_t b[12] = {
0x68u, 4u, 0x43u, 0u, 0u, 0u, /* TESTFR act */
0x68u, 4u, 0x13u, 0u, 0u, 0u /* STOPDT act */
};
iec104_link_on_rx(&g_lk, b, 12u, now);
CHECK((cap_u_count(0x83u) == 1)
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED),
"TC-003 粘帧逐处 + STOPDT 生效");
}
reset_all(now);
{
static const uint8_t b[6] = { 0x69u, 4u, 0x07u, 0u, 0u, 0u };
iec104_link_on_rx(&g_lk, b, 6u, now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 坏起始符 → IDLE");
}
reset_all(now);
{
static const uint8_t b[6] = { 0x68u, 3u, 0x07u, 0u, 0u, 0u };
iec104_link_on_rx(&g_lk, b, 6u, now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-003 坏长度 → IDLE");
}
}
static void tc_startdt(void)
{
uint32_t now = 1000u;
reset_all(now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_OPEN, "TC-004 open → OPEN");
handshake(now);
CHECK((cap_u_count(0x0Bu) == 1)
&& (iec104_link_state(&g_lk) == IEC104_ST_RUN), "TC-004 STARTDT → RUN");
}
static void tc_open_ignore(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint16_t n;
reset_all(now);
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i_count() == 0) && (iec104_link_state(&g_lk) == IEC104_ST_OPEN),
"TC-005 OPEN 态 I 帧忽略");
handshake(now);
m_ns = 0u; /* OPEN 忽略不消费序号 → 主站仍从 0 起 */
feed_i(a, n, now); /* ns=0 仍有效(序号未被消费) */
CHECK(cap_i_count() == 1, "TC-005 RUN 后同 ns 正常应答");
}
static void tc_interrogation(void)
{
uint32_t now = 5000u;
uint8_t a[32];
uint8_t body[1];
uint16_t n;
cap_t c;
reset_all(now);
s_f_yc[0] = 111.0f;
s_f_yc_rc[1] = -1; /* idx1(1Ub)无效通道 */
handshake(now);
body[0] = IEC104_RQT_GROUP1;
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now);
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 100u)
&& (c.cot == IEC104_COT_ACTCON), "TC-006 actcon 即发 COT7");
iec104_link_poll(&g_lk, now);
CHECK(cap_i_count() == 5, "TC-006 首 poll 发满预算 4 分片");
CHECK((cap_i(1, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT)
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 4u)) && (c.ioa == 1u),
"TC-006 YX 双点组 首召 COT=4");
CHECK((cap_i(2, &c) == 0) && (c.type == 1u)
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 29u)) && (c.ioa == 5u),
"TC-006 YX 单点组 29 点");
CHECK((cap_i(3, &c) == 0) && (c.type == 13u)
&& (c.vsq == (uint8_t)(IEC104_VSQ_SQ | 8u)) && (c.ioa == 0x4011u)
&& (c.obj_len == 40u), "TC-006 YC 组 8 点 40B");
if (cap_i(3, &c) == 0) {
CHECK((iec104_get_float(c.obj) == 111.0f) && (c.obj[4] == 0u),
"TC-006 YC 值 111.0 QDS=0");
CHECK(c.obj[9] == IEC104_QDS_IV, "TC-006 无效通道 QDS=IV");
}
CHECK((cap_i(4, &c) == 0) && (c.ioa == 0x401Du) && (c.vsq == 1u),
"TC-006 频率单点 SQ=0");
iec104_link_poll(&g_lk, now + 100u);
CHECK((cap_i_count() == 6) && (cap_i(5, &c) == 0) && (c.type == 100u)
&& (c.cot == IEC104_COT_ACTTERM), "TC-006 actterm 收尾");
/* 二召:init 已清 → COT=2 */
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now + 200u);
iec104_link_poll(&g_lk, now + 200u);
CHECK((cap_i(7, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_BG_SCAN),
"TC-006 二召 COT=2");
/* RBT 位 → COT=4(actcon3 是第 12 个未确认帧 → 先 S 确认窗才开) */
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, IEC104_COT_BIT_RBT,
1u, 0u, body, 1u);
feed_i(a, n, now + 300u);
feed_s(12u, now + 300u);
iec104_link_poll(&g_lk, now + 300u);
CHECK((cap_i(12, &c) == 0) && (c.type == 3u) && (c.cot == IEC104_COT_INIT),
"TC-006 RBT → COT=4");
}
static void tc_cov_yx(void)
{
uint32_t now = 1000u;
cap_t c;
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now); /* 建快照 */
CHECK(cap_i_count() == 0, "TC-007 首 poll 无自发");
s_f_bits |= (1uLL << 15); /* YX16(idx15)动作 */
iec104_link_poll(&g_lk, now + 100u);
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 1u)
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 16u)
&& (c.obj_len == 1u) && (c.obj[0] == 1u), "TC-007 YX16 变位 COT3");
s_f_bits &= ~(1uLL << 15); /* 复归 */
iec104_link_poll(&g_lk, now + 200u);
CHECK((cap_i_count() == 2) && (cap_i(1, &c) == 0) && (c.ioa == 16u)
&& (c.obj[0] == 0u), "TC-007 YX16 复归再变");
s_f_bits |= (1uLL << 0); /* idx0 无效位 → 不触发 */
iec104_link_poll(&g_lk, now + 300u);
CHECK(cap_i_count() == 2, "TC-007 无效位变位不触发");
}
static void tc_cov_yc(void)
{
uint32_t now = 1000u;
cap_t c;
reset_all(now);
handshake(now);
s_f_yc[0] = 100.0f;
iec104_link_poll(&g_lk, now); /* 快照 = 100 */
s_f_yc[0] = 105.0f; /* 死区 10 内 */
iec104_link_poll(&g_lk, now + 100u);
CHECK(cap_i_count() == 0, "TC-008 死区内不上送");
s_f_yc[0] = 130.0f; /* 130-105=25 > 10 */
iec104_link_poll(&g_lk, now + 200u);
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 13u)
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 0x4011u)
&& (iec104_get_float(c.obj) == 130.0f), "TC-008 越死区上送");
}
static void feed_rd(uint32_t now)
{
uint8_t a[16];
uint16_t n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u,
1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
}
static void tc_k_window(void)
{
uint32_t now = 1000u;
int i;
reset_all(now);
handshake(now);
for (i = 0; i < 12; i++) {
feed_rd(now);
}
CHECK(cap_i_count() == 12, "TC-009 k=12 封顶");
feed_rd(now);
CHECK(cap_i_count() == 12, "TC-009 第 13 个被 k 窗抑制");
feed_s(12u, now); /* 全确认 → 窗开 */
feed_rd(now);
CHECK(cap_i_count() == 13, "TC-009 S 确认后恢复发送");
}
static void tc_w_thresh(void)
{
uint32_t now = 1000u;
uint16_t nr = 0xFFFFu;
int i;
reset_all(now);
handshake(now);
for (i = 0; i < 7; i++) {
feed_rd(now);
}
CHECK(cap_has_s(&nr) == 0, "TC-010 收 7 未发 S");
feed_rd(now); /* 第 8 个 → w 阈 */
CHECK((cap_has_s(&nr) == 1) && (nr == 8u), "TC-010 w=8 → S nr=8");
}
static void tc_t2(void)
{
uint32_t now = 1000u;
uint16_t nr = 0xFFFFu;
reset_all(now);
handshake(now);
feed_rd(now); /* rx_unacked=1,t2 起爆 */
iec104_link_poll(&g_lk, now + 9999u);
CHECK(cap_has_s(&nr) == 0, "TC-011 t2 未到期不发 S");
iec104_link_poll(&g_lk, now + 10001u);
CHECK((cap_has_s(&nr) == 1) && (nr == 1u), "TC-011 t2 到期发 S");
}
static void tc_t3_t1(void)
{
uint32_t now = 1000u;
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now + 19999u);
CHECK(cap_u_count(0x43u) == 0, "TC-012 t3 未到期无 TESTFR");
iec104_link_poll(&g_lk, now + 20001u);
CHECK(cap_u_count(0x43u) == 1, "TC-012 t3 到期发 TESTFR act");
iec104_link_poll(&g_lk, now + 20001u + 15001u);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-012 TESTFR 未答 t1 → IDLE");
/* con 清挂起 → 不误杀 */
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now + 20001u);
CHECK(cap_u_count(0x43u) == 1, "TC-012 重连 t3 再触发");
feed_u(0x83u, now + 20100u); /* TESTFR con */
iec104_link_poll(&g_lk, now + 35000u);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_RUN, "TC-012 con 清挂起后 t1 不误杀");
}
static void tc_dc(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint8_t body[1];
uint16_t n;
cap_t c;
reset_all(now);
handshake(now);
body[0] = 0x82u; /* S=1, DPC=ON */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 52u) && (c.cot == IEC104_COT_ACTCON)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-013 选择段 actcon");
CHECK((s_f_yk_sel == 1u) && (s_f_yk_ioa == 0x600Au), "TC-013 选择段转发源");
body[0] = 0x02u; /* 执行 ON */
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u)
&& (s_f_yk_sel == 0u), "TC-013 执行段 actcon");
/* 失配:选 ON 执 OFF */
body[0] = 0x82u;
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
body[0] = 0x01u;
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x600Au, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(3, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-013 执行失配 PN");
/* 拒绝类(YK1):选择段即 PN */
body[0] = 0x82u;
n = mk_asdu(a, IEC104_T_C_DC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0x6000u, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(4, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"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;
uint8_t a[24];
uint8_t body[5];
uint16_t n;
uint32_t addr = g_prot_set_meta[PS_OC1_CUR].comm_addr; /* 0x8858 */
cap_t c;
reset_all(now);
handshake(now);
CHECK(addr == 0x8858u, "TC-014 OC1 电流定值通讯地址");
iec104_put_float(body, 5.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);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 50u)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u), "TC-014 定值选择 actcon");
body[4] = 0u; /* 执行段 */
n = mk_asdu(a, IEC104_T_C_SE_NC_1, 1u, IEC104_COT_ACT, 0u, 1u, addr, body, 5u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) == 0u),
"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);
feed_i(a, n, now);
CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-014 越界选择段 PN");
}
static void tc_rd(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint16_t n;
cap_t c;
reset_all(now);
handshake(now);
s_f_yc[0] = 222.0f;
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_REQ)
&& (iec104_get_float(c.obj) == 222.0f), "TC-015 C_RD YC 命中");
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x8858u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && (c.type == 13u) && (c.ioa == 0x8858u),
"TC-015 C_RD 定值命中");
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x9999u, NULL, 0u);
feed_i(a, n, now);
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)
{
uint32_t now = 1000u;
uint8_t a[24];
uint8_t cp[7];
uint16_t n;
cap_t c;
uint64_t e = 1709188496789ULL;
reset_all(now);
handshake(now);
(void)iec104_epoch_ms_to_cp56(e, cp);
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 103u)
&& ((c.cot_hi & IEC104_COT_BIT_PN) == 0u) && (s_clock_ms == e),
"TC-016 C_CS 对时成功 epoch 精确");
cp[2] |= 0x80u; /* IV */
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-016 CP56 IV → PN");
(void)iec104_epoch_ms_to_cp56(e, cp);
s_clock_rc = -1; /* 系统侧拒绝 */
n = mk_asdu(a, IEC104_T_C_CS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, cp, 7u);
feed_i(a, n, now);
CHECK((cap_i(2, &c) == 0) && ((c.cot_hi & IEC104_COT_BIT_PN) != 0u),
"TC-016 clock_sync 失败 → PN");
}
static void tc_err_resp(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint16_t n, m;
cap_t c;
reset_all(now);
handshake(now);
n = mk_asdu(a, 200u, 1u, IEC104_COT_ACT, 0u, 1u, 0u, NULL, 0u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 200u) && (c.cot == IEC104_COT_UNK_COT)
&& ((c.oa & 0x80u) != 0u), "TC-017 未知类型 COT12+EVI");
m = iec104_asdu_put_hdr(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 2u);
a[m] = IEC104_RQT_GROUP1;
m++;
feed_i(a, m, now); /* ca=2 ≠ 1 */
CHECK((cap_i(1, &c) == 0) && (c.cot == IEC104_COT_UNK_CA)
&& ((c.oa & 0x80u) != 0u), "TC-017 错公共地址 COT13+EVI");
}
static void tc_stopdt(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint8_t body[1];
uint16_t n;
reset_all(now);
handshake(now);
feed_u(0x13u, now); /* STOPDT act */
CHECK((cap_u_count(0x23u) == 1)
&& (iec104_link_state(&g_lk) == IEC104_ST_STOPPED), "TC-018 STOPDT con");
body[0] = IEC104_RQT_GROUP1;
n = mk_asdu(a, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now);
CHECK(cap_i_count() == 0, "TC-018 STOPPED 态数据服务关");
feed_u(0x43u, now);
CHECK(cap_u_count(0x83u) == 1, "TC-018 STOPPED 态 TESTFR 仍应答");
}
static void tc_seq_err(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint16_t n;
reset_all(now);
handshake(now);
m_ns = 5u; /* 期望 0,投 5 */
n = mk_asdu(a, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_REQ, 0u, 1u, 0x4011u, NULL, 0u);
feed_i(a, n, now);
CHECK(iec104_link_state(&g_lk) == IEC104_ST_IDLE, "TC-019 序号错 → IDLE");
}
static void tc_pts(void)
{
float v;
uint8_t q;
uint8_t d;
reset_all(1000u);
CHECK((iec104_pts_yc_count() == 9u) && (iec104_pts_yx_count() == 33u)
&& (iec104_pts_yc_ioa(8u) == 0x401Du), "TC-020 表尺寸/IOA");
s_f_yc[0] = 7.5f;
CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (v == 7.5f) && (q == 0u),
"TC-020 有效 YC");
s_f_yc_rc[1] = -1;
(void)iec104_pts_yc_read(1u, &v, &q);
CHECK((v == 0.0f) && (q == IEC104_QDS_IV), "TC-020 无效通道 IV");
s_f_yc_rc[2] = -2;
(void)iec104_pts_yc_read(2u, &v, &q);
CHECK(q == IEC104_QDS_NT, "TC-020 未就绪 NT");
iec104_pts_yx_read(0u, &d);
CHECK(d == IEC104_DIQ_IV, "TC-020 无效遥信 IV");
CHECK((iec104_pts_yx_is_dp(0u) == 1u) && (iec104_pts_yx_is_dp(4u) == 0u),
"TC-020 is_dp 分界");
s_f_bits = (1uLL << 15);
iec104_pts_yx_read(15u, &d);
CHECK(d == 1u, "TC-020 有效遥信 ON");
}
static void tc_ts_rp(void)
{
uint32_t now = 1000u;
uint8_t a[16];
uint8_t body[2];
uint16_t n;
cap_t c;
reset_all(now);
handshake(now);
body[0] = 0u;
n = mk_asdu(a, IEC104_T_C_TS_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(0, &c) == 0) && (c.type == 104u) && (c.cot == IEC104_COT_ACTCON),
"TC-021 C_TS actcon");
n = mk_asdu(a, IEC104_T_C_RP_NA_1, 1u, IEC104_COT_ACT, 0u, 1u, 0u, body, 1u);
feed_i(a, n, now);
CHECK((cap_i(1, &c) == 0) && (c.type == 105u) && (c.cot == IEC104_COT_ACTCON),
"TC-021 C_RP actcon");
}
static void tc_periodic(void)
{
uint32_t now = 1000u;
cap_t c;
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now + 60001u);
CHECK(cap_i_count() == 2, "TC-022 周期上送 2 分片");
CHECK((cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_PERIODIC)
&& (c.ioa == 0x4011u), "TC-022 周期 YC 组 COT=1");
CHECK((cap_i(1, &c) == 0) && (c.ioa == 0x401Du), "TC-022 周期频率点");
}
static void tc_cov_kfull(void)
{
uint32_t now = 1000u;
int i;
cap_t c;
reset_all(now);
handshake(now);
iec104_link_poll(&g_lk, now); /* 先建快照(首 poll 只建不算变位) */
for (i = 0; i < 12; i++) {
feed_rd(now); /* k 窗占满 */
}
s_f_bits |= (1uLL << 14); /* YX15 变位 */
iec104_link_poll(&g_lk, now + 100u);
CHECK(cap_i_count() == 12, "TC-023 k 满时自发发不出");
feed_s(12u, now + 200u); /* 窗开 */
iec104_link_poll(&g_lk, now + 300u);
CHECK((cap_i_count() == 13) && (cap_i(12, &c) == 0) && (c.ioa == 15u)
&& (c.cot == IEC104_COT_SPONT), "TC-023 窗开后补发不丢事件");
}
int main(void)
{
printf("=== IEC104 Protocol Stack Host Tests ===\n");
tc_cp56();
tc_ioa_float();
tc_apci();
tc_startdt();
tc_open_ignore();
tc_interrogation();
tc_cov_yx();
tc_cov_yc();
tc_k_window();
tc_w_thresh();
tc_t2();
tc_t3_t1();
tc_dc();
tc_dc_yk12();
tc_se();
tc_rd();
tc_cs();
tc_err_resp();
tc_stopdt();
tc_seq_err();
tc_pts();
tc_ts_rp();
tc_periodic();
tc_cov_kfull();
printf("=== IEC104 Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}