Files
STM32F4-Base/IEC60870-Slave/104/IEC104_Connection.c
T
STF 7596f3371a
CI / L0 静态门禁 (pre-commit) (push) Failing after 44s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
补充总召唤的广播地址,电度召唤+时钟同步业务的实现
2026-10-29 03:20:19 -07:00

1183 lines
61 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.
/*
* TCP内部的子站单连接协调:由TcpServer调用Feed、Prepare和Progress,分别处理输入、待发字节及写入结果。
* Drive统一推进保留接收帧、超时和控制应答;发送阶段不报告对端确认超时,交回接收阶段处理。
* 旧确认t1到期时暂停新I帧编号,已准备帧仍可发送以解除接收阻塞。
* 它不会执行TCP读写,也不会创建新业务I帧。
* 接收I帧只提交一次链路事件,TcpServer复制为事件后Release;发送记录转为事件后Remove,收尾时由Close清理。
* 业务只使用TcpServer事件副本。内部接口串行调用,connection_generation/frame_id先匹配当前连接,
* 含now_ms的运行接口再检查单调时间;故障后保留记录供回收,结束旧I/O后才可Close。
*/
#include "IEC104_Connection.h"
#include "IEC104_Link_Slave_StartStop.h"
#include <string.h>
/* 发送取到的新时刻可能跨过t1;确认超时故障须交给能够先处理输入的驱动阶段。 */
typedef enum {
CHECK_ALL_TIMEOUTS,
CHECK_LOCAL_TIMEOUTS
} ConnectionTimeoutScope_t;
/* 每次驱动按当前事实重建,连接对象不缓存第二份期限或起点。 */
typedef enum {
DEADLINE_LOCAL_SEND,
DEADLINE_I_ACK,
DEADLINE_TESTFR,
DEADLINE_STOP,
DEADLINE_T2,
DEADLINE_T3,
DEADLINE_COUNT
} ConnectionDeadlineKind_t;
typedef struct {
uint32_t remaining_ms;
uint8_t active;
} ConnectionDeadline_t;
typedef struct {
ConnectionDeadline_t items[DEADLINE_COUNT];
IEC104_SendFrameId_t head_frame_id;
} ConnectionDeadlines_t;
/**
* 功能:只读核对连接代次及已初始化的生命周期范围,包括CLOSED。
* 输入参数:connection——已初始化或全零对象,允许NULL;generation——事件携带的代次。
* 返回值:1匹配,0不匹配;不判断TCP是否存活、内部模块是否完整或链路是否STARTED。
* 说明:先隔离旧代次,避免旧事件冻结、推进或清理新连接;首次使用须先Init。
*/
static int Identity_Matches(const IEC104_Connection_t *connection, uint64_t generation)
{
return connection != NULL && generation != 0U &&
connection->connection_generation == generation &&
connection->phase >= IEC104_CONNECTION_ACTIVE &&
connection->phase <= IEC104_CONNECTION_CLOSED;
}
/**
* 功能:判断当前代次是否仍处于允许读取记录、释放业务接收帧的阶段。
* 输入参数:connection/generation——待核对对象及事件代次。
* 返回值:ACTIVE或FAILED且代次匹配返回1,否则0;只判断生命周期,不校验底层存储。
* 说明:FAILED仍保留回收所需数据,CLOSED已解除借用;普通收发还须使用Require_Active。
*/
static int Storage_Available(const IEC104_Connection_t *connection, uint64_t generation)
{
return Identity_Matches(connection, generation) && connection->phase != IEC104_CONNECTION_CLOSED;
}
/**
* 功能:为普通收发和驱动入口检查连接身份及运行阶段,不修改对象。
* 返回值:匹配ACTIVE为OK,匹配FAILED为FATAL;空指针、旧代次或不可用阶段为ERROR。
* 说明:FATAL表示旧会话已经冻结,不能像WAIT一样继续重试正常收发。
*/
static IEC104_ConnectionResult_t Require_Active(const IEC104_Connection_t *connection, uint64_t generation)
{
if (!Storage_Available(connection, generation)) {
return IEC104_CONNECTION_ERROR;
}
return connection->phase == IEC104_CONNECTION_ACTIVE ? IEC104_CONNECTION_OK : IEC104_CONNECTION_FATAL;
}
/**
* 功能:先检查运行资格,再拒绝倒退的事件时刻。
* 输入参数:now_ms——当前串行事件的单调毫秒值,允许等于上次时刻及0。
* 返回值:OK允许继续,ERROR身份/阶段/时间非法,FATAL当前连接已冻结。
* 说明:此处不写last_now_ms;例如后续frame_id或写入量校验失败时,时刻也不前移。
*/
static IEC104_ConnectionResult_t Check_Time(
const IEC104_Connection_t *connection, uint64_t generation, uint64_t now_ms)
{
IEC104_ConnectionResult_t result = Require_Active(connection, generation);
if (result != IEC104_CONNECTION_OK) {
return result;
}
return now_ms >= connection->last_now_ms ? IEC104_CONNECTION_OK : IEC104_CONNECTION_ERROR;
}
/**
* 功能:冻结已校验身份的当前连接,登记首次故障原因。
* 输入参数:connection——ACTIVE或FAILED对象;fault——本次发现的故障。
* 输出参数:首次调用将phase置FAILED并保存fault,后续调用不覆盖原故障。
* 返回值:始终FATAL;不关闭TCP、不Abort模块、不修改发送记录或ASDU队列。
* 说明:底层处理在故障前可能已改变部分状态,业务须先核对记录再Close,不能重放事件。
*/
static IEC104_ConnectionResult_t Fail(IEC104_Connection_t *connection, IEC104_ConnectionFault_t fault)
{
if (connection->phase == IEC104_CONNECTION_ACTIVE) {
connection->fault = fault;
connection->phase = IEC104_CONNECTION_FAILED;
}
return IEC104_CONNECTION_FATAL;
}
/* 原始起点仅保存在所属对象;0是合法起点,active与remaining_ms分开表示。
* 先校验时钟再作64位差,不计算started_ms + timeout_ms,避免溢出。 */
static int Set_Deadline(ConnectionDeadline_t *deadline,
uint64_t now_ms, uint64_t started_ms, uint32_t timeout_ms)
{
uint64_t elapsed;
if (timeout_ms == 0U || now_ms < started_ms) {
return 0;
}
elapsed = now_ms - started_ms;
deadline->active = 1U;
deadline->remaining_ms = elapsed >= timeout_ms ? 0U : timeout_ms - (uint32_t)elapsed;
return 1;
}
static int Deadline_Expired(const ConnectionDeadlines_t *deadlines, ConnectionDeadlineKind_t kind)
{
return deadlines->items[kind].active != 0U && deadlines->items[kind].remaining_ms == 0U;
}
/* Check_Timeouts、发送准入、控制推进和Get_Wait_Time共用的期限视图。
* 不处理超时、不推进序号、不改计时起点;是否暂缓确认故障由调用方按接收批次决定。
* t2/t3仅在控制槽和Link准入允许时参与唤醒,容量背压仍由已有发送/确认期限约束。 */
static int Collect_Deadlines(
const IEC104_Connection_t *connection, uint64_t now_ms, ConnectionDeadlines_t *deadlines)
{
IEC104_SendManagerDeadlines_t sending;
ConnectionDeadlines_t prepared;
int ready;
if (IEC104_SendManager_Get_Deadlines(&connection->send_manager, connection->connection_generation,
now_ms, connection->config.local_send_timeout_ms, connection->config.link.t1_ms,
&sending) != IEC104_SEND_MANAGER_OK) {
return 0;
}
memset(&prepared, 0, sizeof(prepared));
prepared.head_frame_id = sending.head_frame_id;
prepared.items[DEADLINE_LOCAL_SEND].active = sending.has_send_head;
prepared.items[DEADLINE_LOCAL_SEND].remaining_ms = sending.local_send_remaining_ms;
prepared.items[DEADLINE_I_ACK].active = sending.has_i_ack_deadline;
prepared.items[DEADLINE_I_ACK].remaining_ms = sending.i_ack_remaining_ms;
if ((connection->testfr_timer_active != 0U &&
!Set_Deadline(&prepared.items[DEADLINE_TESTFR], now_ms,
connection->testfr_started_ms, connection->config.link.t1_ms)) ||
(connection->stop_timer_active != 0U &&
!Set_Deadline(&prepared.items[DEADLINE_STOP], now_ms,
connection->stop_started_ms, connection->config.stop_timeout_ms))) {
return 0;
}
if (sending.control_has_room != 0U) {
if (connection->rx_ack_timer_active != 0U) {
ready = IEC104_Link_Can_Prepare_S_Ack(&connection->link_state, connection->config.link.w, 1);
if (ready < 0 || (ready == 1 && !Set_Deadline(&prepared.items[DEADLINE_T2], now_ms,
connection->rx_ack_started_ms, connection->config.link.t2_ms))) {
return 0;
}
}
ready = IEC104_Link_Can_Prepare_Testfr_Act(&connection->link_state);
if (ready < 0 || (ready == 1 && !Set_Deadline(&prepared.items[DEADLINE_T3], now_ms,
connection->last_activity_ms, connection->config.link.t3_ms))) {
return 0;
}
}
*deadlines = prepared;
return 1;
}
/**
* 功能:本地整帧完成后,检查当前连续未确认接收区间是否已经全部确认。
* 输入参数:connection——对应Link完成处理已提交的连接。
* 输出参数:Link报告没有未确认接收数据时清除rx_ack_timer_active,否则保留原计时起点。
* 返回值:OK查询及更新成功;Link状态异常时冻结并返回FATAL。
* 说明:I帧捎带确认或S确认必须实际写完才计入acked_nr;准备/排队报文不结束t2。
*/
static IEC104_ConnectionResult_t Update_Receive_Timer(IEC104_Connection_t *connection)
{
IEC104_LinkStatus_t status;
if (IEC104_Link_Get_Status(&connection->link_state, &status) != 1) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (status.unconfirmed_rx_count == 0U) {
connection->rx_ack_timer_active = 0U;
}
return IEC104_CONNECTION_OK;
}
/**
* 功能:归还当前已受理S/U帧占用的接收空间,允许后续Feed继续输入。
* 输入参数:connection——控制事件已成功受理,或合法确认已按规则忽略的连接。
* 返回值:OK释放成功;底层释放异常时冻结并返回FATAL。
* 说明:释放接收原文不等于控制应答已发送;应答由SendBuffer继续持有。
* I帧由TcpServer复制为事件后另行Release,本函数不能释放WAIT中的未受理请求。
*/
static IEC104_ConnectionResult_t Release_Control_Frame(IEC104_Connection_t *connection)
{
if (IEC104_Receiver_Release_Frame(&connection->receiver, connection->connection_generation) !=
IEC104_RECEIVER_OK) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
return IEC104_CONNECTION_OK;
}
/* 状态改变的ACT受通知容量约束;重复ACT不额外占用状态通知。 */
static int Transfer_Change_Allowed(const IEC104_Connection_t *connection, const IEC104_ApciInfo_t *apci)
{
int changes = (apci->u_function == IEC104_U_STARTDT_ACT &&
connection->link_state.dt_state != IEC104_DT_STARTED) ||
(apci->u_function == IEC104_U_STOPDT_ACT &&
connection->link_state.dt_state != IEC104_DT_STOPPED);
return !changes || connection->transfer_observer.can_accept == NULL ||
connection->transfer_observer.can_accept(connection->transfer_observer.context);
}
/* 状态可能在后续提交失败之前已经生效;仍先交付该事实,再由调用方报告故障。 */
static int Notify_Transfer_Change(IEC104_Connection_t *connection, IEC104_DataTransferState_t before)
{
return before == connection->link_state.dt_state || connection->transfer_observer.notify == NULL ||
connection->transfer_observer.notify(connection->transfer_observer.context, connection->link_state.dt_state);
}
/**
* 功能:按接收顺序将当前完整I/S交给Manager,将U帧交给对应启停或TESTFR入口。
* 输入参数:connection——ACTIVE连接;now_ms——本次驱动时刻,用于应答入队。
* 输出参数:受理后更新Link/Manager;I帧标记rx_link_applied并保留原文,S/U自动释放。
* 返回值:无完整帧或控制帧已处理为OK;业务I帧为FRAME_READY;请求未受理为WAIT;异常FATAL。
* 说明:WAIT保留当前请求;启停通知容量不足也返回WAIT,不改变状态、不消费请求。
* 已受理I帧直接报告就绪,保证后续驱动不会重复推进V(R)或处理累计ACK。
*/
static IEC104_ConnectionResult_t Dispatch_Received_Frame(IEC104_Connection_t *connection, uint64_t now_ms)
{
IEC104_ReceivedFrame_t frame;
IEC104_ReceiverResult_t received;
IEC104_SendManagerResult_t handled;
IEC104_DataTransferState_t before;
uint64_t generation = connection->connection_generation;
received = IEC104_Receiver_Peek_Frame(&connection->receiver, generation, &frame);
if (received == IEC104_RECEIVER_EMPTY) {
return IEC104_CONNECTION_OK;
}
if (received != IEC104_RECEIVER_FRAME_READY) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (connection->rx_link_applied != 0U) {
return IEC104_CONNECTION_FRAME_READY;
}
if (frame.apci.frame_type == IEC104_FRAME_TYPE_U &&
(frame.apci.u_function == IEC104_U_STARTDT_CON || frame.apci.u_function == IEC104_U_STOPDT_CON)) {
/* 子站不发启停ACT;格式合法的反向CON仅消费,不改变启停、确认期限或t3活动时刻。 */
return Release_Control_Frame(connection);
}
/* 使用成帧时刻登记活动;WAIT重试不续期,也不覆盖更晚的本地整帧完成时刻。 */
if (connection->received_at_ms > connection->last_activity_ms) {
connection->last_activity_ms = connection->received_at_ms;
}
if (frame.apci.frame_type == IEC104_FRAME_TYPE_I || frame.apci.frame_type == IEC104_FRAME_TYPE_S) {
/* I/S的N(R)统一由Manager验证和登记;此处不能再按网络序号直接释放队列原文。 */
handled = IEC104_SendManager_Handle_I_S_Frame(&connection->send_manager, generation, &frame.apci);
if (handled != IEC104_SEND_MANAGER_OK) {
return Fail(connection, handled == IEC104_SEND_MANAGER_PROTOCOL_ERROR ?
IEC104_CONNECTION_FAULT_PROTOCOL : IEC104_CONNECTION_FAULT_INTERNAL);
}
if (frame.apci.frame_type == IEC104_FRAME_TYPE_S) {
return Release_Control_Frame(connection);
}
connection->rx_link_applied = 1U;
if (connection->rx_ack_timer_active == 0U) {
/* 首个尚未确认I帧开启t2;采用成帧时刻,重复驱动或后续I帧均不续期。 */
connection->rx_ack_started_ms = connection->received_at_ms;
connection->rx_ack_timer_active = 1U;
}
return IEC104_CONNECTION_FRAME_READY;
}
if (!Transfer_Change_Allowed(connection, &frame.apci)) {
return IEC104_CONNECTION_WAIT;
}
before = connection->link_state.dt_state;
/* ACT和TESTFR_CON已经通过格式校验,合法重复由底层正常受理或忽略;
* 这些分支的处理失败属于内部状态/交接异常,反向启停CON已在上方消费。 */
switch (frame.apci.u_function) {
case IEC104_U_STARTDT_ACT:
handled = IEC104_SendManager_Slave_Handle_Startdt_Act(
&connection->send_manager, generation, &frame.apci, now_ms);
if (!Notify_Transfer_Change(connection, before)) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (handled != IEC104_SEND_MANAGER_QUEUED && handled != IEC104_SEND_MANAGER_WAIT) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (handled == IEC104_SEND_MANAGER_QUEUED) {
connection->stop_timer_active = 0U;
memset(&connection->stop_con_frame_id, 0, sizeof(connection->stop_con_frame_id));
}
break;
case IEC104_U_STOPDT_ACT:
handled = IEC104_SendManager_Slave_Handle_Stopdt_Act(&connection->send_manager, generation, &frame.apci);
if (!Notify_Transfer_Change(connection, before)) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (handled == IEC104_SEND_MANAGER_OK) {
/* 本轮首次受理才启用计时,起点取原成帧时刻,通知容量不足造成的受理前WAIT也计入。
* 重复STOP可追加CON,但不能延长尚未完成的停止总期限。 */
if (connection->stop_timer_active == 0U) {
connection->stop_started_ms = connection->received_at_ms;
connection->stop_timer_active = 1U;
}
memset(&connection->stop_con_frame_id, 0, sizeof(connection->stop_con_frame_id));
} else if (handled != IEC104_SEND_MANAGER_IGNORED) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
break;
case IEC104_U_TESTFR_ACT:
handled = IEC104_SendManager_Handle_Testfr_Act(&connection->send_manager, generation, &frame.apci, now_ms);
if (handled != IEC104_SEND_MANAGER_QUEUED && handled != IEC104_SEND_MANAGER_WAIT) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
break;
case IEC104_U_TESTFR_CON:
handled = IEC104_SendManager_Handle_Testfr_Con(&connection->send_manager, generation, &frame.apci);
if (handled == IEC104_SEND_MANAGER_OK) {
/* 只有匹配当前等待过程的CON才取消探测t1;提前/无匹配CON不缓存。 */
connection->testfr_timer_active = 0U;
} else if (handled != IEC104_SEND_MANAGER_IGNORED) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
return Release_Control_Frame(connection);
default:
/* 其余功能码仍按协议异常处理,不能扩展为忽略任意U帧。 */
return Fail(connection, IEC104_CONNECTION_FAULT_PROTOCOL);
}
return handled == IEC104_SEND_MANAGER_WAIT ? IEC104_CONNECTION_WAIT : Release_Control_Frame(connection);
}
/* 从同一份期限视图选择确认故障,保持I帧ACK先于TESTFR的顺序。
* 到期事实独立于接收批次:可暂缓断连,但发送准备仍须暂停新I帧编号。 */
static IEC104_ConnectionFault_t Get_Confirmation_Timeout(const ConnectionDeadlines_t *deadlines)
{
if (Deadline_Expired(deadlines, DEADLINE_I_ACK)) {
return IEC104_CONNECTION_FAULT_I_ACK_TIMEOUT;
}
if (Deadline_Expired(deadlines, DEADLINE_TESTFR)) {
return IEC104_CONNECTION_FAULT_TESTFR_TIMEOUT;
}
return IEC104_CONNECTION_FAULT_NONE;
}
/**
* 功能:检查本地发送和STOP总期限;按驱动阶段决定是否检查对端确认t1。
* 输入参数:connection——ACTIVE连接;now_ms——已通过单调检查的当前时刻;scope——期限检查范围。
* 返回值:OK表示本轮没有发现到期/异常;首个到期或状态异常冻结连接并返回FATAL。
* 说明:本地发送超时先向Manager提交对应帧失败,其余期限直接冻结并保留记录。
* 接收/写入事实在调用本函数前已提交,检查的是这些事实处理后仍存在的等待义务。
*/
static IEC104_ConnectionResult_t Check_Timeouts(
IEC104_Connection_t *connection, uint64_t now_ms, ConnectionTimeoutScope_t scope)
{
ConnectionDeadlines_t deadlines;
if (!Collect_Deadlines(connection, now_ms, &deadlines)) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (Deadline_Expired(&deadlines, DEADLINE_LOCAL_SEND)) {
if (IEC104_SendManager_Handle_Send_Failure(&connection->send_manager, deadlines.head_frame_id) !=
IEC104_SEND_MANAGER_FATAL) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
return Fail(connection, IEC104_CONNECTION_FAULT_LOCAL_SEND_TIMEOUT);
}
/* 发送阶段只登记真实写入事实,不凭新取的时刻抢先判定未解析的确认丢失。
* TCP先有界处理已到达的一批输入;预算让出时可立即续收同一份固定快照。
* 只暂缓对端确认期限,快照结束时Tick仍按原始起点判定,新流量不能扩大旧批次。 */
if (scope == CHECK_ALL_TIMEOUTS &&
connection->receive_batch_active == 0U && connection->receive_batch_pending == 0U) {
IEC104_ConnectionFault_t fault = Get_Confirmation_Timeout(&deadlines);
if (fault != IEC104_CONNECTION_FAULT_NONE) {
return Fail(connection, fault);
}
}
if (Deadline_Expired(&deadlines, DEADLINE_STOP)) {
return Fail(connection, IEC104_CONNECTION_FAULT_STOP_TIMEOUT);
}
return IEC104_CONNECTION_OK;
}
/**
* 功能:按STOP、普通确认的顺序,尝试准备本轮的一项控制义务。
* 输入参数:connection——ACTIVE连接;now_ms——本次时刻。
* 返回值:Manager的QUEUED/WAIT/ERROR/FATAL;没有当前可准备义务时EMPTY。
* 说明:Link只读查询与实际构帧共用准入规则;Connection只决定驱动顺序和t2是否到期。
* 查询不消耗发送编号,只有确有义务才向Manager请求控制槽;容量不足留待后续驱动。
*/
static IEC104_SendManagerResult_t Queue_Pending_Control(IEC104_Connection_t *connection, uint64_t now_ms)
{
const IEC104_LinkState_t *link = &connection->link_state;
ConnectionDeadlines_t deadlines;
uint64_t generation = connection->connection_generation;
int ready = IEC104_Link_Can_Prepare_Stopdt_Response(link);
int t2_expired;
if (ready < 0) {
return IEC104_SEND_MANAGER_ERROR;
}
if (ready == 1) {
IEC104_SendFrameId_t stop_con_id = {0U, 0U};
IEC104_SendManagerResult_t result = IEC104_SendManager_Slave_Queue_Stopdt_Response(
&connection->send_manager, generation, now_ms, &stop_con_id);
if (result == IEC104_SEND_MANAGER_QUEUED && stop_con_id.frame_serial != 0U) {
connection->stop_con_frame_id = stop_con_id;
}
return result;
}
/* t2到期只请求立即确认;是否准入、是否已有S在途、w是否满足均交给Link。 */
if (!Collect_Deadlines(connection, now_ms, &deadlines)) {
return IEC104_SEND_MANAGER_ERROR;
}
t2_expired = Deadline_Expired(&deadlines, DEADLINE_T2);
ready = IEC104_Link_Can_Prepare_S_Ack(link, connection->config.link.w, t2_expired);
if (ready < 0) {
return IEC104_SEND_MANAGER_ERROR;
}
if (ready == 1) {
return IEC104_SendManager_Queue_S_Ack(
&connection->send_manager, generation, connection->config.link.w, t2_expired, now_ms);
}
return IEC104_SEND_MANAGER_EMPTY;
}
/**
* 功能:最多推进两项控制义务,使S及STOP_CON可依次排队,再按t3安排主动探测。
* 输入参数:connection——ACTIVE且本轮超时检查通过;now_ms——控制帧入队时刻。
* 返回值:OK本轮正常,包括无义务或容量等待;底层异常冻结并返回FATAL。
* 说明:不组合Link内部枚举和序号,不循环等待资源,也不准备新的业务I帧。
*/
static IEC104_ConnectionResult_t Advance_Control(IEC104_Connection_t *connection, uint64_t now_ms)
{
IEC104_SendManagerResult_t result;
ConnectionDeadlines_t deadlines;
unsigned count;
/* 一次最多两项:S确认后可立即将STOP_CON排在它后面,不等网络完成。 */
for (count = 0U; count < 2U; ++count) {
result = Queue_Pending_Control(connection, now_ms);
if (result != IEC104_SEND_MANAGER_EMPTY && result != IEC104_SEND_MANAGER_QUEUED &&
result != IEC104_SEND_MANAGER_WAIT) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (result != IEC104_SEND_MANAGER_QUEUED) {
break;
}
}
/* 前面的S/STOP_CON可能已用掉控制槽,重新收集后再决定是否启动探测。 */
if (!Collect_Deadlines(connection, now_ms, &deadlines)) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (Deadline_Expired(&deadlines, DEADLINE_T3)) {
result = IEC104_SendManager_Queue_Testfr_Act(
&connection->send_manager, connection->connection_generation, now_ms);
if (result != IEC104_SEND_MANAGER_QUEUED && result != IEC104_SEND_MANAGER_WAIT) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
}
return IEC104_CONNECTION_OK;
}
/**
* 功能:为Feed、Prepare、发送进度回报和Tick执行一次统一调度。
* 输入参数:connection——已提交本次时刻的ACTIVE连接;now_ms——同一本次时刻;scope——期限检查范围。
* 返回值:正常时返回接收分派的OK/FRAME_READY/WAIT;任一步骤FATAL则立即停止后续步骤。
* 说明:一次有界推进,不循环等资源;接收WAIT仍检查超时并推进控制发送,避免互相等待。
* 发送入口只检查本地期限;接收批次内也暂缓确认超时,批次结束由Tick检查,不改变起点。
* 返回OK只描述本轮处理结果,不保证发送缓冲为空;调用方仍可Prepare取得控制帧。
*/
static IEC104_ConnectionResult_t Drive(
IEC104_Connection_t *connection, uint64_t now_ms, ConnectionTimeoutScope_t scope)
{
IEC104_ConnectionResult_t received = Dispatch_Received_Frame(connection, now_ms);
if (received == IEC104_CONNECTION_FATAL ||
Check_Timeouts(connection, now_ms, scope) == IEC104_CONNECTION_FATAL ||
Advance_Control(connection, now_ms) == IEC104_CONNECTION_FATAL) {
return IEC104_CONNECTION_FATAL;
}
return received;
}
/**
* 功能:将Manager的普通操作结果转换为Connection结果,本函数不修改任何对象。
* 返回值:OK/EMPTY/WAIT分别映射同义结果,其他值映射ERROR。
* 说明:用于记录查询、记录移除以及发送准备的EMPTY/WAIT;底层调用本身可能有副作用。
* SEND_READY、FRAME_WRITTEN及FATAL另由调用方处理,不能借此映射推断发送完成。
*/
static IEC104_ConnectionResult_t Record_Result(IEC104_SendManagerResult_t result)
{
switch (result) {
case IEC104_SEND_MANAGER_OK: return IEC104_CONNECTION_OK;
case IEC104_SEND_MANAGER_EMPTY: return IEC104_CONNECTION_EMPTY;
case IEC104_SEND_MANAGER_WAIT: return IEC104_CONNECTION_WAIT;
default: return IEC104_CONNECTION_ERROR;
}
}
/**
* 功能:填写可用于Init的子站连接默认配置。
* 输入参数:config——调用方提供的配置存储,允许NULL。
* 输出参数:写入Link默认k/w/t1/t2/t3,本地发送和STOP总期限取默认t1,HIGH严格优先。
* 返回值:无;NULL时不操作。
* 说明:只填写配置,不初始化连接、不启动计时;调用方可在Init前修改配置字段。
*/
void IEC104_Connection_Config_Default(IEC104_ConnectionConfig_t *config)
{
if (config != NULL) {
memset(config, 0, sizeof(*config));
IEC104_Link_Config_Default(&config->link);
config->local_send_timeout_ms = config->link.t1_ms;
config->stop_timeout_ms = config->link.t1_ms;
}
}
/**
* 功能:建立一条初始STOPPED的子站连接,绑定接收、链路、发送缓冲及发送记录。
* 输入参数:connection——首次使用或已Close的对象;queue——已Init/Rebind到本代次的ASDU队列。
* control_frames/control_capacity——控制槽数组及槽数;records/record_capacity——I帧记录数组及条数。
* config——有效配置;connection_generation——非零且跨连接不复用;now_ms——初始单调时刻。
* 输出参数:成功写入完整Connection;Manager成功绑定时清空HIGH待发原文,保留待发NORMAL。
* 返回值:OK初始化成功;ERROR参数、配置、队列或绑定条件非法,connection和队列保持原值。
* 调用方:TcpServer接受新连接时;旧连接必须先结束I/O和借用并Close,不能运行中直接重新Init。
* 说明:Receiver及配置先准备;Manager校验成功后在最终地址建立Link/Buffer,再提交其余连接字段。
* 最后一次可能失败的调用之后仅复制已校验对象;最终地址必须稳定,所有借用存储持续有效。
*/
IEC104_ConnectionResult_t IEC104_Connection_Init(
IEC104_Connection_t *connection, IEC104_AsduQueue_t *queue,
IEC104_SendBufferControlFrame_t *control_frames, uint16_t control_capacity,
IEC104_IFrameSendRecordStorage_t *records, uint16_t record_capacity,
const IEC104_ConnectionConfig_t *config, uint64_t connection_generation, uint64_t now_ms)
{
IEC104_Connection_t prepared;
if (connection == NULL || config == NULL || config->link.k == 0U || config->link.k > IEC104_SEQUENCE_MASK ||
config->link.w == 0U || config->link.w > IEC104_SEQUENCE_MASK || config->link.t1_ms == 0U ||
config->link.t2_ms == 0U || config->link.t2_ms >= config->link.t1_ms ||
config->link.t3_ms == 0U || config->local_send_timeout_ms == 0U ||
config->stop_timeout_ms == 0U) {
return IEC104_CONNECTION_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
if (IEC104_Receiver_Init(&prepared.receiver, connection_generation) != IEC104_RECEIVER_OK) {
return IEC104_CONNECTION_ERROR;
}
prepared.config = *config;
prepared.connection_generation = connection_generation;
prepared.last_now_ms = now_ms;
prepared.last_activity_ms = now_ms;
prepared.phase = IEC104_CONNECTION_ACTIVE;
/* Manager在最终地址提交子对象;失败时connection、queue及借用数组均保持原值。
* 成功之后没有可失败步骤,prepared中的Manager已绑定最终地址,不需要修补内部字段。 */
if (IEC104_SendManager_Init_Connection(&prepared.send_manager, queue,
&connection->send_buffer, &connection->link_state, control_frames, control_capacity,
records, record_capacity, prepared.config.link.k, prepared.config.high_priority_burst_limit,
connection_generation) != IEC104_SEND_MANAGER_OK) {
return IEC104_CONNECTION_ERROR;
}
prepared.send_buffer = connection->send_buffer;
prepared.link_state = connection->link_state;
*connection = prepared;
return IEC104_CONNECTION_OK;
}
/* 接收批次只改变确认超时的检查时机;原有单帧接口和所有时间起点保持不变。 */
IEC104_ConnectionResult_t IEC104_Connection_Begin_Receive_Batch(
IEC104_Connection_t *connection, uint64_t connection_generation)
{
IEC104_ConnectionResult_t checked = Require_Active(connection, connection_generation);
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
if (connection->receive_batch_active != 0U) {
return IEC104_CONNECTION_ERROR;
}
connection->receive_batch_active = 1U;
return IEC104_CONNECTION_OK;
}
IEC104_ConnectionResult_t IEC104_Connection_Yield_Receive_Batch(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms)
{
IEC104_ConnectionResult_t checked = Check_Time(connection, connection_generation, now_ms);
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
if (connection->receive_batch_active == 0U) {
return IEC104_CONNECTION_ERROR;
}
connection->last_now_ms = now_ms;
connection->receive_batch_active = 0U;
connection->receive_batch_pending = 1U;
return Drive(connection, now_ms, CHECK_ALL_TIMEOUTS);
}
/* 安装后仅由串行Drive调用;默认空适配器保留Connection独立使用方式。 */
IEC104_ConnectionResult_t IEC104_Connection_Set_Transfer_Observer(
IEC104_Connection_t *connection, uint64_t connection_generation,
const IEC104_ConnectionTransferObserver_t *observer)
{
if (Require_Active(connection, connection_generation) != IEC104_CONNECTION_OK ||
observer == NULL || (observer->can_accept == NULL) != (observer->notify == NULL) ||
connection->receive_serial != 0U) {
return IEC104_CONNECTION_ERROR;
}
connection->transfer_observer = *observer;
return IEC104_CONNECTION_OK;
}
/**
* 功能:接收一段TCP字节,最多组装一帧并推进链路受理、控制应答及超时检查。
* 输入参数:connection/connection_generation——本连接及输入所属代次;data/length——本次可用字节。
* now_ms——本次单调时刻;length为0时data可为NULL,用于重试保留帧。
* 输出参数:consumed_length必填,写实际消费量;未消费尾部由调用方保留,不能按length直接丢弃。
* 返回值:OK半帧或控制帧已处理;FRAME_READY业务I帧就绪;WAIT请求尚未受理;ERROR接入非法;FATAL冻结。
* 调用方:TcpServer接收调度;I帧就绪后Peek并复制为事件,再Release;WAIT时驱动发送或Tick后重试。
* 说明:保留完整帧时消费0字节,即使本轮重试成功释放该帧,也要另一次Feed才能消费新输入。
* FRAME_READY只表示链路受理,ASDU内容解析和业务处理由应用取得TcpServer事件副本后完成。
*/
IEC104_ConnectionResult_t IEC104_Connection_Feed(
IEC104_Connection_t *connection, uint64_t connection_generation,
const uint8_t *data, uint32_t length, uint64_t now_ms, uint32_t *consumed_length)
{
IEC104_ConnectionResult_t checked;
IEC104_ReceiverResult_t received;
IEC104_ReceiverStatus_t before_receive;
if (consumed_length == NULL) {
return IEC104_CONNECTION_ERROR;
}
*consumed_length = 0U;
if (data == NULL && length != 0U) {
return IEC104_CONNECTION_ERROR;
}
checked = Check_Time(connection, connection_generation, now_ms);
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
/* 区分新成帧与旧帧重试;分派时按原成帧时刻更新活动,反向启停CON不刷新t3。 */
if (IEC104_Receiver_Get_Status(&connection->receiver, connection_generation, &before_receive) !=
IEC104_RECEIVER_OK) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
received = IEC104_Receiver_Feed(&connection->receiver, connection_generation, data, length, consumed_length);
connection->last_now_ms = now_ms;
if (received == IEC104_RECEIVER_FRAME_ERROR) {
return Fail(connection, IEC104_CONNECTION_FAULT_FRAME);
}
if (received != IEC104_RECEIVER_FRAME_READY && received != IEC104_RECEIVER_IN_PROGRESS) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (received == IEC104_RECEIVER_FRAME_READY && before_receive.has_complete_frame == 0U) {
if (connection->receive_serial == UINT64_MAX) {
/* 不回绕复用身份,否则同一连接上迟到的Release可能匹配另一帧。 */
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
connection->receive_serial++;
connection->received_at_ms = now_ms;
}
return Drive(connection, now_ms, CHECK_ALL_TIMEOUTS);
}
/**
* 功能:向TcpServer提供已通过链路受理、尚未释放的内部I帧只读视图。
* 输入参数:connection/connection_generation——本连接及代次,允许ACTIVE或FAILED。
* 输出参数:仅FRAME_READY时写frame,包含完整APDU/APCI借用及本次receive_serial。
* 返回值:FRAME_READY可读取;EMPTY无完整业务帧;WAIT保留帧尚未受理;ERROR身份、输出或存储非法。
* 说明:不消费原文、不提交链路事件;借用在匹配Release或Close后失效。
* FAILED下WAIT仅描述遗留未受理帧,不表示可以继续驱动故障连接。
*/
IEC104_ConnectionResult_t IEC104_Connection_Peek_Received_Frame(
const IEC104_Connection_t *connection, uint64_t connection_generation, IEC104_ConnectionReceivedFrame_t *frame)
{
IEC104_ConnectionReceivedFrame_t prepared;
IEC104_ReceiverResult_t received;
if (!Storage_Available(connection, connection_generation) || frame == NULL) {
return IEC104_CONNECTION_ERROR;
}
received = IEC104_Receiver_Peek_Frame(&connection->receiver, connection_generation, &prepared.frame);
if (received == IEC104_RECEIVER_EMPTY) {
return IEC104_CONNECTION_EMPTY;
}
if (received != IEC104_RECEIVER_FRAME_READY) {
return IEC104_CONNECTION_ERROR;
}
if (connection->rx_link_applied == 0U) {
return IEC104_CONNECTION_WAIT;
}
prepared.receive_serial = connection->receive_serial;
*frame = prepared;
return IEC104_CONNECTION_FRAME_READY;
}
/**
* 功能:TcpServer成功复制I_FRAME事件后归还当前I帧接收空间,允许下一次Feed继续消费输入。
* 输入参数:connection_generation/receive_serial——本次Peek返回的连接代次及接收身份。
* 输出参数:成功清除接收进度和rx_link_applied;所有指向该APDU/ASDU的旧借用立即失效。
* 返回值:OK释放成功;ERROR身份不符、没有已受理I帧、已Close或底层释放失败。
* 说明:ACTIVE和FAILED均可回收;不再次处理N(S)/N(R),不撤回已经安排的链路确认。
*/
IEC104_ConnectionResult_t IEC104_Connection_Release_Received_Frame(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t receive_serial)
{
if (!Storage_Available(connection, connection_generation) || connection->rx_link_applied == 0U ||
receive_serial != connection->receive_serial ||
IEC104_Receiver_Release_Frame(&connection->receiver, connection_generation) != IEC104_RECEIVER_OK) {
return IEC104_CONNECTION_ERROR;
}
connection->rx_link_applied = 0U;
return IEC104_CONNECTION_OK;
}
/**
* 功能:核对当前会话后提交应答,通过启停准入后尝试直接准备I帧;暂不能准备时复制到HIGH。
* 输入参数:connection/connection_generation——ACTIVE连接及匹配代次;asdu/asdu_length——应答原文。
* now_ms——单调时刻;head_info——非NULL输出,仅SEND_READY时有效。
* 返回值:SEND_READY已准备APDU,OK已入HIGH,WAIT尚未接手,ERROR参数/阶段/容量非法,FATAL冻结。
* 调用关系:TcpServer_Send_Response -> 本接口 -> SendManager_Submit_Response或Enqueue(HIGH);本层不执行socket I/O。
* 说明:到期时由Enqueue检查准入后仅入队,交还Process先接收ACK再检查期限,不能在应答入口抢先判定远端超时。
* 未到期时先Drive处理保留接收及控制流程,再由Manager检查准入;禁止阶段返回ERROR且不入HIGH。
* 两条路径仅在STARTED+IDLE时接手;准入先于容量检查。
* 受理新STOPDT_ACT时HIGH待发应答作废,无逐条回执;当前I帧、发送记录及NORMAL不受清理影响。
* 服务级NORMAL由TcpServer直接缓存,不经过此接口;本函数返回后不再借用输入原文。
*/
static IEC104_ConnectionResult_t Submit_Response(
IEC104_Connection_t *connection, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info, int bypass_queue)
{
IEC104_SendBufferHeadInfo_t prepared;
IEC104_SendManagerResult_t result;
uint32_t wait_ms;
IEC104_ConnectionResult_t checked = Check_Time(connection, connection_generation, now_ms);
if (head_info == NULL || asdu == NULL || asdu_length < IEC104_ASDU_HEADER_LEN ||
asdu_length > IEC104_MAX_ASDU_LEN) {
return IEC104_CONNECTION_ERROR;
}
/* 旧代次及失败会话不能接收应答;快速入口不能插入一个尚未结束的接收批次。 */
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
if (connection->receive_batch_active != 0U) {
return IEC104_CONNECTION_ERROR;
}
if (IEC104_Connection_Get_Wait_Time(connection, connection_generation, now_ms,
UINT32_MAX, &wait_ms) != IEC104_CONNECTION_OK) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
connection->last_now_ms = now_ms;
if (wait_ms == 0U) {
/* 不排队模式交还Process处理到期协议工作,不缓存这次应用取值。 */
if (bypass_queue) { return IEC104_CONNECTION_WAIT; }
/* 此入口不读socket;ACK须由正常接收批次处理后再检查t1,Manager仍须检查应答准入。 */
return Record_Result(IEC104_SendManager_Enqueue(
&connection->send_manager, connection_generation, IEC104_ASDU_PRIORITY_HIGH, asdu, asdu_length));
}
if (Drive(connection, now_ms, CHECK_LOCAL_TIMEOUTS) == IEC104_CONNECTION_FATAL) {
return IEC104_CONNECTION_FATAL;
}
result = bypass_queue ?
IEC104_SendManager_Try_Submit_Response(&connection->send_manager, connection_generation,
asdu, asdu_length, now_ms, &prepared) :
IEC104_SendManager_Submit_Response(&connection->send_manager, connection_generation,
asdu, asdu_length, now_ms, &prepared);
if (result == IEC104_SEND_MANAGER_OK) {
*head_info = prepared;
return IEC104_CONNECTION_SEND_READY;
}
if (result == IEC104_SEND_MANAGER_QUEUED) {
return IEC104_CONNECTION_OK;
}
if (result == IEC104_SEND_MANAGER_FATAL) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
return Record_Result(result);
}
IEC104_ConnectionResult_t IEC104_Connection_Submit_Response(
IEC104_Connection_t *connection, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info)
{
return Submit_Response(connection, connection_generation, asdu, asdu_length, now_ms, head_info, 0);
}
/* 复用会话、协议期限和构帧检查,任何等待均不转入HIGH。 */
IEC104_ConnectionResult_t IEC104_Connection_Try_Submit_Response(
IEC104_Connection_t *connection, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info)
{
return Submit_Response(connection, connection_generation, asdu, asdu_length, now_ms, head_info, 1);
}
/**
* 功能:先驱动保留帧、控制及本地期限,再取得当前待发送APDU的剩余字节。
* 输入参数:connection/connection_generation——当前连接及代次;now_ms——本次单调时刻。
* 输出参数:仅SEND_READY时写head_info,其中pending_data是SendBuffer字节的只读借用。
* 返回值:SEND_READY有可写字节;EMPTY无待发项;WAIT受门控/窗口/容量或确认到期限制;ERROR非法;FATAL冻结。
* 调用方:TcpServer实际写TCP前;无当前帧时才由Manager选择ASDU、分配N(S)并建立发送记录。
* 说明:当前帧须连续写完;重复Prepare只取得剩余部分,不代替真实发送或完成回报。
* 旧ACK/TESTFR确认t1到期时只允许已有帧发送;无已有帧返回WAIT,不选择HIGH/NORMAL或编号。
* 对端确认超时故障交回接收阶段;调用方须持续调度Feed/Tick,不能只驱动发送。
*/
IEC104_ConnectionResult_t IEC104_Connection_Prepare_Next_Send(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info)
{
IEC104_SendBufferHeadInfo_t prepared;
IEC104_SendManagerResult_t result;
ConnectionDeadlines_t deadlines;
IEC104_ConnectionResult_t checked = Check_Time(connection, connection_generation, now_ms);
if (head_info == NULL) {
return IEC104_CONNECTION_ERROR;
}
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
connection->last_now_ms = now_ms;
/* 接收WAIT可能正等待控制槽释放,仍须允许取得已有发送帧。 */
if (Drive(connection, now_ms, CHECK_LOCAL_TIMEOUTS) == IEC104_CONNECTION_FATAL) {
return IEC104_CONNECTION_FATAL;
}
if (!Collect_Deadlines(connection, now_ms, &deadlines)) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (deadlines.items[DEADLINE_LOCAL_SEND].active == 0U &&
Get_Confirmation_Timeout(&deadlines) != IEC104_CONNECTION_FAULT_NONE) {
/* 暂缓断连不等于允许新增业务发送。控制槽解阻后先交还接收处理确认,
* 不能继续从HIGH/NORMAL编号;没有新状态或计时器,后续ACK/CON会自然解除限制。 */
return IEC104_CONNECTION_WAIT;
}
result = IEC104_SendManager_Prepare_Next_Send(&connection->send_manager, connection_generation, now_ms, &prepared);
if (result == IEC104_SEND_MANAGER_OK) {
*head_info = prepared;
return IEC104_CONNECTION_SEND_READY;
}
if (result == IEC104_SEND_MANAGER_EMPTY || result == IEC104_SEND_MANAGER_WAIT) {
return Record_Result(result);
}
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
/* EOF后只有记录槽可在本地回收、其他新I帧条件均满足时,才值得等待通知空间。
* 调用方已取得WAIT;查询不创建帧、不移除记录,也不改变Prepare保存的时刻。 */
IEC104_ConnectionResult_t IEC104_Connection_Get_Record_Wait(
const IEC104_Connection_t *connection, uint64_t connection_generation, uint8_t *record_wait)
{
IEC104_SendManagerStatus_t status;
IEC104_IFrameSendRecord_t record;
IEC104_SendManagerResult_t result;
ConnectionDeadlines_t deadlines;
IEC104_ConnectionResult_t checked = Require_Active(connection, connection_generation);
uint8_t waiting = 0U;
int can_prepare;
if (record_wait == NULL) {
return IEC104_CONNECTION_ERROR;
}
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
if (IEC104_SendManager_Get_Status(&connection->send_manager, &status) != IEC104_SEND_MANAGER_OK) {
return IEC104_CONNECTION_ERROR;
}
if (status.record_count == status.record_capacity) {
/* 使用Link的实际构帧准入查询,不能从观察快照另拼一套启停/窗口判断。 */
can_prepare = IEC104_Link_Can_Prepare_I_Frame(&connection->link_state, connection->config.link.k);
if (can_prepare < 0) {
return IEC104_CONNECTION_ERROR;
}
if (can_prepare != 0) {
if (!Collect_Deadlines(connection, connection->last_now_ms, &deadlines)) {
return IEC104_CONNECTION_ERROR;
}
if (Get_Confirmation_Timeout(&deadlines) == IEC104_CONNECTION_FAULT_NONE) {
result = IEC104_SendManager_Peek_Confirmed_Record(&connection->send_manager, &record);
if (result != IEC104_SEND_MANAGER_OK && result != IEC104_SEND_MANAGER_EMPTY &&
result != IEC104_SEND_MANAGER_WAIT) {
return IEC104_CONNECTION_ERROR;
}
waiting = result == IEC104_SEND_MANAGER_OK;
}
}
}
*record_wait = waiting;
return IEC104_CONNECTION_OK;
}
/**
* 功能:回报一次真实TCP写入增量,并在整帧完成后推进连接计时和控制过程。
* 输入参数:frame_id——Prepare返回的当前帧身份;bytes_written_now——本次增量,非累计值,允许0。
* now_ms——本次单调时刻,最后一段的时刻作为整帧完成时刻;completion必填。
* 输出参数:Manager更新写入进度;仅FRAME_WRITTEN时写completion数值副本。
* 返回值:OK尚未整帧完成;FRAME_WRITTEN完成流程成功;ERROR身份/增量/时间非法;FATAL冻结。
* 调用方:TcpServer取得实际写入结果后;先回报本地已知完成,再Feed依赖该完成的ACK或U帧。
* 说明:Manager负责恰好一次Link完成、I帧记录更新及发送槽释放,本层不能再重复调用Complete。
* 使用真实完成时刻更新记录,只检查本地发送和STOP期限;对端确认t1交回接收阶段判定。
* 后续Drive可能在写入已记账后失败;即使返回FATAL也不能重报增量,须读取记录回收。
*/
IEC104_ConnectionResult_t IEC104_Connection_Handle_Send_Progress(
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id,
uint16_t bytes_written_now, uint64_t now_ms, IEC104_LocalSendCompletion_t *completion)
{
IEC104_LocalSendCompletion_t completed;
IEC104_SendManagerResult_t result;
IEC104_ConnectionResult_t checked = Check_Time(connection, frame_id.connection_generation, now_ms);
if (completion == NULL) {
return IEC104_CONNECTION_ERROR;
}
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
result = IEC104_SendManager_Handle_Send_Progress(
&connection->send_manager, frame_id, bytes_written_now, now_ms, &completed);
if (result == IEC104_SEND_MANAGER_ERROR) {
return IEC104_CONNECTION_ERROR;
}
connection->last_now_ms = now_ms;
if (result != IEC104_SEND_MANAGER_OK && result != IEC104_SEND_MANAGER_FRAME_WRITTEN) {
return Fail(connection, IEC104_CONNECTION_FAULT_INTERNAL);
}
if (result == IEC104_SEND_MANAGER_FRAME_WRITTEN) {
/* 只有整帧完成才刷新发送方向活动时间;0字节和部分写入不重置t3或本地发送期限。 */
connection->last_activity_ms = now_ms;
if (Update_Receive_Timer(connection) == IEC104_CONNECTION_FATAL) {
return IEC104_CONNECTION_FATAL;
}
if (completed.frame_type == IEC104_SEND_BUFFER_FRAME_TESTFR_ACT &&
completed.link_completion_result == IEC104_LINK_RESULT_OK) {
/* TESTFR等待CON的t1从ACT实际发完起算,与t3空闲活动时间独立。 */
connection->testfr_started_ms = completed.written_at_ms;
connection->testfr_timer_active = 1U;
}
if (completed.frame_type == IEC104_SEND_BUFFER_FRAME_STOPDT_CON &&
completed.link_completion_result == IEC104_LINK_RESULT_OK &&
completed.frame_id.connection_generation == connection->stop_con_frame_id.connection_generation &&
completed.frame_id.frame_serial == connection->stop_con_frame_id.frame_serial) {
/* 仅本轮最后一个STOP_CON完成结束期限;旧CON不能干扰后到START/STOP。 */
connection->stop_timer_active = 0U;
memset(&connection->stop_con_frame_id, 0, sizeof(connection->stop_con_frame_id));
}
}
/* 发送完成可能释放控制槽,立即尝试受阻请求;不能把重试延后到下一帧输入。 */
if (Drive(connection, now_ms, CHECK_LOCAL_TIMEOUTS) == IEC104_CONNECTION_FATAL) {
return IEC104_CONNECTION_FATAL;
}
if (result == IEC104_SEND_MANAGER_FRAME_WRITTEN) {
*completion = completed;
return IEC104_CONNECTION_FRAME_WRITTEN;
}
return IEC104_CONNECTION_OK;
}
/**
* 功能:把当前帧的最终传输失败交给Manager,再冻结连接供业务回收。
* 输入参数:connection——当前连接;frame_id——尚未整帧完成的当前发送帧身份。
* 返回值:匹配并处理失败后FATAL;错误代次、帧身份或调用阶段为ERROR;已冻结同代次仍为FATAL。
* 说明:部分字节可能已到达对端,不能撤回帧、重新分配N(S)或重发同一增量继续旧会话。
* 不自动关闭socket或清空业务记录;EAGAIN应走等待/0字节进度,不能当作最终失败。
*/
IEC104_ConnectionResult_t IEC104_Connection_Handle_Send_Failure(
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id)
{
IEC104_ConnectionResult_t checked = Require_Active(connection, frame_id.connection_generation);
IEC104_SendManagerResult_t result;
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
result = IEC104_SendManager_Handle_Send_Failure(&connection->send_manager, frame_id);
if (result == IEC104_SEND_MANAGER_ERROR) {
return IEC104_CONNECTION_ERROR;
}
return Fail(connection, result == IEC104_SEND_MANAGER_FATAL ?
IEC104_CONNECTION_FAULT_TRANSPORT : IEC104_CONNECTION_FAULT_INTERNAL);
}
/**
* 功能:由外部周期或事件唤醒,执行一轮保留帧重试、超时检查和控制应答推进。
* 输入参数:connection/connection_generation——本次唤醒所属连接;now_ms——当前单调时刻。
* 返回值:OK本轮正常;FRAME_READY仍有业务I帧;WAIT当前接收请求未受理;ERROR接入非法;FATAL冻结。
* 说明:不创建系统定时器、不执行I/O、不准备新的业务I帧;可能排队S或TESTFR等控制帧。
* 每次按当前有效过程的起点重新计算,旧回调不能携带旧时间差覆盖新一轮探测。
*/
IEC104_ConnectionResult_t IEC104_Connection_Tick(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms)
{
IEC104_ConnectionResult_t checked = Check_Time(connection, connection_generation, now_ms);
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
connection->last_now_ms = now_ms;
connection->receive_batch_active = 0U;
connection->receive_batch_pending = 0U;
return Drive(connection, now_ms, CHECK_ALL_TIMEOUTS);
}
/**
* 功能:只读汇总各活动期限,给TcpServer提供本轮等待上限。
* 输出参数:仅OK写wait_ms;查询不改变last_now_ms、期限起点、发送队列或故障状态。
* 说明:只查询当前发送头和最早已完成未ACK记录,与现有超时检查选取同一对象。
* t2/t3同时检查Link准入和控制槽容量;已有控制帧/等待CON时由对应发送或t1期限唤醒。
* 接收批次必须先用Tick结束,不能用等待查询代替推进协议或结束批次。
*/
IEC104_ConnectionResult_t IEC104_Connection_Get_Wait_Time(
const IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms,
uint32_t max_wait_ms, uint32_t *wait_ms)
{
ConnectionDeadlines_t deadlines;
IEC104_ConnectionResult_t checked;
uint32_t limited = max_wait_ms;
unsigned i;
if (wait_ms == NULL) {
return IEC104_CONNECTION_ERROR;
}
checked = Check_Time(connection, connection_generation, now_ms);
if (checked != IEC104_CONNECTION_OK) {
return checked;
}
if (connection->receive_batch_active != 0U || !Collect_Deadlines(connection, now_ms, &deadlines)) {
return IEC104_CONNECTION_ERROR;
}
for (i = 0U; i < DEADLINE_COUNT; ++i) {
if (deadlines.items[i].active != 0U && deadlines.items[i].remaining_ms < limited) {
limited = deadlines.items[i].remaining_ms;
}
}
*wait_ms = connection->receive_batch_pending != 0U ? 0U : limited;
return IEC104_CONNECTION_OK;
}
/**
* 功能:查看最早一条同时满足本地整帧完成和有效对端ACK的I帧回执。
* 输入参数:connection/connection_generation——匹配代次且尚未Close的连接,允许FAILED。
* 输出参数:仅OK时写record独立数值副本;不会自动消费记录或重新释放ASDU原文。
* 返回值:OK队首回执就绪;EMPTY无记录;WAIT最早记录尚未同时满足两个完成条件;ERROR接入非法。
* 调用方:TcpServer查询并成功复制RECORD事件后,再按返回的frame_id调用Remove_Confirmed_Record。
*/
IEC104_ConnectionResult_t IEC104_Connection_Peek_Confirmed_Record(
const IEC104_Connection_t *connection, uint64_t connection_generation, IEC104_IFrameSendRecord_t *record)
{
if (!Storage_Available(connection, connection_generation)) {
return IEC104_CONNECTION_ERROR;
}
return Record_Result(IEC104_SendManager_Peek_Confirmed_Record(&connection->send_manager, record));
}
/**
* 功能:TcpServer已复制RECORD事件后,移除匹配的已完成、已ACK队首记录,归还一条记录容量。
* 输入参数:connection——ACTIVE或FAILED连接;frame_id——Peek取得并已复制到事件中的回执身份。
* 返回值:OK移除成功;ERROR身份不符、队首未就绪或记录不可用。
* 说明:这里释放的是发送记录,不是ASDU原文;NORMAL原文已在ACK处理时确认释放或核减覆盖计数。
*/
IEC104_ConnectionResult_t IEC104_Connection_Remove_Confirmed_Record(
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id)
{
if (!Storage_Available(connection, frame_id.connection_generation)) {
return IEC104_CONNECTION_ERROR;
}
return Record_Result(IEC104_SendManager_Remove_Confirmed_Record(&connection->send_manager, frame_id));
}
/**
* 功能:按交接先后读取任意尚未消费的I帧记录,供故障检查和恢复决策使用。
* 输入参数:connection/connection_generation——匹配且尚未Close;offset——从当前最早记录起的条目偏移。
* 输出参数:仅OK时写record数值副本,保留原优先级、帧身份、本地完成及peer_acknowledged事实。
* 返回值:OK读到记录;EMPTY偏移超出当前条数;ERROR参数、身份或记录状态非法。
* 说明:ACTIVE/FAILED均可读取,不要求该记录已确认,也不消费;未ACK不能证明对端未收到。
*/
IEC104_ConnectionResult_t IEC104_Connection_Get_Record_By_Offset(
const IEC104_Connection_t *connection, uint64_t connection_generation,
uint16_t offset, IEC104_IFrameSendRecord_t *record)
{
if (!Storage_Available(connection, connection_generation)) {
return IEC104_CONNECTION_ERROR;
}
return Record_Result(IEC104_SendManager_Get_Record_By_Offset(&connection->send_manager, offset, record));
}
/**
* 功能:只读取得连接生命周期、首次故障、链路过程和保留接收帧的概况。
* 输入参数:connection/connection_generation——匹配对象及代次,允许ACTIVE、FAILED和CLOSED。
* 输出参数:仅OK时写status数值快照,不暴露底层原文指针。
* 返回值:OK查询成功;ERROR身份、初始化阶段、输出指针或模块状态非法,不改输出。
* 说明:CLOSED后的链路字段是收尾时的历史值,不能据此判断当前存在可用TCP连接。
*/
IEC104_ConnectionResult_t IEC104_Connection_Get_Status(
const IEC104_Connection_t *connection, uint64_t connection_generation, IEC104_ConnectionStatus_t *status)
{
IEC104_ConnectionStatus_t prepared;
IEC104_LinkStatus_t link_status;
IEC104_SendManagerStatus_t manager_status;
IEC104_ReceiverStatus_t receiver_status;
if (!Identity_Matches(connection, connection_generation) || status == NULL) {
return IEC104_CONNECTION_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
if (IEC104_Link_Get_Status(&connection->link_state, &link_status) != 1) {
return IEC104_CONNECTION_ERROR;
}
prepared.connection_generation = connection_generation;
prepared.phase = connection->phase;
prepared.fault = connection->fault;
prepared.dt_state = link_status.dt_state;
prepared.dt_procedure = link_status.dt_procedure;
prepared.testfr_state = link_status.testfr_state;
prepared.has_received_i_frame = connection->rx_link_applied;
/* Close已解除模块存储绑定;此时记录数和接收保留数为0,仅展示历史链路状态。 */
if (connection->phase != IEC104_CONNECTION_CLOSED) {
if (IEC104_SendManager_Get_Status(&connection->send_manager, &manager_status) != IEC104_SEND_MANAGER_OK ||
IEC104_Receiver_Get_Status(&connection->receiver, connection_generation, &receiver_status) !=
IEC104_RECEIVER_OK) {
return IEC104_CONNECTION_ERROR;
}
prepared.send_record_count = manager_status.record_count;
prepared.has_unhandled_frame = (uint8_t)(receiver_status.has_complete_frame != 0U &&
connection->rx_link_applied == 0U);
}
*status = prepared;
return IEC104_CONNECTION_OK;
}
/**
* 功能:在EOF、socket关闭通知或外部传输故障到达时冻结当前连接。
* 输入参数:connection/connection_generation——通知所属连接,允许ACTIVE或已FAILED。
* 输出参数:ACTIVE时登记TRANSPORT故障;已FAILED保留首次原因,不清空记录和接收原文。
* 返回值:匹配未Close的连接返回FATAL;旧代次、未初始化或已CLOSED返回ERROR。
* 说明:本入口没有当前帧的写入结果,不能据此推定已发送字节或提交该帧Complete。
* 调用方负责结束真实I/O、回收所需记录,再Close解除借用。
*/
IEC104_ConnectionResult_t IEC104_Connection_Handle_Transport_Closed(
IEC104_Connection_t *connection, uint64_t connection_generation)
{
if (!Storage_Available(connection, connection_generation)) {
return IEC104_CONNECTION_ERROR;
}
return Fail(connection, IEC104_CONNECTION_FAULT_TRANSPORT);
}
/**
* 功能:结束连接内部存储借用,保留外部ASDU队列供显式重连恢复或丢弃。
* 输入参数:connection/connection_generation——准备收尾的连接;必须已结束旧I/O及所有字节借用。
* 输出参数:Abort发送管理及缓冲,清除接收进度和计时有效标志,phase置CLOSED;保留首次故障。
* 返回值:OK收尾完成,包括同代次重复Close;ERROR对象、代次或初始化阶段非法。
* 调用方:TcpServer先复制剩余帧、记录及断开通知,再Close;此后记录查询及原文借用均不能继续使用。
* 说明:不关闭socket、不隐式Rebind;恢复顺序为Close -> Queue.Rebind新代次 -> Init -> 新STARTDT。
*/
IEC104_ConnectionResult_t IEC104_Connection_Close(
IEC104_Connection_t *connection, uint64_t connection_generation)
{
if (!Identity_Matches(connection, connection_generation)) {
return IEC104_CONNECTION_ERROR;
}
if (connection->phase == IEC104_CONNECTION_CLOSED) {
return IEC104_CONNECTION_OK;
}
IEC104_SendManager_Abort(&connection->send_manager);
/* Close以结束旧I/O为前提,清除接收缓冲;不触碰独立持有的ASDU队列。 */
IEC104_Receiver_Abort(&connection->receiver);
connection->rx_link_applied = 0U;
connection->rx_ack_timer_active = 0U;
connection->testfr_timer_active = 0U;
connection->stop_timer_active = 0U;
memset(&connection->stop_con_frame_id, 0, sizeof(connection->stop_con_frame_id));
memset(&connection->transfer_observer, 0, sizeof(connection->transfer_observer));
connection->receive_batch_active = 0U;
connection->receive_batch_pending = 0U;
connection->phase = IEC104_CONNECTION_CLOSED;
return IEC104_CONNECTION_OK;
}