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

1332 lines
74 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.
/**
* 子站串行发送管理:发送流程、子站启停封装和初始化入口集中在本文件。
* 本模块由Connection内部调用;业务通过TcpServer提交ASDU并读取事件副本,不直接操作Manager。
* 完成分派按原帧类型直接调用公共或子站Link接口,不经过角色回调。
* S/TESTFR与子站启停构帧入口复用控制槽预留、取消和提交,状态机仍由Link维护。
* Init只绑定CONTROLLED链路;启停流程接收ACT并发送CON。
*/
#include "IEC104_SendManager.h"
#include "IEC104_Link_Slave_StartStop.h"
#include <stddef.h>
#include <string.h>
/* ==================== 公共管理:调度、进度、确认与回收 ==================== */
/**
* 功能:计算15位序号沿发送方向的循环距离。
* 输入参数:from——起始序号,范围0..32767。
* to——目标序号,范围0..32767。
* 输出参数:无。
* 返回值:0..32767范围内的模32768距离。
* 说明:调用方须先保证区间受窗口或容量约束;不能直接用序号大小判断先后。
*/
static uint16_t Sequence_Distance(uint16_t from, uint16_t to)
{
return (uint16_t)(((uint32_t)to + 32768U - from) & IEC104_SEQUENCE_MASK);
}
/**
* 功能:将业务记录相对队首的逻辑偏移换算为数组下标。
* 输入参数:manager——存储边界已验证且record_capacity非零的发送管理器。
* offset——相对记录队首的槽数偏移,不是字节数,可定位现有记录或下一个空槽。
* 输出参数:无。
* 返回值:0..record_capacity-1范围内的物理下标。
* 说明:用32位加法避免索引相加溢出;本函数不验证该槽是否已有有效记录。
*/
static uint16_t Record_Index_From_Offset(const IEC104_SendManager_t *manager, uint16_t offset)
{
return (uint16_t)(((uint32_t)manager->record_head_index + offset) % manager->record_capacity);
}
/**
* 功能:检查发送管理器自身的存储边界、绑定指针和配置。
* 输入参数:manager——待检查的发送管理器,允许NULL。
* 输出参数:无。
* 返回值:控制结构合法返回1,否则返回0。
* 说明:不要求Link及两个队列仍可运行,也不扫描业务记录。
* 故障回收仍须读取帧身份、NORMAL来源编号和ACK事实,不能被故障后的会话状态阻断。
*/
static int Manager_Storage_Valid(const IEC104_SendManager_t *manager)
{
return manager != NULL && manager->is_initialized == 1U && manager->asdu_queue != NULL &&
manager->send_buffer != NULL && manager->link_state != NULL && manager->records != NULL &&
manager->record_capacity != 0U && manager->record_capacity <= IEC104_SEQUENCE_MASK &&
manager->record_head_index < manager->record_capacity &&
manager->record_count <= manager->record_capacity && manager->connection_generation != 0U &&
manager->high_unacked_count <= manager->record_count &&
manager->send_window_k != 0U && manager->send_window_k <= IEC104_SEQUENCE_MASK &&
manager->next_record_ns <= IEC104_SEQUENCE_MASK &&
manager->high_priority_burst_count <= manager->high_priority_burst_limit;
}
/**
* 功能:检查业务记录的身份、序号快照、完成标志及有效发送时刻。
* 输入参数:manager——已通过Manager_Storage_Valid检查的发送管理器。
* record——指向已登记记录的非NULL指针。
* offset——该记录相对队首的偏移,调用方保证小于record_count。
* 输出参数:无。
* 返回值:记录符合当前位置的约束返回1,否则返回0。
* 说明:队列发送及直接应答共用Prepare_I_Asdu连续登记,记录只从队首释放,因此首条N(S)=next_record_ns-record_count。
* 记录容量小于序号环,可按offset推导唯一N(S)。
* 本地完成和对端ACK分别记录;已确认的业务记录必须已经提交本地完成。
*/
static int I_Frame_Record_Valid(const IEC104_SendManager_t *manager,
const IEC104_IFrameSendRecordStorage_t *record, uint16_t offset)
{
uint16_t expected = (uint16_t)(((uint32_t)manager->next_record_ns + 32768U -
manager->record_count + offset) & IEC104_SEQUENCE_MASK);
return record->frame_serial != 0U &&
((record->asdu_priority == IEC104_ASDU_PRIORITY_HIGH && record->asdu_entry_serial == 0U) ||
(record->asdu_priority == IEC104_ASDU_PRIORITY_NORMAL && record->asdu_entry_serial != 0U)) &&
record->i_frame_snapshot.send_number == expected && record->i_frame_snapshot.receive_number <= IEC104_SEQUENCE_MASK &&
record->i_frame_snapshot.frame_length >= IEC104_APCI_LEN + IEC104_ASDU_HEADER_LEN &&
record->i_frame_snapshot.frame_length <= IEC104_MAX_APDU_LEN &&
record->local_send_completed <= 1U && record->peer_acknowledged <= 1U &&
(record->peer_acknowledged == 0U || record->local_send_completed != 0U) &&
(record->local_send_completed == 0U || record->written_at_ms >= record->queued_at_ms);
}
/**
* 功能:检查当前连接的Link序号、当前I帧、保留ASDU和记录数量是否一致。
* 输入参数:manager——待检查对象,允许NULL;connection_generation——本次事件的代次。
* 输出参数:无,通过公开查询读取各模块数值,不修改它们的控制字段。
* 返回值:运行状态自洽返回1,否则0;不证明TCP仍然可用。
* 说明:Link独占协议边界;Buffer报告至多一个当前I帧;HIGH未确认数量由Manager记录维护。
* 本模块只服务CONTROLLED角色,绑定后的角色变化属于本地状态非法。
* HIGH未确认记录数加NORMAL保留及覆盖待确认条目数,应等于Link已编号未ACK的I帧数。
* 未消费的已ACK回执仍占记录,所以record_count可以大于未ACK帧数。
* 公开入口不允许遗留预留;整个跨模块事务完成后才检查,不能检查提交中间态。
*/
static int Manager_Session_Valid(const IEC104_SendManager_t *manager, uint64_t connection_generation)
{
IEC104_IFrameSendProgress_t link_progress;
IEC104_LinkStatus_t link_status;
IEC104_AsduQueueStatus_t asdu_status;
IEC104_SendBufferStatus_t buffer_status;
uint16_t pending, outstanding;
if (!Manager_Storage_Valid(manager) || connection_generation != manager->connection_generation ||
IEC104_Link_Get_Status(manager->link_state, &link_status) != 1 ||
link_status.role != IEC104_ROLE_CONTROLLED ||
IEC104_Link_Get_Send_Progress(manager->link_state, &link_progress) != 1 ||
IEC104_AsduQueue_Get_Status(manager->asdu_queue, &asdu_status) != IEC104_ASDU_QUEUE_OK ||
IEC104_SendBuffer_Get_Status(manager->send_buffer, &buffer_status) != IEC104_SEND_BUFFER_OK ||
asdu_status.connection_generation != connection_generation ||
buffer_status.connection_generation != connection_generation) {
return 0;
}
/* pending只统计唯一当前I帧;outstanding还包含已经写完但尚未ACK的I帧。 */
pending = Sequence_Distance(link_progress.next_completion_ns, link_progress.next_frame_ns);
outstanding = Sequence_Distance(link_progress.peer_ack_nr, link_progress.next_frame_ns);
return manager->next_record_ns == link_progress.next_frame_ns &&
pending <= 1U && buffer_status.has_i_frame == pending &&
(pending == 0U || buffer_status.i_frame_ns == link_progress.next_completion_ns) &&
buffer_status.has_reservation == 0U && outstanding <= manager->send_window_k &&
manager->record_count >= outstanding &&
asdu_status.retained_unacked_count[IEC104_ASDU_PRIORITY_HIGH] == 0U &&
(uint64_t)manager->high_unacked_count +
asdu_status.retained_unacked_count[IEC104_ASDU_PRIORITY_NORMAL] +
asdu_status.overwritten_unacked_count == outstanding;
}
/* 已校验会话;START受理即允许应答,STOP受理即拒绝;实际发送仍遵守FIFO。 */
static int Response_Submit_Allowed(const IEC104_SendManager_t *manager)
{
const IEC104_LinkState_t *state = manager->link_state;
return state->dt_state == IEC104_DT_STARTED && state->dt_procedure == IEC104_DT_PROC_IDLE;
}
/**
* 功能:返回发送记录存储概况,使连接层无需读取Manager内部字段。
* 输入参数:manager——尚未Abort的管理器;status——不重叠的输出存储。
* 返回值:OK写数值副本,ERROR参数/存储非法且不改输出。
* 说明:不依赖故障后的Link运行状态,可在连接冻结期间用于记录回收和状态展示。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Get_Status(
const IEC104_SendManager_t *manager, IEC104_SendManagerStatus_t *status)
{
IEC104_SendManagerStatus_t prepared;
if (status == NULL || !Manager_Storage_Valid(manager)) {
return IEC104_SEND_MANAGER_ERROR;
}
prepared.connection_generation = manager->connection_generation;
prepared.record_capacity = manager->record_capacity;
prepared.record_count = manager->record_count;
prepared.high_unacked_count = manager->high_unacked_count;
*status = prepared;
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:校验当前发送会话后,将业务ASDU交给绑定队列复制保存。
* 输入参数:manager/connection_generation——有效会话及代次;priority/asdu/asdu_length——待入队业务。
* 返回值:OK成功,WAIT容量/条目编号窗口不足,ERROR参数、会话或HIGH准入阶段非法。
* 调用关系:Connection推迟应答及Submit_Response回退时使用,向上隐藏队列指针及专用返回值。
* 说明:HIGH与Submit_Response共用准入检查,先于队列容量判断;NORMAL不受此检查限制。
* 内部条目编号由队列管理,协议N(S)在实际准备I帧时分配;不推进发送进度。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Enqueue(
IEC104_SendManager_t *manager, uint64_t connection_generation,
IEC104_AsduPriority_t priority, const uint8_t *asdu, uint16_t asdu_length)
{
IEC104_AsduQueueResult_t result;
if (!Manager_Session_Valid(manager, connection_generation)) {
return IEC104_SEND_MANAGER_ERROR;
}
if (priority == IEC104_ASDU_PRIORITY_HIGH && !Response_Submit_Allowed(manager)) {
return IEC104_SEND_MANAGER_ERROR;
}
/* 队列同步复制原文;这里不创建APDU,后续Prepare_Next_Send才选择业务并分配协议序号。 */
result = IEC104_AsduQueue_Enqueue(manager->asdu_queue, connection_generation, priority, asdu, asdu_length);
if (result == IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_OK;
}
/* FULL是可等待的背压;其他非法输入或状态统一返回ERROR,不作为容量不足反复重试。 */
return result == IEC104_ASDU_QUEUE_FULL ? IEC104_SEND_MANAGER_WAIT : IEC104_SEND_MANAGER_ERROR;
}
/**
* 功能:为发送队首定位尚未提交本地完成的I帧业务记录。
* 输入参数:manager——会话及存储边界已验证的发送管理器。
* head_info——当前发送队首的有效I帧信息,代次已由调用入口验证。
* 输出参数:无,不修改记录。
* 返回值:成功返回内部记录指针;找不到或身份、快照不匹配时返回NULL。
* 说明:先按N(S)计算偏移,再核对frame_id的frame_serial、原始N(R)、长度和入队时刻。
* 不能只用可能回绕的N(S)识别完成事件;返回指针只供当前串行操作使用。
*/
static IEC104_IFrameSendRecordStorage_t *Find_Pending_Send_Record(IEC104_SendManager_t *manager,
const IEC104_SendBufferHeadInfo_t *head_info)
{
uint16_t first = (uint16_t)(((uint32_t)manager->next_record_ns + 32768U -
manager->record_count) & IEC104_SEQUENCE_MASK);
uint16_t offset = Sequence_Distance(first, head_info->i_frame_snapshot.send_number);
IEC104_IFrameSendRecordStorage_t *record;
if (offset >= manager->record_count) {
return NULL;
}
record = &manager->records[Record_Index_From_Offset(manager, offset)];
if (!I_Frame_Record_Valid(manager, record, offset) || record->local_send_completed != 0U ||
record->frame_serial != head_info->frame_id.frame_serial ||
record->i_frame_snapshot.receive_number != head_info->i_frame_snapshot.receive_number ||
record->i_frame_snapshot.frame_length != head_info->apdu_length || record->queued_at_ms != head_info->queued_at_ms) {
return NULL;
}
return record;
}
/**
* 功能:按保存的帧类型将完整发送或发送失败结果交给对应链路完成接口。
* 输入参数:manager——已校验的子站管理器,绑定本连接CONTROLLED链路。
* head_info——有效队首信息,保存原始帧类型及I帧快照。
* sent_len——实际整帧发送长度;发送失败时传-1。
* 输出参数:manager->link_state——由匹配的链路完成接口按其约定提交或清理状态。
* 返回值:返回对应接口的OK、IGNORED或FATAL;本端不支持的帧类型返回FATAL。
* 说明:不按当前dt_procedure猜测报文身份,不执行I/O或发送队列出队。
* STARTDT_CON/STOPDT_CON直接提交子站完成接口;帧类型在缓冲提交时已完成校验。
* TESTFR_CON为无状态应答,没有Link完成接口;长度为6返回OK,否则返回FATAL。
*/
static IEC104_LinkResult_t Dispatch_Link_Send_Completion(const IEC104_SendManager_t *manager,
const IEC104_SendBufferHeadInfo_t *head_info, int sent_len)
{
IEC104_LinkState_t *link_state = manager->link_state;
switch (head_info->frame_type) {
case IEC104_SEND_BUFFER_FRAME_I:
return IEC104_Link_Complete_I_Send(link_state, &head_info->i_frame_snapshot, sent_len);
case IEC104_SEND_BUFFER_FRAME_S_ACK:
return IEC104_Link_Complete_S_Ack_Send(link_state, sent_len);
case IEC104_SEND_BUFFER_FRAME_STARTDT_CON:
return IEC104_Link_Complete_Startdt_Con_Send(link_state, sent_len);
case IEC104_SEND_BUFFER_FRAME_STOPDT_CON:
return IEC104_Link_Complete_Stopdt_Con_Send(link_state, sent_len);
case IEC104_SEND_BUFFER_FRAME_TESTFR_ACT:
return IEC104_Link_Complete_Testfr_Act_Send(link_state, sent_len);
case IEC104_SEND_BUFFER_FRAME_TESTFR_CON:
return sent_len == IEC104_APCI_LEN ? IEC104_LINK_RESULT_OK : IEC104_LINK_RESULT_FATAL;
default:
return IEC104_LINK_RESULT_FATAL;
}
}
/**
* 功能:校验同连接各模块的状态,并为一帧S/U取得控制槽。
* 输入参数:manager——待校验的管理器;connection_generation——本次调用所属连接代次。
* 输出参数:reservation——非NULL,仅OK时写入帧身份、可写APDU地址及容量。
* 返回值:OK表示预留成功;缓冲报告FULL/BUSY时返回WAIT;会话或缓冲非法返回ERROR。
* 调用关系:公共S/TESTFR及子站应答入口在调用Link构帧前使用,成功后须提交或取消预留。
* 说明:本函数不调用Link构帧或接收处理,不改变链路过程;接收入口返回WAIT时请求尚未受理。
* 预留只占用控制槽,不进入发送FIFO,也不占用Manager的I帧业务记录槽。
* 会话入口要求无外部遗留预留,因此外部未结束的预留会先被判为ERROR。
*/
static IEC104_SendManagerResult_t Reserve_Control_Frame(
IEC104_SendManager_t *manager, uint64_t connection_generation,
IEC104_SendBufferReservation_t *reservation)
{
IEC104_SendBufferResult_t result;
if (!Manager_Session_Valid(manager, connection_generation)) {
return IEC104_SEND_MANAGER_ERROR;
}
result = IEC104_SendBuffer_Reserve_S_U_Frame(manager->send_buffer, reservation);
if (result == IEC104_SEND_BUFFER_FULL || result == IEC104_SEND_BUFFER_BUSY) {
return IEC104_SEND_MANAGER_WAIT;
}
return result == IEC104_SEND_BUFFER_OK ? IEC104_SEND_MANAGER_OK : IEC104_SEND_MANAGER_ERROR;
}
/**
* 功能:归还本次未生成S/U报文的预留控制槽,并传回调用方选定的处理结果。
* 输入参数:manager——已校验的管理器;reservation——本次有效预留;result——取消成功后的返回值。
* 输出参数:无独立输出对象;成功时由SendBuffer释放预留,已使用的frame_serial不复用。
* 返回值:取消成功原样返回result,取消失败返回FATAL,要求结束连接。
* 前置条件:指针有效,Link尚未为本次预留生成报文;不可用于撤回已经构帧成功的发送义务。
* 说明:只归还尚未提交的控制槽,不回滚Link状态。
*/
static IEC104_SendManagerResult_t Cancel_Control_Reservation(
IEC104_SendManager_t *manager, const IEC104_SendBufferReservation_t *reservation,
IEC104_SendManagerResult_t result)
{
return IEC104_SendBuffer_Cancel_Reservation(manager->send_buffer, reservation->frame_id) ==
IEC104_SEND_BUFFER_OK ? result : IEC104_SEND_MANAGER_FATAL;
}
/**
* 功能:将预留控制槽内已生成的固定6字节S/U报文追加到发送FIFO。
* 输入参数:manager——已校验的管理器;reservation——本次有效预留,APDU已由Link写入;
* frame_type——与APDU一致的S/U完成分派类型;now_ms——本次入队单调时刻,允许0。
* 输出参数:无独立输出对象;SendBuffer保存帧类型、长度及入队时间,不登记I帧业务记录。
* 返回值:QUEUED表示提交成功;FATAL表示提交失败,须结束连接,不能按容量不足重试。
* 前置条件:指针有效且Link已生成报文;S/U不传I帧快照,容量已由Reserve_Control_Frame保障。
* 说明:失败时不取消预留、不回滚Link;成功后实际I/O及Handle_Send_Progress另由接入方驱动。
*/
static IEC104_SendManagerResult_t Commit_Control_Frame(
IEC104_SendManager_t *manager, const IEC104_SendBufferReservation_t *reservation,
IEC104_SendBufferFrameType_t frame_type, uint64_t now_ms)
{
return IEC104_SendBuffer_Commit_Frame(manager->send_buffer, reservation->frame_id,
frame_type, IEC104_APCI_LEN, NULL, now_ms) == IEC104_SEND_BUFFER_OK ?
IEC104_SEND_MANAGER_QUEUED : IEC104_SEND_MANAGER_FATAL;
}
/**
* 功能:将公共S/TESTFR准备接口返回的长度转换为提交或取消操作。
* 输入参数:manager/reservation——已校验的管理器及本次有效预留;frame_type——预期帧类型;
* length——Link.Prepare的原始返回值;now_ms——生成报文时使用的入队时刻。
* 输出参数:无独立输出对象;按length提交FIFO或归还预留,不执行网络发送。
* 返回值:length为6时提交并返回QUEUED;为0时取消并返回WAIT;为负时取消并返回ERROR;
* 提交/取消失败或其他正长度返回FATAL。
* 说明:0/负值表示Link未生成报文,才允许取消;异常正长度可能已有发送义务,保留现场并断连。
*/
static IEC104_SendManagerResult_t Finish_Control_Prepare(
IEC104_SendManager_t *manager, const IEC104_SendBufferReservation_t *reservation,
IEC104_SendBufferFrameType_t frame_type, int length, uint64_t now_ms)
{
if (length <= 0) {
return Cancel_Control_Reservation(manager, reservation,
length == 0 ? IEC104_SEND_MANAGER_WAIT : IEC104_SEND_MANAGER_ERROR);
}
if (length != IEC104_APCI_LEN) {
return IEC104_SEND_MANAGER_FATAL;
}
return Commit_Control_Frame(manager, reservation, frame_type, now_ms);
}
/**
* 功能:按接收门限、立即确认或子站停止排空规则生成S确认并入队。
* 输入参数:manager——当前连接管理器;connection_generation——连接代次;w——1..32767的门限;
* immediate——非0表示立即确认;now_ms——入队单调时刻,允许0。
* 输出参数:无独立输出对象;生成成功时Link保存本帧N(R)及S在途状态,SendBuffer持有报文。
* 返回值:QUEUED表示S入队;WAIT表示控制容量不足,或Link判断当前无需、不能生成;
* ERROR表示参数、会话或准备结果非法;提交/取消异常返回FATAL。
* 调用时机:接收I帧后、启动CON完成后或确认条件变化时,由连接调度代码驱动。
* 说明:immediate只跳过数量门限,不跳过启停门控、已有S在途及无待确认数据检查。
* 构帧只冻结N(R),完整发送后由Handle_Send_Progress提交确认;WAIT不等于已确认。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Queue_S_Ack(
IEC104_SendManager_t *manager, uint64_t connection_generation,
uint16_t w, int immediate, uint64_t now_ms)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result;
int length;
if (w == 0U || w > IEC104_SEQUENCE_MASK) {
return IEC104_SEND_MANAGER_ERROR;
}
result = Reserve_Control_Frame(manager, connection_generation, &reservation);
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
length = IEC104_Link_Prepare_S_Ack(manager->link_state, w, immediate,
reservation.apdu, reservation.apdu_capacity);
return Finish_Control_Prepare(manager, &reservation, IEC104_SEND_BUFFER_FRAME_S_ACK, length, now_ms);
}
/**
* 功能:待确认数据的t2到期后,按公共S确认规则构帧并入队。
* 输入参数:manager——当前连接管理器;connection_generation——连接代次;
* elapsed_ms——从原待确认数据起点计算的经过毫秒数;t2_ms——非0的t2配置;
* now_ms——本次入队单调时刻,与elapsed_ms的相对时间含义不同。
* 输出参数:无独立输出对象;成功时Link记录S在途状态,报文进入SendBuffer。
* 返回值:QUEUED表示S入队;WAIT表示控制容量不足、未到期或Link当前不生成S;
* ERROR表示参数、会话或准备结果非法;提交/取消异常返回FATAL。
* 调用时机:接入方的定时唤醒或连接调度;elapsed_ms等于t2_ms即满足到期条件。
* 说明:到期仍受启停及已有S在途约束;函数不读取时钟、不重置起点,重试须沿用原计时。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Queue_T2_Ack(
IEC104_SendManager_t *manager, uint64_t connection_generation,
uint32_t elapsed_ms, uint32_t t2_ms, uint64_t now_ms)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result;
int length;
if (t2_ms == 0U) {
return IEC104_SEND_MANAGER_ERROR;
}
result = Reserve_Control_Frame(manager, connection_generation, &reservation);
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
length = IEC104_Link_Prepare_T2_Ack(manager->link_state, elapsed_ms, t2_ms,
reservation.apdu, reservation.apdu_capacity);
return Finish_Control_Prepare(manager, &reservation, IEC104_SEND_BUFFER_FRAME_S_ACK, length, now_ms);
}
/**
* 功能:子站主动生成并排队一帧TESTFR_ACT。
* 输入参数:manager——当前连接管理器;connection_generation——连接代次;now_ms——入队单调时刻。
* 输出参数:无独立输出对象;生成后Link进入ACT_SEND_PENDING,SendBuffer持有ACT报文。
* 返回值:QUEUED表示ACT入队;WAIT表示控制容量不足或已有探测;ERROR表示会话或准备非法;
* 提交/取消异常返回FATAL。
* 调用时机:连接调度代码决定主动探测时;按空闲时间触发可使用Queue_T3_Testfr。
* 说明:本入口不检查空闲时长。ACT完整发送后才由Handle_Send_Progress提交CON_PENDING,
* 接入方以实际written_at_ms作为等待CON的t1起点;不能在入队时提前提交发送完成。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Queue_Testfr_Act(
IEC104_SendManager_t *manager, uint64_t connection_generation, uint64_t now_ms)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result = Reserve_Control_Frame(manager, connection_generation, &reservation);
int length;
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
length = IEC104_Link_Prepare_Testfr_Act(manager->link_state, reservation.apdu, reservation.apdu_capacity);
return Finish_Control_Prepare(manager, &reservation, IEC104_SEND_BUFFER_FRAME_TESTFR_ACT, length, now_ms);
}
/**
* 功能:链路空闲达到t3后,复用Link的主动探测流程生成TESTFR_ACT并入队。
* 输入参数:manager——当前连接管理器;connection_generation——连接代次;
* idle_elapsed_ms——接入方计算的空闲毫秒数;t3_ms——非0的t3配置;now_ms——入队单调时刻。
* 输出参数:无独立输出对象;生成成功后记录ACT待发送状态,并发布到同一FIFO。
* 返回值:QUEUED表示ACT入队;WAIT表示控制容量不足、未到期或已有探测;
* ERROR表示参数、会话或准备结果非法;提交/取消异常返回FATAL。
* 调用时机:接入方定时唤醒后,以当前有效活动起点计算时间差;等于t3_ms即满足到期条件。
* 说明:不创建定时器、不更新活动起点;已有探测的t1不能因活动、重复调用或WAIT而续期。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Queue_T3_Testfr(
IEC104_SendManager_t *manager, uint64_t connection_generation,
uint32_t idle_elapsed_ms, uint32_t t3_ms, uint64_t now_ms)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result;
int length;
if (t3_ms == 0U) {
return IEC104_SEND_MANAGER_ERROR;
}
result = Reserve_Control_Frame(manager, connection_generation, &reservation);
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
length = IEC104_Link_Prepare_T3_Testfr(manager->link_state, idle_elapsed_ms, t3_ms,
reservation.apdu, reservation.apdu_capacity);
return Finish_Control_Prepare(manager, &reservation, IEC104_SEND_BUFFER_FRAME_TESTFR_ACT, length, now_ms);
}
/**
* 功能:子站接收TESTFR_ACT,为其生成并排队一帧TESTFR_CON。
* 输入参数:manager——当前连接管理器;connection_generation——接收所属连接代次;
* apci——已通过Parse_APCI且功能码为TESTFR_ACT的U帧;now_ms——应答入队单调时刻。
* 输出参数:无独立输出对象;QUEUED时SendBuffer持有CON,Link的主动探测状态不变。
* 返回值:QUEUED表示应答入队;WAIT表示预留失败、本次请求尚未受理;
* ERROR表示输入、会话或预留状态非法;Link拒绝或提交/取消异常返回FATAL。
* 调用链:接收路由 -> Reserve_Control_Frame -> Link.Handle_Testfr_Act -> Commit_Control_Frame。
* 说明:WAIT须按序保留请求后重试,无法保留则断连;收到多个ACT时分别调用并逐个应答。
* TESTFR_CON没有Link完成接口,但实际发送仍交给Handle_Send_Progress/Failure统一处理。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Handle_Testfr_Act(
IEC104_SendManager_t *manager, uint64_t connection_generation,
const IEC104_ApciInfo_t *apci, uint64_t now_ms)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result;
if (apci == NULL || apci->frame_type != IEC104_FRAME_TYPE_U || apci->u_function != IEC104_U_TESTFR_ACT) {
return IEC104_SEND_MANAGER_ERROR;
}
result = Reserve_Control_Frame(manager, connection_generation, &reservation);
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
if (IEC104_Link_Handle_Testfr_Act(apci, reservation.apdu, reservation.apdu_capacity) != IEC104_LINK_RESULT_OK) {
return Cancel_Control_Reservation(manager, &reservation, IEC104_SEND_MANAGER_FATAL);
}
return Commit_Control_Frame(manager, &reservation, IEC104_SEND_BUFFER_FRAME_TESTFR_CON, now_ms);
}
/* TESTFR的接收确认与发送完成使用同一个Manager入口边界。
* 不预留控制槽,槽满不影响接受确认;只有CON_PENDING才结束本端探测。 */
IEC104_SendManagerResult_t IEC104_SendManager_Handle_Testfr_Con(
IEC104_SendManager_t *manager, uint64_t connection_generation, const IEC104_ApciInfo_t *apci)
{
IEC104_LinkResult_t result;
if (apci == NULL || apci->frame_type != IEC104_FRAME_TYPE_U || apci->u_function != IEC104_U_TESTFR_CON ||
!Manager_Session_Valid(manager, connection_generation)) {
return IEC104_SEND_MANAGER_ERROR;
}
result = IEC104_Link_Handle_Testfr_Con(manager->link_state, apci);
if (result == IEC104_LINK_RESULT_OK) {
return IEC104_SEND_MANAGER_OK;
}
return result == IEC104_LINK_RESULT_IGNORED ? IEC104_SEND_MANAGER_IGNORED : IEC104_SEND_MANAGER_FATAL;
}
/* HIGH快速路径也遵守事件公平配额,不能通过反复调用应答接口绕过已配置的调度规则。 */
static int High_Has_Priority(const IEC104_SendManager_t *manager, int normal_waiting)
{
return !normal_waiting || manager->high_priority_burst_limit == 0U ||
manager->high_priority_burst_count < manager->high_priority_burst_limit;
}
/* 统一交接一帧I报文:预留、Link编号、提交APDU、建立记录、更新来源及调度额度。
* queue_entry非NULL表示来自队列;NULL仅供直接HIGH应答使用,不虚构队列条目或执行Mark。
* 调用方已验证会话、ASDU及发送顺序;WAIT尚未接手原文,Link编号后的失败必须FATAL收尾。 */
static IEC104_SendManagerResult_t Prepare_I_Asdu(
IEC104_SendManager_t *manager, const uint8_t *asdu, uint16_t asdu_length,
IEC104_AsduPriority_t priority, const IEC104_AsduEntryId_t *queue_entry,
int normal_waiting, uint64_t now_ms, IEC104_SendBufferHeadInfo_t *head_info)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendBufferResult_t tx_result;
IEC104_SendBufferHeadInfo_t prepared_head;
IEC104_IFrameSendRecordStorage_t prepared;
int apdu_length;
/* 已ACK但未消费的记录仍占槽;没有记录容量时不得分配协议序号。 */
if (manager->record_count == manager->record_capacity) {
return IEC104_SEND_MANAGER_WAIT;
}
tx_result = IEC104_SendBuffer_Reserve_I_Frame(manager->send_buffer, &reservation);
if (tx_result == IEC104_SEND_BUFFER_FULL || tx_result == IEC104_SEND_BUFFER_BUSY) {
return IEC104_SEND_MANAGER_WAIT;
}
if (tx_result != IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
apdu_length = IEC104_Link_Prepare_I_Frame(manager->link_state, manager->send_window_k,
asdu, asdu_length,
reservation.apdu, reservation.apdu_capacity, &prepared.i_frame_snapshot);
if (apdu_length <= 0) {
if (IEC104_SendBuffer_Cancel_Reservation(manager->send_buffer, reservation.frame_id) != IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
return apdu_length == 0 ? IEC104_SEND_MANAGER_WAIT : IEC104_SEND_MANAGER_ERROR;
}
/* 从Prepare成功起N(S)不可撤回,后续异常必须结束连接;此时原始ASDU尚未释放。 */
if (prepared.i_frame_snapshot.send_number != manager->next_record_ns ||
IEC104_SendBuffer_Commit_Frame(manager->send_buffer, reservation.frame_id, IEC104_SEND_BUFFER_FRAME_I,
(uint16_t)apdu_length, &prepared.i_frame_snapshot, now_ms) != IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
/* 两条路径共用同一发送记录及ACK记账。队列来源随后Mark;直接应答没有队列原文,serial为0。
* SendBuffer已复制完整APDU,成功后不再借用业务输入;本步骤尚未执行socket发送。 */
prepared.frame_serial = reservation.frame_id.frame_serial;
prepared.asdu_entry_serial = queue_entry != NULL ? queue_entry->entry_serial : 0U;
prepared.asdu_priority = priority;
prepared.queued_at_ms = now_ms;
manager->records[Record_Index_From_Offset(manager, manager->record_count)] = prepared;
manager->record_count++;
if (priority == IEC104_ASDU_PRIORITY_HIGH) {
manager->high_unacked_count++;
}
manager->next_record_ns = (uint16_t)((manager->next_record_ns + 1U) & IEC104_SEQUENCE_MASK);
if (queue_entry != NULL && IEC104_AsduQueue_Mark_Submitted(manager->asdu_queue, priority,
*queue_entry) != IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
/* 只计本次成功选择;NORMAL无未提交条目时清零,不为未来业务累积高优先欠额。 */
if (priority == IEC104_ASDU_PRIORITY_HIGH &&
normal_waiting && manager->high_priority_burst_limit != 0U) {
manager->high_priority_burst_count++;
} else {
manager->high_priority_burst_count = 0U;
}
if (IEC104_SendBuffer_Peek_Send_Head(manager->send_buffer, &prepared_head) != IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
*head_info = prepared_head;
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:紧邻实际写socket前取得待写字节;没有已准备帧时选择ASDU并分配一个N(S)。
* 输入参数:manager/connection_generation——当前管理器及匹配代次;
* now_ms——单调毫秒,仅准备新I帧时成为本地发送期限起点。
* 输出参数:head_info——非NULL,仅OK时写入当前帧身份、描述和剩余字节借用指针。
* 返回值:OK返回当前帧剩余字节;EMPTY表示既无已准备帧,也无未提交ASDU;WAIT表示门控、窗口或记录容量阻塞;
* ERROR表示输入、会话或准备非法;Link准备成功后的提交异常返回FATAL。
* 调用链:续传当前I/控制帧;无当前帧时Peek_Unsubmitted -> Reserve_I_Frame -> Link.Prepare
* -> Buffer.Commit_Frame接手报文 -> 登记记录 -> Queue.Mark_Submitted完成队列交接记账 -> 返回待写字节。
* 说明:APDU提交且发送记录建立后,HIGH原文立即出队,NORMAL标记已提交并保留原文至ACK或覆盖。
* 控制帧先于尚未分配协议N(S)的ASDU,已准备的I帧即使EAGAIN零进度也必须先完成。
* 重复取得当前帧不编号、不改变配额或期限;函数不执行实际I/O。
* 跨尾部原文用249字节局部缓冲拼接后调用Link;连续原文直接借用,不增加动态分配。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Prepare_Next_Send(
IEC104_SendManager_t *manager, uint64_t connection_generation, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info)
{
IEC104_AsduHeadInfo_t high_head_info, normal_head_info, selected_head_info;
IEC104_AsduQueueResult_t high_result, normal_result;
IEC104_SendBufferResult_t tx_result;
IEC104_SendBufferHeadInfo_t prepared_head;
uint8_t wrapped_asdu[IEC104_MAX_ASDU_LEN];
const uint8_t *selected_asdu;
if (head_info == NULL || !Manager_Session_Valid(manager, connection_generation)) {
return IEC104_SEND_MANAGER_ERROR;
}
/* 续传已准备帧,不为后续业务预建APDU;控制FIFO在选择新I帧之前排空。 */
tx_result = IEC104_SendBuffer_Peek_Send_Head(manager->send_buffer, &prepared_head);
if (tx_result == IEC104_SEND_BUFFER_OK) {
*head_info = prepared_head;
return IEC104_SEND_MANAGER_OK;
}
if (tx_result != IEC104_SEND_BUFFER_EMPTY) {
return IEC104_SEND_MANAGER_ERROR;
}
high_result = IEC104_AsduQueue_Peek_Unsubmitted(manager->asdu_queue, IEC104_ASDU_PRIORITY_HIGH, &high_head_info);
normal_result = IEC104_AsduQueue_Peek_Unsubmitted(manager->asdu_queue, IEC104_ASDU_PRIORITY_NORMAL, &normal_head_info);
if (high_result == IEC104_ASDU_QUEUE_ERROR || normal_result == IEC104_ASDU_QUEUE_ERROR) {
return IEC104_SEND_MANAGER_ERROR;
}
if (high_result == IEC104_ASDU_QUEUE_EMPTY && normal_result == IEC104_ASDU_QUEUE_EMPTY) {
return IEC104_SEND_MANAGER_EMPTY;
}
/* limit为0时严格HIGH优先;NORMAL有未提交条目且配额用尽时,选择NORMAL。 */
if (high_result == IEC104_ASDU_QUEUE_OK &&
High_Has_Priority(manager, normal_result == IEC104_ASDU_QUEUE_OK)) {
selected_head_info = high_head_info;
} else {
selected_head_info = normal_head_info;
}
/* 连续原文直接借用;跨尾部的两段仅在此拼成连续ASDU,原队列数据不移动。 */
selected_asdu = selected_head_info.asdu_first;
if (selected_head_info.asdu_second != NULL) {
if (IEC104_AsduQueue_Copy_Asdu(&selected_head_info, wrapped_asdu, sizeof(wrapped_asdu)) != IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_ERROR;
}
selected_asdu = wrapped_asdu;
}
return Prepare_I_Asdu(manager, selected_asdu, selected_head_info.asdu_length,
selected_head_info.priority, &selected_head_info.entry_id,
normal_result == IEC104_ASDU_QUEUE_OK, now_ms, head_info);
}
/**
* 功能:按启停阶段接手当前连接应答;能开始一帧时直接交给SendBuffer,否则复制到HIGH。
* 输入参数:asdu/asdu_length——业务原文;now_ms——准备帧时的单调时刻。
* 输出参数:仅OK写head_info,返回直接准备好的应答帧;成功后不再借用业务原文。
* 返回值:OK已准备、尚未实际写;QUEUED已缓存HIGH;WAIT未接手、由调用方保留重试;
* ERROR非法参数/状态或HIGH总容量装不下;FATAL表示编号后的交接失败,必须结束连接。
* 说明:仅STARTED/IDLE时接手;否则ERROR且不入HIGH。
* 准入先于容量及窗口判断;通过后已有I/S/U、旧HIGH、配额或记录/k窗口限制均走队列。
* 新STOP受理时取消HIGH待发应答;已经交给SendBuffer的I帧继续原发送及确认流程。
* 直接路径与队列路径共用Prepare_I_Asdu,不另建序号、帧缓冲或ACK逻辑。
*/
static IEC104_SendManagerResult_t Submit_Response(
IEC104_SendManager_t *manager, 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_AsduHeadInfo_t high_head, normal_head;
IEC104_SendBufferHeadInfo_t current_head;
IEC104_AsduQueueResult_t high_result, normal_result;
IEC104_SendBufferResult_t current_result;
IEC104_SendManagerResult_t result;
if (head_info == NULL || asdu == NULL || asdu_length < IEC104_ASDU_HEADER_LEN ||
asdu_length > IEC104_MAX_ASDU_LEN || !Manager_Session_Valid(manager, connection_generation)) {
return IEC104_SEND_MANAGER_ERROR;
}
if (!Response_Submit_Allowed(manager)) {
return IEC104_SEND_MANAGER_ERROR;
}
current_result = IEC104_SendBuffer_Peek_Send_Head(manager->send_buffer, &current_head);
high_result = IEC104_AsduQueue_Peek_Unsubmitted(manager->asdu_queue, IEC104_ASDU_PRIORITY_HIGH, &high_head);
normal_result = IEC104_AsduQueue_Peek_Unsubmitted(manager->asdu_queue, IEC104_ASDU_PRIORITY_NORMAL, &normal_head);
if ((current_result != IEC104_SEND_BUFFER_OK && current_result != IEC104_SEND_BUFFER_EMPTY) ||
high_result == IEC104_ASDU_QUEUE_ERROR || normal_result == IEC104_ASDU_QUEUE_ERROR) {
return IEC104_SEND_MANAGER_ERROR;
}
if (current_result == IEC104_SEND_BUFFER_EMPTY && high_result == IEC104_ASDU_QUEUE_EMPTY &&
(bypass_queue ? normal_result == IEC104_ASDU_QUEUE_EMPTY :
High_Has_Priority(manager, normal_result == IEC104_ASDU_QUEUE_OK))) {
result = Prepare_I_Asdu(manager, asdu, asdu_length, IEC104_ASDU_PRIORITY_HIGH, NULL,
normal_result == IEC104_ASDU_QUEUE_OK, now_ms, head_info);
if (result != IEC104_SEND_MANAGER_WAIT) {
return result;
}
}
/* 不排队路径不得越过旧NORMAL,也不得在窗口/记录/缓冲背压时保存旧业务值。 */
if (bypass_queue) { return IEC104_SEND_MANAGER_WAIT; }
result = IEC104_SendManager_Enqueue(manager, connection_generation, IEC104_ASDU_PRIORITY_HIGH, asdu, asdu_length);
return result == IEC104_SEND_MANAGER_OK ? IEC104_SEND_MANAGER_QUEUED : result;
}
IEC104_SendManagerResult_t IEC104_SendManager_Submit_Response(
IEC104_SendManager_t *manager, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info)
{
return Submit_Response(manager, connection_generation, asdu, asdu_length, now_ms, head_info, 0);
}
/* 仅在两级队列无待发数据且能准备新I帧时接手;WAIT不缓存、不分配N(S)。 */
IEC104_SendManagerResult_t IEC104_SendManager_Try_Submit_Response(
IEC104_SendManager_t *manager, uint64_t connection_generation,
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
IEC104_SendBufferHeadInfo_t *head_info)
{
return Submit_Response(manager, connection_generation, asdu, asdu_length, now_ms, head_info, 1);
}
/**
* 功能:提交队首实际写入增量,整帧写完后提交适用的链路完成结果并释放发送缓冲槽。
* 输入参数:manager——有效的本连接发送管理器。
* frame_id——此前Prepare_Next_Send或Submit_Response返回的当前帧身份。
* bytes_written_now——本次实际成功写入的增量,不能超过剩余长度;0不推进进度。
* now_ms——本次完成时刻;最后一段须传实际整帧写完的单调时刻。
* 输出参数:completion——仅FRAME_WRITTEN时写入数值副本;其他结果保持原值。
* 发送队列、链路及I帧记录——按实际发送进度提交状态。
* 返回值:OK表示尚未写完;FRAME_WRITTEN表示本地完成处理及缓冲释放成功;ERROR表示输入或匹配非法;
* FATAL表示整帧写完后的状态提交失败,必须结束当前连接。
* 调用方:Connection接收TcpServer的实际写入结果后,用准备帧时取得的frame_id提交本次增量。
* 调用链:SendBuffer.Record_Written_Bytes;仅整帧写完时继续分派适用的Link.Complete
* -> Manager登记I帧本地完成 -> SendBuffer.Commit_Send_Completion。
* 说明:本函数不写socket;调用方先执行实际I/O,再恰好提交一次对应进度。
* TESTFR_CON没有Link完成接口;完整写入后直接进入缓冲完成处理。
* 整帧完成时刻不能早于入队时刻;部分写入不更新计时起点。
* 同一增量不能重复提交,也不能另行调用Buffer.Record_Written_Bytes、Link.Complete或Buffer完成接口。
* 完成本函数后再分派依赖本地TESTFR_ACT完成的TESTFR_CON;不自动创建t1定时器。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Handle_Send_Progress(
IEC104_SendManager_t *manager, IEC104_SendFrameId_t frame_id,
uint16_t bytes_written_now, uint64_t now_ms, IEC104_LocalSendCompletion_t *completion)
{
IEC104_SendBufferHeadInfo_t head_info;
IEC104_SendBufferResult_t tx_result;
IEC104_LinkResult_t link_completion_result;
IEC104_IFrameSendRecordStorage_t *record = NULL;
IEC104_LocalSendCompletion_t prepared;
if (completion == NULL || !Manager_Session_Valid(manager, frame_id.connection_generation) ||
IEC104_SendBuffer_Peek_Send_Head(manager->send_buffer, &head_info) != IEC104_SEND_BUFFER_OK ||
head_info.frame_id.frame_serial != frame_id.frame_serial || head_info.remaining_bytes == 0U) {
return IEC104_SEND_MANAGER_ERROR;
}
/* 在推进写入进度之前核对业务记录,避免错误frame_id或关联造成部分状态更新。 */
if (head_info.frame_type == IEC104_SEND_BUFFER_FRAME_I) {
record = Find_Pending_Send_Record(manager, &head_info);
if (record == NULL) {
return IEC104_SEND_MANAGER_ERROR;
}
}
tx_result = IEC104_SendBuffer_Record_Written_Bytes(manager->send_buffer, frame_id, bytes_written_now, now_ms);
if (tx_result == IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_OK;
}
if (tx_result != IEC104_SEND_BUFFER_FRAME_WRITTEN) {
return IEC104_SEND_MANAGER_ERROR;
}
/*
* 整帧完成第1步:按原始类型分派适用的Link完成接口;TESTFR_CON无需调用,I帧推进sent_vs及捎带确认。
* head_info保留构帧时的类型和I帧快照,实际完整发送时刻取本次now_ms。
*/
link_completion_result = Dispatch_Link_Send_Completion(manager, &head_info, head_info.apdu_length);
if (link_completion_result != IEC104_LINK_RESULT_OK && link_completion_result != IEC104_LINK_RESULT_IGNORED) {
return IEC104_SEND_MANAGER_FATAL;
}
if (record != NULL) {
if (link_completion_result != IEC104_LINK_RESULT_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
/* 第2步:登记Manager业务记录的本地完成;后续接收路径才允许ACK覆盖该帧。 */
record->local_send_completed = 1U;
record->written_at_ms = now_ms;
}
/*
* 第3步:释放当前I槽或控制FIFO队首;独立业务记录继续等待ACK,即使NORMAL原文已覆盖。
* 这里只处理报文存储,就绪记录由TcpServer转为事件后再移除。Complete已提交,缓冲提交失败不能重做Complete。
*/
if (IEC104_SendBuffer_Commit_Send_Completion(manager->send_buffer, frame_id) != IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
/* 输出只复制数值,避免把已释放帧的借用指针交给后续定时器或业务。 */
prepared.frame_id = frame_id;
prepared.frame_type = head_info.frame_type;
prepared.link_completion_result = link_completion_result;
prepared.i_frame_snapshot = head_info.i_frame_snapshot;
prepared.queued_at_ms = head_info.queued_at_ms;
prepared.written_at_ms = now_ms;
*completion = prepared;
return IEC104_SEND_MANAGER_FRAME_WRITTEN;
}
/**
* 功能:将匹配队首的发送失败或本地发送超时交给链路完成接口。
* 输入参数:manager——有效的本连接发送管理器。
* frame_id——尚未整帧完成的发送队首身份。
* 输出参数:link_state——匹配时按Complete(-1)约定清理;队列及业务记录保留供故障回收。
* 返回值:ERROR表示参数、会话、队首或身份非法;匹配并提交失败后始终返回FATAL。
* 说明:即使底层Complete返回IGNORED,实际发送失败仍要求连接层断连。
* 不自动Abort;调用方须回收业务、关闭TCP并结束或隔离旧I/O。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Handle_Send_Failure(
IEC104_SendManager_t *manager, IEC104_SendFrameId_t frame_id)
{
IEC104_SendBufferHeadInfo_t head_info;
if (!Manager_Session_Valid(manager, frame_id.connection_generation) ||
IEC104_SendBuffer_Peek_Send_Head(manager->send_buffer, &head_info) != IEC104_SEND_BUFFER_OK ||
head_info.frame_id.frame_serial != frame_id.frame_serial || head_info.remaining_bytes == 0U ||
(head_info.frame_type == IEC104_SEND_BUFFER_FRAME_I && Find_Pending_Send_Record(manager, &head_info) == NULL)) {
return IEC104_SEND_MANAGER_ERROR;
}
/* 网络失败已决定必须断连,不因底层完成接口返回IGNORED而继续发送。 */
(void)Dispatch_Link_Send_Completion(manager, &head_info, -1);
return IEC104_SEND_MANAGER_FATAL;
}
/**
* 功能:处理已解析I/S帧,验证累计ACK并登记确认事实;仅NORMAL继续确认原文队列。
* 输入参数:manager/connection_generation——当前管理器及匹配代次;
* apci——成功Parse_APCI得到的完整I/S帧描述。
* 输出参数:Link——提交有效ACK及I帧接收位置;records——标记新确认事实;
* Manager——核减HIGH未确认记录数;AsduQueue——释放NORMAL原文或核减覆盖待确认数;不消费业务回执。
* 返回值:OK处理成功;ERROR表示参数、会话或新确认区间的记录非法;
* PROTOCOL_ERROR表示合法本地会话下Link拒绝报文;FATAL表示受理后的确认同步或原文释放失败。
* 调用链:读取旧位置 -> 预检新ACK记录 -> Link.Handle -> 登记全部新ACK事实 -> NORMAL逐条Confirm_Next_Normal_Entry。
* 说明:ACK只能覆盖已提交本地完成的I帧;异步接入须先提交真实发送结果。
* 重复ACK的新确认数量为0,不重复释放、不重置期限;I帧接收仍由Link处理。
* 确认处理只访问新确认区间,复杂度O(新确认数)。释放中途失败仍保留整个已接受ACK区间供恢复。
* NORMAL原文覆盖不撤销帧;记录仍严格匹配序号和本地完成,已覆盖原文由Queue跳过释放。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Handle_I_S_Frame(
IEC104_SendManager_t *manager, uint64_t connection_generation, const IEC104_ApciInfo_t *apci)
{
IEC104_IFrameSendProgress_t before_progress, after_progress;
uint16_t acknowledged, first_offset, i, high_acknowledged = 0U;
int ack_in_range;
IEC104_LinkResult_t result;
if (!Manager_Session_Valid(manager, connection_generation) || apci == NULL ||
(apci->frame_type != IEC104_FRAME_TYPE_I && apci->frame_type != IEC104_FRAME_TYPE_S) ||
IEC104_Link_Get_Send_Progress(manager->link_state, &before_progress) != 1) {
return IEC104_SEND_MANAGER_ERROR;
}
acknowledged = Sequence_Distance(before_progress.peer_ack_nr, apci->receive_number);
ack_in_range = apci->receive_number <= IEC104_SEQUENCE_MASK &&
acknowledged <= Sequence_Distance(before_progress.peer_ack_nr, before_progress.next_completion_ns);
/* 记录前缀是已ACK未消费回执,后缀是未ACK帧;相减直接定位旧V(A)对应记录。 */
first_offset = (uint16_t)(manager->record_count -
Sequence_Distance(before_progress.peer_ack_nr, before_progress.next_frame_ns));
/* 非法网络N(R)不用于访问记录,仍由Link处理并拒绝。 */
if (ack_in_range) {
for (i = 0U; i < acknowledged; ++i) {
uint16_t offset = (uint16_t)(first_offset + i);
const IEC104_IFrameSendRecordStorage_t *record = &manager->records[Record_Index_From_Offset(manager, offset)];
if (!I_Frame_Record_Valid(manager, record, offset) ||
record->local_send_completed != 1U || record->peer_acknowledged != 0U) {
return IEC104_SEND_MANAGER_ERROR;
}
if (record->asdu_priority == IEC104_ASDU_PRIORITY_HIGH) {
high_acknowledged++;
}
}
if (high_acknowledged > manager->high_unacked_count) {
return IEC104_SEND_MANAGER_ERROR;
}
}
result = apci->frame_type == IEC104_FRAME_TYPE_I ?
IEC104_Link_Handle_I_Frame(manager->link_state, apci) :
IEC104_Link_Handle_S_Frame(manager->link_state, apci);
/* 本地会话及记录已预检;Link的拒绝才表示接收阶段、序号或ACK违反协议。 */
if (result == IEC104_LINK_RESULT_FATAL) {
return IEC104_SEND_MANAGER_PROTOCOL_ERROR;
}
if (result != IEC104_LINK_RESULT_OK || !ack_in_range ||
IEC104_Link_Get_Send_Progress(manager->link_state, &after_progress) != 1 ||
after_progress.peer_ack_nr != apci->receive_number) {
return IEC104_SEND_MANAGER_FATAL;
}
/* 即使之后释放原文失败,也不能丢失Link已经接受的累计确认事实。 */
for (i = 0U; i < acknowledged; ++i) {
manager->records[Record_Index_From_Offset(manager, (uint16_t)(first_offset + i))].peer_acknowledged = 1U;
}
manager->high_unacked_count = (uint16_t)(manager->high_unacked_count - high_acknowledged);
/* HIGH原文在提交时已出队,ACK不再访问HIGH队列;NORMAL仍按真实身份释放原文或核减覆盖计数。 */
for (i = 0U; i < acknowledged; ++i) {
const IEC104_IFrameSendRecordStorage_t *record =
&manager->records[Record_Index_From_Offset(manager, (uint16_t)(first_offset + i))];
IEC104_AsduEntryId_t entry_id;
if (record->asdu_priority == IEC104_ASDU_PRIORITY_HIGH) {
continue;
}
entry_id.connection_generation = connection_generation;
entry_id.entry_serial = record->asdu_entry_serial;
if (IEC104_AsduQueue_Confirm_Next_Normal_Entry(manager->asdu_queue, record->asdu_priority, entry_id) != IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
}
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:按交接顺序复制指定偏移处的业务记录,供观察或故障回收。
* 输入参数:manager——存储结构有效的发送管理器,不要求故障后的Link仍合法。
* offset——相对当前记录队首的记录数偏移,0表示最早记录,单位不是字节。
* 输出参数:record——仅OK时写入带完整frame_id的数值副本;EMPTY和ERROR时保持原值。
* 返回值:OK表示取得记录;EMPTY表示偏移处无记录;ERROR表示参数、存储或记录非法。
* 说明:frame_id由Manager共享代次和记录frame_serial组合,不依赖Link或原文借用。
* 不删除记录、不修改完成标志;已关闭TCP不妨碍读取保存的ACK事实。
* 已确认与未确认事实须供业务回收策略使用;未确认不等于对端未收到。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Get_Record_By_Offset(
const IEC104_SendManager_t *manager, uint16_t offset, IEC104_IFrameSendRecord_t *record)
{
const IEC104_IFrameSendRecordStorage_t *stored;
IEC104_IFrameSendRecord_t prepared;
if (!Manager_Storage_Valid(manager) || record == NULL) {
return IEC104_SEND_MANAGER_ERROR;
}
if (offset >= manager->record_count) {
return IEC104_SEND_MANAGER_EMPTY;
}
stored = &manager->records[Record_Index_From_Offset(manager, offset)];
if (!I_Frame_Record_Valid(manager, stored, offset)) {
return IEC104_SEND_MANAGER_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
prepared.frame_id.connection_generation = manager->connection_generation;
prepared.frame_id.frame_serial = stored->frame_serial;
prepared.asdu_entry_serial = stored->asdu_entry_serial;
prepared.queued_at_ms = stored->queued_at_ms;
prepared.written_at_ms = stored->written_at_ms;
prepared.i_frame_snapshot = stored->i_frame_snapshot;
prepared.asdu_priority = stored->asdu_priority;
prepared.local_send_completed = stored->local_send_completed;
prepared.peer_acknowledged = stored->peer_acknowledged;
*record = prepared;
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:读取最早已完成本地发送、尚未被对端确认的I帧记录。
* 输入参数:manager——当前会话有效的子站管理器;record——非NULL输出。
* 输出参数:仅OK时复制记录,不返回APDU指针,不改变队列。
* 返回值:OK、EMPTY(包括只有待发送I帧)或ERROR。
* 说明:记录环形数组中,已确认未消费的前缀后就是V(A)对应的记录,可直接定位。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Peek_Oldest_Sent_Unacked_Record(
const IEC104_SendManager_t *manager, IEC104_IFrameSendRecord_t *record)
{
IEC104_IFrameSendProgress_t progress;
IEC104_IFrameSendRecord_t prepared;
uint16_t offset;
if (record == NULL || !Manager_Storage_Valid(manager) ||
!Manager_Session_Valid(manager, manager->connection_generation) ||
IEC104_Link_Get_Send_Progress(manager->link_state, &progress) != 1) {
return IEC104_SEND_MANAGER_ERROR;
}
if (progress.peer_ack_nr == progress.next_completion_ns) {
return IEC104_SEND_MANAGER_EMPTY;
}
offset = (uint16_t)(manager->record_count -
Sequence_Distance(progress.peer_ack_nr, progress.next_frame_ns));
if (IEC104_SendManager_Get_Record_By_Offset(manager, offset, &prepared) != IEC104_SEND_MANAGER_OK ||
prepared.i_frame_snapshot.send_number != progress.peer_ack_nr ||
prepared.local_send_completed != 1U || prepared.peer_acknowledged != 0U) {
return IEC104_SEND_MANAGER_ERROR;
}
*record = prepared;
return IEC104_SEND_MANAGER_OK;
}
/* 起点与期限已校验;先作64位差值再截断,避免绝对期限加法或长时间差溢出。 */
static uint32_t Deadline_Remaining(uint64_t now_ms, uint64_t started_ms, uint32_t timeout_ms)
{
uint64_t elapsed = now_ms - started_ms;
return elapsed >= timeout_ms ? 0U : timeout_ms - (uint32_t)elapsed;
}
/* 向Connection提供同一时刻的队首期限、帧身份、I帧t1和控制容量。
* 原始起点仍分别来自Buffer和发送记录;此快照只在当前串行调用中使用,不缓存到对象内。 */
IEC104_SendManagerResult_t IEC104_SendManager_Get_Deadlines(
const IEC104_SendManager_t *manager, uint64_t connection_generation,
uint64_t now_ms, uint32_t local_send_timeout_ms, uint32_t t1_ms,
IEC104_SendManagerDeadlines_t *deadlines)
{
IEC104_SendManagerDeadlines_t prepared;
IEC104_SendBufferStatus_t buffer_status;
IEC104_SendBufferHeadInfo_t head;
IEC104_IFrameSendRecord_t record;
IEC104_SendBufferResult_t buffered;
IEC104_SendManagerResult_t recorded;
if (deadlines == NULL || local_send_timeout_ms == 0U || t1_ms == 0U ||
!Manager_Session_Valid(manager, connection_generation) ||
IEC104_SendBuffer_Get_Status(manager->send_buffer, &buffer_status) != IEC104_SEND_BUFFER_OK) {
return IEC104_SEND_MANAGER_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
prepared.control_has_room = buffer_status.control_has_room;
buffered = IEC104_SendBuffer_Peek_Send_Head(manager->send_buffer, &head);
if (buffered == IEC104_SEND_BUFFER_OK) {
if (now_ms < head.queued_at_ms) {
return IEC104_SEND_MANAGER_ERROR;
}
prepared.has_send_head = 1U;
prepared.head_frame_id = head.frame_id;
prepared.local_send_remaining_ms = Deadline_Remaining(now_ms, head.queued_at_ms, local_send_timeout_ms);
} else if (buffered != IEC104_SEND_BUFFER_EMPTY) {
return IEC104_SEND_MANAGER_ERROR;
}
recorded = IEC104_SendManager_Peek_Oldest_Sent_Unacked_Record(manager, &record);
if (recorded == IEC104_SEND_MANAGER_OK) {
if (now_ms < record.written_at_ms) {
return IEC104_SEND_MANAGER_ERROR;
}
prepared.has_i_ack_deadline = 1U;
prepared.i_ack_remaining_ms = Deadline_Remaining(now_ms, record.written_at_ms, t1_ms);
} else if (recorded != IEC104_SEND_MANAGER_EMPTY) {
return IEC104_SEND_MANAGER_ERROR;
}
*deadlines = prepared;
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:检查最早已本地完成、尚未收到ACK的I帧是否达到t1。
* 输入参数:manager/connection_generation——当前管理器及匹配代次;
* now_ms——当前单调毫秒;t1_ms——非零确认等待期限。
* 输出参数:无,不更新记录、不启动定时器或关闭连接。
* 返回值:1到期;0未到期或没有已完成未确认I帧;-1参数、状态或时间非法。
* 说明:起点取最早未ACK记录的原written_at_ms,等于期限即到期。
* 部分ACK后选下一帧的原时间,重复ACK或后续帧发送不为前帧续期;
* 尚未整帧完成的当前I帧使用Buffer的本地发送期限。
*/
int IEC104_SendManager_T1_I_Ack_Expired(
const IEC104_SendManager_t *manager, uint64_t connection_generation,
uint64_t now_ms, uint32_t t1_ms)
{
IEC104_IFrameSendRecord_t record;
IEC104_SendManagerResult_t result;
if (!Manager_Storage_Valid(manager) || connection_generation != manager->connection_generation || t1_ms == 0U) {
return -1;
}
result = IEC104_SendManager_Peek_Oldest_Sent_Unacked_Record(manager, &record);
if (result == IEC104_SEND_MANAGER_EMPTY) {
return 0;
}
if (result != IEC104_SEND_MANAGER_OK || now_ms < record.written_at_ms) {
return -1;
}
return Deadline_Remaining(now_ms, record.written_at_ms, t1_ms) == 0U;
}
/**
* 功能:读取同时满足本地完成和对端确认的队首业务回执。
* 输入参数:manager——存储结构有效的发送管理器。
* 输出参数:record——仅OK时写入回执副本;其余结果保持原值。
* 返回值:OK表示队首回执就绪;EMPTY表示无记录;WAIT表示队首尚未同时满足两个条件;
* ERROR表示参数、存储或记录非法。
* 调用方:Connection代TcpServer查询;返回OK后,TcpServer复制RECORD事件,再经Connection调用Remove。
* 说明:只检查队首,不跳过尚未完成的旧记录;不依赖运行中的Link状态。
* 不自动释放记录;事件成功保存后才调用Remove_Confirmed_Record,尚未移除的回执仍占容量。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Peek_Confirmed_Record(
const IEC104_SendManager_t *manager, IEC104_IFrameSendRecord_t *record)
{
IEC104_IFrameSendRecord_t prepared;
IEC104_SendManagerResult_t result;
if (record == NULL) {
return IEC104_SEND_MANAGER_ERROR;
}
result = IEC104_SendManager_Get_Record_By_Offset(manager, 0U, &prepared);
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
if (prepared.local_send_completed == 0U || prepared.peer_acknowledged == 0U) {
return IEC104_SEND_MANAGER_WAIT;
}
*record = prepared;
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:在TcpServer成功保存RECORD事件后,按frame_id释放已就绪的队首内部记录。
* 输入参数:manager——存储结构有效的发送管理器。
* frame_id——已读取并成功复制为事件的队首回执身份。
* 输出参数:manager——成功时推进record_head_index并减少record_count。
* 返回值:OK表示释放成功;ERROR表示参数、记录、frame_id非法,或队首尚未就绪/不存在。
* 调用方:Connection接到TcpServer的记录移除请求后,用Peek_Confirmed_Record返回的frame_id调用一次。
* 说明:失败不修改记录;成功只释放业务记录槽,不改变Link序号、ACK或原始ASDU存储。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Remove_Confirmed_Record(
IEC104_SendManager_t *manager, IEC104_SendFrameId_t frame_id)
{
IEC104_IFrameSendRecord_t record;
if (IEC104_SendManager_Peek_Confirmed_Record(manager, &record) != IEC104_SEND_MANAGER_OK ||
frame_id.connection_generation != record.frame_id.connection_generation || frame_id.frame_serial != record.frame_id.frame_serial) {
return IEC104_SEND_MANAGER_ERROR;
}
manager->record_head_index = Record_Index_From_Offset(manager, 1U);
manager->record_count--;
return IEC104_SEND_MANAGER_OK;
}
/**
* 功能:终止Manager,并在自身存储结构有效时终止绑定的SendBuffer。
* 输入参数:manager——待终止对象,允许NULL。
* 输出参数:manager——非NULL时清零;AsduQueue及调用方记录数组保留。
* 返回值:无。
* 说明:先结束旧I/O并读取需要的业务记录;清零后不能再通过Manager取得这些回执。
* AsduQueue.Rebind清空HIGH并恢复NORMAL现存原文;明确全部丢弃须另调AsduQueue.Abort。
* 本函数不关闭socket、不回滚Link,不能代替旧异步操作的结束或隔离。
*/
void IEC104_SendManager_Abort(IEC104_SendManager_t *manager)
{
if (manager != NULL) {
if (Manager_Storage_Valid(manager)) {
IEC104_SendBuffer_Abort(manager->send_buffer);
}
memset(manager, 0, sizeof(*manager));
}
}
/* ==================== 子站:STARTDT_CON / STOPDT_CON ==================== */
/* Link已生成启动应答后提交;仅每轮首个CON清理HIGH,重复应答保留队列及公平配额。 */
static IEC104_SendManagerResult_t Commit_Startdt_Con(
IEC104_SendManager_t *manager, const IEC104_SendBufferReservation_t *reservation,
uint64_t now_ms, int first_start)
{
if (first_start) {
if (IEC104_AsduQueue_Reset_High(manager->asdu_queue, manager->connection_generation) != IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
manager->high_priority_burst_count = 0U;
}
return Commit_Control_Frame(manager, reservation, IEC104_SEND_BUFFER_FRAME_STARTDT_CON, now_ms);
}
/**
* 先取得控制槽再受理START;受理时立即激活并结束停止等待,CON按FIFO发送。
* WAIT保留原请求、状态和期限;相反方向ACT不暂存。
* 保留本项目的队列约定:从停用状态重新启动清理HIGH,已激活时重复START保留HIGH。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Slave_Handle_Startdt_Act(
IEC104_SendManager_t *manager, uint64_t connection_generation,
const IEC104_ApciInfo_t *apci, uint64_t now_ms)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result;
int first_start;
if (apci == NULL || apci->frame_type != IEC104_FRAME_TYPE_U || apci->u_function != IEC104_U_STARTDT_ACT) {
return IEC104_SEND_MANAGER_ERROR;
}
result = Reserve_Control_Frame(manager, connection_generation, &reservation);
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
first_start = manager->link_state->dt_state == IEC104_DT_STOPPED;
if (IEC104_Link_Handle_Startdt_Act(manager->link_state, apci,
reservation.apdu, reservation.apdu_capacity) != IEC104_LINK_RESULT_OK) {
return Cancel_Control_Reservation(manager, &reservation, IEC104_SEND_MANAGER_FATAL);
}
return Commit_Startdt_Con(manager, &reservation, now_ms, first_start);
}
/**
* STOP受理立即停用并清理HIGH;当前I帧和已有控制帧仍须按原顺序完成。
* 排空中的重复STOP返回IGNORED;控制队列满也可受理STOP,不暂存相反请求。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Slave_Handle_Stopdt_Act(
IEC104_SendManager_t *manager, uint64_t connection_generation, const IEC104_ApciInfo_t *apci)
{
IEC104_LinkResult_t result;
if (apci == NULL || apci->frame_type != IEC104_FRAME_TYPE_U || apci->u_function != IEC104_U_STOPDT_ACT ||
!Manager_Session_Valid(manager, connection_generation)) {
return IEC104_SEND_MANAGER_ERROR;
}
result = IEC104_Link_Handle_Stopdt_Act(manager->link_state, apci);
if (result == IEC104_LINK_RESULT_OK) {
if (IEC104_AsduQueue_Reset_High(manager->asdu_queue, connection_generation) != IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_FATAL;
}
return IEC104_SEND_MANAGER_OK;
}
return result == IEC104_LINK_RESULT_IGNORED ? IEC104_SEND_MANAGER_IGNORED : IEC104_SEND_MANAGER_FATAL;
}
/**
* 为STOP排队S或CON;stop_con_id仅在STOP_CON成功入队时写出,S/等待/失败不修改。
* Connection用该帧身份结束本轮STOP总计时,旧CON完成不能结束后续STOP的期限。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Slave_Queue_Stopdt_Response(
IEC104_SendManager_t *manager, uint64_t connection_generation, uint64_t now_ms,
IEC104_SendFrameId_t *stop_con_id)
{
IEC104_SendBufferReservation_t reservation;
IEC104_SendManagerResult_t result = Reserve_Control_Frame(manager, connection_generation, &reservation);
IEC104_StopdtPrepareResult_t prepared;
IEC104_StopdtTx_t tx;
if (result != IEC104_SEND_MANAGER_OK) {
return result;
}
prepared = IEC104_Link_Prepare_Stopdt_Response(manager->link_state, reservation.apdu,
reservation.apdu_capacity, &tx);
switch (prepared) {
case IEC104_STOPDT_FRAME_READY:
if (tx.frame_length != IEC104_APCI_LEN ||
(tx.frame_type != IEC104_STOPDT_FRAME_S_ACK && tx.frame_type != IEC104_STOPDT_FRAME_CON)) {
return IEC104_SEND_MANAGER_FATAL;
}
result = Commit_Control_Frame(manager, &reservation,
tx.frame_type == IEC104_STOPDT_FRAME_S_ACK ? IEC104_SEND_BUFFER_FRAME_S_ACK :
IEC104_SEND_BUFFER_FRAME_STOPDT_CON, now_ms);
if (result == IEC104_SEND_MANAGER_QUEUED && tx.frame_type == IEC104_STOPDT_FRAME_CON &&
stop_con_id != NULL) {
*stop_con_id = reservation.frame_id;
}
return result;
case IEC104_STOPDT_NOT_ACTIVE:
result = IEC104_SEND_MANAGER_EMPTY;
break;
case IEC104_STOPDT_WAIT_SEND:
case IEC104_STOPDT_WAIT_PEER_ACK:
result = IEC104_SEND_MANAGER_WAIT;
break;
default:
result = IEC104_SEND_MANAGER_ERROR;
break;
}
return Cancel_Control_Reservation(manager, &reservation, result);
}
/* ==================== 子站初始化 ==================== */
/**
* 功能:绑定子站新连接的业务队列、发送队列、链路状态和业务记录数组。
* 输入参数:asdu_queue——已初始化或Rebind的业务队列;允许非空,但不能有保留或已覆盖的待确认条目。
* send_buffer——已初始化的发送缓冲,须与业务队列同代次且无当前I帧、控制帧或预留。
* link_state——已按CONTROLLED角色State_Init的初始链路,序号为0,各过程空闲。
* records——调用方持有的固定存储记录数组;帧身份只存frame_serial,共用Manager代次。
* record_capacity——记录容量,范围1..32767。
* send_window_k——未确认I帧窗口上限,范围1..32767。
* high_priority_burst_limit——0表示严格高优先;非0限制NORMAL有未提交条目时连续HIGH交接次数。
* 输出参数:manager——仅成功时整体写入绑定结果;asdu_queue——成功时清空HIGH未提交条目,保留NORMAL;ERROR均不修改。
* 返回值:OK表示初始化成功;ERROR表示参数或初始状态非法。
* 说明:所有对象不得重叠,须持续有效且按连接串行访问;不清零records数组。
* 绑定后I帧由Prepare_Next_Send或Submit_Response统一准备,I/S接收经Handle_I_S_Frame,发送进度经Handle_Send_Progress提交。
* S/TESTFR及子站启停由本模块封装构帧入队,完成结果直接提交对应Link接口。
* 不接管运行中的连接,不创建TCP、线程或定时器。
* 发送缓冲空闲及链路初始状态由各模块查询判断;查询成功后核对Link的固定角色配置。
* 非CONTROLLED链路返回ERROR;初始化后不得改写Link角色或绑定对象。
*/
IEC104_SendManagerResult_t IEC104_SendManager_Init(
IEC104_SendManager_t *manager, IEC104_AsduQueue_t *asdu_queue,
IEC104_SendBuffer_t *send_buffer, IEC104_LinkState_t *link_state,
IEC104_IFrameSendRecordStorage_t *records, uint16_t record_capacity,
uint16_t send_window_k, uint16_t high_priority_burst_limit)
{
IEC104_SendManager_t prepared;
IEC104_LinkStatus_t link_status;
IEC104_AsduQueueStatus_t asdu_status;
IEC104_SendBufferStatus_t buffer_status;
if (manager == NULL || records == NULL || record_capacity == 0U ||
record_capacity > IEC104_SEQUENCE_MASK || send_window_k == 0U || send_window_k > IEC104_SEQUENCE_MASK ||
IEC104_Link_Is_Initial_State(link_state) != 1 || IEC104_Link_Get_Status(link_state, &link_status) != 1 ||
link_status.role != IEC104_ROLE_CONTROLLED ||
IEC104_AsduQueue_Get_Status(asdu_queue, &asdu_status) != IEC104_ASDU_QUEUE_OK ||
IEC104_SendBuffer_Get_Status(send_buffer, &buffer_status) != IEC104_SEND_BUFFER_OK ||
asdu_status.connection_generation != buffer_status.connection_generation ||
asdu_status.retained_unacked_count[IEC104_ASDU_PRIORITY_HIGH] != 0U ||
asdu_status.retained_unacked_count[IEC104_ASDU_PRIORITY_NORMAL] != 0U ||
asdu_status.overwritten_unacked_count != 0U || buffer_status.is_idle != 1U) {
return IEC104_SEND_MANAGER_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
prepared.asdu_queue = asdu_queue;
prepared.send_buffer = send_buffer;
prepared.link_state = link_state;
prepared.records = records;
prepared.record_capacity = record_capacity;
prepared.send_window_k = send_window_k;
prepared.high_priority_burst_limit = high_priority_burst_limit;
prepared.connection_generation = buffer_status.connection_generation;
prepared.is_initialized = 1U;
if (IEC104_AsduQueue_Reset_High(asdu_queue, prepared.connection_generation) != IEC104_ASDU_QUEUE_OK) {
return IEC104_SEND_MANAGER_ERROR;
}
*manager = prepared;
return IEC104_SEND_MANAGER_OK;
}
/* 先完成所有可失败的校验和HIGH清理,再向最终地址提交;不读取旧目标对象的未初始化内容。
* 绑定关系只在Manager内部建立,不提供对运行会话重新绑定指针的入口。 */
IEC104_SendManagerResult_t IEC104_SendManager_Init_Connection(
IEC104_SendManager_t *manager, IEC104_AsduQueue_t *asdu_queue,
IEC104_SendBuffer_t *send_buffer, IEC104_LinkState_t *link_state,
IEC104_SendBufferControlFrame_t *control_frames, uint16_t control_capacity,
IEC104_IFrameSendRecordStorage_t *records, uint16_t record_capacity,
uint16_t send_window_k, uint16_t high_priority_burst_limit, uint64_t connection_generation)
{
IEC104_SendManager_t prepared;
IEC104_LinkState_t prepared_link;
IEC104_SendBuffer_t prepared_buffer;
if (manager == NULL || send_buffer == NULL || link_state == NULL ||
IEC104_Link_State_Init(&prepared_link, IEC104_ROLE_CONTROLLED) != 1 ||
IEC104_SendBuffer_Init(&prepared_buffer, control_frames, control_capacity, connection_generation) !=
IEC104_SEND_BUFFER_OK ||
IEC104_SendManager_Init(&prepared, asdu_queue, &prepared_buffer, &prepared_link, records, record_capacity,
send_window_k, high_priority_burst_limit) != IEC104_SEND_MANAGER_OK) {
return IEC104_SEND_MANAGER_ERROR;
}
*link_state = prepared_link;
*send_buffer = prepared_buffer;
prepared.link_state = link_state;
prepared.send_buffer = send_buffer;
*manager = prepared;
return IEC104_SEND_MANAGER_OK;
}