Files
STM32F4-Base/IEC60870-Slave/104/business/IEC104_Command.c
T
STF cd42b4d61d
CI / L0 静态门禁 (pre-commit) (push) Failing after 2m51s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 18s
添加遥控业务代码
2026-10-30 20:03:19 -07:00

1268 lines
69 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.
/*
* 模块职责:IEC104单点/双点遥控(45/46),处理校验、选择、执行、撤销、期限及协议应答。
* 数据来源:Slave交付连接/启停通知和I帧;应用通过纯值业务事件显式决定、启动和结束操作。
* 本模块不维护设备点表、不直接控制硬件、不创建线程,也不保存应用回调或申请堆内存。
*
* 状态主线:SELECT_WAIT -> SELECTED -> EXECUTE_WAIT -> EXECUTING -> ENDED;
* 允许直接执行时从EXECUTE_WAIT开始,拒绝、撤销、超时及会话失效可提前进入ENDED。
* Accept消费工作事件后最多调用一次动作;Report_Result停止设备执行期限;Complete关闭返回信息阶段。
* ENDED只表示协议交互结束,不保证物理输出已停止,也不立即代表事务槽可以复用。
*
* 存储与所有权:一个事务槽保存工作请求、业务事件和必要回复,独立短应答使用固定池。
* 模块借用Slave;事务持有一个站点引用直到槽释放;独立短应答只保留会话身份和原文字节。
* 应用若跨循环持有站点上下文或异步设备资源,须提前取得自己的引用,并自行维护设备忙状态。
* FINISHED已消费(或无需交付)且事务自身回复全部交接/取消,才归还事务槽及模块站点引用。
*
* 发送顺序:事务确认、终止、撤销/不匹配回复与独立短应答共用递增序号,只有最早者可提交。
* 返回信息不在模块缓存,先等待模块FIFO清空,再通过原连接的Try接口固定帧序。
* 发送OK只是发送层接手,不等于socket写完或主站ACK;WAIT未接手,只可重试发送,不能重做动作。
*
* 执行约束:全部接口与Slave在同一事件循环串行调用;修改入口在动作回调期间拒绝重入。
* Handle_Event按FIFO消费通知,不受后续未读通知的门禁阻挡;动作和发送先查会话,再判期限。
* FINISHED可在离线时继续读取;取消不回滚设备,实例/事务/事件身份阻止迟到调用操作新事务。
*/
#include "IEC104_Command.h"
#include "IEC104_TcpServer_Config.h"
#include "IEC104_Net.h"
#include <string.h>
#ifndef IEC104_COMMAND_CAPACITY
/* 实例数默认与TCP服务池一致;每个Slave至多绑定一个遥控实例。 */
#define IEC104_COMMAND_CAPACITY IEC104_TCP_SERVER_CAPACITY
#endif
#ifndef IEC104_COMMAND_REPLY_CAPACITY
/* 独立短回复容量;事务自己的必要确认、终止及撤销回复不占用这些槽。 */
#define IEC104_COMMAND_REPLY_CAPACITY 4U
#endif
_Static_assert(IEC104_COMMAND_CAPACITY > 0U, "Command needs instance storage");
_Static_assert(IEC104_COMMAND_REPLY_CAPACITY > 0U &&
IEC104_COMMAND_REPLY_CAPACITY < UINT32_MAX / 2U, "Command needs bounded reply storage");
/* 唯一事务槽。槽地址固定,当前工作事件、历史已交付事件身份和待发应答分别记账。 */
typedef struct {
IEC104_SlaveEventIdentity_t identity; /* 原服务实例和连接代次;不能替换为重连后的当前身份。 */
/* request_bytes:当前选择/执行请求,供动作、事件、ACT_CON及ACT_TERM使用。
* reply_bytes:撤销或不匹配执行请求,仅供对应短回复回显,不覆盖工作请求。 */
uint8_t request_bytes[10], reply_bytes[10];
IEC104_Station_t *station; /* 本事务Acquire的原站点;删除注册后仍持有,Reap时Release。 */
/* transaction_id:实例内单调事务号;receive_serial:当前工作请求的原I帧接收号。
* accepted_ms:当前请求在Handle_Event受理的时刻;timer_start_ms:当前阶段的单调计时起点。 */
uint64_t transaction_id, receive_serial, accepted_ms, timer_start_ms;
/* event_serial:当前工作/终结事件号;accepted_event_serial:已消费工作事件号,0表示尚未消费。
* select_event_serial/execute_event_serial:本事务两个工作事件的历史身份,供只读查询验证。 */
uint64_t event_serial, accepted_event_serial, select_event_serial, execute_event_serial;
/* confirm_serial/term_serial/reply_serial:待交确认、终止及撤销/不匹配回复的全局FIFO号,0表示无。 */
uint64_t confirm_serial, term_serial, reply_serial;
/* phase:事务阶段;event_type:当前事件种类;finish_reason:首次终结原因,后续取消不得覆盖。
* result:设备执行结果,0表示尚未报告;confirm_cot/reply_cot:两类待发回复各自的传送原因。 */
uint8_t phase, event_type, finish_reason, result, confirm_cot, reply_cot;
/* occupied:槽被占用;event_taken:当前事件已交付;ever_work_delivered:曾交付过任一工作事件。
* finish_pending:仍欠应用一个FINISHED;该义务不能因创建新的EXECUTE事件而丢失。 */
bool occupied, event_taken, ever_work_delivered, finish_pending;
/* select_delivered/execute_delivered:对应历史工作事件确实交付过,避免伪造查询身份。
* selected:当前执行经过了本事务的选择匹配,直接执行时为false。 */
bool select_delivered, execute_delivered, selected;
/* confirm_negative:当前确认P/N;confirm_try:确认是否采用不排队Try路径。
* send_waiting:事务回复上次提交遇到背压;return_waiting:应用返回信息上次提交遇到背压。 */
bool confirm_negative, confirm_try, send_waiting, return_waiting;
/* confirm_submitted:当前工作请求确认已被发送层接手;completed:Complete已关闭返回阶段。
* term_submitted:ACT_TERM已被发送层接手;select_effect/execute_effect:动作可能留有设备副作用。 */
bool confirm_submitted, completed, term_submitted, select_effect, execute_effect;
} CommandTransaction;
/* 与事务槽分离的拒绝回复,不保存站点指针,也不产生应用业务事件。 */
typedef struct {
IEC104_SlaveEventIdentity_t identity; /* 请求原会话身份;断线/换代清理,不能跨连接重发。 */
uint8_t bytes[10]; /* 单个完整命令原文,VSQ已规范为1;提交时仅改COT及P/N。 */
uint64_t serial; /* 与事务内回复共用的生成序号,决定全局FIFO位置。 */
uint8_t cot; /* 错误原因或否定ACT_CON/DEACT_CON的COT;独立回复P/N固定为1。 */
bool occupied, send_waiting; /* 槽占用标志;上次发送WAIT标志,用于避免need_process空转。 */
} CommandReply;
struct IEC104_Command {
IEC104_Slave_t *slave; /* 借用的所属服务,寿命必须覆盖本实例。 */
IEC104_SlaveEventIdentity_t session; /* 已按FIFO消费的连接/启停通知所确定的会话身份。 */
IEC104_CommandConfig_t config; /* Create时按值复制的配置;不借用调用方配置指针。 */
IEC104_CommandStatus_t status; /* 累计计数及模块活动状态;调度字段由Get_Status即时计算。 */
CommandTransaction transaction; /* 固定地址的唯一事务;占用期间所有站点的新遥控均忙拒绝。 */
CommandReply replies[IEC104_COMMAND_REPLY_CAPACITY]; /* 独立短回复池;满时丢新、不覆盖。 */
/* serial:进程内唯一实例号;next_transaction/next_event/next_reply:实例内身份计数器。
* last_generation:已接手CONNECTED的最高代次,断线后保留,用于拒绝旧连接通知。 */
uint64_t serial, next_transaction, next_event, next_reply, last_generation;
bool in_use, in_action; /* 静态实例池占用标志;同步动作回调的修改入口重入保护。 */
};
/* 静态池不迁移对象、不进行堆分配;实例编号跨Destroy保留递增,旧事件不能匹配复用的池槽。 */
static IEC104_Command_t command_pool[IEC104_COMMAND_CAPACITY];
static uint64_t next_instance;
/*
* 功能:验证句柄是否指向本模块当前占用的静态池槽。
* 参数:self为待检查地址,可以是NULL或不属于池的地址。
* 返回:归属合法且in_use为true时返回true;否则false,不修改状态。
* 约束:先比较地址再解引用,所有公开入口据此拒绝无效句柄。
*/
static bool Owned(const IEC104_Command_t *self)
{
unsigned i;
for (i = 0U; i < IEC104_COMMAND_CAPACITY; ++i) {
if (self == &command_pool[i]) { return self->in_use; }
}
return false;
}
/*
* 功能:从固定104命令布局读取24位小端信息对象地址。
* 参数:bytes指向至少10字节的45/46 ASDU;调用方已完成长度检查。
* 返回:0..0xFFFFFF的IOA;不判断该地址是否存在或适用于遥控。
*/
static uint32_t Read_IOA(const uint8_t *bytes)
{
return (uint32_t)bytes[6] | ((uint32_t)bytes[7] << 8) | ((uint32_t)bytes[8] << 16);
}
/*
* 功能:从固定104布局读取16位小端公共地址。
* 参数:bytes为具有完整6字节公共头的ASDU,COT/CA宽度均为2。
* 返回:原CA数值;不查询站点注册表,不修改原文。
*/
static uint16_t Read_CA(const uint8_t *bytes)
{
return (uint16_t)((uint16_t)bytes[4] | ((uint16_t)bytes[5] << 8));
}
/*
* 功能:从事务内部保留值重建交给应用的纯值请求。
* 参数:task为占用且持有站点引用的事务;request为非NULL输出。
* 输出:完整初始化request,含当前原文取值、站点注册号及受理时刻。
* 返回:无;不改变事务、不分配内存。
* 约束:动作不能读取应用回传event中的可修改request,必须以本函数的内部值为准。
*/
static void Copy_Request(const CommandTransaction *task, IEC104_CommandRequest_t *request)
{
memset(request, 0, sizeof(*request));
request->generation = task->identity.generation;
request->receive_serial = task->receive_serial;
request->station_id = IEC104_Station_Get_Id(task->station);
request->common_address = Read_CA(task->request_bytes);
request->originator_address = task->request_bytes[3];
request->type_id = task->request_bytes[0];
request->ioa = Read_IOA(task->request_bytes);
request->is_select = (task->request_bytes[9] & 0x80U) != 0U;
request->selected = task->selected;
request->state = task->request_bytes[9] & (request->type_id == 45U ? 1U : 3U);
request->qualifier = (task->request_bytes[9] >> 2) & 31U;
request->accepted_ms = task->accepted_ms;
}
/*
* 功能:统计模块仍持有、尚未被发送层接手的回复数。
* 参数:self为有效实例。
* 返回:事务内最多三种回复与全部已占用独立短回复的总数。
* 约束:不包括已经进入HIGH/当前发送帧的回复,也不包括应用尚未交付的返回信息。
*/
static unsigned Pending_Replies(const IEC104_Command_t *self)
{
const CommandTransaction *task = &self->transaction;
unsigned i, count = (task->confirm_serial != 0U ? 1U : 0U) +
(task->term_serial != 0U ? 1U : 0U) + (task->reply_serial != 0U ? 1U : 0U);
for (i = 0U; i < IEC104_COMMAND_REPLY_CAPACITY; ++i) {
if (self->replies[i].occupied) { ++count; }
}
return count;
}
/*
* 功能:在应用清理通知与本事务发送收尾均完成后释放事务槽。
* 参数:self为有效实例;允许事务尚未结束或已经空闲,此时无操作。
* 输出:满足条件时Release模块自己的站点引用,并清零事务全部状态。
* 返回:无;不清理独立短应答、不关闭连接、不回滚设备动作。
* 约束:只有ENDED且FINISHED已读取(或无需生成)、本事务回复已全部交接(或取消),
* 才能回收;两个条件分别记账。独立短应答不借用事务内存,不阻止本事务回收。
*/
static void Reap(IEC104_Command_t *self)
{
CommandTransaction *task = &self->transaction;
if (!task->occupied || task->phase != IEC104_COMMAND_PHASE_ENDED || task->finish_pending ||
task->confirm_serial != 0U || task->term_serial != 0U || task->reply_serial != 0U) { return; }
(void)IEC104_Slave_Release_Station(self->slave, task->station);
memset(task, 0, sizeof(*task));
}
/*
* 功能:结束当前应用交互,冻结首次原因并按交付历史生成一次FINISHED。
* 参数:self为有效实例;reason为本次终结原因。
* 输出:活动事务进入ENDED;未读工作事件被替换或移除,累计相应终结统计。
* 返回:无;不会自行丢弃待发回复,末尾仅尝试Reap。
* 约束:是否欠FINISHED取决于ever_work_delivered,而非当前事件是否取走。
* 已经ENDED时保持原原因和通知,不因后续STOP/Cancel重复交付。
* 结束应用交互不等于释放槽,仍须由Reap同时核对FINISHED与本事务回复的收尾条件。
*/
static void Finish(IEC104_Command_t *self, IEC104_CommandFinishReason_t reason)
{
CommandTransaction *task = &self->transaction;
if (!task->occupied || task->phase == IEC104_COMMAND_PHASE_ENDED) { Reap(self); return; }
task->phase = IEC104_COMMAND_PHASE_ENDED;
task->finish_reason = (uint8_t)reason;
task->event_type = IEC104_COMMAND_FINISHED;
task->event_taken = false;
task->finish_pending = task->ever_work_delivered;
task->return_waiting = false;
/* 创建事务时为最多两个工作事件和一个FINISHED检查了序号余量。 */
if (task->finish_pending) { task->event_serial = ++self->next_event; }
switch (reason) {
case IEC104_COMMAND_FINISH_COMPLETED: ++self->status.completed; break;
case IEC104_COMMAND_FINISH_EXECUTION_FAILED: ++self->status.execution_failed; break;
case IEC104_COMMAND_FINISH_ACTION_FAILED: ++self->status.action_failed; break;
case IEC104_COMMAND_FINISH_MISMATCH: ++self->status.mismatches; break;
case IEC104_COMMAND_FINISH_DEACTIVATED: ++self->status.deactivated; break;
case IEC104_COMMAND_FINISH_SELECT_TIMEOUT: ++self->status.select_timeouts; break;
case IEC104_COMMAND_FINISH_DECISION_TIMEOUT: ++self->status.decision_timeouts; break;
case IEC104_COMMAND_FINISH_EXECUTE_TIMEOUT: ++self->status.execute_timeouts; break;
case IEC104_COMMAND_FINISH_CANCELLED:
case IEC104_COMMAND_FINISH_STOPPED:
case IEC104_COMMAND_FINISH_DISCONNECTED:
case IEC104_COMMAND_FINISH_CONNECTION_CHANGED:
case IEC104_COMMAND_FINISH_SEND_ERROR:
case IEC104_COMMAND_FINISH_SERVICE_ERROR: ++self->status.cancelled; break;
default: break;
}
Reap(self);
}
/*
* 功能:取消模块仍持有的全部回复,并终结尚未结束的事务。
* 参数:self为有效实例;reason交给Finish记录,仅首次终结生效。
* 输出:事务回复序号及独立回复池清空;欠应用的FINISHED仍保留。
* 返回:无;不修改发送层已经接手的帧,也不负责设置模块激活标志。
*/
static void Cancel_All(IEC104_Command_t *self, IEC104_CommandFinishReason_t reason)
{
CommandTransaction *task = &self->transaction;
task->confirm_serial = task->term_serial = task->reply_serial = 0U;
task->send_waiting = false;
memset(self->replies, 0, sizeof(self->replies));
Finish(self, reason);
}
/*
* 功能:动作或发送之前核对Slave会话,并按失效原因清理模块状态。
* 参数:self为有效实例,其session来自已经消费的连接通知。
* 返回:READY可推进;WAIT_EVENTS须先读空通知;STALE表示旧事务不能继续;ERROR表示查询失败。
* 输出:刷新events_pending;失效/错误时取消未提交回复、终结事务并清active。
* 约束:WAIT_EVENTS优先且不取消事务。Handle_Event消费通知时不能调用本门禁,
* 否则队列还有后续通知时,当前已经出队的通知会被阻挡或遗漏。
*/
static IEC104_SlaveRequestState_t Check_Session(IEC104_Command_t *self)
{
IEC104_SlaveRequestState_t state = IEC104_Slave_Check_Request(self->slave, &self->session);
self->status.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
if (state == IEC104_SLAVE_REQUEST_ERROR) {
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
self->status.active = false;
} else if (state != IEC104_SLAVE_REQUEST_WAIT_EVENTS &&
(!self->status.active || state != IEC104_SLAVE_REQUEST_READY)) {
Cancel_All(self, IEC104_COMMAND_FINISH_CONNECTION_CHANGED);
self->status.active = false;
state = IEC104_SLAVE_REQUEST_STALE;
}
return state;
}
/*
* 功能:根据当前事务阶段选择唯一生效的时长限制。
* 参数:self为有效实例。
* 返回:决定、选择或执行期限的毫秒值;无计时阶段、已报告结果、回调中或配置禁用时为0。
* 约束:只读,不判定到期;0不屏蔽Clock_Reversed单独检查的单调时钟异常。
*/
static uint32_t Timer_Limit(const IEC104_Command_t *self)
{
const CommandTransaction *task = &self->transaction;
if (!task->occupied || self->in_action) { return 0U; }
switch (task->phase) {
case IEC104_COMMAND_PHASE_SELECT_WAIT:
case IEC104_COMMAND_PHASE_EXECUTE_WAIT: return self->config.decision_timeout_ms;
case IEC104_COMMAND_PHASE_SELECTED: return self->config.select_timeout_ms;
case IEC104_COMMAND_PHASE_EXECUTING:
return task->result == 0U ? self->config.execute_timeout_ms : 0U;
default: return 0U;
}
}
/*
* 功能:计算当前阶段距离启用的期限还剩多少毫秒。
* 参数:task提供timer_start_ms;limit为非零期限;now是本次统一采样的单调时间。
* 返回:剩余毫秒;恰好到期、已经超期或时钟倒退均为0。
* 约束:先比较64位差值再截窄,不使用可能溢出的起点加期限计算。
*/
static uint32_t Remaining(const CommandTransaction *task, uint32_t limit, uint64_t now)
{
if (now < task->timer_start_ms || now - task->timer_start_ms >= limit) { return 0U; }
return limit - (uint32_t)(now - task->timer_start_ms);
}
/*
* 功能:检查仍等待决定、执行命令或设备结果的阶段是否遇到单调时间倒退。
* 参数:self为有效实例;now为本次单调时钟采样。
* 返回:等待阶段中now早于计时起点时为true;空闲、ENDED、已报告结果或回调中为false。
* 约束:期限0仅关闭经过时长限制,不允许等待状态依赖倒退的时钟继续执行。
*/
static bool Clock_Reversed(const IEC104_Command_t *self, uint64_t now)
{
const CommandTransaction *task = &self->transaction;
return !self->in_action && task->occupied && now < task->timer_start_ms &&
(task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT || task->phase == IEC104_COMMAND_PHASE_SELECTED ||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT ||
(task->phase == IEC104_COMMAND_PHASE_EXECUTING && task->result == 0U));
}
/*
* 功能:分配模块内所有回复共用的递增FIFO序号。
* 参数:self为有效实例。
* 返回:成功为非零新序号;耗尽返回0,并以SERVICE_ERROR取消事务及回复、清active。
* 约束:0是未排队哨兵;计数不回绕,避免新回复与历史排序身份重合。
*/
static uint64_t Reply_Serial(IEC104_Command_t *self)
{
if (self->next_reply == UINT64_MAX) {
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
self->status.active = false;
return 0U;
}
return ++self->next_reply;
}
/*
* 功能:使用事务自己的存储生成一条确认,分配全局FIFO位置。
* 参数:self持有当前事务;cause为确认/错误COT;negative为P/N;try_send选择不排队Try路径。
* 输出:成功时写确认序号和策略,清confirm_submitted及send_waiting。
* 返回:true已准备确认;false为序号耗尽,Reply_Serial已执行服务错误清理。
* 约束:动作调用前必须准备必要确认资源,不能执行后再依赖独立回复池空位。
*/
static bool Set_Confirm(IEC104_Command_t *self, uint8_t cause, bool negative, bool try_send)
{
CommandTransaction *task = &self->transaction;
uint64_t serial = Reply_Serial(self);
if (serial == 0U) { return false; }
task->confirm_serial = serial;
task->confirm_cot = cause;
task->confirm_negative = negative;
task->confirm_try = try_send;
task->confirm_submitted = false;
task->send_waiting = false;
return true;
}
/*
* 功能:按统一采样时刻执行当前阶段的到期转换,不调用应用动作。
* 参数:self为有效实例;now同时用于回退检测和期限比较。
* 输出:决定到期生成否定确认;选择到期丢弃本模块尚持有的选择确认;
* 执行到期保留原肯定确认但不发终止;对应事务进入ENDED并按需产生FINISHED。
* 返回:无;未到期且时钟未回退时不改变状态,时钟回退单独计clock_errors。
* 期限为0时仍执行回退检测,不将失效的单调时钟视为无限期有效。
* 约束:动作/提交入口先通过会话门禁再调用;有效I帧按FIFO受理时也须在匹配前调用,
* 防止迟到执行先停掉选择计时、使已过期选择重新生效。
*/
static void Expire(IEC104_Command_t *self, uint64_t now)
{
CommandTransaction *task = &self->transaction;
uint32_t limit = Timer_Limit(self);
bool reversed = Clock_Reversed(self, now);
if (!reversed && (limit == 0U || Remaining(task, limit, now) != 0U)) { return; }
if (reversed) { ++self->status.clock_errors; }
switch (task->phase) {
case IEC104_COMMAND_PHASE_SELECT_WAIT:
case IEC104_COMMAND_PHASE_EXECUTE_WAIT:
if (Set_Confirm(self, IEC60870_ASDU_COT_ACTIVATION_CON, true, false)) {
Finish(self, IEC104_COMMAND_FINISH_DECISION_TIMEOUT);
}
break;
case IEC104_COMMAND_PHASE_SELECTED:
task->confirm_serial = 0U; /* 只能撤回模块尚持有的确认。 */
Finish(self, IEC104_COMMAND_FINISH_SELECT_TIMEOUT);
break;
case IEC104_COMMAND_PHASE_EXECUTING:
/* 动作已启动,不能因结果超时把原肯定确认改成否定。 */
Finish(self, IEC104_COMMAND_FINISH_EXECUTE_TIMEOUT);
break;
default: break;
}
}
/*
* 功能:识别发送入口处理新网络输入后遇到的正常STOP,保留回复等待通知收尾。
* 参数:self为有效实例;identity是本次提交的原身份;submitted是发送入口返回值。
* 返回:仅ERROR且同一连接健康、已停用、仍有未读通知时为true;其他错误及FATAL均false。
* 输出:匹配正常STOP时置events_pending,不计发送背压次数,不直接改变事务阶段。
* 约束:FATAL可能已经写出字节,绝不能借此入口解释成可重试WAIT。
*/
static bool Defer_For_Stop(IEC104_Command_t *self, const IEC104_SlaveEventIdentity_t *identity,
IEC104_TcpSubmitResult_t submitted)
{
IEC104_TcpServerStatus_t status;
if (submitted != IEC104_TCP_SUBMIT_ERROR || identity->slave != self->slave ||
identity->instance_id != self->session.instance_id || identity->generation != self->session.generation ||
IEC104_Slave_Get_Status(self->slave, &status, NULL) != IEC104_TCP_OK ||
status.generation != identity->generation || !status.connected || !status.connection_status_valid ||
!status.asdu_queue_status_valid || status.connection.phase != IEC104_TCP_SESSION_ACTIVE ||
status.connection.fault != IEC104_TCP_FAULT_NONE || status.connection.data_transfer_started ||
status.pending_event_count == 0U) { return false; }
self->status.events_pending = true;
return true;
}
/* 队首种类:NONE无待发;CONFIRM/TERM/REPLY对应事务内三份排序记录;SHORT对应独立回复池。
* 三种事务内回复和独立回复共享一个序号域,不另建可溢出的指针FIFO。 */
enum { COMMAND_HEAD_NONE, COMMAND_HEAD_CONFIRM, COMMAND_HEAD_TERM, COMMAND_HEAD_REPLY, COMMAND_HEAD_SHORT };
/*
* 功能:只读查找所有待提交回复中生成序号最小的一项。
* 参数:self为有效实例;slot为非NULL输出,仅队首为SHORT时写入独立槽下标。
* 返回:COMMAND_HEAD_*种类;没有待发回复时返回NONE。
* 约束:不弹出、不预留发送资源;序号0表示无回复,不能参与排序。
*/
static unsigned Reply_Head(const IEC104_Command_t *self, unsigned *slot)
{
const CommandTransaction *task = &self->transaction;
uint64_t first = 0U;
unsigned head = COMMAND_HEAD_NONE, i;
if (task->confirm_serial != 0U) { first = task->confirm_serial; head = COMMAND_HEAD_CONFIRM; }
if (task->term_serial != 0U && (first == 0U || task->term_serial < first)) {
first = task->term_serial; head = COMMAND_HEAD_TERM;
}
if (task->reply_serial != 0U && (first == 0U || task->reply_serial < first)) {
first = task->reply_serial; head = COMMAND_HEAD_REPLY;
}
for (i = 0U; i < IEC104_COMMAND_REPLY_CAPACITY; ++i) {
if (self->replies[i].occupied && (first == 0U || self->replies[i].serial < first)) {
first = self->replies[i].serial; head = COMMAND_HEAD_SHORT; *slot = i;
}
}
return head;
}
/*
* 功能:在Complete关闭返回阶段后,检查协议收尾是否已经全部交接。
* 参数:self为有效实例。
* 输出:EXECUTING且completed、确认已交接、无确认/终止待发时,按设备结果产生
* FINISHED(COMPLETED/EXECUTION_FAILED);其余状态只尝试Reap。
* 返回:无;既不发送报文也不回调应用。
* 约束:无ACT_TERM策略仍须等待原确认交接;发送交接不表示主站ACK或设备成功。
*/
static void Complete_If_Sent(IEC104_Command_t *self)
{
CommandTransaction *task = &self->transaction;
if (task->occupied && task->phase == IEC104_COMMAND_PHASE_EXECUTING && task->completed &&
task->confirm_submitted && task->confirm_serial == 0U && task->term_serial == 0U) {
Finish(self, task->result == IEC104_COMMAND_RESULT_SUCCEEDED ?
IEC104_COMMAND_FINISH_COMPLETED : IEC104_COMMAND_FINISH_EXECUTION_FAILED);
}
Reap(self);
}
/*
* 功能:按全局FIFO向发送层交付至多一条回复。
* 参数:self为有效实例;内部先核对会话,再读取当前队首。
* 输出:OK后移除对应待交标记并计数,随后检查完成/释放;WAIT只保留原回复;
* 正常STOP先让出,其他发送错误终结事务并清回复池。
* 返回:1表示有一条回复已交接;0表示无回复、等待或已处理发送失败;
* -1表示服务查询/组包错误,调用方应报告本轮推进失败。
* 约束:局部10字节副本承载COT/P/N改写,原工作请求和撤销回显来源不会被修改。
*/
static int Submit_Head(IEC104_Command_t *self)
{
CommandTransaction *task = &self->transaction;
const IEC104_SlaveEventIdentity_t *identity;
const uint8_t *source;
uint8_t bytes[10], cause;
IEC60870_Asdu_t asdu;
IEC104_TcpSubmitResult_t submitted;
IEC104_SlaveRequestState_t state = Check_Session(self);
unsigned slot = 0U, head;
bool negative, try_send;
if (state == IEC104_SLAVE_REQUEST_ERROR) { return -1; }
if (state != IEC104_SLAVE_REQUEST_READY) { return 0; }
head = Reply_Head(self, &slot);
if (head == COMMAND_HEAD_NONE) { return 0; }
identity = &task->identity;
source = task->request_bytes;
cause = IEC60870_ASDU_COT_ACTIVATION_TERMINATION;
negative = false;
try_send = true;
/* 默认分支为ACT_TERM;即使配置允许失败后终止,P/N=0也只表示流程结束。
* 普通确认可进入HIGH,执行后确认和终止必须走Try,防止越过既有过程信息。 */
if (head == COMMAND_HEAD_CONFIRM) {
cause = task->confirm_cot; negative = task->confirm_negative; try_send = task->confirm_try;
} else if (head == COMMAND_HEAD_REPLY) {
source = task->reply_bytes; cause = task->reply_cot;
negative = cause != IEC60870_ASDU_COT_DEACTIVATION_CON; try_send = false;
} else if (head == COMMAND_HEAD_SHORT) {
identity = &self->replies[slot].identity;
source = self->replies[slot].bytes; cause = self->replies[slot].cot;
negative = true; try_send = false;
}
memcpy(bytes, source, sizeof(bytes));
if (IEC60870_Asdu_Create_From_Buffer(&asdu, IEC104_Slave_Get_Application_Layer_Parameters(self->slave),
bytes, sizeof(bytes)) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_COT(&asdu, cause) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_Negative(&asdu, negative) != IEC60870_ASDU_OK) {
Cancel_All(self, IEC104_COMMAND_FINISH_SERVICE_ERROR);
return -1;
}
submitted = try_send ? IEC104_Slave_Try_Send_Response(self->slave, identity, &asdu) :
IEC104_Slave_Send_Response(self->slave, identity, &asdu);
if (submitted == IEC104_TCP_SUBMIT_OK) {
/* 发送层已复制/接手字节,立即撤销模块待发标记;不能等ACK再重复提交。 */
++self->status.submitted;
if (negative) { ++self->status.negative_submitted; }
if (head == COMMAND_HEAD_SHORT) { memset(&self->replies[slot], 0, sizeof(self->replies[slot])); }
else {
task->send_waiting = false;
if (head == COMMAND_HEAD_CONFIRM) { task->confirm_serial = 0U; task->confirm_submitted = true; }
else if (head == COMMAND_HEAD_TERM) { task->term_serial = 0U; task->term_submitted = true; }
else { task->reply_serial = 0U; }
}
Complete_If_Sent(self);
return 1;
}
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
/* WAIT未接手,不改变FIFO号;仅标记等待,使状态查询不会要求无网络推进的忙轮询。 */
if (head == COMMAND_HEAD_SHORT) { self->replies[slot].send_waiting = true; }
else { task->send_waiting = true; }
++self->status.waits;
return 0;
}
if (Defer_For_Stop(self, identity, submitted)) { return 0; }
Cancel_All(self, IEC104_COMMAND_FINISH_SEND_ERROR);
self->status.active = false;
return 0;
}
/*
* 功能:为校验失败、实例忙或不允许撤销的请求保存一条独立否定回复。
* 参数:self为有效实例;identity为原会话身份;data至少包含完整10字节命令;cause为回复COT。
* 输出:有空槽时复制原文、规范VSQ并分配FIFO号;池满计overloads/dropped_requests。
* 返回:true表示已处理(含丢新或发送等待);false表示序号/服务/组包内部错误。
* 约束:此前没有模块回复时才尝试当场提交;新短回复不能抢占事务必要确认存储,
* 也不能覆盖旧回复或因过载断开连接。
*/
static bool Short_Reply(IEC104_Command_t *self, const IEC104_SlaveEventIdentity_t *identity,
const uint8_t *data, uint8_t cause)
{
unsigned slot;
bool empty = Pending_Replies(self) == 0U;
uint64_t serial;
for (slot = 0U; slot < IEC104_COMMAND_REPLY_CAPACITY; ++slot) {
if (!self->replies[slot].occupied) { break; }
}
if (slot == IEC104_COMMAND_REPLY_CAPACITY) {
++self->status.overloads;
++self->status.dropped_requests;
return true;
}
serial = Reply_Serial(self);
if (serial == 0U) { return false; }
self->replies[slot].identity = *identity;
memcpy(self->replies[slot].bytes, data, sizeof(self->replies[slot].bytes));
self->replies[slot].bytes[1] = 1U;
self->replies[slot].serial = serial;
self->replies[slot].cot = cause;
self->replies[slot].occupied = true;
self->replies[slot].send_waiting = false;
return !empty || Submit_Head(self) >= 0;
}
/*
* 功能:在已占用的事务槽中建立SELECT或EXECUTE工作事件。
* 参数:self已持有事务/站点;data为通过校验的命令原文;receive_serial为原I帧编号;
* now为受理时刻;selected说明本执行是否已经匹配本事务选择。
* 输出:替换工作原文,生成新事件号,重置当前交付/消费和确认标志,开始决定期限并计accepted。
* 返回:无;不调用应用、不提交确认。
* 约束:初始事务或匹配执行才可调用;保留事务身份、选择副作用和ever_work_delivered,
* 因此SELECT已交付而EXECUTE尚未交付时,取消仍须通知应用清理选择资源。
*/
static void Work_Event(IEC104_Command_t *self, const uint8_t *data, uint64_t receive_serial,
uint64_t now, bool selected)
{
CommandTransaction *task = &self->transaction;
memcpy(task->request_bytes, data, sizeof(task->request_bytes));
task->request_bytes[1] = 1U;
task->receive_serial = receive_serial;
task->accepted_ms = task->timer_start_ms = now;
task->event_type = (data[9] & 0x80U) != 0U ? IEC104_COMMAND_SELECT : IEC104_COMMAND_EXECUTE;
task->phase = task->event_type == IEC104_COMMAND_SELECT ?
IEC104_COMMAND_PHASE_SELECT_WAIT : IEC104_COMMAND_PHASE_EXECUTE_WAIT;
task->event_serial = ++self->next_event;
task->accepted_event_serial = 0U;
if (task->event_type == IEC104_COMMAND_SELECT) { task->select_event_serial = task->event_serial; }
else { task->execute_event_serial = task->event_serial; }
task->event_taken = false;
task->selected = selected;
task->confirm_submitted = false;
task->send_waiting = false;
++self->status.accepted;
}
/*
* 功能:使用事务自带回复存储接受撤销或处理同点执行不匹配,然后结束应用交互。
* 参数:self持有待终结事务;data为本次撤销/不匹配请求;cause为9或7;reason为终结原因。
* 输出:丢弃模块尚持有的旧确认/终止,保存本次请求回显及新的FIFO号,生成必要FINISHED。
* 返回:true已处理(可仍等待发送);false为序号/服务/组包错误。
* 约束:不依赖独立回复池空闲;原工作请求不覆盖,已提交给发送层的旧确认无法撤回。
*/
static bool End_With_Reply(IEC104_Command_t *self, const uint8_t *data, uint8_t cause,
IEC104_CommandFinishReason_t reason)
{
CommandTransaction *task = &self->transaction;
uint64_t serial = Reply_Serial(self);
if (serial == 0U) { return false; }
task->confirm_serial = task->term_serial = 0U;
memcpy(task->reply_bytes, data, sizeof(task->reply_bytes));
task->reply_bytes[1] = 1U;
task->reply_cot = cause;
task->reply_serial = serial;
task->send_waiting = false;
Finish(self, reason);
return Submit_Head(self) >= 0;
}
/*
* 功能:受理一个当前代次的I帧,完成遥控校验、撤销、选择执行匹配和新事务建立。
* 参数:self为有效实例;event是Slave交付且归属已核验的事件;identity为其原会话身份。
* 输出:有效命令生成工作事件或改变匹配事务;可回复错误生成否定;决定期间同点激活静默丢弃计数。
* 返回:IGNORED为非本模块类型或当前不能接手;HANDLED包括保存、拒绝和丢弃;
* ERROR为内部序号、引用或发送准备错误,调用方不能继续按未知类型兜底。
* 约束:先确认完整单对象,再按COT、CA、IOA、对象字段的优先级判错;
* 完整10字节可以规范回显,截短或多对象不能截取/补零伪造单命令。
*/
static IEC104_CommandEventResult_t Request(IEC104_Command_t *self, const IEC104_SlaveEvent_t *event,
const IEC104_SlaveEventIdentity_t *identity)
{
CommandTransaction *task = &self->transaction;
IEC60870_AsduInfo_t info;
IEC104_Station_t *station;
const uint8_t *data;
uint16_t length;
uint8_t cause = 0U, state;
uint32_t ioa;
uint64_t now;
bool same_point, is_select;
if (!self->status.active || event->asdu_result != IEC60870_ASDU_OK ||
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK || length == 0U ||
(data[0] != 45U && data[0] != 46U) ||
(self->config.type_mask & IEC104_COMMAND_TYPE_BIT(data[0])) == 0U ||
!IEC60870_Parse_ASDU_Header(event->asdu.parameters, data, length, &info)) {
return IEC104_COMMAND_EVENT_IGNORED;
}
now = IEC104_Net_Now_Ms();
/* 已经按FIFO消费到当前I帧;先转换期限,随后匹配不得重新激活已过期选择。 */
Expire(self, now);
if (info.object_count != 1U || info.information_objects_len < 4U) {
++self->status.rejected;
return IEC104_COMMAND_EVENT_HANDLED;
}
ioa = Read_IOA(data);
station = IEC104_Slave_Get_Station(self->slave, info.common_address);
state = data[9] & 3U;
is_select = (data[9] & 0x80U) != 0U;
if (info.cause_of_transmission != IEC60870_ASDU_COT_ACTIVATION &&
info.cause_of_transmission != IEC60870_ASDU_COT_DEACTIVATION) {
cause = IEC60870_ASDU_COT_UNKNOWN_CAUSE;
} else if (station == NULL) { cause = IEC60870_ASDU_COT_UNKNOWN_COMMON_ADDRESS; }
else if (ioa == 0U) { cause = IEC60870_ASDU_COT_UNKNOWN_IOA; }
else if (info.is_sequence || length != 10U || info.is_negative_confirm || info.is_test ||
(data[0] == 45U ? (data[9] & 2U) != 0U : (state == 0U || state == 3U))) {
cause = info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION ?
IEC60870_ASDU_COT_DEACTIVATION_CON : IEC60870_ASDU_COT_ACTIVATION_CON;
}
if (cause != 0U) {
++self->status.rejected;
return Short_Reply(self, identity, data, cause) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
}
same_point = task->occupied && task->identity.generation == identity->generation &&
IEC104_Station_Get_Id(task->station) == IEC104_Station_Get_Id(station) &&
Read_IOA(task->request_bytes) == ioa;
/* 站点注册编号参与匹配,同CA删除再注册不能接管旧选择;撤销不要求状态/QU相同。 */
if (info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION) {
if (same_point && task->request_bytes[0] == data[0] && task->request_bytes[3] == data[3] &&
(task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT || task->phase == IEC104_COMMAND_PHASE_SELECTED ||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT)) {
return End_With_Reply(self, data, IEC60870_ASDU_COT_DEACTIVATION_CON,
IEC104_COMMAND_FINISH_DEACTIVATED) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
}
++self->status.rejected;
return Short_Reply(self, identity, data, IEC60870_ASDU_COT_DEACTIVATION_CON) ?
IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
}
if (same_point && (task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT ||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT)) {
/* 原请求尚待应用决定,先回忙否定可能被主站误认为原请求的结果。
* 只对通过上述校验的同会话/站点注册号/IOA激活静默丢弃;不要求命令字段相同。
* 不占应答槽,不覆盖原请求/事件,不刷新决定期限;撤销已在前面独立处理。 */
++self->status.dropped_requests;
return IEC104_COMMAND_EVENT_HANDLED;
}
/* SELECTED阶段选择确认尚未交接时忙拒绝;交接后同点执行先匹配,其余新激活不影响选择。 */
if (same_point && !is_select && task->phase == IEC104_COMMAND_PHASE_SELECTED && task->confirm_submitted) {
if (task->request_bytes[0] != data[0] || task->request_bytes[3] != data[3] ||
(task->request_bytes[9] & 0x7FU) != (data[9] & 0x7FU)) {
++self->status.rejected;
return End_With_Reply(self, data, IEC60870_ASDU_COT_ACTIVATION_CON,
IEC104_COMMAND_FINISH_MISMATCH) ? IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
}
Work_Event(self, data, event->transport.data.i_frame.receive_serial, now, true);
return IEC104_COMMAND_EVENT_HANDLED;
}
if (task->occupied || (!is_select && (self->config.select_required_mask & IEC104_COMMAND_TYPE_BIT(data[0])) != 0U)) {
/* ENDED但尚欠FINISHED/事务回复也算忙;其他点的新请求不能破坏当前选择。 */
++self->status.rejected;
if (task->occupied) { ++self->status.busy_rejected; }
return Short_Reply(self, identity, data, IEC60870_ASDU_COT_ACTIVATION_CON) ?
IEC104_COMMAND_EVENT_HANDLED : IEC104_COMMAND_EVENT_ERROR;
}
if (self->next_transaction == UINT64_MAX || self->next_event > UINT64_MAX - 3U) {
/* 每事务最多SELECT、EXECUTE、FINISHED三次生成,开始前保留足够事件身份余量。 */
++self->status.overloads;
++self->status.dropped_requests;
return IEC104_COMMAND_EVENT_HANDLED;
}
if (IEC104_Slave_Acquire_Station(self->slave, station) != IEC104_STATION_OK) { return IEC104_COMMAND_EVENT_ERROR; }
/* Acquire成功后才占事务槽;独立短回复中的数值CA不借用这个引用。 */
memset(task, 0, sizeof(*task));
task->occupied = true;
task->identity = *identity;
task->station = station;
task->transaction_id = ++self->next_transaction;
Work_Event(self, data, event->transport.data.i_frame.receive_serial, now, false);
return IEC104_COMMAND_EVENT_HANDLED;
}
/*
* 功能:填写第一版遥控默认配置,不创建任何运行对象。
* 参数:config为输出结构;NULL时无操作。
* 输出:启用45/46且默认均先选择,选择/决定/执行期限为30000/15000/60000毫秒,
* 成功发送ACT_TERM,失败默认不发送ACT_TERM。
* 返回:无;调用方可在Create之前修改配置。
*/
void IEC104_Command_Config_Default(IEC104_CommandConfig_t *config)
{
if (config == NULL) { return; }
memset(config, 0, sizeof(*config));
config->type_mask = IEC104_COMMAND_TYPE_BIT(45U) | IEC104_COMMAND_TYPE_BIT(46U);
config->select_required_mask = config->type_mask;
config->select_timeout_ms = 30000U;
config->decision_timeout_ms = 15000U;
config->execute_timeout_ms = 60000U;
config->send_act_term = true;
}
/*
* 功能:从静态池创建一个与Slave独占绑定的遥控实例。
* 参数:slave为有效服务句柄,寿命覆盖本实例;config为非NULL且按值复制的配置。
* 返回:成功为池内不透明句柄;类型位/期限配置非法、重复绑定、池满或实例号耗尽返回NULL。
* 输出:成功时初始化空事务、空回复池和零统计,不建立网络监听,也不调用应用代码。
* 约束:必须早于消费Slave通知创建;select_required_mask须为type_mask子集,
* 当前仅允许45/46位,后续修改调用方config不会影响实例。
*/
IEC104_Command_t *IEC104_Command_Create(IEC104_Slave_t *slave, const IEC104_CommandConfig_t *config)
{
const uint16_t supported = IEC104_COMMAND_TYPE_BIT(45U) | IEC104_COMMAND_TYPE_BIT(46U);
IEC104_Command_t *free_slot = NULL;
unsigned i;
if (config == NULL || config->type_mask == 0U || (config->type_mask & (uint16_t)~supported) != 0U ||
(config->select_required_mask & (uint16_t)~config->type_mask) != 0U || config->select_timeout_ms == 0U ||
next_instance == UINT64_MAX || IEC104_Slave_Get_Application_Layer_Parameters(slave) == NULL) { return NULL; }
for (i = 0U; i < IEC104_COMMAND_CAPACITY; ++i) {
if (command_pool[i].in_use && command_pool[i].slave == slave) { return NULL; }
if (!command_pool[i].in_use && free_slot == NULL) { free_slot = &command_pool[i]; }
}
if (free_slot == NULL) { return NULL; }
memset(free_slot, 0, sizeof(*free_slot));
free_slot->slave = slave;
free_slot->config = *config;
free_slot->serial = ++next_instance;
free_slot->in_use = true;
return free_slot;
}
/*
* 功能:归还空闲实例的静态池槽。
* 参数:self为待销毁句柄。
* 返回:true成功;无效句柄、动作回调中、事务仍占用或独立回复未清空时false。
* 输出:成功清零实例;不隐式Cancel、不读取FINISHED、不关闭Slave。
* 约束:先显式取消并完成事件/应答清理;成功后不得继续使用旧句柄。
*/
bool IEC104_Command_Destroy(IEC104_Command_t *self)
{
if (!Owned(self) || self->in_action || self->transaction.occupied || Pending_Replies(self) != 0U) { return false; }
memset(self, 0, sizeof(*self));
return true;
}
/*
* 功能:本地取消事务和模块尚未交接的回复,保留必要的应用清理通知。
* 参数:self为实例句柄;无效句柄或动作回调中调用时无操作。
* 输出:活动事务以CANCELLED结束,所有独立回复丢弃;会话激活状态保持,后续仍可接新请求。
* 返回:无;不撤回发送层已接手的字节,不回滚输出;已ENDED事务不改首次终结原因。
*/
void IEC104_Command_Cancel(IEC104_Command_t *self)
{
if (Owned(self) && !self->in_action) { Cancel_All(self, IEC104_COMMAND_FINISH_CANCELLED); }
}
/*
* 功能:按Slave FIFO顺序分派当前通知或I帧,不调用应用动作。
* 参数:self为实例;event须来自所属Slave的NextEvent或合法Copy_Event副本。
* 返回:HANDLED包括会话通知和已识别遥控请求;IGNORED为旧代次/未接手事件;
* ERROR为归属、实例、参数或重入错误,不可继续生成未知类型兜底回复。
* 输出:连接/启停通知更新会话并按原因取消旧事务;I帧交Request处理。
* 约束:状态通知先处理,不能统一套Check_Request;后续未出队通知不应阻挡当前通知消费。
*/
IEC104_CommandEventResult_t IEC104_Command_Handle_Event(IEC104_Command_t *self,
const IEC104_SlaveEvent_t *event)
{
IEC104_SlaveEventIdentity_t identity;
if (!Owned(self) || self->in_action ||
!IEC104_Slave_Get_Event_Identity(self->slave, event, &identity) ||
(self->session.instance_id != 0U && self->session.instance_id != identity.instance_id)) {
return IEC104_COMMAND_EVENT_ERROR;
}
self->session.slave = identity.slave;
self->session.instance_id = identity.instance_id;
if (event->transport.type == IEC104_TCP_EVENT_CONNECTED) {
/* 最高代次跨断线保留,历史CONNECTED副本不能重新绑定已经退役的连接。 */
if (identity.generation <= self->last_generation) { return IEC104_COMMAND_EVENT_IGNORED; }
Cancel_All(self, IEC104_COMMAND_FINISH_CONNECTION_CHANGED);
self->session.generation = self->last_generation = identity.generation;
self->status.active = false;
return IEC104_COMMAND_EVENT_HANDLED;
}
if (identity.generation != self->session.generation || identity.generation == 0U) {
return IEC104_COMMAND_EVENT_IGNORED;
}
switch (event->transport.type) {
case IEC104_TCP_EVENT_ACTIVATED:
self->status.active = true;
return IEC104_COMMAND_EVENT_HANDLED;
case IEC104_TCP_EVENT_DEACTIVATED:
Cancel_All(self, IEC104_COMMAND_FINISH_STOPPED);
self->status.active = false;
return IEC104_COMMAND_EVENT_HANDLED;
case IEC104_TCP_EVENT_DISCONNECTED:
Cancel_All(self, IEC104_COMMAND_FINISH_DISCONNECTED);
self->session.generation = 0U;
self->status.active = false;
return IEC104_COMMAND_EVENT_HANDLED;
case IEC104_TCP_EVENT_I_FRAME: return Request(self, event, &identity);
default: return IEC104_COMMAND_EVENT_IGNORED;
}
}
/*
* 功能:有界推进会话检查、期限转换及全局FIFO队首发送。
* 参数:self为实例句柄。
* 返回:true表示本轮已处理,可仍等待发送/应用或刚刚取消;false为参数、重入、服务或组包错误。
* 输出:每轮至多推进2*(1+独立回复容量)步;队首WAIT或有待消费通知时立即让出。
* 约束:不调用应用;每轮网络推进并分派通知后仍须调用,不能只在need_process为true时调用。
*/
bool IEC104_Command_Process(IEC104_Command_t *self)
{
unsigned step;
if (!Owned(self) || self->in_action) { return false; }
for (step = 0U; step < 2U * (1U + IEC104_COMMAND_REPLY_CAPACITY); ++step) {
IEC104_SlaveRequestState_t state = Check_Session(self);
int submitted;
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
if (state != IEC104_SLAVE_REQUEST_READY) { return true; }
Expire(self, IEC104_Net_Now_Ms());
Complete_If_Sent(self);
submitted = Submit_Head(self);
if (submitted <= 0) { return submitted == 0; }
}
return true;
}
/*
* 功能:交付当前最早且可读取的工作/终结事件。
* 参数:self为实例;event为非NULL输出结构,只有READY时改写。
* 返回:READY交付一个纯值事件;EMPTY为无事件或需先消费Slave通知;ERROR为参数/重入/服务错误。
* 输出:工作事件交付记录当前和历史交付标志;FINISHED交付清通知义务并尝试释放事务槽。
* 约束:已有FINISHED不受离线门禁阻挡;工作事件先检查会话与期限,过期事件不会交给动作。
* 返回FINISHED时模块引用可能已释放,应用跨循环使用站点必须提前取得自己的引用。
*/
IEC104_CommandNextResult_t IEC104_Command_NextEvent(IEC104_Command_t *self, IEC104_CommandEvent_t *event)
{
CommandTransaction *task;
IEC104_CommandEvent_t result;
IEC104_SlaveRequestState_t state = IEC104_SLAVE_REQUEST_READY;
if (!Owned(self) || self->in_action || event == NULL) { return IEC104_COMMAND_NEXT_ERROR; }
task = &self->transaction;
/* 已存在的FINISHED不受离线或未读通知门禁阻挡;它只清理已结束的协议交互。 */
if (!task->finish_pending) {
state = Check_Session(self);
if (state == IEC104_SLAVE_REQUEST_READY) { Expire(self, IEC104_Net_Now_Ms()); }
}
if (!task->finish_pending && state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_COMMAND_NEXT_ERROR; }
if (!task->occupied || (!task->finish_pending &&
(state != IEC104_SLAVE_REQUEST_READY || task->event_taken ||
(task->phase != IEC104_COMMAND_PHASE_SELECT_WAIT && task->phase != IEC104_COMMAND_PHASE_EXECUTE_WAIT)))) {
return IEC104_COMMAND_NEXT_EMPTY;
}
memset(&result, 0, sizeof(result));
result.type = (IEC104_CommandEventType_t)task->event_type;
result.instance_id = self->serial;
result.transaction_id = task->transaction_id;
result.event_serial = task->event_serial;
Copy_Request(task, &result.request);
if (task->finish_pending) {
result.reason = (IEC104_CommandFinishReason_t)task->finish_reason;
result.select_effect = task->select_effect;
result.execute_effect = task->execute_effect;
task->finish_pending = false;
} else {
/* 分开记录当前事件和事务历史:生成后续EXECUTE不能抹掉已交付SELECT的清理义务。 */
task->ever_work_delivered = true;
if (result.type == IEC104_COMMAND_SELECT) { task->select_delivered = true; }
else { task->execute_delivered = true; }
}
task->event_taken = true;
*event = result;
Reap(self);
return IEC104_COMMAND_NEXT_READY;
}
/*
* 功能:检查事件是否为当前事务当前阶段已经交付的那一个事件。
* 参数:self为有效实例;event可为NULL,其他情况下仅读取身份和类型。
* 返回:实例号、事务号、当前事件号/种类和已交付标志全部匹配时true。
* 约束:只读,不检查会话/期限;Slave身份不能代替这里的应用事务与工作事件身份。
*/
static bool Current_Event(const IEC104_Command_t *self, const IEC104_CommandEvent_t *event)
{
const CommandTransaction *task = &self->transaction;
return event != NULL && task->occupied && event->instance_id == self->serial &&
event->transaction_id == task->transaction_id && event->event_serial == task->event_serial &&
event->type == task->event_type && task->event_taken;
}
/*
* 功能:核对应用事件是否允许用于本类应答接口。
* 参数:self为有效实例;event为待应答事件;work=true要求已由NextEvent取走、
* 尚未由Accept/Reject消费的SELECT/EXECUTE,
* work=false要求已Accept且动作启动成功、仍处于EXECUTING的原EXECUTE令牌。
* 返回:身份、交付/消费状态及阶段相符时true,否则false;不改变状态。
* 约束:执行令牌可供报告结果、逐帧返回信息和Complete使用,各接口再检查自己的一次性条件。
*/
static bool Reply_Stage(const IEC104_Command_t *self, const IEC104_CommandEvent_t *event, bool work)
{
const CommandTransaction *task = &self->transaction;
if (!Current_Event(self, event)) { return false; }
if (work) {
return task->accepted_event_serial == 0U &&
((event->type == IEC104_COMMAND_SELECT && task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT) ||
(event->type == IEC104_COMMAND_EXECUTE && task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT));
}
return event->type == IEC104_COMMAND_EXECUTE && task->phase == IEC104_COMMAND_PHASE_EXECUTING &&
task->accepted_event_serial == event->event_serial && task->execute_effect;
}
/*
* 功能:统一执行应用应答入口的参数、事件、会话及期限检查。
* 参数:self/event为调用方传回的实例和纯值事件;work选择工作决定或已启动执行的校验模式。
* 返回:OK可继续本次应答;WAIT须先消费Slave通知且本次未消费事件;STALE为身份/阶段失效;
* ERROR为参数、重入或服务查询错误。
* 输出:仅会话失效或期限到期时进行统一收尾;成功检查本身不消费事件、不调用动作。
* 约束:先查会话再取一次now检查期限,之后再次校验阶段,避免到期转换后仍使用旧事件。
*/
static IEC104_CommandReplyResult_t Check_Reply(IEC104_Command_t *self,
const IEC104_CommandEvent_t *event, bool work)
{
IEC104_SlaveRequestState_t state;
if (!Owned(self) || self->in_action || event == NULL) { return IEC104_COMMAND_REPLY_ERROR; }
if (!Reply_Stage(self, event, work)) { return IEC104_COMMAND_REPLY_STALE; }
state = Check_Session(self);
if (state == IEC104_SLAVE_REQUEST_ERROR) { return IEC104_COMMAND_REPLY_ERROR; }
if (state == IEC104_SLAVE_REQUEST_WAIT_EVENTS) { return IEC104_COMMAND_REPLY_WAIT; }
if (state != IEC104_SLAVE_REQUEST_READY) { return IEC104_COMMAND_REPLY_STALE; }
Expire(self, IEC104_Net_Now_Ms());
return Reply_Stage(self, event, work) ? IEC104_COMMAND_REPLY_OK : IEC104_COMMAND_REPLY_STALE;
}
/*
* 功能:接受已交付的SELECT/EXECUTE,在事件有效时最多调用一次同步应用动作。
* 参数:self/event为实例及原工作事件;action/context仅在本次调用借用,不保存。
* SELECT允许action=NULL,表示逻辑选择生效而无硬件副作用;EXECUTE必须提供action。
* 返回:OK仅表示工作交互已消费,动作可能NOT_STARTED/FAILED,确认也可能仍待发送;
* WAIT未调用动作、未消费事件;STALE不得执行;ERROR为参数/重入/服务或资源错误。
* 输出:准备事务自带确认资源后先消费事件,再调用动作;按结果进入选择保持、执行等待结果或ENDED。
* 约束:回调期间只允许只读查询;返回后重新采样阶段计时起点,不把回调耗时追认为决定超时。
*/
IEC104_CommandReplyResult_t IEC104_Command_Accept(IEC104_Command_t *self,
const IEC104_CommandEvent_t *event, IEC104_CommandAction_t action, void *context)
{
CommandTransaction *task;
IEC104_CommandRequest_t request;
IEC104_CommandActionResult_t action_result;
IEC104_CommandReplyResult_t checked;
bool selecting;
if (event != NULL && event->type == IEC104_COMMAND_EXECUTE && action == NULL) {
return IEC104_COMMAND_REPLY_ERROR;
}
checked = Check_Reply(self, event, true);
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
task = &self->transaction;
selecting = task->event_type == IEC104_COMMAND_SELECT;
/* 确认资源和序号在动作前准备。事务自己的回复不会被独立短回复洪泛挤占。 */
if (!Set_Confirm(self, IEC60870_ASDU_COT_ACTIVATION_CON, false, !selecting)) {
return IEC104_COMMAND_REPLY_ERROR;
}
Copy_Request(task, &request);
/* 先记录已消费的事件身份,回调即使经应用代码再次进入也不能重做同一动作。 */
task->accepted_event_serial = task->event_serial;
self->in_action = true;
action_result = action == NULL ? IEC104_COMMAND_ACTION_STARTED : action(context, &request);
self->in_action = false;
/* 非法动作返回值按可能有副作用的失败处理;已消费的动作绝不重试。 */
if (action_result != IEC104_COMMAND_ACTION_STARTED && action_result != IEC104_COMMAND_ACTION_NOT_STARTED) {
action_result = IEC104_COMMAND_ACTION_FAILED;
}
if (selecting) { task->select_effect = action != NULL && action_result != IEC104_COMMAND_ACTION_NOT_STARTED; }
else { task->execute_effect = action_result != IEC104_COMMAND_ACTION_NOT_STARTED; }
if (action_result == IEC104_COMMAND_ACTION_STARTED) {
task->phase = selecting ? IEC104_COMMAND_PHASE_SELECTED : IEC104_COMMAND_PHASE_EXECUTING;
task->timer_start_ms = IEC104_Net_Now_Ms();
if (selecting) { ++self->status.selected; }
else { ++self->status.executed; }
} else {
task->confirm_negative = true;
task->confirm_try = !selecting && action_result == IEC104_COMMAND_ACTION_FAILED;
if (action_result == IEC104_COMMAND_ACTION_NOT_STARTED) {
++self->status.app_rejected;
Finish(self, IEC104_COMMAND_FINISH_REJECTED);
} else { Finish(self, IEC104_COMMAND_FINISH_ACTION_FAILED); }
}
/* 提交可能接收STOP或产生故障;OK仍只描述动作交互已消费,应用继续读取FINISHED。 */
(void)Submit_Head(self);
return IEC104_COMMAND_REPLY_OK;
}
/*
* 功能:拒绝已交付且尚未消费的选择/执行工作事件。
* 参数:self/event为实例和原事件;cause只接受否定ACT_CON或未知IOA两种原因。
* 返回:OK交互已消费、否定回复曾安排并尝试提交;WAIT未消费;STALE为旧事件;ERROR为非法参数或内部错误。
* 输出:以当前工作请求回显7/47,计app_rejected并产生FINISHED(REJECTED)。
* 约束:仅改变协议状态,不调用设备动作;回复使用事务自己的存储,不能因独立池满丢失。
* OK不保证回复仍在队列或已经发出:本次发送若失败,待交回复可能已被取消,须继续读取FINISHED。
*/
IEC104_CommandReplyResult_t IEC104_Command_Reject(IEC104_Command_t *self,
const IEC104_CommandEvent_t *event, IEC104_CommandRejectCause_t cause)
{
IEC104_CommandReplyResult_t checked;
if (cause != IEC104_COMMAND_REJECT_NEGATIVE && cause != IEC104_COMMAND_REJECT_UNKNOWN_IOA) {
return IEC104_COMMAND_REPLY_ERROR;
}
checked = Check_Reply(self, event, true);
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
if (!Set_Confirm(self, cause == IEC104_COMMAND_REJECT_UNKNOWN_IOA ?
IEC60870_ASDU_COT_UNKNOWN_IOA : IEC60870_ASDU_COT_ACTIVATION_CON, true, false)) {
return IEC104_COMMAND_REPLY_ERROR;
}
self->transaction.accepted_event_serial = self->transaction.event_serial;
++self->status.app_rejected;
Finish(self, IEC104_COMMAND_FINISH_REJECTED);
(void)Submit_Head(self);
return IEC104_COMMAND_REPLY_OK;
}
/*
* 功能:一次性记录已启动设备动作的最终成功/失败结果,停止执行期限。
* 参数:self/event为实例及启动成功的原EXECUTE事件;result为SUCCEEDED或FAILED。
* 返回:OK已记录;WAIT结果未记录、先消费通知;STALE表示事务或事件已失效;
* ERROR为结果值非法、重复报告、已经Complete或参数/服务错误。
* 输出:写入事务result;不发送确认/终止、不关闭返回信息阶段、不调用应用。
* 约束:允许早于ACT_CON交接,设备执行时长和发送拥塞等待必须分开记账。
*/
IEC104_CommandReplyResult_t IEC104_Command_Report_Result(IEC104_Command_t *self,
const IEC104_CommandEvent_t *event, IEC104_CommandResult_t result)
{
IEC104_CommandReplyResult_t checked;
if (result != IEC104_COMMAND_RESULT_SUCCEEDED && result != IEC104_COMMAND_RESULT_FAILED) {
return IEC104_COMMAND_REPLY_ERROR;
}
checked = Check_Reply(self, event, false);
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
if (self->transaction.result != 0U || self->transaction.completed) { return IEC104_COMMAND_REPLY_ERROR; }
self->transaction.result = (uint8_t)result;
return IEC104_COMMAND_REPLY_OK;
}
/*
* 功能:沿原事务连接直接提交一帧命令返回信息,并保持模块与传输层既有帧序。
* 参数:self/event为实例和启动成功的EXECUTE令牌;asdu为调用方持有的完整监视ASDU。
* 返回:OK发送层已接手,不能重交;WAIT未接手且不缓存,应用保留或重建后重试;
* STALE为事务结束/发送故障已取消;ERROR为ASDU、阶段或参数错误。
* 正常STOP且通知尚未读取的发送ERROR先返回WAIT,保留由会话通知收尾的机会。
* 输出:完整验证104布局、对象、类型、COT、CA及标志;OK后累计发送和返回信息计数。
* 约束:只能在肯定确认交接之后、Complete之前;模块内有任何更早回复时也必须WAIT。
* 返回信息不进入NORMAL、没有跨重连缓存;WAIT不取得模块内持久FIFO位置。
*/
IEC104_CommandReplyResult_t IEC104_Command_Submit_Return_Info(IEC104_Command_t *self,
const IEC104_CommandEvent_t *event, const IEC60870_Asdu_t *asdu)
{
CommandTransaction *task;
IEC60870_AsduInfo_t info;
const uint8_t *data;
uint16_t length;
IEC104_TcpSubmitResult_t submitted;
IEC104_CommandReplyResult_t checked = Check_Reply(self, event, false);
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
task = &self->transaction;
if (task->completed || asdu == NULL || !IEC60870_AppLayerParameters_Valid(asdu->parameters) ||
asdu->parameters->sizeOfCOT != 2 || asdu->parameters->sizeOfCA != 2 || asdu->parameters->sizeOfIOA != 3 ||
asdu->parameters->maxSizeOfASDU > IEC104_MAX_ASDU_LEN ||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK || length > IEC104_MAX_ASDU_LEN ||
IEC60870_Asdu_Parse(IEC104_Slave_Get_Application_Layer_Parameters(self->slave), data, length, &info) !=
IEC60870_ASDU_OK ||
!((info.type_id >= 1U && info.type_id <= 21U) || (info.type_id >= 30U && info.type_id <= 40U)) ||
info.object_count == 0U ||
(info.cause_of_transmission != IEC60870_ASDU_COT_RETURN_INFO_REMOTE &&
info.cause_of_transmission != IEC60870_ASDU_COT_SPONTANEOUS) ||
info.is_negative_confirm || info.is_test != ((task->request_bytes[2] >> 7) & 1U) ||
info.common_address != Read_CA(task->request_bytes)) { return IEC104_COMMAND_REPLY_ERROR; }
/* 底层Try只知道传输队列;这里还必须检查模块内更早的独立/事务应答。 */
if (!task->confirm_submitted || Pending_Replies(self) != 0U) { return IEC104_COMMAND_REPLY_WAIT; }
submitted = IEC104_Slave_Try_Send_Response(self->slave, &task->identity, asdu);
if (submitted == IEC104_TCP_SUBMIT_WAIT) {
task->return_waiting = true;
++self->status.waits;
return IEC104_COMMAND_REPLY_WAIT;
}
if (submitted == IEC104_TCP_SUBMIT_OK) {
task->return_waiting = false;
++self->status.submitted;
++self->status.return_info_submitted;
return IEC104_COMMAND_REPLY_OK;
}
if (Defer_For_Stop(self, &task->identity, submitted)) { return IEC104_COMMAND_REPLY_WAIT; }
Cancel_All(self, IEC104_COMMAND_FINISH_SEND_ERROR);
self->status.active = false;
return IEC104_COMMAND_REPLY_STALE;
}
/*
* 功能:声明本事务不再提交返回信息,并按配置启动协议终止收尾。
* 参数:self/event为实例及启动成功的原EXECUTE事件;设备结果必须已经Report_Result。
* 返回:OK已关闭返回阶段,后续ACT_TERM由Process推进;WAIT未消费,先处理通知;
* STALE为事务失效;ERROR为未报告结果、重复调用或参数/内部错误。
* 输出:必要时分配终止FIFO序号,置completed;无终止且确认已交接时可立即产生FINISHED。
* 约束:OK不保证ACT_TERM已经发送;不能因等待终止而再次Complete或再提交返回信息。
*/
IEC104_CommandReplyResult_t IEC104_Command_Complete(IEC104_Command_t *self,
const IEC104_CommandEvent_t *event)
{
CommandTransaction *task;
IEC104_CommandReplyResult_t checked = Check_Reply(self, event, false);
if (checked != IEC104_COMMAND_REPLY_OK) { return checked; }
task = &self->transaction;
if (task->result == 0U || task->completed) { return IEC104_COMMAND_REPLY_ERROR; }
if (self->config.send_act_term &&
(task->result == IEC104_COMMAND_RESULT_SUCCEEDED || self->config.act_term_on_failure)) {
uint64_t serial = Reply_Serial(self);
if (serial == 0U) { return IEC104_COMMAND_REPLY_ERROR; }
task->term_serial = serial;
}
task->completed = true;
task->return_waiting = false;
/* 无TERM且确认已交接可立即结束;否则Process只重试协议收尾,不要求应用重复Complete。 */
Complete_If_Sent(self);
return IEC104_COMMAND_REPLY_OK;
}
/*
* 功能:通过该事务任一已经交付的事件,只读查询仍保留的事务状态。
* 参数:self为实例;event为本事务历史SELECT/EXECUTE或已取FINISHED;info为非NULL输出。
* 返回:true成功;句柄、身份、交付状态非法或槽已回收时false,失败保持info原值。
* 输出:当前阶段、是否尚未ENDED、确认/结果/Complete/终止状态;不推进会话或期限。
* 约束:可在动作回调中查询;active只表示本地事务阶段,不保证当前连接仍允许操作。
* 不保存历史事务,槽回收后旧事件不能再查询,即使其字节副本仍由应用持有。
*/
bool IEC104_Command_Get_Transaction(const IEC104_Command_t *self,
const IEC104_CommandEvent_t *event, IEC104_CommandTransactionInfo_t *info)
{
const CommandTransaction *task;
IEC104_CommandTransactionInfo_t result;
bool delivered;
if (!Owned(self) || event == NULL || info == NULL) { return false; }
task = &self->transaction;
if (!task->occupied || event->instance_id != self->serial || event->transaction_id != task->transaction_id) {
return false;
}
delivered = (event->type == IEC104_COMMAND_SELECT && task->select_delivered &&
event->event_serial == task->select_event_serial) ||
(event->type == IEC104_COMMAND_EXECUTE && task->execute_delivered &&
event->event_serial == task->execute_event_serial) ||
(event->type == IEC104_COMMAND_FINISHED && task->event_type == IEC104_COMMAND_FINISHED &&
task->event_taken && event->event_serial == task->event_serial);
if (!delivered) { return false; }
memset(&result, 0, sizeof(result));
result.active = task->phase != IEC104_COMMAND_PHASE_ENDED;
result.phase = (IEC104_CommandPhase_t)task->phase;
result.confirm_submitted = task->confirm_submitted;
result.result_reported = task->result != 0U;
result.completed = task->completed;
result.term_submitted = task->term_submitted;
*info = result;
return true;
}
/*
* 功能:生成实例累计统计和当前调度条件的只读快照。
* 参数:self为实例;status为非NULL输出,可在动作回调内使用。
* 返回:true成功;无效参数或Slave查询失败时false,失败不改输出。
* 输出:在累计统计副本上即时计算会话通知、事务占用、待交回复/事件及期限剩余量。
* 约束:不触发到期转换、不取消事务、不预留发送容量;发送WAIT不要求持续空转,
* 但每轮网络推进后仍须调用Process重试。已有FINISHED由NextEvent读取,不靠Process清理。
*/
bool IEC104_Command_Get_Status(const IEC104_Command_t *self, IEC104_CommandStatus_t *status)
{
const CommandTransaction *task;
IEC104_CommandStatus_t result;
IEC104_SlaveRequestState_t state;
uint32_t limit;
uint64_t now;
unsigned slot = 0U, head;
bool waiting;
if (!Owned(self) || status == NULL) { return false; }
state = IEC104_Slave_Check_Request(self->slave, &self->session);
if (state == IEC104_SLAVE_REQUEST_ERROR) { return false; }
task = &self->transaction;
result = self->status;
result.generation = self->session.generation;
result.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
result.busy = task->occupied;
result.pending_replies = Pending_Replies(self);
result.pending_events = task->occupied && (task->finish_pending ||
(!task->event_taken && (task->phase == IEC104_COMMAND_PHASE_SELECT_WAIT ||
task->phase == IEC104_COMMAND_PHASE_EXECUTE_WAIT))) ? 1U : 0U;
limit = Timer_Limit(self);
now = IEC104_Net_Now_Ms();
result.timer_active = limit != 0U;
result.timeout_remaining_ms = limit != 0U ? Remaining(task, limit, now) : 0U;
result.need_process = false;
head = Reply_Head(self, &slot);
waiting = head == COMMAND_HEAD_SHORT ? self->replies[slot].send_waiting : task->send_waiting;
if (!self->in_action && !result.events_pending) {
/* 有通知时先交回Slave;离线仅有FINISHED时无需反复推进,让应用直接读清理事件。 */
if (state != IEC104_SLAVE_REQUEST_READY || !result.active) {
result.need_process = result.pending_replies != 0U ||
(task->occupied && task->phase != IEC104_COMMAND_PHASE_ENDED);
} else {
/* 到期/回退或尚未尝试的队首需要立即推进;已WAIT的队首等待网络进展再重试。 */
result.need_process = Clock_Reversed(self, now) ||
(result.timer_active && result.timeout_remaining_ms == 0U) ||
(head != COMMAND_HEAD_NONE && !waiting);
}
}
*status = result;
return true;
}