90db528032
W1-0 台账三件套:scripts/ram_baseline.py(四模式)+ ram_baseline.tsv 趋势 + docs/内存管理/RAM预算台账.md + pre-commit mem-budget 钩子(RW_IRAM1/RW_CCM >92% 即 FAIL + Top-10 附表);审查报告 §5.5 三条闭环。 B4 IEC 60870-5-104 上行(被控站,TCP 2404):App/protocol/iec104/ 三模块 (asdu 编解码 / pts 点表+数据源 vtable / link APCI+FSM,纯逻辑零 I/O)+ App/task/iec104_serv.c(iec104Task,NET_READY 屏障 100ms 分片喂位)。 82 断言 L0.5 全绿 + iec104-unit-test 钩子(第 31 钩子); 设计说明 docs/子系统设计/IEC104上行设计说明.md 全量回填。 B5 protectTask(L3 板端接线):App/task/protect_task.c——20ms 判据节拍 prot_elm_run + L5-4 采样故障闭锁(adc_health 电平位→元件复位)+ L5-1 定值 校验和巡检;新增 prot_elm_request_reset() 收口跨上下文复归(104 YK11 落请求、 eval 头部消费);定值/元件初始化移入 app_main_init(内核前)。TC-PE-010 (prot_elm 28 用例)。 门禁与内存:TASK_WD_PROTECT=8 入 required_mask(产品 0x77→0x1F7、HW 层 0x1F5,看门狗单测 39 断言同步);FreeRTOS 堆 48→56KB(.ioc L1 参数 + FreeRTOSConfig.h 同步,RAM预算台账 §6 登记配额 48→56KB)。 ZI 144,008→152,204(+8,196 B = 堆 +8KB,B5 扩容);Code=204,348。 stack_report:protectTask 512 words 静态链 872 B/余 57%、iec104Task 640 words 1456 B/余 43% 均 PASS;RW_IRAM1 73.1% / RW_CCM 87.9%(<92%)。 验证:@build 0 错 0 警;ci_local 全绿(31 钩子 + 13 主机侧单测); doc-links 135 文档 PASS。登记簿 B4/B5 销项、W1 波次完成;保护规划/看门狗 说明/AGENTS(9 任务)/RAM 三文档全部同步。 遗留(验收窗口,非代码空洞):台架第三方 104 主站往返(送检窗口)、 标准源注入动作判据、@test PROT 锚点 + protect 栈水位 + 新堆 min_ever 复测。
778 lines
25 KiB
C
778 lines
25 KiB
C
/*
|
||
* 模块名称: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) {
|
||
uint8_t body[BODY_MAX];
|
||
uint16_t n = obj_len;
|
||
|
||
if (n > BODY_MAX) {
|
||
n = 0u;
|
||
}
|
||
if (n > 0u) {
|
||
(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);
|
||
}
|
||
|
||
/* ---------------- 数据上送 ---------------- */
|
||
|
||
/*
|
||
* 函数功能:单点上送遥测(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,发成功才清位
|
||
*/
|
||
static void s_spont_step(iec104_link_t *lk, uint8_t *budget) {
|
||
uint8_t body[5];
|
||
uint8_t i;
|
||
uint8_t d;
|
||
int rc;
|
||
|
||
while ((*budget > 0u) && ((lk->pend_yx != 0u) || (lk->pend_yc != 0u))) {
|
||
if (lk->pend_yx != 0u) {
|
||
i = 0u;
|
||
while (i < 33u) {
|
||
if ((lk->pend_yx & (1uLL << i)) != 0u) {
|
||
break;
|
||
}
|
||
i++;
|
||
}
|
||
iec104_pts_yx_read(i, &d);
|
||
body[0] = d;
|
||
rc = s_send_asdu(lk,
|
||
(i < 4u) ? IEC104_T_M_DP_NA_1
|
||
: IEC104_T_M_SP_NA_1,
|
||
1u, IEC104_COT_SPONT, 0u, 1u,
|
||
(uint32_t)i + 1u, body, 1u, 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;
|
||
}
|