Files
STM32F4-Base/App/protocol/iec104/iec104_link.c
T
wandering1 5869c0307b fix(104): 消掉 cppcheck 两根常驻红——回显不再传未初始化缓冲、blen 收作用域
L0 静态门禁(pre-commit)历史 36 次启动里 28 红 / 7 绿 / 1 取消,最后一次绿是
10-12 19:17 的 run 23;此后连续全红。恒定的一根就是 cppcheck:本机(与 runner
同机、同一 Machine 级 Cppcheck 2.21.0)以钩子原样参数复现,诊断与 runner 日志
task-54/58/60/62/64、以及 master 上那条「Failing after 6m49s」的归档 run 26
逐字一致——不是版本漂移,也不是抑制文件缺失。

两条诊断与处置:
- s_cmd_con:body 仅在 n>0 时写入,n==0(回显超长退化为空信息体)时把未初始
  化缓冲连同长度 0 一起传给 s_send_asdu。现实现内 len>0 才 memcpy,故无运行时
  缺陷,但契约脆弱、门禁照拦。改为把 body 声明收进真要回显的分支,空信息体
  分支显式传 NULL。
- s_spont_step:blen 只在 pend_yx 分支使用,声明移入该分支。

验证(基线 b24b2b5):cppcheck 原样复刻钩子 args → RC=0 零输出;@build 0 错误
0 警告(Code 231712→231732,+20 B);ram_baseline.py --update 重盖体积同码凭证、
--write 同步受管区台账,check_mem_numbers.py --write 同步 RAM 快照表。本次触发的
14 根钩子全 Passed(含 test-coverage)。

环境与遗留(非本笔引入):这台 runner 原本只有 VS2019/MSVC 14.29,没有
Microsoft.CodeCoverage.Console.exe,test-coverage 恒 [ENV] exit 2;今日已装
VS2026 BuildTools 18.11.12324.101(E 盘,含 mscoverage 18.11 与 MSVC
14.44/14.51/14.52),本笔按 14.51.36231 跑通覆盖率总闸 40/40 套、相对基线零
回退——与量基线那台同版本,故分母可比。CI 侧仍差两步:① 机器级 DTU_MSVC_CL
还指 C 盘 14.29,需改指 E 盘这套并重启 runner 守护进程(SYSTEM 只在进程启动
时读机器变量);② ci_runner_bootstrap.ps1 的 Find-MsvcCl 只扫 %ProgramFiles%
派生的 C 盘根且取首个命中(2019 在前),重跑 -SetEnv 会把对齐抹掉,应改用
vswhere + 版本降序。
2026-10-27 04:27:11 +08:00

