234 lines
17 KiB
C
234 lines
17 KiB
C
/* 子站两级变长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
|