Files
STM32F4-Base/IEC60870-Slave/104/IEC104_SendBuffer.c
T
STF 4c55544f05
CI / L0 静态门禁 (pre-commit) (push) Failing after 45s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
通信协议代码
2026-10-26 08:31:48 -04:00

527 lines
25 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.
/**
* 子站发送字节存储:一份当前I APDU,以及独立的固定6字节S/U控制FIFO。
* 已准备的当前帧连续发送;本地完成提交后复用存储,等待ACK的记录由Manager保存。
* HIGH原文已在提交时出队;NORMAL原文由Queue保留至ACK或被后续入队覆盖。
* 本文件不进行I/O,不分配协议序号,不推进Link状态;所有操作由同连接上下文串行执行。
*/
#include "IEC104_SendBuffer.h"
#include "IEC104_Codec.h"
#include <stddef.h>
#include <string.h>
/**
* 功能:将控制队列的逻辑偏移换算为数组下标。
* 输入参数:buffer——容量非零且边界已验证的缓冲;offset——相对控制队首的槽数偏移,不是字节数。
* 输出参数:无。
* 返回值:0..control_capacity-1范围内的物理下标。
* 说明:队尾位置也用此公式计算;使用32位加法,避免两个16位索引相加溢出。
*/
static uint16_t Control_Index(const IEC104_SendBuffer_t *buffer, uint16_t offset)
{
return (uint16_t)(((uint32_t)buffer->control_head_index + offset) % buffer->control_capacity);
}
/**
* 功能:检查一帧已提交报文的身份、类型、长度和写入进度。
* 输入参数:buffer——已检查公共字段的缓冲;state——非NULL帧元数据;is_i——非0检查当前I帧。
* 输出参数:无。
* 返回值:合法返回1,否则0。
* 说明:I帧另核对构帧快照;控制帧固定6字节。这里只检查元数据,不重新解析APDU字节。
*/
static int Frame_State_Valid(const IEC104_SendBuffer_t *buffer,
const IEC104_SendBufferFrameState_t *state, int is_i)
{
if (state->frame_serial == 0U || state->frame_serial >= buffer->next_frame_serial ||
state->written_bytes > state->apdu_length || state->encoded_nr > IEC104_SEQUENCE_MASK ||
(state->written_bytes == state->apdu_length && state->written_at_ms < state->queued_at_ms)) {
return 0;
}
if (is_i) {
return state->frame_type == IEC104_SEND_BUFFER_FRAME_I &&
state->apdu_length >= IEC104_APCI_LEN + IEC104_ASDU_HEADER_LEN &&
state->apdu_length <= IEC104_MAX_APDU_LEN &&
buffer->i_frame_snapshot.send_number <= IEC104_SEQUENCE_MASK &&
buffer->i_frame_snapshot.receive_number == state->encoded_nr &&
buffer->i_frame_snapshot.frame_length == state->apdu_length;
}
return state->frame_type >= IEC104_SEND_BUFFER_FRAME_S_ACK &&
state->frame_type <= IEC104_SEND_BUFFER_FRAME_TESTFR_CON &&
state->apdu_length == IEC104_APCI_LEN &&
(state->frame_type == IEC104_SEND_BUFFER_FRAME_S_ACK || state->encoded_nr == 0U);
}
/**
* 功能:检查缓冲控制结构、当前I帧、控制FIFO端点和唯一预留。
* 输入参数:buffer——待检查对象,允许NULL。
* 输出参数:无。
* 返回值:访问到的边界及元数据自洽返回1,否则0。
* 说明:不遍历控制FIFO中间槽,不维护空闲链、保留链或另一套协议序号。
* 当前I帧存在时,后续控制队首必须尚未写出字节,避免两个APDU交错发送。
*/
static int Buffer_State_Valid(const IEC104_SendBuffer_t *buffer)
{
const IEC104_SendBufferFrameState_t *state;
if (buffer == NULL || buffer->is_initialized != 1U || buffer->control_frames == NULL ||
buffer->control_capacity == 0U || buffer->control_head_index >= buffer->control_capacity ||
buffer->control_count > buffer->control_capacity || buffer->connection_generation == 0U ||
buffer->next_frame_serial == 0U || buffer->has_i_frame > 1U || buffer->has_reservation > 1U ||
buffer->reservation_is_i_frame > 1U ||
(buffer->has_reservation == 0U && buffer->reservation_is_i_frame != 0U)) {
return 0;
}
if (buffer->has_i_frame != 0U && !Frame_State_Valid(buffer, &buffer->i_frame_state, 1)) {
return 0;
}
if (buffer->control_count != 0U) {
state = &buffer->control_frames[buffer->control_head_index].state;
if (!Frame_State_Valid(buffer, state, 0) ||
(buffer->has_i_frame != 0U && state->written_bytes != 0U) ||
!Frame_State_Valid(buffer, &buffer->control_frames[
Control_Index(buffer, (uint16_t)(buffer->control_count - 1U))].state, 0)) {
return 0;
}
}
/* 预留槽还未进入发送集合,类型与长度保持初始值;身份必须是刚分配的最后一个。 */
if (buffer->has_reservation != 0U) {
if (buffer->reservation_is_i_frame != 0U) {
if (buffer->has_i_frame != 0U || buffer->control_count != 0U) {
return 0;
}
state = &buffer->i_frame_state;
} else {
if (buffer->control_count == buffer->control_capacity) {
return 0;
}
state = &buffer->control_frames[Control_Index(buffer, buffer->control_count)].state;
}
if (state->frame_serial == 0U || state->frame_serial != buffer->next_frame_serial - 1U ||
state->frame_type != IEC104_SEND_BUFFER_FRAME_INVALID ||
state->apdu_length != 0U || state->written_bytes != 0U) {
return 0;
}
}
return 1;
}
/**
* 功能:核对事件是否属于指定帧的当前连接代次和预留身份。
* 输入参数:buffer/state——已验证对象;id——调用方提交的帧身份。
* 输出参数:无。
* 返回值:代次及非零frame_serial都匹配返回1,否则0。
* 说明:N(S)会回绕,不能用协议序号代替本地发送身份。
*/
static int Frame_Id_Matches(const IEC104_SendBuffer_t *buffer,
const IEC104_SendBufferFrameState_t *state, IEC104_SendFrameId_t id)
{
return id.connection_generation == buffer->connection_generation &&
id.frame_serial != 0U && id.frame_serial == state->frame_serial;
}
/**
* 功能:核对本地完成分派类型与已解析APCI是否一致。
* 输入参数:type——请求保存的帧类型;apci——成功解析的非NULL描述。
* 输出参数:无。
* 返回值:匹配返回1,否则0。
* 说明:只允许子站发送I、S、STARTDT_CON、STOPDT_CON及TESTFR ACT/CON。
*/
static int Frame_Type_Matches(IEC104_SendBufferFrameType_t type, const IEC104_ApciInfo_t *apci)
{
switch (type) {
case IEC104_SEND_BUFFER_FRAME_I:
return apci->frame_type == IEC104_FRAME_TYPE_I;
case IEC104_SEND_BUFFER_FRAME_S_ACK:
return apci->frame_type == IEC104_FRAME_TYPE_S;
case IEC104_SEND_BUFFER_FRAME_STARTDT_CON:
return apci->frame_type == IEC104_FRAME_TYPE_U && apci->u_function == IEC104_U_STARTDT_CON;
case IEC104_SEND_BUFFER_FRAME_STOPDT_CON:
return apci->frame_type == IEC104_FRAME_TYPE_U && apci->u_function == IEC104_U_STOPDT_CON;
case IEC104_SEND_BUFFER_FRAME_TESTFR_ACT:
return apci->frame_type == IEC104_FRAME_TYPE_U && apci->u_function == IEC104_U_TESTFR_ACT;
case IEC104_SEND_BUFFER_FRAME_TESTFR_CON:
return apci->frame_type == IEC104_FRAME_TYPE_U && apci->u_function == IEC104_U_TESTFR_CON;
default:
return 0;
}
}
/**
* 功能:初始化新连接的当前I帧存储,并绑定独立的控制槽数组。
* 输入参数:control_frames——持续有效的调用方数组;control_capacity——1..65535个槽;
* connection_generation——非零且跨连接不复用的代次。
* 输出参数:buffer——非NULL,仅成功时写入空闲状态。
* 返回值:OK或ERROR,失败不改buffer。
* 说明:唯一I APDU内置于buffer;不malloc、不清空控制数组、不创建socket或定时器。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Init(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferControlFrame_t *control_frames,
uint16_t control_capacity, uint64_t connection_generation)
{
IEC104_SendBuffer_t prepared;
if (buffer == NULL || control_frames == NULL || control_capacity == 0U || connection_generation == 0U) {
return IEC104_SEND_BUFFER_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
prepared.control_frames = control_frames;
prepared.control_capacity = control_capacity;
prepared.connection_generation = connection_generation;
prepared.next_frame_serial = 1U;
prepared.is_initialized = 1U;
*buffer = prepared;
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:查询控制帧数量、当前I帧及预留状态。
* 输入参数:buffer——有效发送缓冲。
* 输出参数:status——非NULL,仅OK时写入数值副本。
* 返回值:OK或ERROR,失败不改输出。
* 说明:i_frame_ns仅在has_i_frame=1时有效;is_idle表示当前为空闲,
* 不证明TCP为新连接,也不报告Link的完成或ACK边界。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Get_Status(
const IEC104_SendBuffer_t *buffer, IEC104_SendBufferStatus_t *status)
{
IEC104_SendBufferStatus_t prepared;
if (!Buffer_State_Valid(buffer) || status == NULL) {
return IEC104_SEND_BUFFER_ERROR;
}
prepared.connection_generation = buffer->connection_generation;
prepared.control_count = buffer->control_count;
prepared.has_i_frame = buffer->has_i_frame;
prepared.i_frame_ns = buffer->has_i_frame != 0U ? buffer->i_frame_snapshot.send_number : 0U;
prepared.has_reservation = buffer->has_reservation;
prepared.is_idle = buffer->control_count == 0U &&
buffer->has_i_frame == 0U && buffer->has_reservation == 0U;
/* Connection据此判断到期的t2/t3是否能准备新控制帧,避免容量阻塞时反复零等待。 */
prepared.control_has_room = buffer->has_reservation == 0U &&
buffer->control_count < buffer->control_capacity;
*status = prepared;
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:取得唯一预留,为随后Link构帧提供可写存储。
* 输入参数:buffer——有效缓冲;is_i——1预留当前I槽,0预留控制队尾。
* 输出参数:reservation——非NULL,仅OK时写入身份、APDU地址和容量;buffer记录预留。
* 返回值:OK成功;BUSY表示已有预留或I帧不能取得当前发送位置;
* FULL表示控制槽满;ERROR表示参数、状态或身份编号耗尽。
* 说明:I槽只能在当前I及控制队列均空时预留;S/U可以排在已准备的I帧之后。
* 编号在成功预留时消耗,取消后也不复用;非OK不修改对象或输出。
*/
static IEC104_SendBufferResult_t Reserve_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferReservation_t *reservation, uint8_t is_i)
{
IEC104_SendBufferFrameState_t *state;
IEC104_SendBufferReservation_t prepared;
if (!Buffer_State_Valid(buffer) || reservation == NULL) {
return IEC104_SEND_BUFFER_ERROR;
}
if (buffer->has_reservation != 0U ||
(is_i != 0U && (buffer->has_i_frame != 0U || buffer->control_count != 0U))) {
return IEC104_SEND_BUFFER_BUSY;
}
if (is_i == 0U && buffer->control_count == buffer->control_capacity) {
return IEC104_SEND_BUFFER_FULL;
}
if (buffer->next_frame_serial == UINT64_MAX) {
return IEC104_SEND_BUFFER_ERROR;
}
if (is_i != 0U) {
state = &buffer->i_frame_state;
prepared.apdu = buffer->i_apdu;
prepared.apdu_capacity = IEC104_MAX_APDU_LEN;
memset(&buffer->i_frame_snapshot, 0, sizeof(buffer->i_frame_snapshot));
} else {
IEC104_SendBufferControlFrame_t *control = &buffer->control_frames[
Control_Index(buffer, buffer->control_count)];
state = &control->state;
prepared.apdu = control->apdu;
prepared.apdu_capacity = IEC104_APCI_LEN;
}
memset(state, 0, sizeof(*state));
memset(prepared.apdu, 0, prepared.apdu_capacity);
state->frame_serial = buffer->next_frame_serial++;
buffer->has_reservation = 1U;
buffer->reservation_is_i_frame = is_i;
prepared.frame_id.connection_generation = buffer->connection_generation;
prepared.frame_id.frame_serial = state->frame_serial;
*reservation = prepared;
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:预留控制FIFO的下一个空槽。
* 输入参数:buffer——有效发送缓冲。
* 输出参数:reservation——仅OK时返回frame_id和6字节可写APDU存储。
* 返回值:OK、FULL、BUSY或ERROR,含义见Reserve_Frame。
* 说明:不占I帧窗口或业务记录;成功后须构帧提交,或在未构帧成功时取消预留。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Reserve_S_U_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferReservation_t *reservation)
{
return Reserve_Frame(buffer, reservation, 0U);
}
/**
* 功能:预留唯一当前I帧的APDU存储。
* 输入参数:buffer——有效发送缓冲。
* 输出参数:reservation——仅OK时返回frame_id和最大255字节可写存储。
* 返回值:OK、BUSY或ERROR,含义见Reserve_Frame。
* 说明:此处尚未分配协议N(S);后续由Link.Prepare_I_Frame在实际发送准备时分配。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Reserve_I_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendBufferReservation_t *reservation)
{
return Reserve_Frame(buffer, reservation, 1U);
}
/**
* 功能:校验预留报文并发布为当前I帧或控制FIFO的新队尾。
* 输入参数:buffer/frame_id——本次预留及身份;frame_type/apdu_length——实际报文描述;
* i_frame_snapshot——I帧必须传原Prepare快照,S/U必须为NULL;
* queued_at_ms——原始本地发送期限起点,使用单调毫秒,允许0。
* 输出参数:buffer及对应槽元数据——成功时保存描述、时间、快照并解除预留。
* 返回值:OK或ERROR;ERROR不修改缓冲状态,调用前存在的合法预留仍保留。
* 说明:调用前APDU已写入reservation.apdu。Link构帧成功后本接口出错须结束连接,
* 不能通过Cancel撤回已经分配的N(S)或已建立的控制发送义务。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Commit_Frame(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id,
IEC104_SendBufferFrameType_t frame_type, uint16_t apdu_length,
const IEC104_IFrameTx_t *i_frame_snapshot, uint64_t queued_at_ms)
{
IEC104_SendBufferFrameState_t *state;
const uint8_t *apdu;
IEC104_ApciInfo_t parsed;
if (!Buffer_State_Valid(buffer) || buffer->has_reservation == 0U) {
return IEC104_SEND_BUFFER_ERROR;
}
if (buffer->reservation_is_i_frame != 0U) {
if (frame_type != IEC104_SEND_BUFFER_FRAME_I || i_frame_snapshot == NULL ||
apdu_length < IEC104_APCI_LEN + IEC104_ASDU_HEADER_LEN || apdu_length > IEC104_MAX_APDU_LEN ||
i_frame_snapshot->frame_length != apdu_length) {
return IEC104_SEND_BUFFER_ERROR;
}
state = &buffer->i_frame_state;
apdu = buffer->i_apdu;
} else {
const IEC104_SendBufferControlFrame_t *control = &buffer->control_frames[
Control_Index(buffer, buffer->control_count)];
if (frame_type == IEC104_SEND_BUFFER_FRAME_I || i_frame_snapshot != NULL || apdu_length != IEC104_APCI_LEN) {
return IEC104_SEND_BUFFER_ERROR;
}
state = &buffer->control_frames[Control_Index(buffer, buffer->control_count)].state;
apdu = control->apdu;
}
/* 完成全部匹配检查后才发布元数据;实际字节和保存的完成分派类型不能分离。 */
if (!Frame_Id_Matches(buffer, state, frame_id) || !IEC104_Parse_APCI(apdu, apdu_length, &parsed) ||
!Frame_Type_Matches(frame_type, &parsed) ||
(i_frame_snapshot != NULL &&
(i_frame_snapshot->send_number != parsed.send_number ||
i_frame_snapshot->receive_number != parsed.receive_number))) {
return IEC104_SEND_BUFFER_ERROR;
}
state->apdu_length = apdu_length;
state->frame_type = (uint8_t)frame_type;
state->encoded_nr = parsed.receive_number;
state->queued_at_ms = queued_at_ms;
if (i_frame_snapshot != NULL) {
buffer->i_frame_snapshot = *i_frame_snapshot;
buffer->has_i_frame = 1U;
} else {
buffer->control_count++;
}
buffer->has_reservation = 0U;
buffer->reservation_is_i_frame = 0U;
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:取消身份匹配的唯一预留。
* 输入参数:buffer——有效缓冲;frame_id——Reserve返回的完整身份。
* 输出参数:buffer——成功时清除预留标志,不推进控制队列或发布当前I帧。
* 返回值:OK或ERROR,身份不符或没有预留时不修改状态。
* 说明:调用方保证Link尚未成功构帧;这里只归还存储,不回滚Link,不复用frame_serial。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Cancel_Reservation(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id)
{
const IEC104_SendBufferFrameState_t *state;
if (!Buffer_State_Valid(buffer) || buffer->has_reservation == 0U) {
return IEC104_SEND_BUFFER_ERROR;
}
state = buffer->reservation_is_i_frame != 0U ? &buffer->i_frame_state :
&buffer->control_frames[Control_Index(buffer, buffer->control_count)].state;
if (!Frame_Id_Matches(buffer, state, frame_id)) {
return IEC104_SEND_BUFFER_ERROR;
}
buffer->has_reservation = 0U;
buffer->reservation_is_i_frame = 0U;
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:取得已经准备好的当前发送帧及剩余字节。
* 输入参数:buffer——有效发送缓冲。
* 输出参数:head_info——非NULL,仅OK时写入身份、元数据及剩余字节借用指针。
* 返回值:OK有当前帧,EMPTY没有已提交报文,ERROR表示参数或状态非法。
* 说明:优先续传当前I帧,否则取控制队首;不选择ASDU、不重新编号。
* 已写完但尚未Commit_Send_Completion的帧仍返回OK,此时remaining_bytes为0。
* 原始帧即使因EAGAIN尚未写出字节也保持当前位置,借用在完成释放前结束。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Peek_Send_Head(
const IEC104_SendBuffer_t *buffer, IEC104_SendBufferHeadInfo_t *head_info)
{
IEC104_SendBufferHeadInfo_t prepared;
const IEC104_SendBufferFrameState_t *state;
const uint8_t *apdu;
if (!Buffer_State_Valid(buffer) || head_info == NULL) {
return IEC104_SEND_BUFFER_ERROR;
}
memset(&prepared, 0, sizeof(prepared));
if (buffer->has_i_frame != 0U) {
state = &buffer->i_frame_state;
apdu = buffer->i_apdu;
prepared.i_frame_snapshot = buffer->i_frame_snapshot;
} else if (buffer->control_count != 0U) {
state = &buffer->control_frames[buffer->control_head_index].state;
apdu = buffer->control_frames[buffer->control_head_index].apdu;
} else {
return IEC104_SEND_BUFFER_EMPTY;
}
prepared.frame_id.connection_generation = buffer->connection_generation;
prepared.frame_id.frame_serial = state->frame_serial;
prepared.pending_data = apdu + state->written_bytes;
prepared.remaining_bytes = (uint16_t)(state->apdu_length - state->written_bytes);
prepared.apdu_length = state->apdu_length;
prepared.written_bytes = state->written_bytes;
prepared.frame_type = (IEC104_SendBufferFrameType_t)state->frame_type;
prepared.encoded_nr = state->encoded_nr;
prepared.queued_at_ms = state->queued_at_ms;
prepared.written_at_ms = state->written_at_ms;
*head_info = prepared;
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:记录当前帧本次实际成功写入的字节增量。
* 输入参数:buffer/frame_id——当前缓冲及帧身份;bytes_written_now——不超过剩余长度的增量;
* written_at_ms——本次实际写入时刻;仅整帧写完时校验不早于queued_at_ms并保存。
* 输出参数:buffer——推进written_bytes;整帧写完时登记written_at_ms。
* 返回值:OK尚未完整;FRAME_WRITTEN恰好整帧写完;ERROR表示身份、长度、时间或状态非法。
* 说明:增量0不推进;部分写入不续期。函数只记账,不写socket、不调用Link.Complete,
* 同一成功增量不能重报;最终完成后再重复记账返回ERROR。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Record_Written_Bytes(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id,
uint16_t bytes_written_now, uint64_t written_at_ms)
{
IEC104_SendBufferFrameState_t *state;
if (!Buffer_State_Valid(buffer) || (buffer->has_i_frame == 0U && buffer->control_count == 0U)) {
return IEC104_SEND_BUFFER_ERROR;
}
state = buffer->has_i_frame != 0U ? &buffer->i_frame_state :
&buffer->control_frames[buffer->control_head_index].state;
if (!Frame_Id_Matches(buffer, state, frame_id) || state->written_bytes == state->apdu_length ||
bytes_written_now > (uint16_t)(state->apdu_length - state->written_bytes) ||
(bytes_written_now == (uint16_t)(state->apdu_length - state->written_bytes) &&
written_at_ms < state->queued_at_ms)) {
return IEC104_SEND_BUFFER_ERROR;
}
/* 只累计成功字节;queued_at_ms始终保留,最后一段才建立实际写完时刻。 */
state->written_bytes = (uint16_t)(state->written_bytes + bytes_written_now);
if (state->written_bytes == state->apdu_length) {
state->written_at_ms = written_at_ms;
return IEC104_SEND_BUFFER_FRAME_WRITTEN;
}
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:释放已整帧写完且身份匹配的当前I槽或控制队首。
* 输入参数:buffer——有效缓冲;frame_id——本次已经完成的当前帧身份。
* 输出参数:buffer——清除has_i_frame,或推进控制队首并减少数量。
* 返回值:OK或ERROR;未写完、身份不符或新队首非法时不释放。
* 说明:调用方须先提交该帧适用的Link完成结果;I帧还须登记本地完成事实。
* I槽不等待ACK;Manager继续保留发送记录,NORMAL原文可能仍在Queue或已被覆盖。
* TESTFR_CON没有Link完成接口;本接口本身不分派Link完成或解释ACK。
*/
IEC104_SendBufferResult_t IEC104_SendBuffer_Commit_Send_Completion(
IEC104_SendBuffer_t *buffer, IEC104_SendFrameId_t frame_id)
{
IEC104_SendBufferFrameState_t *state;
if (!Buffer_State_Valid(buffer) || (buffer->has_i_frame == 0U && buffer->control_count == 0U)) {
return IEC104_SEND_BUFFER_ERROR;
}
state = buffer->has_i_frame != 0U ? &buffer->i_frame_state :
&buffer->control_frames[buffer->control_head_index].state;
if (!Frame_Id_Matches(buffer, state, frame_id) || state->written_bytes != state->apdu_length) {
return IEC104_SEND_BUFFER_ERROR;
}
if (buffer->has_i_frame != 0U) {
buffer->has_i_frame = 0U;
} else {
/* 先验证即将成为队首的槽,再整体推进,避免错误返回时丢失当前帧引用。 */
if (buffer->control_count > 1U &&
!Frame_State_Valid(buffer, &buffer->control_frames[Control_Index(buffer, 1U)].state, 0)) {
return IEC104_SEND_BUFFER_ERROR;
}
buffer->control_head_index = Control_Index(buffer, 1U);
buffer->control_count--;
}
return IEC104_SEND_BUFFER_OK;
}
/**
* 功能:判断当前发送帧是否达到本地排队及写入总期限。
* 输入参数:buffer/connection_generation——有效缓冲及匹配代次;
* now_ms——当前单调毫秒;timeout_ms——非零期限。
* 输出参数:无,不修改进度或关闭连接。
* 返回值:1到期,0无当前帧或未到期,-1参数/状态非法或当前时刻早于起点。
* 说明:从原queued_at_ms作差,等于期限即到期;部分写入和重复查询不续期。
* 尚未提交到本缓冲区的ASDU排队时间不在此计算;I帧等待ACK的t1由Manager检查。
* 已整帧写完但尚未提交完成释放的帧仍在检查范围内。
*/
int IEC104_SendBuffer_Head_Send_Expired(
const IEC104_SendBuffer_t *buffer, uint64_t connection_generation, uint64_t now_ms, uint32_t timeout_ms)
{
IEC104_SendBufferHeadInfo_t head;
IEC104_SendBufferResult_t result;
if (buffer == NULL || connection_generation != buffer->connection_generation || timeout_ms == 0U) {
return -1;
}
result = IEC104_SendBuffer_Peek_Send_Head(buffer, &head);
if (result == IEC104_SEND_BUFFER_EMPTY) {
return 0;
}
if (result != IEC104_SEND_BUFFER_OK || now_ms < head.queued_at_ms) {
return -1;
}
return now_ms - head.queued_at_ms >= timeout_ms;
}
/**
* 功能:清除发送缓冲控制结构和内置当前I帧,解除对控制数组的引用。
* 输入参数:buffer——待终止对象,允许NULL。
* 输出参数:buffer——非NULL时清零;调用方控制数组不清零、不释放。
* 返回值:无。
* 说明:不关闭socket、不取消异步I/O,调用方必须先结束旧I/O及报文字节借用。
*/
void IEC104_SendBuffer_Abort(IEC104_SendBuffer_t *buffer)
{
if (buffer != NULL) {
memset(buffer, 0, sizeof(*buffer));
}
}