804 lines
26 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 链路层(实现)
* 模块功能:实现 iec104_link.h——APCI 流重组与 U/S/I 编解码、STARTDT/STOPDT/
* TESTFR、k/w 窗与 t1/t2/t3(差值法,tick 回绕安全)、总召分片、
* COV 突发(k 窗满暂存不丢)、周期上送、遥控/定值两段式、C_RD、
* C_CS 对时、错误应答(COT 12/13/14 + EVI)。
* 失败语义:坏帧/序号错/N(R) 越窗/缓冲溢出 → 实例置 IDLE(链路故障),
* 由胶水层检测 IDLE 跳变断开 socket;本层不感知 I/O。
* 创建日期:2026-10-12(B4)
*/
#include "iec104_link.h"
#include "iec104_asdu.h"
#include <string.h>
/* ---------------- APCI 常量 ---------------- */
#define APCI_START 0x68u
#define APCI_CTRL_LEN 4u /* len 字段最小值(纯控制域) */
#define APCI_LEN_MIN 4u
#define APCI_LEN_MAX 252u
#define SEQ_MOD 32768u /* 序号 15bit */
/* U 帧控制字节(DL/T 634.5104 表:低 2 位恒 11) */
#define U_STARTDT_ACT 0x07u
#define U_STARTDT_CON 0x0Bu
#define U_STOPDT_ACT 0x13u
#define U_STOPDT_CON 0x23u
#define U_TESTFR_ACT 0x43u
#define U_TESTFR_CON 0x83u
/* 本实现单 ASDU 信息体区上限(背景 YC 组 8×5=40 B 最大) */
#define BODY_MAX 64u
/* 周期上送周期(COT=1) */
#define PERIOD_MS 60000UL
/*
* 函数功能:超时判定(now 到达/越过 deadline,tick 回绕安全)
* 返 回 值:1 已到期 / 0 未到期
*/
static int s_expired(uint32_t now, uint32_t deadline) {
return (((int32_t)(now - deadline)) >= 0) ? 1 : 0;
}
/* ---------------- 发送原语 ---------------- */
/*
* 函数功能:发 U 帧(STARTDT/STOPDT/TESTFR act|con)
* 说明:t1 只约束"需确认的发送"(标准语义)——TESTFR act 置挂起并起 t1;
* STARTDT/STOPDT con 不启动 t1(主站可合法静默,靠 t3 探活)。
*/
static void s_send_u(iec104_link_t *lk, uint8_t q) {
uint8_t buf[6];
if (lk->cfg->send_apdu == NULL) {
return;
}
buf[0] = APCI_START;
buf[1] = APCI_CTRL_LEN;
buf[2] = q;
buf[3] = 0u;
buf[4] = 0u;
buf[5] = 0u;
lk->cfg->send_apdu(lk->ctx, buf, 6u);
/* t1 只约束"需确认的发送"(标准语义):TESTFR act 等 con;
* STARTDT/STOPDT con 不启动 t1——主站可合法静默,靠 t3 探活 */
if (q == U_TESTFR_ACT) {
lk->testfr_pending = 1u;
lk->t1_deadline = lk->now_ms + IEC104_T1_MS;
}
lk->t3_deadline = lk->now_ms + IEC104_T3_MS;
}
/*
* 函数功能:发 S 帧(确认已收序号,rx_unacked 清零)
* 说明:S 不需对端确认 → 不起 t1,只刷 t3。
*/
static void s_send_s(iec104_link_t *lk) {
uint8_t buf[6];
uint16_t r = (uint16_t)(lk->rx_seq << 1);
if (lk->cfg->send_apdu == NULL) {
return;
}
buf[0] = APCI_START;
buf[1] = APCI_CTRL_LEN;
buf[2] = 0x01u;
buf[3] = 0u;
buf[4] = (uint8_t)(r & 0xFFu);
buf[5] = (uint8_t)(r >> 8);
lk->cfg->send_apdu(lk->ctx, buf, 6u);
lk->rx_unacked = 0u;
lk->t3_deadline = lk->now_ms + IEC104_T3_MS; /* S 不需确认 → 不起 t1 */
}
/*
* 函数功能:发一个 I 帧(asdu 由调用方组好,N(S)/N(R) 由本层填)
* 返 回 值:0 已发出 / -1 k 窗满或超长(调用方保留数据下次重试)
*/
static int s_send_i(iec104_link_t *lk, const uint8_t *asdu, uint16_t len) {
uint8_t buf[6u + BODY_MAX];
uint16_t s16;
uint16_t r16;
if ((lk->cfg->send_apdu == NULL) || (len > BODY_MAX)) {
return -1;
}
if (lk->tx_unacked >= IEC104_K_MAX) {
return -1;
}
buf[0] = APCI_START;
buf[1] = (uint8_t)(APCI_CTRL_LEN + len);
s16 = (uint16_t)(lk->tx_seq << 1);
r16 = (uint16_t)(lk->rx_seq << 1);
buf[2] = (uint8_t)(s16 & 0xFFu);
buf[3] = (uint8_t)(s16 >> 8);
buf[4] = (uint8_t)(r16 & 0xFFu);
buf[5] = (uint8_t)(r16 >> 8);
(void)memcpy(&buf[6], asdu, len);
lk->tx_seq = (uint16_t)((lk->tx_seq + 1u) % SEQ_MOD);
lk->tx_unacked++;
lk->cfg->send_apdu(lk->ctx, buf, (uint16_t)(6u + len));
lk->t1_deadline = lk->now_ms + IEC104_T1_MS;
lk->t3_deadline = lk->now_ms + IEC104_T3_MS;
return 0;
}
/*
* 函数功能:组 ASDU(hdr [+ ioa] + body)并发 I 帧
* 说明:evi=1 → OA bit7(EVI,未知 IOA/类型/地址类应答)
* 返 回 值:同 s_send_i
*/
static int s_send_asdu(iec104_link_t *lk, 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 body_len, uint8_t evi) {
uint8_t buf[6u + BODY_MAX];
uint16_t n;
n = iec104_asdu_put_hdr(buf, type, vsq, cot, cot_hi, lk->cfg->ca);
if (evi != 0u) {
buf[3] |= 0x80u; /* OA bit7 = EVI */
}
if (with_ioa != 0u) {
iec104_put_ioa(&buf[n], ioa);
n += 3u;
}
if (body_len > 0u) {
if ((uint32_t)n + (uint32_t)body_len > (uint32_t)sizeof(buf)) {
return -1;
}
(void)memcpy(&buf[n], body, body_len);
n += body_len;
}
return s_send_i(lk, buf, n);
}
/*
* 函数功能:命令应答(actcon/deactcon 等)——原样回显信息体(ioa+限定词)
* 入口参数:pn = 1 置否定确认位
*/
static void s_cmd_con(iec104_link_t *lk, uint8_t type, const uint8_t *obj,
uint16_t obj_len, uint8_t cot, uint8_t pn) {
uint16_t n = obj_len;
if (n > BODY_MAX) {
n = 0u; /* 超长回显退化为空信息体 */
}
if (n > 0u) {
uint8_t body[BODY_MAX]; /* 作用域收进真要回显的分支,不把未初始化缓冲传下去 */
(void)memcpy(body, obj, n);
(void)s_send_asdu(lk, type, 1u, cot,
(pn != 0u) ? IEC104_COT_BIT_PN : 0u,
0u, 0u, body, n, 0u);
} else {
(void)s_send_asdu(lk, type, 1u, cot,
(pn != 0u) ? IEC104_COT_BIT_PN : 0u,
0u, 0u, NULL, 0u, 0u);
}
}
/* ---------------- 数据上送 ---------------- */
/*
* 函数功能:单点上送遥测(M_ME_NC_1 SQ=0,值+QDS)
* 返 回 值:同 s_send_i
*/
static int s_send_yc_single(iec104_link_t *lk, uint8_t idx, uint8_t cot) {
uint8_t body[5];
float v;
uint8_t qds;
(void)iec104_pts_yc_read(idx, &v, &qds);
iec104_put_float(body, v);
body[4] = qds;
return s_send_asdu(lk, IEC104_T_M_ME_NC_1, 1u, cot, 0u, 1u,
iec104_pts_yc_ioa(idx), body, 5u, 0u);
}
/*
* 函数功能:总召/周期扫描一步(phase 游标 = interp_yx_idx:
* 0 双点组→1 单点组→2 YC 组→3 频率→4 actterm)
* 入口参数:budget = 本 poll 发送预算(成功一步减一);periodic = 周期扫描
* 返 回 值:0 本步已发出 / -1 k 窗满原地保留(poll 循环必须就此停步,
* 否则死循环——2026-10-12 单测抓到)
*/
static int s_scan_step(iec104_link_t *lk, uint8_t *budget, uint8_t periodic) {
uint8_t body[BODY_MAX];
uint8_t n = 0u;
uint8_t i;
uint8_t d;
uint8_t qds;
float v;
int rc = -1;
switch (lk->interp_yx_idx) {
case 0u: /* YX 双点组 SQ=1,IOA 1..4 */
for (i = 0u; i < 4u; i++) {
iec104_pts_yx_read(i, &d);
body[n++] = d;
}
rc = s_send_asdu(lk, IEC104_T_M_DP_NA_1, (uint8_t)(IEC104_VSQ_SQ | 4u),
lk->interp_cot, 0u, 1u, 1u, body, n, 0u);
break;
case 1u: /* YX 单点组 SQ=1,IOA 5..33 */
for (i = 4u; i < 33u; i++) {
iec104_pts_yx_read(i, &d);
body[n++] = d;
}
rc = s_send_asdu(lk, IEC104_T_M_SP_NA_1, (uint8_t)(IEC104_VSQ_SQ | 29u),
lk->interp_cot, 0u, 1u, 5u, body, n, 0u);
break;
case 2u: /* YC 组 SQ=1,IOA 0x4011..0x4018 */
for (i = 0u; i < 8u; i++) {
(void)iec104_pts_yc_read(i, &v, &qds);
iec104_put_float(&body[n], v);
n += 4u;
body[n++] = qds;
}
rc = s_send_asdu(lk, IEC104_T_M_ME_NC_1, (uint8_t)(IEC104_VSQ_SQ | 8u),
lk->interp_cot, 0u, 1u, 0x4011u, body, n, 0u);
break;
case 3u: /* 频率单点 SQ=0,IOA 0x401D */
(void)iec104_pts_yc_read(8u, &v, &qds);
iec104_put_float(&body[n], v);
n += 4u;
body[n++] = qds;
rc = s_send_asdu(lk, IEC104_T_M_ME_NC_1, 1u, lk->interp_cot, 0u,
1u, 0x401Du, body, n, 0u);
break;
default: /* actterm(仅总召;周期扫描不走这步) */
iec104_put_ioa(&body[n], 0u);
n += 3u;
body[n++] = IEC104_RQT_GROUP1;
rc = s_send_asdu(lk, IEC104_T_C_IC_NA_1, 1u, IEC104_COT_ACTTERM, 0u,
0u, 0u, body, n, 0u);
break;
}
if (rc != 0) {
return -1; /* k 窗满:下次 poll 重试本 phase */
}
(*budget)--;
lk->interp_yx_idx++;
if (periodic != 0u) {
if (lk->interp_yx_idx >= 4u) {
lk->interp_step = 0u; /* 周期只发 YC(phase 2..3),无 actterm */
}
} else if (lk->interp_yx_idx >= 5u) {
lk->interp_step = 0u;
lk->init_pending = 0u;
}
return 0;
}
/*
* 函数功能:突发(COV)上送——pend 暂存逐点 SQ=0 COT=3,发成功才清位
* 函数说明:单点遥信(idx≥4)自发用 **M_SP_TB_1**(SIQ + CP56Time2a,B9:
* 变位带时标 = SOE 上送路径);双点(idx<4,V0.1 恒 IV)保持
* M_DP_NA_1;总召/周期扫描仍走 s_scan_step 的 M_SP_NA_1/M_DP_NA_1
* (时标只用于自发,标准语义——防"全量换型")。
* 时标 = cfg->epoch_ms(ctx);NULL/UINT64_MAX → CP56 IV 位如实置位。
*/
static void s_spont_step(iec104_link_t *lk, uint8_t *budget) {
uint8_t body[8]; /* 最大 = SIQ + CP56(7)(M_SP_TB_1) */
uint8_t i;
uint8_t d;
uint8_t type;
int rc;
while ((*budget > 0u) && ((lk->pend_yx != 0u) || (lk->pend_yc != 0u))) {
if (lk->pend_yx != 0u) {
uint8_t blen;
i = 0u;
while (i < 33u) {
if ((lk->pend_yx & (1uLL << i)) != 0u) {
break;
}
i++;
}
iec104_pts_yx_read(i, &d);
body[0] = d;
blen = 1u;
if (i < 4u) {
type = (uint8_t)IEC104_T_M_DP_NA_1;
} else {
uint64_t ems = (lk->cfg->epoch_ms != NULL)
? lk->cfg->epoch_ms(lk->ctx)
: UINT64_MAX;
type = (uint8_t)IEC104_T_M_SP_TB_1;
(void)iec104_epoch_ms_to_cp56((ems == UINT64_MAX) ? 0u : ems,
&body[1]);
if (ems == UINT64_MAX) {
body[1 + 2] |= 0x80u; /* CP56 byte2 bit7 IV:时钟无效如实上送 */
}
blen = 8u;
}
rc = s_send_asdu(lk, type, 1u, IEC104_COT_SPONT, 0u, 1u,
(uint32_t)i + 1u, body, blen, 0u);
if (rc != 0) {
return; /* k 窗满:pend 位保留,下次续发 */
}
lk->pend_yx &= ~(1uLL << i);
} else {
i = 0u;
while (i < 16u) {
if ((lk->pend_yc & (uint16_t)(1u << i)) != 0u) {
break;
}
i++;
}
rc = s_send_yc_single(lk, i, IEC104_COT_SPONT);
if (rc != 0) {
return;
}
lk->pend_yc &= (uint16_t)~(1u << i);
}
(*budget)--;
}
}
/* ---------------- 命令处理 ---------------- */
/*
* 函数功能:C_IC_NA_1 总召处理(ACT → actcon + 启动扫描;DEACT → deactcon)
* 说明:RBT(bit4)落在 COT 6bit 值域内,ACT+RBT 上收为 22——先剥再判;
* 首召/RBT → COT=4(init),后续 COT=2(bg)
*/
static void s_h_ic(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
/* RBT(bit4)落在 COT 6bit 值域内:ACT+RBT 上收为 22——先剥 RBT 再判值 */
uint8_t cot_val = (uint8_t)(h->cot & 0xEFu);
if (cot_val == IEC104_COT_DEACT) {
lk->interp_step = 0u;
s_cmd_con(lk, IEC104_T_C_IC_NA_1, h->obj, h->obj_len,
IEC104_COT_DEACTCON, 0u);
return;
}
if (cot_val != IEC104_COT_ACT) {
return;
}
if ((h->obj_len < 4u) || (h->obj[3] != IEC104_RQT_GROUP1)) {
s_cmd_con(lk, IEC104_T_C_IC_NA_1, h->obj, h->obj_len,
IEC104_COT_ACTCON, 1u);
return;
}
s_cmd_con(lk, IEC104_T_C_IC_NA_1, h->obj, h->obj_len, IEC104_COT_ACTCON, 0u);
lk->interp_step = 1u;
lk->interp_yx_idx = 0u;
lk->interp_cot = ((lk->init_pending != 0u)
|| ((h->cot & IEC104_COT_BIT_RBT) != 0u))
? (uint8_t)IEC104_COT_INIT : (uint8_t)IEC104_COT_BG_SCAN;
}
/*
* 函数功能:C_CS_NA_1 对时(CP56 → epoch → cfg->clock_sync)
* 说明:IV/字段坏/系统侧拒绝 → actcon + PN
*/
static void s_h_cs(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint64_t epoch = 0u;
int rc = -1;
if (h->obj_len >= 10u) {
rc = iec104_cp56_to_epoch_ms(&h->obj[3], &epoch);
if ((rc == 0) && (lk->cfg->clock_sync != NULL)) {
rc = lk->cfg->clock_sync(lk->ctx, epoch);
}
}
s_cmd_con(lk, IEC104_T_C_CS_NA_1, h->obj, h->obj_len, IEC104_COT_ACTCON,
(rc == 0) ? 0u : 1u);
}
/*
* 函数功能:C_DC_NA_1 遥控两段式(选择记 ioa+dpc;执行须匹配,否则 PN)
*/
static void s_h_dc(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint32_t ioa;
uint8_t qdc;
uint8_t sel;
uint8_t dpc;
int rc;
if (h->obj_len < 4u) {
return;
}
ioa = iec104_get_ioa(h->obj);
qdc = h->obj[3];
sel = (uint8_t)(((qdc & IEC104_QDC_S) != 0u) ? 1u : 0u);
dpc = (uint8_t)(qdc & 0x03u);
if (sel != 0u) {
rc = iec104_pts_yk_exec(ioa, dpc, 1u);
if (rc == 0) {
lk->yk_sel_ioa = ioa;
lk->yk_sel_dpc = dpc;
}
} else {
if ((lk->yk_sel_ioa == ioa) && (lk->yk_sel_dpc == dpc)) {
rc = iec104_pts_yk_exec(ioa, dpc, 0u);
} else {
rc = -1; /* 无选择/失配 → 否定确认 */
}
lk->yk_sel_ioa = 0u;
lk->yk_sel_dpc = 0u;
}
s_cmd_con(lk, IEC104_T_C_DC_NA_1, h->obj, h->obj_len, IEC104_COT_ACTCON,
(rc == 0) ? 0u : 1u);
}
/*
* 函数功能:C_SE_NC_1 定值两段式写(选择段只校验;执行段落盘)
* 说明:越界/未知地址 → actcon + PN
*/
static void s_h_se(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint32_t addr;
float v;
uint8_t sel;
int rc;
if (h->obj_len < 8u) {
return;
}
addr = iec104_get_ioa(h->obj);
v = iec104_get_float(&h->obj[3]);
sel = (uint8_t)(((h->obj[7] & IEC104_QDC_S) != 0u) ? 1u : 0u);
rc = iec104_pts_set_write(addr, v, sel);
s_cmd_con(lk, IEC104_T_C_SE_NC_1, h->obj, h->obj_len, IEC104_COT_ACTCON,
(rc == 0) ? 0u : 1u);
}
/*
* 函数功能:C_RD_NA_1 读单个信息体(YC 命中 → M_ME COT5;定值命中 → 同;
* 未知 IOA → 原样回 C_RD + COT14 + EVI)
*/
static void s_h_rd(iec104_link_t *lk, const iec104_asdu_hdr_t *h) {
uint32_t ioa;
uint8_t i;
float v;
if (h->obj_len < 3u) {
return;
}
ioa = iec104_get_ioa(h->obj);
for (i = 0u; i < iec104_pts_yc_count(); i++) {
if (iec104_pts_yc_ioa(i) == ioa) {
s_send_yc_single(lk, i, IEC104_COT_REQ);
return;
}
}
if (iec104_pts_set_read(ioa, &v) == 0) {
uint8_t body[5];
iec104_put_float(body, v);
body[4] = 0u;
(void)s_send_asdu(lk, IEC104_T_M_ME_NC_1, 1u, IEC104_COT_REQ, 0u,
1u, ioa, body, 5u, 0u);
return;
}
(void)s_send_asdu(lk, IEC104_T_C_RD_NA_1, 1u, IEC104_COT_UNK_IOA, 0u,
1u, ioa, NULL, 0u, 1u);
}
/*
* 函数功能:ASDU 分派(公共地址校验 → 类型路由)
* 说明:错 ca → COT13+EVI;未知类型 → COT12+EVI(原类型回)
*/
static void s_h_asdu(iec104_link_t *lk, const uint8_t *asdu, uint16_t len) {
iec104_asdu_hdr_t h;
if (iec104_asdu_parse_hdr(asdu, len, &h) != 0) {
return;
}
if (h.ca != lk->cfg->ca) {
(void)s_send_asdu(lk, h.type, 1u, IEC104_COT_UNK_CA, 0u,
0u, 0u, NULL, 0u, 1u);
return;
}
switch (h.type) {
case IEC104_T_C_IC_NA_1:
s_h_ic(lk, &h);
break;
case IEC104_T_C_CS_NA_1:
s_h_cs(lk, &h);
break;
case IEC104_T_C_DC_NA_1:
s_h_dc(lk, &h);
break;
case IEC104_T_C_SE_NC_1:
s_h_se(lk, &h);
break;
case IEC104_T_C_RD_NA_1:
s_h_rd(lk, &h);
break;
case IEC104_T_C_TS_NA_1:
s_cmd_con(lk, IEC104_T_C_TS_NA_1, h.obj, h.obj_len,
IEC104_COT_ACTCON, 0u);
break;
case IEC104_T_C_RP_NA_1:
iec104_pts_cov_check(NULL); /* 进程复位:重建快照(清历史) */
s_cmd_con(lk, IEC104_T_C_RP_NA_1, h.obj, h.obj_len,
IEC104_COT_ACTCON, 0u);
break;
default:
(void)s_send_asdu(lk, h.type, 1u, IEC104_COT_UNK_COT, 0u,
0u, 0u, NULL, 0u, 1u);
break;
}
}
/* ---------------- APDU 分派 ---------------- */
/*
* 函数功能:U 帧处理(STARTDT/STOPDT act → con + 态迁移;TESTFR act → con;
* TESTFR con → 清挂起)
*/
static void s_h_u(iec104_link_t *lk, uint8_t q) {
switch (q) {
case U_STARTDT_ACT:
s_send_u(lk, U_STARTDT_CON);
lk->state = IEC104_ST_RUN;
lk->interp_step = 0u;
lk->yk_sel_ioa = 0u;
break;
case U_STOPDT_ACT:
s_send_u(lk, U_STOPDT_CON);
lk->state = IEC104_ST_STOPPED;
lk->interp_step = 0u;
break;
case U_TESTFR_ACT:
s_send_u(lk, U_TESTFR_CON);
break;
case U_TESTFR_CON:
lk->testfr_pending = 0u;
break;
default:
break; /* con/未知 U:t1/t3 已由外层刷新 */
}
}
/*
* 函数功能:N(R) 校验并更新 tx_unacked(V(A) ≤ V(A)' ≤ V(S),即 0 ≤ d ≤ 窗内)
* 说明:N(R) 倒退(早于上次确认)= 协议违例 → 链路故障(标准语义);
* tx_unacked 恒 ≤ k(发送侧门控),故 d ≤ tx_unacked 已蕴含窗校验
* 返 回 值:0 / -1 违例
*/
static int s_apply_nr(iec104_link_t *lk, uint16_t nr) {
uint16_t d = (uint16_t)(((uint32_t)lk->tx_seq + SEQ_MOD - (uint32_t)nr)
% SEQ_MOD);
if ((uint32_t)d > (uint32_t)lk->tx_unacked) {
return -1;
}
lk->tx_unacked = d;
return 0;
}
/*
* 函数功能:单个完整 APDU 分派(U/S/I);任何接收只刷 t3(不刷 t1)
* 说明:OPEN 态只认 U;I 帧 N(S) 不连续 → IDLE;RUN 态才处理 ASDU;
* 收满 w 个未确认 → 回 S
*/
static void s_h_apdu(iec104_link_t *lk, const uint8_t *p, uint16_t total) {
const uint8_t *c = &p[2]; /* 控制域 4B */
uint16_t ns;
uint16_t nr;
lk->t3_deadline = lk->now_ms + IEC104_T3_MS;
if ((c[0] & 0x03u) == 0x03u) {
s_h_u(lk, c[0]);
return;
}
if (lk->state == IEC104_ST_OPEN) {
return; /* OPEN 态只认 U 帧 */
}
if ((c[0] & 0x01u) != 0u) { /* S 格式 */
nr = (uint16_t)(((uint16_t)c[2] | ((uint16_t)c[3] << 8)) >> 1);
if (s_apply_nr(lk, nr) != 0) {
lk->state = IEC104_ST_IDLE;
}
return;
}
/* I 格式 */
ns = (uint16_t)(((uint16_t)c[0] | ((uint16_t)c[1] << 8)) >> 1);
nr = (uint16_t)(((uint16_t)c[2] | ((uint16_t)c[3] << 8)) >> 1);
if (ns != lk->rx_seq) {
lk->state = IEC104_ST_IDLE; /* 序号错 → 链路故障 */
return;
}
if (s_apply_nr(lk, nr) != 0) {
lk->state = IEC104_ST_IDLE;
return;
}
lk->rx_seq = (uint16_t)((ns + 1u) % SEQ_MOD);
lk->rx_unacked++;
if (lk->rx_unacked == 1u) {
lk->t2_deadline = lk->now_ms + IEC104_T2_MS;
}
if (lk->state == IEC104_ST_RUN) {
s_h_asdu(lk, &p[6], (uint16_t)(total - 6u));
}
if (lk->rx_unacked >= IEC104_W_MAX) {
s_send_s(lk);
}
}
/* ---------------- 公开接口 ---------------- */
/*
* 函数功能:链路实例初始化(全清零 + 绑配置与上下文,态 = IDLE)
*/
void iec104_link_init(iec104_link_t *lk, const iec104_link_cfg_t *cfg,
void *ctx) {
(void)memset(lk, 0, sizeof(*lk));
lk->cfg = cfg;
lk->ctx = ctx;
lk->state = IEC104_ST_IDLE;
}
/*
* 函数功能:TCP 连接建立 → OPEN(序号/窗口/定时/暂存全部复位,init_pending=1)
*/
void iec104_link_open(iec104_link_t *lk, uint32_t now_ms) {
lk->state = IEC104_ST_OPEN;
lk->tx_seq = 0u;
lk->rx_seq = 0u;
lk->tx_unacked = 0u;
lk->rx_unacked = 0u;
lk->testfr_pending = 0u;
lk->init_pending = 1u;
lk->rx_len = 0u;
lk->yk_sel_ioa = 0u;
lk->yk_sel_dpc = 0u;
lk->interp_step = 0u;
lk->interp_yx_idx = 0u;
lk->pend_yx = 0u;
lk->pend_yc = 0u;
lk->now_ms = now_ms;
lk->t1_deadline = now_ms + IEC104_T1_MS;
lk->t2_deadline = now_ms + IEC104_T2_MS;
lk->t3_deadline = now_ms + IEC104_T3_MS;
lk->period_next = now_ms + PERIOD_MS;
}
/*
* 函数功能:链路断开/故障 → IDLE(清重组缓冲与所有暂存)
*/
void iec104_link_close(iec104_link_t *lk) {
lk->state = IEC104_ST_IDLE;
lk->rx_len = 0u;
lk->interp_step = 0u;
lk->pend_yx = 0u;
lk->pend_yc = 0u;
lk->yk_sel_ioa = 0u;
lk->yk_sel_dpc = 0u;
}
/*
* 函数功能:喂入收到的原始字节(APCI 流重组:半帧/粘帧逐处)
* 说明:坏起始符/坏长度/缓冲溢出 → IDLE(胶水层检测后断开 socket)
*/
void iec104_link_on_rx(iec104_link_t *lk, const uint8_t *data, uint16_t len,
uint32_t now_ms) {
lk->now_ms = now_ms;
if ((lk->state == IEC104_ST_IDLE) || (data == NULL)) {
return;
}
if ((uint32_t)len > (uint32_t)(IEC104_RXBUF_SZ - lk->rx_len)) {
lk->state = IEC104_ST_IDLE; /* 重组缓冲溢出 = 坏流 */
lk->rx_len = 0u;
return;
}
(void)memcpy(&lk->rx_buf[lk->rx_len], data, len);
lk->rx_len = (uint16_t)(lk->rx_len + len);
while (lk->rx_len >= 2u) {
uint16_t lf;
uint16_t need;
if (lk->rx_buf[0] != APCI_START) {
lk->state = IEC104_ST_IDLE;
break;
}
lf = lk->rx_buf[1];
if ((lf < APCI_LEN_MIN) || (lf > APCI_LEN_MAX)) {
lk->state = IEC104_ST_IDLE;
break;
}
need = (uint16_t)(lf + 2u);
if (lk->rx_len < need) {
break; /* 半帧:等下次 on_rx */
}
s_h_apdu(lk, lk->rx_buf, need);
if (lk->state == IEC104_ST_IDLE) {
break;
}
if (need < lk->rx_len) {
(void)memmove(lk->rx_buf, &lk->rx_buf[need],
(uint16_t)(lk->rx_len - need));
}
lk->rx_len = (uint16_t)(lk->rx_len - need);
}
if (lk->state == IEC104_ST_IDLE) {
lk->rx_len = 0u;
}
}
/*
* 函数功能:周期节拍(胶水层每次服务循环调用一次,时基由调用方注入)
* 说明:t1 超时 → close;t3 到期 → TESTFR act;t2 到期 → S;
* RUN 态按优先级:总召续扫 > 周期续扫 > COV 突发 > 周期触发
*/
void iec104_link_poll(iec104_link_t *lk, uint32_t now_ms) {
uint8_t budget = IEC104_POLL_BUDGET;
lk->now_ms = now_ms;
if ((lk->state != IEC104_ST_RUN) && (lk->state != IEC104_ST_STOPPED)) {
return;
}
if (((lk->tx_unacked > 0u) || (lk->testfr_pending != 0u))
&& (s_expired(now_ms, lk->t1_deadline) != 0)) {
iec104_link_close(lk); /* t1:未确认 I/未答 TESTFR 超时 = 故障 */
return;
}
if (s_expired(now_ms, lk->t3_deadline) != 0) {
s_send_u(lk, U_TESTFR_ACT);
}
if ((lk->rx_unacked > 0u) && (s_expired(now_ms, lk->t2_deadline) != 0)) {
s_send_s(lk);
}
if (lk->state != IEC104_ST_RUN) {
return;
}
if (lk->interp_step == 1u) {
while ((budget > 0u) && (lk->interp_step == 1u)) {
if (s_scan_step(lk, &budget, 0u) != 0) {
break; /* k 窗满:本 poll 停步(防死循环) */
}
}
return;
}
if (lk->interp_step == 3u) {
while ((budget > 0u) && (lk->interp_step == 3u)) {
if (s_scan_step(lk, &budget, 1u) != 0) {
break;
}
}
return;
}
if ((lk->pend_yx != 0u) || (lk->pend_yc != 0u)) {
s_spont_step(lk, &budget);
if (budget == 0u) {
return;
}
}
if (s_expired(now_ms, lk->period_next) != 0) {
lk->interp_step = 3u;
lk->interp_yx_idx = 2u; /* 周期只扫 YC(phase 2..3) */
lk->interp_cot = IEC104_COT_PERIODIC;
lk->period_next = now_ms + PERIOD_MS;
while ((budget > 0u) && (lk->interp_step == 3u)) {
if (s_scan_step(lk, &budget, 1u) != 0) {
break;
}
}
return;
}
{
iec104_pts_cov_t cov;
iec104_pts_cov_check(&cov);
lk->pend_yx |= cov.yx_changed;
lk->pend_yc |= cov.yc_changed;
s_spont_step(lk, &budget);
}
}
/*
* 函数功能:查询链路状态(胶水层检测 IDLE 跳变以断开/重建 socket)
*/
iec104_state_t iec104_link_state(const iec104_link_t *lk) {
return lk->state;
}