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

234 lines
17 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.
/* 子站两级变长ASDU字节环:借用调用方存储,不动态分配;HIGH满时拒绝,NORMAL覆盖最旧原文。 */
#ifndef IEC104_ASDUQUEUE_H
#define IEC104_ASDUQUEUE_H
#include "IEC104_Codec.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 区分条目头:HIGH只有uint16长度;NORMAL保存编号、状态及长度。
* NORMAL通过memcpy存取,可跨尾部且不要求环内地址对齐;布局仅供本次构建使用,不能持久化。
* 此类型公开用于容量计算,调用方不得把字节环强转为结构体指针。 */
typedef struct {
uint64_t entry_serial;
unsigned int entry_state : 2; /* 头内编码:1队列待发,2已登记发送交接且保留待ACK;0/3无效 */
unsigned int asdu_length : 8; /* 6..249 */
} IEC104_AsduNormalEntryHeader_t;
#define IEC104_ASDU_HIGH_ENTRY_HEADER_LEN ((uint32_t)sizeof(uint16_t))
#define IEC104_ASDU_NORMAL_ENTRY_HEADER_LEN ((uint32_t)sizeof(IEC104_AsduNormalEntryHeader_t))
#define IEC104_ASDU_HIGH_QUEUE_MIN_CAPACITY (IEC104_ASDU_HIGH_ENTRY_HEADER_LEN + IEC104_ASDU_HEADER_LEN)
#define IEC104_ASDU_NORMAL_QUEUE_MIN_CAPACITY (IEC104_ASDU_NORMAL_ENTRY_HEADER_LEN + IEC104_ASDU_HEADER_LEN)
typedef enum {
IEC104_ASDU_QUEUE_ERROR = -1, /* 条目超过桶总容量,或参数、代次、状态非法 */
IEC104_ASDU_QUEUE_FULL = 0, /* HIGH空闲字节不足,或NORMAL被旧条目占住编号窗口 */
IEC104_ASDU_QUEUE_OK = 1, /* 本次操作成功,查询输出有效 */
IEC104_ASDU_QUEUE_EMPTY = 2 /* 无匹配条目,不代表操作成功 */
} IEC104_AsduQueueResult_t;
typedef struct {
uint64_t connection_generation; /* 队列绑定代次;0表示离线NORMAL,Detach/Rebind后旧身份失效 */
uint64_t entry_serial; /* NORMAL为1..UINT64_MAX循环编号;HIGH固定0,无条目身份 */
} IEC104_AsduEntryId_t;
/* 条目逻辑连续,头和原文均可跨数组尾部。
* 队列待发区是尚未完成Mark_Submitted记账的条目;unsubmitted_*沿用此接口命名。
* HIGH在发送模块接手后直接出队,NORMAL另有已登记交接、保留原文待ACK的前缀。 */
typedef struct {
uint8_t *bytes; /* 调用方持有的字节数组,模块只借用 */
uint32_t byte_capacity; /* 数组字节容量,至少对应优先级的MIN_CAPACITY */
uint32_t head_index; /* 最旧保留条目头的字节下标;是否为空由entry_count决定 */
uint32_t tail_index; /* 下一次追加位置,直接从这里跨尾部写入 */
uint32_t used_bytes; /* 所有保留条目的本级条目头+ASDU实际长度之和 */
uint32_t entry_count; /* 原文仍保留的ASDU条目数;HIGH只有待发区,NORMAL另含待ACK前缀 */
uint32_t unsubmitted_index; /* 队列待发区首条头的字节下标;待发区为空时等于tail_index */
uint32_t unsubmitted_count; /* 尚未完成队列交接记账的条目数,不表示SendBuffer中的待发帧数 */
} IEC104_AsduPriorityQueue_t;
/*
* overwritten_unacked表示NORMAL原文已覆盖、对应I帧仍未被有效ACK确认;具体发送身份由Manager记录。
* 区间从overwritten_unacked_serial_begin沿编号递增方向到overwritten_unacked_serial_end,包含两端。
* 仅NORMAL使用条目编号,丢弃未提交条目等操作可造成编号间隔;ACK推进后的下界不一定对应实际条目。
* 待ACK计数为0时两端均为0;非空区间和保留条目共同受半环编号窗口约束。
*/
typedef struct {
IEC104_AsduPriorityQueue_t priority_queues[IEC104_ASDU_PRIORITY_COUNT]; /* HIGH/NORMAL两个独立字节环 */
uint64_t connection_generation; /* 当前绑定代次;0为服务离线缓存,只允许NORMAL待发原文 */
uint64_t next_entry_serial; /* 仅NORMAL分配;UINT64_MAX之后回到1,Rebind不重置 */
uint64_t overwritten_unacked_serial_end; /* 待确认循环区间的结束边界,含端点;计数为0时为0 */
uint64_t overwritten_unacked_serial_begin; /* 待确认循环区间的起始边界,含端点;ACK后推进,计数为0时为0 */
uint32_t overwritten_unacked_count; /* NORMAL原文已覆盖、已提交且待确认的ASDU条目数 */
uint8_t normal_send_limited; /* 0/1:EOF后停止选取全部未提交NORMAL;Detach/Rebind解除 */
uint8_t is_initialized; /* 1表示已初始化,不表示TCP存活或可发送 */
} IEC104_AsduQueue_t;
/*
* 只读ASDU条目状态快照,各count均以ASDU条目为单位;本队列保存原文,SendBuffer仅保存当前待完成的I帧APDU。
* 队列“已提交”表示发送模块接手后又完成了Mark_Submitted记账,不表示整帧已写完。
* 顺序是:SendBuffer持有完整I帧 -> Manager建立记录 -> Mark_Submitted更新队列。
* 最后一步执行前,队列仍保留旧的待发状态;所有步骤串行,不得插入其他队列修改。
* 待确认表示对应I帧尚未收到有效ACK,涵盖尚未写出、部分写出及整帧写完后的等待阶段。
* HIGH在交接记账时消费原文,其retained_unacked_count恒为0;未确认HIGH数量由Manager发送记录维护。
* NORMAL的retained_unacked_count加overwritten_unacked_count等于NORMAL已提交待确认条目数。
* is_storage_empty只说明原文存储为空,HIGH或NORMAL仍可能有对应I帧等待ACK。
*/
typedef struct {
uint64_t connection_generation; /* 查询时的绑定代次;0为未绑定连接的服务队列 */
uint32_t entry_count[IEC104_ASDU_PRIORITY_COUNT]; /* 各优先级仍保留原文的ASDU总条目数,包含未提交条目 */
uint32_t retained_unacked_count[IEC104_ASDU_PRIORITY_COUNT]; /* 原文仍保留的已提交待确认ASDU条目数;HIGH恒为0 */
uint32_t overwritten_unacked_count; /* NORMAL原文已覆盖、已提交且待确认的ASDU条目数 */
uint8_t is_storage_empty; /* 两级ASDU原文环均为空,不表示所有对应I帧均已确认 */
} IEC104_AsduQueueStatus_t;
/*
* ASDU原文最多分两段借用,先first后second;总长度为asdu_length。
* asdu_second==NULL时全部位于第一段;否则第二段长度=asdu_length-asdu_first_length。
* 本条目被覆盖、确认释放、Remove或HIGH提交后借用失效;Reset_High使HIGH借用失效。
* Detach/Rebind/Abort/Init使所有借用失效。
* NORMAL再次Enqueue可能覆盖旧借用;需要连续副本时,在借用仍有效期间使用Copy_Asdu。
*/
typedef struct {
IEC104_AsduEntryId_t entry_id; /* NORMAL核对完整身份;HIGH仅有当前代次,entry_serial为0 */
IEC104_AsduPriority_t priority; /* 条目所属字节环 */
const uint8_t *asdu_first; /* 原文第一段,非NULL,不包含APCI */
const uint8_t *asdu_second; /* 跨尾部时指向数组起点,否则NULL */
uint16_t asdu_first_length; /* 第一段长度,1..asdu_length */
uint16_t asdu_length; /* ASDU实际长度,6..249字节 */
uint8_t is_submitted; /* 0队列尚未登记交接;1 NORMAL已登记交接、保留待ACK;HIGH出队前恒0 */
} IEC104_AsduHeadInfo_t;
/*
* 两个缓冲区容量单位均为字节,至少各自MIN_CAPACITY;不分配内存,不清空字节数组。
* 条目占本级HEADER_LEN+asdu_length,支持跨尾部写入;最大ASDU需要本级HEADER_LEN+249字节。
* Enqueue中条目超过桶总容量返回ERROR;空间不足时HIGH返回FULL,NORMAL覆盖最旧原文。
* NORMAL覆盖包括已提交未ACK条目,不产生丢弃通知;发送记录及ACK义务由Manager独立保留。
* 结构、数组、输入、输出不得重叠;所有操作串行。generation为0时只缓存NORMAL,不能提交发送。
* 绑定连接须使用非零且跨连接不复用的generation;服务可先Init(0),接入后再Rebind。
* NORMAL编号在1..UINT64_MAX循环,回绕不改变generation;所有活跃编号距next不超过UINT64_MAX/2。
* NORMAL的entry_id只在原文条目或覆盖待ACK记录存活期间有效;释放后不得跨编号复用再次提交。
* HIGH没有条目身份;从Peek到Mark/Discard之间不得移除、清空或替换队首,不能异步保存0编号当作身份。
* 两级后续发送完成和ACK均由Manager的frame_id及协议序号处理,不保存业务user_tag。
*/
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Init(
IEC104_AsduQueue_t *queue,
uint8_t *high_bytes, uint32_t high_capacity,
uint8_t *normal_bytes, uint32_t normal_capacity,
uint64_t connection_generation);
/* 只读数值快照;仅OK写status,查询不预留容量,也不证明TCP或链路状态。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Get_Status(
const IEC104_AsduQueue_t *queue, IEC104_AsduQueueStatus_t *status);
/*
* HIGH用于连接请求应答,提交后出队;Manager初始化成功、受理新STOP或从STOPPED受理START时清空待发条目。
* 重复START的CON不清理HIGH;HIGH重连不保留,启停准入由Manager控制,本队列不读取链路状态。
* NORMAL用于允许向同一业务接收方恢复的事件,离线时以generation=0入队;HIGH必须绑定非零代次。
* Detach/Rebind保留仍存在的NORMAL原文并恢复未提交;EOF后全部未提交NORMAL均留给下一连接。
* 长度6..249;条目头加ASDU超过目标桶总容量时ERROR,不为装不下的条目清空队列。
* HIGH空间不足时FULL,等待释放后重试;NORMAL按队首顺序计划移除旧条目,全部检查通过后才覆盖并返回OK。
* NORMAL旧条目/覆盖待ACK区间阻止编号窗口前进时也返回FULL;HIGH不分配编号,不受该窗口阻塞。
* NORMAL的UINT64_MAX仍可分配,成功后下一编号为1,不重连、不清空已有条目。
* 覆盖包括已提交原文,但不撤回已经编号的帧、不伪造ACK、不产生逐条丢弃通知。
* 仅OK后生产者推进游标;FULL/ERROR不修改队列、字节或编号。ERROR不能按背压盲目重试。
*/
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Enqueue(
IEC104_AsduQueue_t *queue, uint64_t connection_generation,
IEC104_AsduPriority_t priority, const uint8_t *asdu,
uint16_t asdu_length);
/*
* Peek_Head查看最旧保留条目,Peek_Unsubmitted查看队列待发区首条;均不修改状态。
* Peek_Unsubmitted返回EMPTY表示当前没有可交接的条目;EOF后全部待发NORMAL留待下次绑定,不代表整个队列为空。
* 离线时仍可查看NORMAL待发原文,但必须Rebind到非零代次后才能Mark_Submitted。
* 查询只描述队列记账状态,不检查SendBuffer;is_submitted为0不证明发送模块尚未接手。
*/
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Peek_Head(
const IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority,
IEC104_AsduHeadInfo_t *head_info);
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Peek_Unsubmitted(
const IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority,
IEC104_AsduHeadInfo_t *head_info);
/*
* 将仍有效的两段原文复制到destination;capacity至少asdu_length,且不得与原文或描述重叠。
* OK写完整ASDU,ERROR不改输出;不重新验证条目身份,调用方须保证借用仍有效。
*/
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Copy_Asdu(
const IEC104_AsduHeadInfo_t *head_info, uint8_t *destination, uint16_t capacity);
/* 登记发送交接的队列结果;调用前完整I帧须已交给SendBuffer且Manager已建立记录。
* 必须绑定非零代次;本函数入口处,队列仍保留待发旧状态。
* HIGH直接消费原文,NORMAL改为保留待ACK并推进待发游标;EOF后NORMAL不允许新的发送交接。
* 出队或修改状态均在发送模块接手之后,不表示报文已写完或已收到ACK。
* 本队列无法验证发送缓冲是否接手,调用方须保证上述顺序,不能提前调用本函数取消待发业务。
* HIGH要求当前代次及0编号,不提供同代次逐条身份保护;NORMAL另核对条目编号。
* 从Peek到本次记账之间不得移除、清空或替换所选条目;成功前的原文借用须持续有效。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Mark_Submitted(
IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority, IEC104_AsduEntryId_t entry_id);
/*
* 仅接受NORMAL的有效ACK路径,按发送顺序提交Manager记录中的真实身份;HIGH的ACK只由Manager处理。
* 仍有原文时严格匹配已提交队首并释放;NORMAL原文已覆盖时核减待确认计数,不删除新队首。
* 已覆盖身份仅以计数和循环编号区间检查,准确身份及顺序由Manager的记录和Link有效ACK保证。
*/
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Confirm_Next_Normal_Entry(
IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority, IEC104_AsduEntryId_t entry_id);
/* 主动丢弃只允许尚未登记交接的物理队首,不能越过NORMAL待ACK前缀删除后面的待发条目。
* HIGH只核对代次及0编号,NORMAL另核对条目编号;成功使该条目的原文借用失效。
* 本函数无法检查SendBuffer是否已接手,调用方须在发送交接开始前使用,不得插入Manager交接过程。
* 不能用它撤回已编号I帧;ERROR不修改队列。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Discard_Unsubmitted_Head(
IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority, IEC104_AsduEntryId_t entry_id);
/* 故障检查/业务恢复使用,offset为从保留队首起的条目序位,0表示队首,单位不是字节;O(offset+1)。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Get_Entry_By_Offset(
const IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority,
uint32_t offset, IEC104_AsduHeadInfo_t *head_info);
/* 返回未占用字节数;空闲>=本级HEADER_LEN+ASDU长度即有足够容量;NORMAL入队另检查编号窗口。
* NORMAL空闲不足时仍可能覆盖旧原文入队;查询不预留空间,最终以Enqueue返回值为准。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Get_Free_Bytes(
const IEC104_AsduQueue_t *queue, IEC104_AsduPriority_t priority, uint32_t *free_bytes);
/* 清空HIGH未提交条目,保留缓冲区、代次及编号进度;不影响NORMAL、已提交I帧和Manager发送记录。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Reset_High(
IEC104_AsduQueue_t *queue, uint64_t connection_generation);
/* EOF收尾前停止选取全部未提交NORMAL;已有及后来新增的未提交原文均留给下一连接,仍按正常容量策略缓存。
* 必须匹配当前非零代次;重复调用不解除限制,已提交I帧继续由SendBuffer/Manager收尾。
* 不影响HIGH,不修改ASDU原文或已有交接记账;Detach/Rebind解除限制。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Limit_Normal_Send(
IEC104_AsduQueue_t *queue, uint64_t connection_generation);
/* 旧I/O结束、旧Manager已Abort且已知ACK事实已处理后,解除与当前非零代次的绑定。
* 清空HIGH,保留NORMAL全部现存原文并恢复待发,清除旧ACK记账和发送限制;编号分配进度不变。
* 成功后generation为0,可继续缓存离线NORMAL;所有旧身份及借用失效,ERROR不改变队列。 */
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Detach(
IEC104_AsduQueue_t *queue, uint64_t connection_generation);
/*
* 旧连接及所有旧I/O结束、Manager已Abort后显式调用;要求新的非零代次,也可从离线代次0绑定。
* 清空HIGH,保留NORMAL全部现存原文并恢复未提交;清除旧代次的覆盖待确认计数、边界及发送限制。
* 先验证两个环的全部条目,再把NORMAL头内状态恢复未提交并更新控制字段,不搬移原文。
* ERROR不改控制字段或字节,成功使所有旧借用失效。
* 已覆盖原文无法恢复;不自动发报,允许新Manager绑定恢复后的非空队列。
* 调用前先处理已知ACK事实;NORMAL只能恢复到业务认可的接收方或冗余域,未确认不等于对端未收到。
* Rebind后旧entry_id失效,保留NORMAL条目的serial和原文不变;新连接使用新N(S)。
* next_entry_serial继续沿用;本接口只检查新代次非零且不同于当前值,历史代次不复用由调用方保证。
*/
extern IEC104_AsduQueueResult_t IEC104_AsduQueue_Rebind(
IEC104_AsduQueue_t *queue, uint64_t connection_generation);
/* 明确丢弃全部引用;先完成业务回收。Manager.Abort不会隐式调用本接口。
* 不清零或释放调用方数组;队列随后须Init才能重新使用。需要保留NORMAL恢复时应使用Rebind。 */
extern void IEC104_AsduQueue_Abort(IEC104_AsduQueue_t *queue);
#ifdef __cplusplus
}
#endif
#endif