1332 lines
74 KiB
C
1332 lines
74 KiB
C
/**
|
||
* 子站串行发送管理:发送流程、子站启停封装和初始化入口集中在本文件。
|
||
* 本模块由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, ¤t_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;
|
||
}
|