527 lines
25 KiB
C
527 lines
25 KiB
C
/**
|
||
* 子站发送字节存储:一份当前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));
|
||
}
|
||
}
|