Files
STM32F4-Base/IEC60870-Slave/104/IEC104_Connection.h
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

295 lines
20 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内部的子站连接协调:串行衔接Receiver、Link和SendManager,不执行socket I/O。
* 本头文件供TcpServer实现使用;业务通过IEC104_TcpServer.h接入。
* 下文的调用方指TcpServer;对象及借用存储由其管理,内部帧和记录由其转为业务事件。
*/
#ifndef IEC104_CONNECTION_H
#define IEC104_CONNECTION_H
#include "IEC104_Receiver.h"
#include "IEC104_SendManager.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 按具体值分支;只有SEND_READY提供发送字节,FRAME_WRITTEN提供完成结果。 */
typedef enum {
IEC104_CONNECTION_FATAL = -2, /* 已冻结连接;停止I/O,读取记录后Close */
IEC104_CONNECTION_ERROR = -1, /* 参数、身份、调用阶段或时钟非法 */
IEC104_CONNECTION_EMPTY = 0, /* 无可查询项或无待发送业务 */
IEC104_CONNECTION_OK = 1, /* 本次操作成功;发送进度接口中表示尚未整帧写完 */
IEC104_CONNECTION_WAIT = 2, /* 接收请求未受理,或发送门控/容量阻塞 */
IEC104_CONNECTION_FRAME_READY = 3, /* I帧已完成链路处理,等待TcpServer复制为事件并释放 */
IEC104_CONNECTION_SEND_READY = 4, /* 当前帧的剩余字节可交给实际传输 */
IEC104_CONNECTION_FRAME_WRITTEN = 5 /* 整帧写完及对应完成处理均已提交 */
} IEC104_ConnectionResult_t;
typedef enum {
IEC104_CONNECTION_UNINITIALIZED = 0, /* 零值标记;仍须成功Init后才能调用运行接口 */
IEC104_CONNECTION_ACTIVE, /* TCP由TcpServer管理;不表示已经STARTED */
IEC104_CONNECTION_FAILED, /* 保留接收原文、发送记录及ASDU队列供回收 */
IEC104_CONNECTION_CLOSED /* 借用已解除;ASDU队列仍由TcpServer持有 */
} IEC104_ConnectionPhase_t;
typedef enum {
IEC104_CONNECTION_FAULT_NONE = 0, /* 尚未登记故障 */
IEC104_CONNECTION_FAULT_FRAME, /* 起始字节、APCI或ASDU最低长度非法 */
IEC104_CONNECTION_FAULT_PROTOCOL, /* 序号、ACK或接收阶段不符合协议 */
IEC104_CONNECTION_FAULT_INTERNAL, /* 模块状态或交接失败 */
IEC104_CONNECTION_FAULT_TRANSPORT, /* 发送失败或外部传输故障;正常有序EOF由TcpServer收尾 */
IEC104_CONNECTION_FAULT_LOCAL_SEND_TIMEOUT, /* 当前APDU排队及写入总期限到达 */
IEC104_CONNECTION_FAULT_I_ACK_TIMEOUT, /* 已完整发出的I帧等待对端ACK达到t1 */
IEC104_CONNECTION_FAULT_TESTFR_TIMEOUT, /* 本端TESTFR_ACT已发完,等待CON达到t1 */
IEC104_CONNECTION_FAULT_STOP_TIMEOUT /* STOP已受理,确认及对应CON发送未在总期限内完成 */
} IEC104_ConnectionFault_t;
/* 内部连接配置的类型别名;配置值由公共Types定义,与TcpServer使用同一布局。 */
typedef IEC104_ProtocolConfig_t IEC104_ConnectionConfig_t;
/* TcpServer借用原文至匹配的Release或Close;代次与receive_serial共同标识这次内部读取。 */
typedef struct {
IEC104_ReceivedFrame_t frame; /* 完整APDU/APCI;仅交付已受理的I帧 */
uint64_t receive_serial; /* 本连接内不复用,拒绝迟到的重复Release */
} IEC104_ConnectionReceivedFrame_t;
/* 无指针的观察快照;CLOSED后的链路字段是收尾时的历史值,不用这些字段自行组合收发门控。 */
typedef struct {
uint64_t connection_generation; /* 本快照所属连接代次 */
IEC104_ConnectionPhase_t phase; /* Connection运行、故障冻结或已关闭阶段 */
IEC104_ConnectionFault_t fault; /* 首次故障原因;正常关闭仍可为NONE */
IEC104_DataTransferState_t dt_state; /* 受理START/STOP后立即更新的数据传输状态 */
IEC104_DtProcedure_t dt_procedure; /* 子站仅IDLE或STOP_DRAINING,CON进度由发送缓冲维护 */
IEC104_TestfrState_t testfr_state; /* 本端主动探测的发送/等待CON进度 */
uint16_t send_record_count; /* 全部未消费I帧记录数,包括已经ACK的回执 */
uint8_t has_received_i_frame; /* TcpServer可Peek,复制为事件后显式Release */
uint8_t has_unhandled_frame; /* WAIT或故障时保留的尚未受理帧 */
} IEC104_ConnectionStatus_t;
/*
* 仅供TcpServer内部的启停通知适配;不是业务回调,不得重入修改接口,可只读查询状态。
* can_accept只读查询启停通知容量;notify在实际状态改变后、请求处理返回前复制通知。
* 两个函数必须同时设置或同时为空;为空时独立Connection不产生通知背压。
*/
typedef struct {
void *context;
int (*can_accept)(void *context);
int (*notify)(void *context, IEC104_DataTransferState_t state);
} IEC104_ConnectionTransferObserver_t;
/*
* TcpServer内部持有,完整布局用于静态存储;全部字段由接口维护,无malloc、无锁、无业务回调。
* Init后地址固定,不能复制/移动:Manager引用本结构内唯一的Link和SendBuffer。
* 同一连接的所有调用必须串行;不要再直接调用内含对象的修改接口。
* Link和SendBuffer的运行期修改统一经Manager;Connection只读Link,发送期限也通过Manager查询。
* ASDU队列独立持有以支持重连恢复;对象、数组及输入/输出参数存储不得重叠。
*/
typedef struct {
IEC104_Receiver_t receiver; /* 组装半帧并保留完整APDU;I帧由TcpServer复制为事件后释放 */
IEC104_LinkState_t link_state; /* 本连接唯一协议状态,收发共用序号和启停进度 */
IEC104_SendBuffer_t send_buffer; /* 当前I帧及控制FIFO,保存实际写入进度 */
IEC104_SendManager_t send_manager; /* 关联ASDU、发送帧和ACK记录,引用本对象内的Link/Buffer */
IEC104_ConnectionConfig_t config; /* Init时复制的配置,运行期间保持不变 */
IEC104_ConnectionTransferObserver_t transfer_observer; /* 内部通知容量及变化交接 */
IEC104_SendFrameId_t stop_con_frame_id; /* 当前STOP对应的最后一帧CON,拒绝旧完成结束新期限 */
uint64_t connection_generation; /* 非零且跨连接不复用,所有外部事件均须匹配 */
uint64_t last_now_ms; /* 串行事件的单调时刻,不接受倒退 */
uint64_t last_activity_ms; /* 完整合法APDU到达或本地整帧完成;反向启停CON除外 */
uint64_t received_at_ms; /* 当前完整接收帧到达时刻,WAIT不续期 */
uint64_t receive_serial; /* 每次成帧递增;耗尽时结束连接 */
uint64_t rx_ack_started_ms; /* 当前连续未确认接收区间的最早起点 */
uint64_t testfr_started_ms; /* 本次TESTFR_ACT完整写出的时刻 */
uint64_t stop_started_ms; /* 本轮STOP原成帧时刻;受理时启用计时,受理前WAIT也计入总期限 */
IEC104_ConnectionPhase_t phase; /* Connection生命周期,独立于Link的STARTED/STOPPED */
IEC104_ConnectionFault_t fault; /* 只登记首次故障;Close保留该值供查询 */
uint8_t rx_link_applied; /* I帧已交给Manager,不得因重复Peek/驱动再交一次 */
uint8_t rx_ack_timer_active; /* 0/1:存在尚未全部确认的接收区间,rx_ack_started_ms有效 */
uint8_t testfr_timer_active; /* 0/1:本次ACT已完整发送,testfr_started_ms有效 */
uint8_t stop_timer_active; /* 0/1:STOP已受理、未取消且CON尚未完成,stop_started_ms有效 */
uint8_t receive_batch_active; /* 有界接收批次内暂缓对端确认超时,下一次Tick结束批次 */
uint8_t receive_batch_pending; /* 固定输入快照尚未处理完,下一轮必须立即续收 */
} IEC104_Connection_t;
/* 写入Link默认值;本地发送和停止期限默认等于默认t1,HIGH严格优先。NULL无操作。 */
extern void IEC104_Connection_Config_Default(IEC104_ConnectionConfig_t *config);
/*
* 初始化新子站连接,初始STOPPED;generation非零且跨连接不复用,now_ms允许0。
* queue已Init/Rebind到该代次且无待ACK条目;可有待发NORMAL,成功时清空HIGH。
* control_capacity>=1,record_capacity为1..32767;config不可为NULL。
* 配置须满足0<t2_ms<t1_ms;不满足时返回ERROR,不改connection或队列。
* OK后对象地址及借用存储保持有效;失败不改connection或队列。
* Manager负责校验并在最终地址初始化Link/Buffer;成功后仅提交其余连接字段,不修补Manager内部指针。
* 仅用于首次使用或Close后的对象;重新Init前必须结束旧I/O和所有借用。
*/
extern 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);
/*
* Init后、首次Feed前安装内部启停通知适配;observer复制入连接,context须持续有效。
* 参数非法或已经接收过APDU返回ERROR,不改变适配器;不执行通知或推进协议。
*/
extern IEC104_ConnectionResult_t IEC104_Connection_Set_Transfer_Observer(
IEC104_Connection_t *connection, uint64_t connection_generation,
const IEC104_ConnectionTransferObserver_t *observer);
/*
* 可选:开始一轮有界的非阻塞接收,暂缓I帧ACK和TESTFR_CON的t1判定。
* 用于先处理socket中已经到达的多帧输入,再检查对端是否仍欠确认;不重置期限。
* 同一轮必须调用Tick或Yield_Receive_Batch结束批次;重复Begin返回ERROR。
* 期间仍检查本地发送和STOP总期限,格式/协议错误仍立即失败;Close也会结束批次。
* 未调用本接口时,Feed仍即时检查全部期限;发送准备/回报仅检查本地发送和STOP期限。
*/
extern IEC104_ConnectionResult_t IEC104_Connection_Begin_Receive_Batch(
IEC104_Connection_t *connection, uint64_t connection_generation);
/* 仅供调度预算让出:结束当前轮次,但已到达的有限输入快照尚未全部处理。
* 不改任何期限起点,暂缓I_ACK/TESTFR判定;本地发送、STOP及控制推进仍有效。
* Get_Wait_Time返回0,调用方必须立即续收,且不得把后来到达的输入加入旧快照。
* 快照耗尽、实际容量背压或接收无进展时必须调用Tick恢复完整期限检查。
* 不能用于等待业务消费或socket可读;仅ACTIVE且已Begin时可调用。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Yield_Receive_Batch(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms);
/*
* 最多消费至一帧末尾,consumed_length必填;非NULL时始终写入实际消费量。
* 半帧或已受理S/U返回OK;I帧返回FRAME_READY;控制槽或启停通知容量不足返回WAIT并保留当前帧。
* 后两者重入时消费0字节。保留未消费尾部;Release/驱动解除阻塞后再Feed尾部。
* 长度为0时data可为NULL,用于重试当前帧。格式/协议错误冻结连接并返回FATAL。
* 格式合法的STARTDT_CON/STOPDT_CON消费后忽略,不应答、不改变启停或确认期限、不刷新t3;
* 不占业务通知或控制发送槽,处理后仍照常检查期限并推进已有控制义务。
* 链路受理不代表ASDU业务执行成功;ASDU内容由业务解析。无效参数不消费输入。
*/
extern 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);
/* FRAME_READY时写只读视图;无业务帧EMPTY,尚未受理WAIT。FAILED仍可读取已受理I帧。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Peek_Received_Frame(
const IEC104_Connection_t *connection, uint64_t connection_generation,
IEC104_ConnectionReceivedFrame_t *frame);
/* TcpServer成功复制I_FRAME事件后释放匹配I帧,OK/ERROR;允许FAILED回收,不再次提交链路事件。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Release_Received_Frame(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t receive_serial);
/*
* 提交当前连接的应答;仅接收批次之外、同代次ACTIVE连接可用,不执行socket I/O。
* 两条路径均由Manager按实际状态准入:仅STARTED+IDLE时接手。
* 其他阶段在容量检查前返回ERROR且不入HIGH;普通路径先Drive,再检查准入。
* SEND_READY已生成I帧并写head_info;OK已复制到HIGH;两者都不再借用输入原文。
* WAIT尚未接手,ERROR参数/阶段/容量非法,FATAL连接失败;准备后的故障不能直接重放原文。
* 到期且准入允许时仅入HIGH,由Process先读取待处理ACK再检查期限;未到期时统一推进控制流程。
* 受理新STOPDT_ACT时,HIGH待发应答作废且无逐条回执;重复STARTDT_ACT不清理HIGH。
* 服务级NORMAL直接进入TcpServer持有的ASDU队列;应答的HIGH顺序、窗口及公平配额由Manager保持。
*/
extern 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);
/* 不排队变体:仅SEND_READY接手;两级待发数据、窗口/缓冲背压均返回WAIT。
* 到期协议工作也先返回WAIT,由正常接收/Process处理后重试,不提前判远端超时。
* WAIT不保存原文、不编号;其余参数、会话及输出约定沿用Submit_Response。 */
extern 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);
/*
* 推进接收重试、控制应答及本地发送/STOP期限,再取得剩余发送字节;仅SEND_READY写head_info。
* 不在此处报告I帧ACK或TESTFR_CON超时故障;调用方须按Get_Wait_Time及时接收并Tick。
* 任一确认t1已到时只取得已有帧;无已有帧返回WAIT,不选择HIGH/NORMAL、不分配N(S)。
* 有效确认解除所有已到期等待后重新准入,不重置原期限;接收批次内外均适用。
* 无当前帧时才选ASDU并编号。RX的WAIT不阻止已有帧发送,避免控制槽满时互等。
* pending_data借用至该帧完成或Close;一次只能有一个未回报的写操作,禁止APDU交错。
*/
extern 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);
/* 内部EOF收尾查询:仅在Prepare_Next_Send返回WAIT、转移就绪事件后使用。
* 记录容量已满、队首可回收,且启停/k窗口和上次准备时刻的确认t1均允许新I帧时,record_wait=1。
* 表示当前等待可通过转移RECORD解除;其他限制为0,不把还需新ACK/STARTDT的等待误判为可恢复。
* 只读,不推进协议或预留序号;仅OK写输出,ERROR/FATAL沿用连接查询约定。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Get_Record_Wait(
const IEC104_Connection_t *connection, uint64_t connection_generation, uint8_t *record_wait);
/*
* 回报实际写入增量,0表示暂不可写;OK尚未写完,FRAME_WRITTEN才写completion。
* now_ms使用实际写入后的时刻;记录及计时照常更新,对端确认t1由后续接收/Tick判定。
* 本地发送和STOP期限仍检查;不得通过回报发送进度替代接收及Tick调度。
* 完成后自动重试保留接收帧/控制流程。FATAL可能发生在已登记写入之后,不能重放增量。
* 先回报本地已知完成,再Feed覆盖该帧的ACK;不允许用对端ACK推定写入结果。
*/
extern 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);
/* 最终写失败:匹配当前帧时提交失败并冻结,返回FATAL;错身份ERROR,不影响当前帧。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Handle_Send_Failure(
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id);
/*
* TcpServer在Poll中调用,结束可选接收批次并检查全部期限;会排队到期的S/TESTFR。
* 不创建系统定时器、不准备新I帧。固定输入快照耗尽或实际背压时必须调用Tick。
* 返回OK、接收FRAME_READY/WAIT或FATAL;参数、阶段、旧代次或时钟非法返回ERROR。
* 所有now_ms须为同一单调时钟且按调用顺序不倒退,时间戳0有效,是否计时由active标志区分。
* t2保留连续未确认区间的最早起点;只确认区间前缀时不续期,剩余部分可能提前确认。
*/
extern IEC104_ConnectionResult_t IEC104_Connection_Tick(
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms);
/*
* 供TcpServer在接收批次结束且已推进协议后,查询下一次驱动前可等待的毫秒数。
* max_wait_ms是调用方本轮允许的上限,0表示不等待,UINT32_MAX仍是有限毫秒数。
* OK才写wait_ms,范围0..max_wait_ms;到期返回0,没有更早期限则返回max_wait_ms。
* 覆盖本地发送、I帧/TESTFR的t1、STOP总期限及当前可准备控制帧的t2/t3。
* 与超时检查、发送准入和控制推进共用即时期限视图,不另存或改写各帧的原始起点。
* S/TESTFR已在途或控制槽满时不反复被旧t2/t3唤醒,仍受发送及其他活动期限约束。
* 只读查询,不更新时刻、不续期、不排队或冻结连接;实际处理仍由Tick/收发驱动。
* 参数、代次、时钟、内部状态非法或接收批次未结束返回ERROR;FAILED返回FATAL。
*/
extern 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);
/* 供TcpServer查询内部回执并转换为RECORD事件;允许ACTIVE或FAILED,OK才写record。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Peek_Confirmed_Record(
const IEC104_Connection_t *connection, uint64_t connection_generation,
IEC104_IFrameSendRecord_t *record);
/* TcpServer成功复制RECORD事件后移除匹配队首回执;允许FAILED回收,不再次释放ASDU原文。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Remove_Confirmed_Record(
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id);
/* 按交接顺序读取记录,供故障回收;OK写副本,越界EMPTY。Close后不能再读取。 */
extern 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);
/* 返回数值状态,允许FAILED/CLOSED;OK写status,参数、旧代次或模块状态非法时ERROR不改输出。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Get_Status(
const IEC104_Connection_t *connection, uint64_t connection_generation,
IEC104_ConnectionStatus_t *status);
/* EOF或外部传输失败时冻结;ACTIVE/FAILED下同代次返回FATAL并保留首次原因。
* 旧代次、未初始化或CLOSED返回ERROR;不关闭socket、提交当前帧Complete或清空记录。 */
extern IEC104_ConnectionResult_t IEC104_Connection_Handle_Transport_Closed(
IEC104_Connection_t *connection, uint64_t connection_generation);
/*
* 先结束旧I/O、结束字节借用、读取所需记录,再Close;同代次重复Close返回OK。
* 清理Receiver/Manager/SendBuffer,保留外部ASDU队列;不执行socket关闭或隐式Rebind。
* 重连顺序:Close -> Queue.Rebind(新代次) -> Connection.Init -> 新STARTDT。
*/
extern IEC104_ConnectionResult_t IEC104_Connection_Close(
IEC104_Connection_t *connection, uint64_t connection_generation);
#ifdef __cplusplus
}
#endif
#endif /* IEC104_CONNECTION_H */