1183 lines
61 KiB
C
1183 lines
61 KiB
C
/*
|
||
* 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;
|
||
}
|