@@ -0,0 +1,868 @@
/*
* 模块名称: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 ;
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 " ) ;
}
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 通过 " ) ;
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 " ) ;
}
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_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 ;
}