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

1037 lines
57 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.
/*
* 事件式总召状态机。Slave负责通信,应用通过BEGIN/NEED_DATA/FINISHED交互。
* 请求任务固定地址保存含自引用的SlaveEvent;业务事件为纯值,可以移动和复制。
* 每个交付BEGIN预留一个FINISHED槽,断线/STOP/取消/撤销/超时仍能通知应用清理资源。
* 所有应答沿保存请求的connection走HIGH,避免跨连接重放和确认/数据/终止乱序。
*
* 正常阶段:START生成BEGIN并等待应用接受 -> CONFIRM提交激活确认 -> DATA反复索取并提交批次
* -> TERMINATE提交激活终止 -> 释放任务并交付FINISHED。
* REPLY表示只需提交一条确认或错误应答的任务,提交后直接释放,不再索取数据。
* Handle_Event更新会话和接收请求,条件允许时立即提交短应答;Process推进保存的任务。
* NextEvent把业务事件交给应用;一个实例至多一个未终结总召,忙时否定所有新激活。
* Reply_Begin/Submit_Batch/Complete_Data只接收应用决定,实际发帧由后续Process推进。
* 事件来源和会话可用性统一由Slave接口校验,本模块只维护总召自己的阶段和业务期限。
* 单帧应答与总召分开选择,双方可推进时轮转;总召等待应用不阻塞单帧应答。
* Type100解析后确定应答CA,建立内部请求副本时写入;发送及重试不再转换地址。
*/
#include "IEC104_Interrogation.h"
#include "IEC104_TcpServer_Config.h"
#include "IEC104_Net.h"
#include <string.h>
/* 三类存储均为静态有界容量:实例池、每实例协议任务池、每实例业务事件FIFO。
* 业务事件至少需要两个位置,才能同时容纳BEGIN及为该请求预留的FINISHED。 */
#ifndef IEC104_INTERROGATION_CAPACITY
#define IEC104_INTERROGATION_CAPACITY IEC104_TCP_SERVER_CAPACITY
#endif
#ifndef IEC104_INTERROGATION_TASK_CAPACITY
#define IEC104_INTERROGATION_TASK_CAPACITY 4U
#endif
#ifndef IEC104_INTERROGATION_EVENT_CAPACITY
#define IEC104_INTERROGATION_EVENT_CAPACITY 4U
#endif
_Static_assert(IEC104_INTERROGATION_CAPACITY > 0U, "Interrogation needs instance storage");
_Static_assert(IEC104_INTERROGATION_TASK_CAPACITY > 0U, "Interrogation needs request storage");
_Static_assert(IEC104_INTERROGATION_EVENT_CAPACITY >= 2U, "BEGIN needs a reserved FINISHED slot");
/* 一项协议任务独占请求副本和当前数据批次。request与outgoing含有指向本项存储的指针,
* 因此仅移动顺序索引,不能用结构赋值或memmove整理任务数组。 */
typedef struct {
IEC104_SlaveEvent_t request; /* 独立请求副本及原连接,复制时已设置应答CA;不移动任务地址。 */
IEC60870_Asdu_t outgoing; /* 仅借用本任务bytes,不借用应用的批次存储。 */
uint8_t bytes[IEC104_MAX_ASDU_LEN]; /* 当前批次的独立原文,发送等待期间保持有效。 */
IEC104_InterrogationRequest_t info; /* 唯一业务元数据;供事件交付及批次校验,终结时仍描述原激活。 */
uint64_t request_id; /* 激活任务编号;未建立总召事务的单帧应答为0。 */
uint64_t started_ms, application_since_ms; /* 单调毫秒:BEGIN生成、当前工作事件入队。 */
size_t batch_index, objects_submitted, prepared_count; /* 已提交批次数、对象总数、当前缓存批次对象数。 */
IEC104_InterrogationPhase_t phase; /* 决定下一步等待应用、提交哪种ASDU或释放任务。 */
uint8_t reply_cause; /* 非零为单帧确认/错误应答,肯定撤销确认也使用此路径。 */
bool reply_negative; /* REPLY阶段发送的P/N标志;肯定撤销确认为false。 */
bool prepared; /* DATA阶段已有完整批次;为true时不再生成新的NEED_DATA。 */
bool event_outstanding, event_delivered; /* 当前交互待应答、该工作事件已被应用取出。 */
bool begin_delivered, finish_reserved; /* 应用已取过BEGIN、为本任务保留了一个终结事件位置。 */
bool occupied; /* 固定任务槽正在使用;任务可非队首完成,释放后立即复用此槽。 */
} InterrogationTask;
struct IEC104_Interrogation {
IEC104_Slave_t *slave; /* NULL为空闲槽;有效期间Slave必须存活。 */
IEC104_InterrogationConfig_t config; /* Create复制的配置,不借用调用方配置对象。 */
IEC104_InterrogationStatus_t status; /* 会话状态和累计统计;即时进度由Get_Status补齐。 */
IEC104_SlaveEventIdentity_t session; /* Slave校验后交付的服务身份和已处理的连接代次。 */
uint64_t instance_id, next_request_id;
/* instance_id区分总召静态槽的历次使用;next_request_id在本实例内递增,
* 两者共同标识一次总召,独立于Slave的服务实例和连接身份。 */
unsigned count, event_count, reserved_finishes;
/* count描述有效任务数;event_count是实际业务事件数;reserved_finishes仅为容量预留。
* 始终保证event_count + reserved_finishes不超过业务事件容量。 */
unsigned order[IEC104_INTERROGATION_TASK_CAPACITY]; /* 有效任务槽下标,保持应答形成的先后顺序。 */
bool prefer_interrogation; /* 单帧提交后给可推进总召一次机会,跨Process返回保留。 */
InterrogationTask tasks[IEC104_INTERROGATION_TASK_CAPACITY]; /* 固定地址的协议任务池。 */
IEC104_InterrogationEvent_t events[IEC104_INTERROGATION_EVENT_CAPACITY]; /* 可移动的纯值事件FIFO。 */
};
static IEC104_Interrogation_t interrogation_pool[IEC104_INTERROGATION_CAPACITY];
/* 不随槽释放清零,避免旧事件误命中新实例;达到上限拒绝创建,不回绕。 */
static uint64_t instance_serial;
/* 检查句柄是否指向仍被占用的实例槽;先比较地址再解引用,NULL和池外指针返回false。
* 这里只验证实例占用,Slave的寿命仍由调用方保证。 */
static bool Owned(const IEC104_Interrogation_t *self)
{
size_t i;
for (i = 0U; i < IEC104_INTERROGATION_CAPACITY; ++i) {
if (self == &interrogation_pool[i]) { return self->slave != NULL; }
}
return false;
}
/* 取得固定任务槽并追加顺序索引;调用方完成请求复制,失败时须立即Remove_Task。
* 不移动已有任务,确保它们的SlaveEvent和ASDU视图继续指向原存储。 */
static InterrogationTask *Allocate_Task(IEC104_Interrogation_t *self)
{
unsigned slot;
if (self->count == IEC104_INTERROGATION_TASK_CAPACITY) { return NULL; }
for (slot = 0U; slot < IEC104_INTERROGATION_TASK_CAPACITY; ++slot) {
InterrogationTask *task = &self->tasks[slot];
if (!task->occupied) {
memset(task, 0, sizeof(*task));
task->occupied = true;
self->order[self->count++] = slot;
return task;
}
}
return NULL;
}
/* 释放一个已终结或复制失败的任务;仅压紧索引,非队首完成也立即归还容量。
* 有应用交互的任务须先Finish,避免丢失预留的FINISHED。 */
static void Remove_Task(IEC104_Interrogation_t *self, InterrogationTask *task)
{
unsigned i;
for (i = 0U; i < self->count; ++i) {
if (&self->tasks[self->order[i]] == task) {
memset(task, 0, sizeof(*task));
memmove(self->order + i, self->order + i + 1U,
(self->count - i - 1U) * sizeof(self->order[0]));
self->order[--self->count] = 0U;
return;
}
}
}
/* 原总召转为拒绝/撤销确认时,复用原槽并按应答形成时刻放到顺序末尾。
* 满池也能转换;不让这条新应答越过已经等待的其他单帧应答。 */
static void Move_Reply_To_Tail(IEC104_Interrogation_t *self, const InterrogationTask *task)
{
unsigned i;
for (i = 0U; i < self->count; ++i) {
unsigned slot = self->order[i];
if (&self->tasks[slot] == task) {
memmove(self->order + i, self->order + i + 1U,
(self->count - i - 1U) * sizeof(self->order[0]));
self->order[self->count - 1U] = slot;
self->prefer_interrogation = false;
return;
}
}
}
/* 返回唯一未终结总召的固定槽下标;request_id为0查当前总召,非0核对指定请求。
* 无匹配返回TASK_CAPACITY;REPLY即使保留原request_id,也已退出应用交互。 */
static unsigned Find_Interrogation(const IEC104_Interrogation_t *self, uint64_t request_id)
{
unsigned i;
for (i = 0U; i < self->count; ++i) {
unsigned slot = self->order[i];
const InterrogationTask *task = &self->tasks[slot];
if (task->request_id != 0U && task->phase != IEC104_INTERROGATION_REPLY &&
(request_id == 0U || task->request_id == request_id)) { return slot; }
}
return IEC104_INTERROGATION_TASK_CAPACITY;
}
/* 最早形成的单帧应答;立即提交也必须遵守此顺序,不能越过旧REPLY。 */
static unsigned Find_Reply(const IEC104_Interrogation_t *self)
{
unsigned i;
for (i = 0U; i < self->count; ++i) {
unsigned slot = self->order[i];
if (self->tasks[slot].phase == IEC104_INTERROGATION_REPLY) { return slot; }
}
return IEC104_INTERROGATION_TASK_CAPACITY;
}
/* 业务推进无需等待应用或事件空位时为true;发送容量仍由实际提交结果决定。 */
static bool Interrogation_Can_Progress(const IEC104_Interrogation_t *self, const InterrogationTask *task)
{
if (task->phase == IEC104_INTERROGATION_START ||
(task->phase == IEC104_INTERROGATION_DATA && !task->prepared)) {
unsigned needed = task->phase == IEC104_INTERROGATION_DATA ? 1U : 2U;
return !task->event_outstanding &&
self->event_count + self->reserved_finishes + needed <= IEC104_INTERROGATION_EVENT_CAPACITY;
}
return true;
}
/* 单帧保持FIFO;双方可推进时先单帧再总召,轮转位置跨调用保存。
* 总召等应用或业务FIFO空位时只选单帧;均不能推进时返回NULL,让Process立即返回。 */
static InterrogationTask *Select_Task(IEC104_Interrogation_t *self)
{
unsigned slot = Find_Interrogation(self, 0U), reply_slot = Find_Reply(self);
InterrogationTask *gi = slot != IEC104_INTERROGATION_TASK_CAPACITY ? &self->tasks[slot] : NULL;
InterrogationTask *reply = reply_slot != IEC104_INTERROGATION_TASK_CAPACITY ? &self->tasks[reply_slot] : NULL;
if (gi != NULL && !Interrogation_Can_Progress(self, gi)) { gi = NULL; }
if (reply != NULL && (!self->prefer_interrogation || gi == NULL)) { return reply; }
return gi != NULL ? gi : reply;
}
/* 将任务身份、请求元数据和当前进度复制成一条业务事件;本函数不执行应用代码。
* type决定应用需要接受、交付数据还是清理;reason仅在FINISHED时有业务意义。
* 调用方须已检查容量,或已将本任务的终结预留释放为可写位置。 */
static void Push_Event(IEC104_Interrogation_t *self, const InterrogationTask *task,
IEC104_InterrogationEventType_t type, IEC104_InterrogationFinishReason_t reason)
{
IEC104_InterrogationEvent_t *event = &self->events[self->event_count++];
memset(event, 0, sizeof(*event));
event->type = type;
event->instance_id = self->instance_id;
event->request_id = task->request_id;
event->request = task->info;
event->batch_index = task->batch_index;
event->objects_submitted = task->objects_submitted;
event->reason = reason;
}
/* 结束一个总召的应用交互,reason保存为该请求的终结原因;不自行释放协议任务。
* 移除本请求尚未取出的BEGIN/NEED_DATA,保留其他事件和所有历史FINISHED。
* BEGIN已取出时,用预留位置交付一次FINISHED;BEGIN未取出时只撤销事件和预留,
* 因为应用尚未获得请求,无需通知它清理资源。finish_reserved同时防止重复终结。 */
static void Finish(IEC104_Interrogation_t *self, InterrogationTask *task,
IEC104_InterrogationFinishReason_t reason)
{
unsigned read, write = 0U;
if (task->request_id != 0U && task->phase != IEC104_INTERROGATION_REPLY) {
self->status.last_finish_reason = reason;
}
if (!task->finish_reserved) { return; }
for (read = 0U; read < self->event_count; ++read) {
const IEC104_InterrogationEvent_t *event = &self->events[read];
if (event->request_id == task->request_id && event->type != IEC104_INTERROGATION_FINISHED) { continue; }
self->events[write++] = *event;
}
self->event_count = write;
--self->reserved_finishes;
task->finish_reserved = false;
task->event_outstanding = task->event_delivered = false;
if (task->begin_delivered) {
Push_Event(self, task, IEC104_INTERROGATION_FINISHED, reason);
task->begin_delivered = false;
}
}
/* 肯定ACT_CON尚未被发送层接手时,结束本次业务并复用原槽保存否定确认。
* 应用主动拒绝、BEGIN失败以及START/CONFIRM超时共用此路径,保留各自终结原因。
* Finish清除未读工作事件并兑现终结预留;不取消其他任务、不额外占槽、不关闭连接。
* 新形成的否定确认排在已有REPLY之后;后续Process提交,WAIT时保留重试。 */
static void Reject_Request(IEC104_Interrogation_t *self, InterrogationTask *task,
IEC104_InterrogationFinishReason_t reason)
{
++self->status.rejected;
Finish(self, task, reason);
task->reply_cause = IEC60870_ASDU_COT_ACTIVATION_CON;
task->reply_negative = true;
task->phase = IEC104_INTERROGATION_REPLY;
self->status.waiting = false;
Move_Reply_To_Tail(self, task);
}
/* 按reason结束本实例全部任务,再清空任务池和总召占用状态;不关闭Slave或连接。
* Slave已复制接手的帧不撤销,终结业务事件保留到应用读取。
* cancelled按被移除的协议任务数累计,包含排队的单帧应答,并非仅统计已启动总召。 */
static void Cancel(IEC104_Interrogation_t *self, IEC104_InterrogationFinishReason_t reason)
{
unsigned i;
for (i = 0U; i < self->count; ++i) {
Finish(self, &self->tasks[self->order[i]], reason);
}
self->status.cancelled += self->count;
memset(self->tasks, 0, sizeof(self->tasks));
memset(self->order, 0, sizeof(self->order));
self->count = 0U;
self->prefer_interrogation = false;
self->status.waiting = false;
}
/* 严重请求错误或肯定确认提交后的执行失败,关闭event所属连接并按reason取消全部任务。
* event可能位于任务中,必须先关闭原连接再清任务;旧代次不会关闭新连接。
* true包括成功关闭、原连接已断开和代次已过期;false表示底层关闭失败,任务仍已取消。 */
static bool Reject_Connection(IEC104_Interrogation_t *self, const IEC104_SlaveEvent_t *event,
IEC104_InterrogationFinishReason_t reason)
{
IEC104_TcpResult_t result = IEC104_Slave_Close_Connection(self->slave, event, NULL);
Cancel(self, reason);
self->status.active = false;
return result == IEC104_TCP_OK || result == IEC104_TCP_NOT_CONNECTED ||
result == IEC104_TCP_STALE_GENERATION;
}
/* 按肯定确认的提交边界处理业务失败:START/CONFIRM仍可否定激活并保留连接;
* CONFIRM只有在ACT_CON被发送层接手后才转为DATA,即使仅在HIGH中排队也已越过边界。
* DATA/TERMINATE失败沿用关闭原连接的策略,不补发成功ACT_TERM。
* 调用方已校验会话与任务;true表示失败已处理,并不表示否定确认已经提交。 */
static bool Fail_Interrogation(IEC104_Interrogation_t *self, InterrogationTask *task,
IEC104_InterrogationFinishReason_t reason)
{
if (task->phase == IEC104_INTERROGATION_START || task->phase == IEC104_INTERROGATION_CONFIRM) {
Reject_Request(self, task, reason);
return true;
}
return Reject_Connection(self, &task->request, reason);
}
/* 立即提交短应答的结果。DEFER表示尚未接手,调用方保存原请求,或在满槽时丢弃并计数。
* 只使用现有HIGH接口,不越过Slave通知或旧REPLY,不改变总召数据的发送顺序。 */
typedef enum { REPLY_DEFER, REPLY_SENT, REPLY_FAILED } ImmediateReplyResult;
/* 本实例只服务一个实际站地址;广播CA映射到本站,其他地址保持原值。
* Request解析Type100后计算一次,地址校验、应答副本和撤销匹配使用同一结果。
* 错误站地址必须保留原值,不能因构造应答而把未知站误认为本站。 */
static uint16_t Resolve_Common_Address(const IEC104_Interrogation_t *self, uint16_t address)
{
return address == IEC104_CA_BROADCAST ? self->config.common_address : address;
}
/* 为立即应答、新任务或匹配撤销建立内部副本,不修改调用方的源事件。
* source是已完整校验布局的Type100;response_ca由Request根据原CA确定,可保留错误CA。
* Copy_Event先重建自引用并保留原服务/连接代次,再由Asdu层按字段宽度写入应答CA。
* 每份副本只在此准备地址,后续COT/P/N修改及发送WAIT重试均沿用该CA,无需额外缓存头部。
* 返回false表示复制或设置字段失败,调用方不得提交该副本。 */
static bool Copy_Response_Request(IEC104_SlaveEvent_t *destination,
const IEC104_SlaveEvent_t *source, uint16_t response_ca)
{
return IEC104_Slave_Copy_Event(destination, source) &&
IEC60870_Asdu_Set_CA(&destination->asdu, response_ca) == IEC60870_ASDU_OK;
}
static ImmediateReplyResult Try_Immediate_Reply(IEC104_Interrogation_t *self,
const IEC104_SlaveEvent_t *event, uint16_t response_ca,
uint8_t cause, bool negative, const InterrogationTask *queued)
{
IEC104_SlaveEvent_t copy;
IEC104_SlaveRequestState_t state;
unsigned first = Find_Reply(self);
if (first != IEC104_INTERROGATION_TASK_CAPACITY && &self->tasks[first] != queued) { return REPLY_DEFER; }
state = IEC104_Slave_Check_Request(self->slave, &self->session);
self->status.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
if (self->status.events_pending) { return REPLY_DEFER; }
if (state == IEC104_SLAVE_REQUEST_ERROR || !self->status.active || state != IEC104_SLAVE_REQUEST_READY) {
Cancel(self, state == IEC104_SLAVE_REQUEST_ERROR ? IEC104_INTERROGATION_FINISH_SERVICE_ERROR :
IEC104_INTERROGATION_FINISH_CONNECTION_CHANGED);
self->status.active = false;
return REPLY_FAILED;
}
/* 调用方event为只读且可能指向固定任务槽;副本单独重建自引用并准备应答CA。
* OK后发送层已复制;WAIT后本地副本可释放,保存任务时继续使用已确定的response_ca。 */
if (!Copy_Response_Request(&copy, event, response_ca) ||
IEC60870_Asdu_Set_COT(&copy.asdu, cause) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_Negative(&copy.asdu, negative) != IEC60870_ASDU_OK) { return REPLY_FAILED; }
if (IEC60870_MasterConnection_Send_ASDU(&copy.connection, &copy.asdu)) {
++self->status.submitted;
if (negative) { ++self->status.negative_submitted; }
self->prefer_interrogation = true;
return REPLY_SENT;
}
if (IEC104_Slave_Get_Last_Submit_Result(&copy.connection) == IEC104_TCP_SUBMIT_WAIT) {
++self->status.waits;
self->status.waiting = true;
return REPLY_DEFER;
}
Cancel(self, IEC104_INTERROGATION_FINISH_SEND_ERROR);
self->status.active = false;
return REPLY_FAILED;
}
/* 将一项期限并入当前最近期限:now/since为单调毫秒,duration为允许时长。
* remaining/reason是累积输入输出;reason为NONE表示此前还没有有效候选。
* 用经过时间比较,避免绝对期限相加溢出;0时长禁用,到期剩余值为0。
* 相同剩余时间保留先检查的原因,因此Task_Timeout先检查总期限以确定并发超时的归因。 */
static void Limit_Timeout(uint64_t now, uint64_t since, uint32_t duration,
IEC104_InterrogationFinishReason_t candidate, uint32_t *remaining,
IEC104_InterrogationFinishReason_t *reason)
{
uint64_t elapsed;
uint32_t left;
if (duration == 0U) { return; }
elapsed = now - since;
left = elapsed >= duration ? 0U : duration - (uint32_t)elapsed;
if (*reason == IEC104_INTERROGATION_FINISH_NONE || left < *remaining) {
*remaining = left;
*reason = candidate;
}
}
/* 只读计算task在now时刻最近的期限,写remaining并返回原因;NONE表示没有启用的期限。
* 只有尚未终结的激活业务受定时器约束,单帧应答不参加业务超时计算。
* START尚未生成BEGIN时不计业务期限;生成后启用执行总期限,覆盖取数及发送等待。
* BEGIN/NEED_DATA期限从工作事件入队起计算,包含应用尚未取出事件的时间。
* 本函数不触发超时动作,供Expire执行处理和Get_Status计算唤醒时间共用。 */
static IEC104_InterrogationFinishReason_t Task_Timeout(const IEC104_Interrogation_t *self,
const InterrogationTask *task, uint64_t now, uint32_t *remaining)
{
IEC104_InterrogationFinishReason_t reason = IEC104_INTERROGATION_FINISH_NONE;
*remaining = 0U;
if (task->request_id == 0U || task->phase == IEC104_INTERROGATION_REPLY) { return reason; }
if (task->phase == IEC104_INTERROGATION_START && !task->event_outstanding) { return reason; }
Limit_Timeout(now, task->started_ms, self->config.total_timeout_ms,
IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT, remaining, &reason);
if (task->event_outstanding) {
bool begin = task->phase == IEC104_INTERROGATION_START;
Limit_Timeout(now, task->application_since_ms,
begin ? self->config.begin_timeout_ms : self->config.data_timeout_ms,
begin ? IEC104_INTERROGATION_FINISH_BEGIN_TIMEOUT : IEC104_INTERROGATION_FINISH_DATA_TIMEOUT,
remaining, &reason);
}
return reason;
}
/* 只检查唯一未终结总召;确认前超时转为否定REPLY,确认后超时关闭原连接并取消。
* 已交付BEGIN的FINISHED必有预留槽,迟到应答随即失效;REPLY不再计算业务期限。
* 调用方须先确认Slave通知已读空,使先到的STOP/断线/撤销先于超时处理。
* 返回0为未超时,1为已处理超时,-1为任务已取消但底层关闭失败。 */
static int Expire(IEC104_Interrogation_t *self)
{
unsigned slot = Find_Interrogation(self, 0U);
InterrogationTask *task;
uint32_t remaining;
IEC104_InterrogationFinishReason_t reason;
if (slot == IEC104_INTERROGATION_TASK_CAPACITY) { return 0; }
task = &self->tasks[slot];
reason = Task_Timeout(self, task, IEC104_Net_Now_Ms(), &remaining);
if (reason != IEC104_INTERROGATION_FINISH_NONE && remaining == 0U) {
++self->status.timeouts;
return Fail_Interrogation(self, task, reason) ? 1 : -1;
}
return 0;
}
/* 查找唯一未终结总召;它可能等待BEGIN或排在错误应答后面,不能只看任务队首。
* event/info/qualifier来自已校验的撤销请求,response_ca已解析为实际站CA;
* 原连接代次、实际站CA、OA、TEST和QOI须全部相同。
* 广播与定向地址解析到同一本站,允许广播激活后定向撤销,也允许广播撤销本站任务。
* 返回内部任务指针供Request原位改为撤销确认;NULL表示没有匹配的未终结激活任务。 */
static InterrogationTask *Find_Activation(IEC104_Interrogation_t *self,
const IEC104_SlaveEvent_t *event, const IEC60870_AsduInfo_t *info,
uint16_t response_ca, uint8_t qualifier)
{
unsigned i;
for (i = 0U; i < self->count; ++i) {
InterrogationTask *task = &self->tasks[self->order[i]];
if (task->request_id != 0U && task->phase != IEC104_INTERROGATION_REPLY &&
task->info.generation == event->transport.generation &&
task->info.common_address == response_ca &&
task->info.originator_address == info->originator_address &&
task->info.is_test == (info->is_test != 0U) && task->info.qualifier == qualifier) { return task; }
}
return NULL;
}
/* 解析一个I帧通知中的总召请求,完成布局、地址、QOI和事务状态校验。
* 未启用时或有效的其他Type返回IGNORED;接手、拒绝或满槽丢弃请求均返回HANDLED。
* 无法解析的报文关闭原连接;容量耗尽只拒绝/丢弃新请求,不取消已有任务。
* 单帧应答先尝试安全提交,未接手才保存;匹配的撤销复用原任务槽。
* 只有空闲时才建立START任务;此时已占用总召资格,Process有事件空位后生成BEGIN。 */
static IEC104_InterrogationEventResult_t Request(IEC104_Interrogation_t *self,
const IEC104_SlaveEvent_t *event)
{
IEC60870_AsduInfo_t info;
IEC60870_InterrogationCommand_t command;
const uint8_t *data;
uint16_t length;
uint16_t response_ca;
uint8_t reply_cause = 0U;
bool deactivation;
bool overload_counted = false;
InterrogationTask *task;
if (!self->status.active) { return IEC104_INTERROGATION_EVENT_IGNORED; }
if (event->asdu_result != IEC60870_ASDU_OK ||
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK ||
!IEC60870_Parse_ASDU_Header(event->asdu.parameters, data, length, &info)) {
++self->status.rejected;
return Reject_Connection(self, event, IEC104_INTERROGATION_FINISH_INVALID_REQUEST) ? IEC104_INTERROGATION_EVENT_HANDLED :
IEC104_INTERROGATION_EVENT_ERROR;
}
if (info.type_id != IEC60870_ASDU_C_IC_NA_1) { return IEC104_INTERROGATION_EVENT_IGNORED; }
if (IEC60870_Asdu_Parse(event->asdu.parameters, data, length, &info) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Get_Element(&info, 0U, &command, sizeof(command)) != IEC60870_ASDU_OK) {
++self->status.rejected;
return Reject_Connection(self, event, IEC104_INTERROGATION_FINISH_INVALID_REQUEST) ? IEC104_INTERROGATION_EVENT_HANDLED :
IEC104_INTERROGATION_EVENT_ERROR;
}
/* 只决定应答地址,不改变原请求;后续仍按COT优先级决定是否接受及如何否定。 */
response_ca = Resolve_Common_Address(self, info.common_address);
deactivation = info.cause_of_transmission == IEC60870_ASDU_COT_DEACTIVATION;
/* 布局先完整校验;错误原因优先级COT -> CA -> IOA -> QOI/P/N/事务状态。 */
if (info.cause_of_transmission != IEC60870_ASDU_COT_ACTIVATION && !deactivation) {
reply_cause = IEC60870_ASDU_COT_UNKNOWN_CAUSE;
} else if (response_ca != self->config.common_address) {
reply_cause = IEC60870_ASDU_COT_UNKNOWN_COMMON_ADDRESS;
} else if (command.object.ioa != 0U) {
reply_cause = IEC60870_ASDU_COT_UNKNOWN_IOA;
} else if (command.qualifier < 20U || command.qualifier > 36U || info.is_negative_confirm != 0U) {
reply_cause = deactivation ? IEC60870_ASDU_COT_DEACTIVATION_CON : IEC60870_ASDU_COT_ACTIVATION_CON;
} else if (deactivation) {
task = Find_Activation(self, event, &info, response_ca, command.qualifier);
if (task != NULL) {
ImmediateReplyResult sent;
/* 原位替换为撤销确认,不移动自引用任务,也不要求额外任务槽。
* 先复制撤销请求并设置应答CA,Finish仍使用原激活的info/id/进度通知应用清理。
* 已提交HIGH的帧按原顺序在确认前发出;本地未提交批次就此丢弃。 */
if (!Copy_Response_Request(&task->request, event, response_ca)) { return IEC104_INTERROGATION_EVENT_ERROR; }
Finish(self, task, IEC104_INTERROGATION_FINISH_DEACTIVATED);
task->phase = IEC104_INTERROGATION_REPLY;
task->reply_cause = IEC60870_ASDU_COT_DEACTIVATION_CON;
task->reply_negative = false;
task->prepared = false;
task->request_id = 0U;
Move_Reply_To_Tail(self, task);
self->status.waiting = false;
++self->status.deactivated;
++self->status.cancelled;
sent = Try_Immediate_Reply(self, &task->request, response_ca, task->reply_cause, false, task);
if (sent == REPLY_SENT) { Remove_Task(self, task); }
if (sent == REPLY_FAILED) { return IEC104_INTERROGATION_EVENT_ERROR; }
return IEC104_INTERROGATION_EVENT_HANDLED;
}
reply_cause = IEC60870_ASDU_COT_DEACTIVATION_CON;
} else if (Find_Interrogation(self, 0U) != IEC104_INTERROGATION_TASK_CAPACITY) {
/* 从保存激活到终结全程占用总召资格;相同/不同QOI都否定,不替换原请求。
* 此判断仅作用于Type100激活,其他业务及匹配的COT=8撤销不受忙状态拦截。 */
reply_cause = IEC60870_ASDU_COT_ACTIVATION_CON;
}
/* 合法的新总召没有空槽时改为否定ACT_CON,仍可尝试提交,不影响当前任务。 */
if (reply_cause == 0U && self->count == IEC104_INTERROGATION_TASK_CAPACITY) {
++self->status.overloads;
overload_counted = true;
reply_cause = IEC60870_ASDU_COT_ACTIVATION_CON;
}
if (reply_cause != 0U) {
ImmediateReplyResult sent;
++self->status.rejected;
sent = Try_Immediate_Reply(self, event, response_ca, reply_cause, true, NULL);
if (sent == REPLY_SENT) { return IEC104_INTERROGATION_EVENT_HANDLED; }
if (sent == REPLY_FAILED) { return IEC104_INTERROGATION_EVENT_ERROR; }
if (self->count == IEC104_INTERROGATION_TASK_CAPACITY) {
if (!overload_counted) { ++self->status.overloads; }
++self->status.dropped_requests;
return IEC104_INTERROGATION_EVENT_HANDLED; /* 无法保存/提交的新请求已丢弃;不能重投或断链。 */
}
}
if (reply_cause == 0U && self->next_request_id == UINT64_MAX) {
return Reject_Connection(self, event, IEC104_INTERROGATION_FINISH_SERVICE_ERROR) ?
IEC104_INTERROGATION_EVENT_HANDLED : IEC104_INTERROGATION_EVENT_ERROR;
}
task = Allocate_Task(self);
if (task == NULL) {
(void)Reject_Connection(self, event, IEC104_INTERROGATION_FINISH_SERVICE_ERROR);
return IEC104_INTERROGATION_EVENT_ERROR;
}
if (!Copy_Response_Request(&task->request, event, response_ca)) {
Remove_Task(self, task);
return IEC104_INTERROGATION_EVENT_ERROR;
}
task->reply_cause = reply_cause;
task->reply_negative = reply_cause != 0U;
task->phase = reply_cause != 0U ? IEC104_INTERROGATION_REPLY : IEC104_INTERROGATION_START;
task->info.generation = event->transport.generation;
task->info.receive_serial = event->transport.data.i_frame.receive_serial;
task->info.common_address = response_ca; /* 合法总召均为本站CA;错误应答保留原错误CA且不交付BEGIN。 */
task->info.originator_address = info.originator_address;
task->info.qualifier = command.qualifier;
task->info.is_test = info.is_test != 0U;
task->info.max_objects_per_asdu = self->config.max_objects_per_asdu;
if (reply_cause == 0U) {
task->request_id = ++self->next_request_id;
}
return IEC104_INTERROGATION_EVENT_HANDLED;
}
/* 检查应用提交的数据Type是否属于本模块支持的无时标监视信息,返回true才可作为总召批次。
* 累计量不在此范围,留给总电度业务;各类型的对象编码、SQ和长度由Asdu层统一校验。 */
static bool Data_Type_Allowed(uint8_t type_id)
{
switch (type_id) {
case IEC60870_ASDU_M_SP_NA_1:
case IEC60870_ASDU_M_DP_NA_1:
case IEC60870_ASDU_M_ST_NA_1:
case IEC60870_ASDU_M_BO_NA_1:
case IEC60870_ASDU_M_ME_NA_1:
case IEC60870_ASDU_M_ME_NB_1:
case IEC60870_ASDU_M_ME_NC_1:
case IEC60870_ASDU_M_PS_NA_1:
case IEC60870_ASDU_M_ME_ND_1: return true;
default: return false;
}
}
/* 为总召任务生成一次BEGIN或NEED_DATA,并标记等待应用应答;task须处于START或未备好批次的DATA。
* event_outstanding为true时复用现有交互,不重复入队或重置等待起点。
* BEGIN需要自身和未来FINISHED两个位置;NEED_DATA不能占用终结预留。
* BEGIN前等FIFO空位不计业务期限,但已占用总召资格;BEGIN生成后开始执行整体期限。
* NEED_DATA等FIFO空位仍受整体期限约束;每次工作事件入队才启动本次应用应答期限。 */
static void Request_Application(IEC104_Interrogation_t *self, InterrogationTask *task)
{
self->status.waiting = true;
if (task->event_outstanding) { return; }
if (task->phase == IEC104_INTERROGATION_START) {
if (self->event_count + self->reserved_finishes + 2U > IEC104_INTERROGATION_EVENT_CAPACITY) { return; }
task->finish_reserved = true;
++self->reserved_finishes;
Push_Event(self, task, IEC104_INTERROGATION_BEGIN, IEC104_INTERROGATION_FINISH_NONE);
} else {
if (self->event_count + self->reserved_finishes >= IEC104_INTERROGATION_EVENT_CAPACITY) { return; }
Push_Event(self, task, IEC104_INTERROGATION_NEED_DATA, IEC104_INTERROGATION_FINISH_NONE);
}
task->event_outstanding = true;
task->event_delivered = false;
task->application_since_ms = IEC104_Net_Now_Ms();
if (task->phase == IEC104_INTERROGATION_START) { task->started_ms = task->application_since_ms; }
}
/* 应用应答的共同入口检查;expected指定应答BEGIN还是NEED_DATA,不在这里消费交互。
* 核对实例、请求、批次、类型、阶段和已取出标记,旧事件或重复应答返回STALE。
* Slave仍有通知时返回WAIT,调用方先按FIFO处理它们,防止跳过STOP/撤销/断线。
* 当前会话失效时取消;业务超时按确认边界终结,两者均返回STALE;参数/状态查询失败返回ERROR。
* OK只表示本次可以继续校验和应用应答;实际请求元数据始终取内部副本,不信任回传字段。 */
static IEC104_InterrogationReplyResult_t Check_Reply(IEC104_Interrogation_t *self,
const IEC104_InterrogationEvent_t *event, IEC104_InterrogationEventType_t expected,
InterrogationTask **matched_task)
{
InterrogationTask *task;
IEC104_SlaveRequestState_t state;
unsigned slot;
if (!Owned(self) || event == NULL) { return IEC104_INTERROGATION_REPLY_ERROR; }
slot = Find_Interrogation(self, event->request_id);
if (slot == IEC104_INTERROGATION_TASK_CAPACITY) { return IEC104_INTERROGATION_REPLY_STALE; }
task = &self->tasks[slot];
if (event->instance_id != self->instance_id || event->request_id != task->request_id ||
event->type != expected || event->batch_index != task->batch_index ||
!task->event_outstanding || !task->event_delivered ||
(expected == IEC104_INTERROGATION_BEGIN && task->phase != IEC104_INTERROGATION_START) ||
(expected == IEC104_INTERROGATION_NEED_DATA && task->phase != IEC104_INTERROGATION_DATA)) {
return IEC104_INTERROGATION_REPLY_STALE;
}
state = IEC104_Slave_Check_Request(self->slave, &self->session);
if (state == IEC104_SLAVE_REQUEST_ERROR) {
return IEC104_INTERROGATION_REPLY_ERROR;
}
self->status.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
if (self->status.events_pending) { return IEC104_INTERROGATION_REPLY_WAIT; }
if (!self->status.active || state != IEC104_SLAVE_REQUEST_READY) {
Cancel(self, IEC104_INTERROGATION_FINISH_CONNECTION_CHANGED);
self->status.active = false;
return IEC104_INTERROGATION_REPLY_STALE;
}
if (Expire(self) != 0) { return IEC104_INTERROGATION_REPLY_STALE; }
*matched_task = task; /* 仅OK交付内部任务地址,调用方不再假定它位于队首。 */
return IEC104_INTERROGATION_REPLY_OK;
}
/* 标记当前工作事件已经应答,解除应用等待;只在应答内容校验并接手后调用。
* 关闭event_outstanding也停止本次BEGIN/NEED_DATA期限,后续事件会设置新的计时起点。 */
static void Consume_Reply(IEC104_Interrogation_t *self, InterrogationTask *task)
{
task->event_outstanding = task->event_delivered = false;
self->status.waiting = false;
}
/* Process确认发送层已接手ASDU后推进所选任务;发送WAIT/失败时不得调用。
* CONFIRM转入DATA;DATA累加已提交进度并清prepared,允许下一次索取数据;
* TERMINATE生成FINISHED(COMPLETED),REPLY直接结束,两者均释放对应任务槽。
* 释放时只移动顺序索引,保持其他任务和ASDU的自引用;此处不等待主站ACK。 */
static void Advance(IEC104_Interrogation_t *self, InterrogationTask *task)
{
if (task->phase == IEC104_INTERROGATION_CONFIRM) {
task->phase = IEC104_INTERROGATION_DATA;
} else if (task->phase == IEC104_INTERROGATION_DATA) {
++task->batch_index;
task->objects_submitted += task->prepared_count;
task->prepared = false;
} else {
if (task->phase == IEC104_INTERROGATION_TERMINATE) {
++self->status.gi_submitted;
Finish(self, task, IEC104_INTERROGATION_FINISH_COMPLETED);
}
Remove_Task(self, task);
}
}
/**
* 功能:初始化调用方提供的配置,设置站地址、每批对象数以及应用应答和执行期限。
* 参数:config为可写配置对象;NULL时不执行任何操作。
* 说明:先清零再设置默认值,不创建实例或占用资源;应用可在Create前修改配置。
*/
void IEC104_Interrogation_Config_Default(IEC104_InterrogationConfig_t *config)
{
if (config == NULL) { return; }
memset(config, 0, sizeof(*config));
config->common_address = 1U;
config->max_objects_per_asdu = 127U;
config->begin_timeout_ms = 15000U;
config->data_timeout_ms = 15000U;
config->total_timeout_ms = 60000U;
}
/**
* 功能:从静态池创建总召实例,复制配置并借用一个有效Slave。
* 参数:slave须在本实例整个寿命内保持有效;config可在成功返回后复用。
* 返回:新实例句柄;配置非法、Slave无效、重复绑定、池满或编号耗尽时返回NULL。
* 说明:扫描完整静态池拒绝重复绑定,失败不占槽;实例编号在整个进程内不重复。
* 创建时尚无活动会话,后续依靠Handle_Event接收CONNECTED/ACTIVATED建立状态。
*/
IEC104_Interrogation_t *IEC104_Interrogation_Create(IEC104_Slave_t *slave,
const IEC104_InterrogationConfig_t *config)
{
size_t i;
IEC104_Interrogation_t *free_slot = NULL;
if (config == NULL || config->common_address == 0U || config->common_address == IEC104_CA_BROADCAST ||
config->max_objects_per_asdu == 0U || config->max_objects_per_asdu > 127U ||
IEC104_Slave_Get_Application_Layer_Parameters(slave) == NULL || instance_serial == UINT64_MAX) { return NULL; }
for (i = 0U; i < IEC104_INTERROGATION_CAPACITY; ++i) {
if (interrogation_pool[i].slave == slave) { return NULL; }
if (free_slot == NULL && interrogation_pool[i].slave == NULL) { free_slot = &interrogation_pool[i]; }
}
if (free_slot != NULL) {
memset(free_slot, 0, sizeof(*free_slot));
free_slot->slave = slave;
free_slot->config = *config;
free_slot->instance_id = ++instance_serial;
}
return free_slot;
}
/**
* 功能:释放已经没有协议任务和业务事件的实例槽,不销毁借用的Slave。
* 参数:self为Create返回的有效实例。
* 返回:true已释放,句柄失效;false为非法句柄或仍有任务/事件,原状态不变。
* 说明:不会隐式Cancel或丢弃通知;调用方须先取消任务、读完FINISHED并清理应用资源。
*/
bool IEC104_Interrogation_Destroy(IEC104_Interrogation_t *self)
{
if (!Owned(self) || self->count != 0U || self->event_count != 0U) { return false; }
memset(self, 0, sizeof(*self));
return true;
}
/**
* 功能:以CANCELLED原因放弃本实例尚未提交的工作,保留终结事件供应用清理。
* 参数:self为总召实例;非法句柄无操作。
* 说明:不关闭连接、不发送确认或终止,也不撤回发送层已经接手的ASDU。
* 该入口是应用本地取消;主站撤销请求由Handle_Event执行匹配和确认流程。
*/
void IEC104_Interrogation_Cancel(IEC104_Interrogation_t *self)
{
if (Owned(self)) { Cancel(self, IEC104_INTERROGATION_FINISH_CANCELLED); }
}
/**
* 功能:处理应用转交的一个Slave历史通知,更新会话状态或保存总召请求。
* 参数:self为有效实例;event须为同一Slave按FIFO取出的原事件或Copy_Event副本。
* 返回:HANDLED已处理请求(含拒绝/满槽丢弃)或会话变化;IGNORED可交给其他业务;ERROR为参数/处理失败。
* 说明:校验事件所属服务及代次,不自行读取Slave FIFO,不保存调用方事件指针。
* 不调用应用代码;短应答在Slave通知已读空、会话有效且没有旧REPLY时尝试立即提交。
* 满槽只拒绝/丢弃新请求;取消时保留FINISHED,严重请求错误会关闭原连接。
*/
IEC104_InterrogationEventResult_t IEC104_Interrogation_Handle_Event(
IEC104_Interrogation_t *self, const IEC104_SlaveEvent_t *event)
{
IEC104_SlaveEventIdentity_t identity;
/* Slave负责来源和视图校验;本模块额外保持首次绑定的服务实例,不接管复用后的另一服务。 */
if (!Owned(self) || !IEC104_Slave_Get_Event_Identity(self->slave, event, &identity) ||
(self->session.instance_id != 0U && self->session.instance_id != identity.instance_id)) {
return IEC104_INTERROGATION_EVENT_ERROR;
}
self->session.slave = identity.slave;
self->session.instance_id = identity.instance_id;
if (event->transport.type == IEC104_TCP_EVENT_CONNECTED) {
/* TCP连通仅建立新代次;必须再收到ACTIVATED,才能接受本代次总召。 */
Cancel(self, IEC104_INTERROGATION_FINISH_CONNECTION_CHANGED);
self->session.generation = identity.generation;
self->status.active = false;
return IEC104_INTERROGATION_EVENT_HANDLED;
}
if (identity.generation != self->session.generation) {
/* 旧连接遗留的断开、回执或I帧不能改变新连接的业务状态。 */
return IEC104_INTERROGATION_EVENT_IGNORED;
}
switch (event->transport.type) {
case IEC104_TCP_EVENT_ACTIVATED:
self->status.active = true;
return IEC104_INTERROGATION_EVENT_HANDLED;
case IEC104_TCP_EVENT_DEACTIVATED:
case IEC104_TCP_EVENT_DISCONNECTED:
Cancel(self, event->transport.type == IEC104_TCP_EVENT_DEACTIVATED ?
IEC104_INTERROGATION_FINISH_STOPPED : IEC104_INTERROGATION_FINISH_DISCONNECTED);
self->status.active = false;
if (event->transport.type == IEC104_TCP_EVENT_DISCONNECTED) { self->session.generation = 0U; }
return IEC104_INTERROGATION_EVENT_HANDLED;
case IEC104_TCP_EVENT_I_FRAME: return Request(self, event);
default: return IEC104_INTERROGATION_EVENT_IGNORED; /* ACK回执不触发业务重发。 */
}
}
/**
* 功能:自动推进当前可处理的单帧应答和总召任务,直到空闲或需要等待。
* 参数:self为有效实例;调用方不需要提供处理步数。
* 返回:true为本轮正常结束,包括等待、取消或成功处理超时;false为参数、状态查询、组包或关闭失败。
* 说明:Slave通知未读空时仅设置events_pending,应用先转交网络通知再继续调用。
* START有事件空位时生成BEGIN并开始执行计时;START及尚无批次的DATA等待应用处理。
* 双方可推进时轮转;总召等应用或业务FIFO空位时仍处理单帧应答。
* 每步重查Slave通知,发送过程中新交付的通知也须先处理;只有接手ASDU后才Advance。
* 发送WAIT保留任务并返回,下轮优先检查单帧应答。
* 本函数不接收新请求、不执行应用代码;每条REPLY提交后即释放,总召至多一个,
* 当前帧提交后结束或等待应用。任务池有界,本轮推进会自然结束。
* 调用方持续驱动本函数,并依据Get_Status给出的业务期限安排下次唤醒。
*/
bool IEC104_Interrogation_Process(IEC104_Interrogation_t *self)
{
IEC104_SlaveRequestState_t state;
if (!Owned(self)) { return false; }
for (;;) {
InterrogationTask *task;
IEC60870_MasterConnection_t *connection;
int expired;
bool accepted;
state = IEC104_Slave_Check_Request(self->slave, &self->session);
if (state == IEC104_SLAVE_REQUEST_ERROR) {
Cancel(self, IEC104_INTERROGATION_FINISH_SERVICE_ERROR);
self->status.active = false;
return false;
}
self->status.events_pending = state == IEC104_SLAVE_REQUEST_WAIT_EVENTS;
if (self->status.events_pending) { return true; }
self->status.waiting = false;
if (self->count == 0U) { return true; }
if (!self->status.active || state != IEC104_SLAVE_REQUEST_READY) {
Cancel(self, IEC104_INTERROGATION_FINISH_CONNECTION_CHANGED);
self->status.active = false;
return true;
}
expired = Expire(self);
if (expired != 0) {
if (expired < 0 || !self->status.active) { return expired > 0; }
continue; /* 确认前超时仅转为REPLY;重查通知后,本轮即可尝试提交否定确认。 */
}
task = Select_Task(self);
if (task == NULL) { self->status.waiting = true; return true; }
connection = &task->request.connection;
if (task->phase == IEC104_INTERROGATION_START ||
(task->phase == IEC104_INTERROGATION_DATA && !task->prepared)) {
Request_Application(self, task);
self->prefer_interrogation = false;
continue; /* 应用稍后取事件;继续处理当前已有的单帧应答。 */
}
/* 请求副本创建时已准备应答CA;此处只设置本阶段COT/P/N,WAIT后继续使用同一副本。 */
if (task->phase == IEC104_INTERROGATION_CONFIRM) {
accepted = IEC60870_MasterConnection_Send_ACT_CON(connection, &task->request.asdu, false);
} else if (task->phase == IEC104_INTERROGATION_TERMINATE) {
accepted = IEC60870_MasterConnection_Send_ACT_TERM(connection, &task->request.asdu);
} else if (task->phase == IEC104_INTERROGATION_REPLY) {
if (IEC60870_Asdu_Set_COT(&task->request.asdu, task->reply_cause) != IEC60870_ASDU_OK ||
IEC60870_Asdu_Set_Negative(&task->request.asdu, task->reply_negative) != IEC60870_ASDU_OK) {
(void)Reject_Connection(self, &task->request, IEC104_INTERROGATION_FINISH_SERVICE_ERROR);
return false;
}
accepted = IEC60870_MasterConnection_Send_ASDU(connection, &task->request.asdu);
} else {
accepted = IEC60870_MasterConnection_Send_ASDU(connection, &task->outgoing);
}
if (!accepted) {
IEC104_TcpSubmitResult_t result = IEC104_Slave_Get_Last_Submit_Result(connection);
if (result == IEC104_TCP_SUBMIT_WAIT) {
/* 未接手时保留phase、prepared和原文,下次重试同一帧,不重复向应用取数。 */
++self->status.waits;
self->status.waiting = true;
self->prefer_interrogation = false;
} else {
Cancel(self, IEC104_INTERROGATION_FINISH_SEND_ERROR);
self->status.active = false;
}
return true;
}
self->prefer_interrogation = task->phase == IEC104_INTERROGATION_REPLY;
++self->status.submitted;
if (task->phase == IEC104_INTERROGATION_REPLY && task->reply_negative) { ++self->status.negative_submitted; }
Advance(self, task);
}
}
/**
* 功能:将最早一条业务事件复制到调用方并从FIFO移除,不执行网络操作。
* 参数:self为有效实例;event为非NULL输出对象,接收可独立保存的纯值副本。
* 返回:READY写出事件;EMPTY表示暂时无事件;ERROR表示参数非法,后两者不改输出。
* 说明:工作事件出队后才允许应答;BEGIN出队同时标记应用已持有本请求,后续须配对FINISHED。
* 业务事件不含自引用指针,因此可以memmove压紧FIFO;协议任务数组不能如此移动。
*/
IEC104_InterrogationNextResult_t IEC104_Interrogation_NextEvent(
IEC104_Interrogation_t *self, IEC104_InterrogationEvent_t *event)
{
InterrogationTask *task = NULL;
if (!Owned(self) || event == NULL) { return IEC104_INTERROGATION_NEXT_ERROR; }
if (self->event_count == 0U) { return IEC104_INTERROGATION_NEXT_EMPTY; }
if (self->events[0].type != IEC104_INTERROGATION_FINISHED) {
unsigned slot = Find_Interrogation(self, self->events[0].request_id);
if (slot == IEC104_INTERROGATION_TASK_CAPACITY) { return IEC104_INTERROGATION_NEXT_ERROR; }
task = &self->tasks[slot];
}
*event = self->events[0];
--self->event_count;
memmove(self->events, self->events + 1, self->event_count * sizeof(self->events[0]));
if (task != NULL) {
task->event_delivered = true;
if (event->type == IEC104_INTERROGATION_BEGIN) { task->begin_delivered = true; }
}
return IEC104_INTERROGATION_NEXT_READY;
}
/**
* 功能:接收应用对已取出BEGIN事件的接受或拒绝决定。
* 参数:event为本实例当前BEGIN的原样副本;accept为true接受、false拒绝。
* 返回:沿用Check_Reply的WAIT/STALE/ERROR;OK表示决定已消费,不可用同一事件重复应答。
* 说明:接受后转为CONFIRM,等待Process提交肯定ACT_CON;拒绝先生成FINISHED(REJECTED),
* 再将任务转为否定ACT_CON应答。资源由应用读取FINISHED后释放,本函数不执行清理回调。
*/
IEC104_InterrogationReplyResult_t IEC104_Interrogation_Reply_Begin(
IEC104_Interrogation_t *self, const IEC104_InterrogationEvent_t *event, bool accept)
{
InterrogationTask *task;
IEC104_InterrogationReplyResult_t result = Check_Reply(self, event, IEC104_INTERROGATION_BEGIN, &task);
if (result != IEC104_INTERROGATION_REPLY_OK) { return result; }
Consume_Reply(self, task);
if (accept) { task->phase = IEC104_INTERROGATION_CONFIRM; }
else { Reject_Request(self, task, IEC104_INTERROGATION_FINISH_REJECTED); }
return IEC104_INTERROGATION_REPLY_OK;
}
/**
* 功能:接收一次NEED_DATA对应的非空数据批次,校验后复制到任务自有缓冲。
* 参数:event为当前已取出的NEED_DATA;asdu为应用构造的完整监视信息ASDU。
* 返回:OK已接手批次;WAIT/STALE沿用交互校验结果;ERROR为参数、布局、类型或元数据不符。
* 说明:按原请求验证COT、OA、TEST,按配置验证本站CA和对象数,并检查批次/累计计数溢出。
* 全部校验通过才消费工作事件;非法批次可修正后用同一事件再交付,空数据应调用Complete_Data。
* 成功后不借用应用缓冲或参数,实际提交及进度计数由后续Process完成。
*/
IEC104_InterrogationReplyResult_t IEC104_Interrogation_Submit_Batch(
IEC104_Interrogation_t *self, const IEC104_InterrogationEvent_t *event,
const IEC60870_Asdu_t *asdu)
{
InterrogationTask *task;
IEC60870_AsduInfo_t info;
const uint8_t *data;
uint16_t length;
const IEC60870_AppLayerParameters_t *parameters;
IEC104_InterrogationReplyResult_t result = Check_Reply(self, event, IEC104_INTERROGATION_NEED_DATA, &task);
if (result != IEC104_INTERROGATION_REPLY_OK) { return result; }
parameters = IEC104_Slave_Get_Application_Layer_Parameters(self->slave);
if (asdu == NULL || asdu->parameters == NULL ||
asdu->parameters->sizeOfCOT != 2 || asdu->parameters->sizeOfCA != 2 ||
asdu->parameters->sizeOfIOA != 3 ||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK ||
length > sizeof(task->bytes) ||
IEC60870_Asdu_Parse(parameters, data, length, &info) != IEC60870_ASDU_OK ||
!Data_Type_Allowed(info.type_id) || info.object_count == 0U ||
info.object_count > self->config.max_objects_per_asdu ||
info.cause_of_transmission != task->info.qualifier ||
info.common_address != self->config.common_address ||
info.originator_address != task->info.originator_address ||
info.is_test != (uint8_t)task->info.is_test || info.is_negative_confirm != 0U ||
task->batch_index == SIZE_MAX || task->objects_submitted > SIZE_MAX - info.object_count) {
return IEC104_INTERROGATION_REPLY_ERROR;
}
/* 原文复制到任务内,并以Slave的稳定参数重建视图,避免引用应用临时对象。 */
memcpy(task->bytes, data, length);
if (IEC60870_Asdu_Create_From_Buffer(&task->outgoing, parameters, task->bytes, length) != IEC60870_ASDU_OK) {
return IEC104_INTERROGATION_REPLY_ERROR;
}
task->prepared_count = info.object_count;
task->prepared = true;
Consume_Reply(self, task);
return IEC104_INTERROGATION_REPLY_OK;
}
/**
* 功能:应答当前NEED_DATA,声明本次总召已没有更多数据。
* 参数:event须为当前已取出的NEED_DATA,不能使用BEGIN或已经交付过批次的旧事件。
* 返回:OK已消费交互并转为TERMINATE;其他结果沿用Check_Reply,不进入正常终止流程。
* 说明:此处不发送ACT_TERM;后续Process提交成功才产生FINISHED(COMPLETED),发送等待时保留任务。
*/
IEC104_InterrogationReplyResult_t IEC104_Interrogation_Complete_Data(
IEC104_Interrogation_t *self, const IEC104_InterrogationEvent_t *event)
{
InterrogationTask *task;
IEC104_InterrogationReplyResult_t result = Check_Reply(self, event, IEC104_INTERROGATION_NEED_DATA, &task);
if (result != IEC104_INTERROGATION_REPLY_OK) { return result; }
Consume_Reply(self, task);
task->phase = IEC104_INTERROGATION_TERMINATE;
return IEC104_INTERROGATION_REPLY_OK;
}
/**
* 功能:应用在建立快照或取得数据失败时,按肯定确认的提交边界终结本次总召。
* 参数:event为当前已取出且尚未应答的BEGIN或NEED_DATA。
* 返回:WAIT要求先处理Slave通知;STALE表示交互已失效;ERROR为检查或关闭失败。
* 通过检查后,BEGIN失败转为否定REPLY并保留连接;NEED_DATA失败关闭原连接。
* OK表示失败已处理,否定确认由Process提交;若关闭失败返回ERROR,任务仍已取消,不能重试。
* 说明:两种路径均保留SOURCE_ERROR并交付FINISHED供应用清理,不发送成功ACT_TERM。
*/
IEC104_InterrogationReplyResult_t IEC104_Interrogation_Fail_Request(
IEC104_Interrogation_t *self, const IEC104_InterrogationEvent_t *event)
{
IEC104_InterrogationReplyResult_t result;
InterrogationTask *task;
if (event == NULL) { return IEC104_INTERROGATION_REPLY_ERROR; }
if (event->type != IEC104_INTERROGATION_BEGIN && event->type != IEC104_INTERROGATION_NEED_DATA) {
return Owned(self) ? IEC104_INTERROGATION_REPLY_STALE : IEC104_INTERROGATION_REPLY_ERROR;
}
result = Check_Reply(self, event, event->type, &task);
if (result != IEC104_INTERROGATION_REPLY_OK) { return result; }
return Fail_Interrogation(self, task, IEC104_INTERROGATION_FINISH_SOURCE_ERROR) ?
IEC104_INTERROGATION_REPLY_OK : IEC104_INTERROGATION_REPLY_ERROR;
}
/**
* 功能:组合会话状态、累计统计、活动总召进度及最近业务期限,生成独立状态副本。
* 参数:self为有效实例;status为非NULL输出对象。
* 返回:true已写出快照;false为非法句柄/NULL,输出保持不变。
* 说明:phase和批次进度优先描述唯一总召;仅有应答时REPLY,无任务时IDLE。
* 尚未生成BEGIN不计业务期限;timer_active为false时剩余值无意义。
* 只读取当前单调时钟,不触发超时、发送或事件入队;到期后仍由Process或应答入口处理。
*/
bool IEC104_Interrogation_Get_Status(const IEC104_Interrogation_t *self,
IEC104_InterrogationStatus_t *status)
{
const InterrogationTask *task;
unsigned slot;
uint32_t remaining;
if (!Owned(self) || status == NULL) { return false; }
slot = Find_Interrogation(self, 0U);
task = slot != IEC104_INTERROGATION_TASK_CAPACITY ? &self->tasks[slot] :
(self->count != 0U ? &self->tasks[self->order[0]] : NULL);
*status = self->status;
status->gi_pending = slot != IEC104_INTERROGATION_TASK_CAPACITY;
status->generation = self->session.generation;
status->pending_requests = self->count;
status->pending_events = self->event_count;
status->phase = task != NULL ? task->phase : IEC104_INTERROGATION_IDLE;
status->batch_index = task != NULL ? task->batch_index : 0U;
status->objects_submitted = task != NULL ? task->objects_submitted : 0U;
status->application_waiting = task != NULL && task->event_outstanding;
if (task != NULL && task->phase != IEC104_INTERROGATION_REPLY &&
!Interrogation_Can_Progress(self, task)) { status->waiting = true; }
status->timer_active = false;
status->timeout_remaining_ms = 0U;
if (task != NULL && Task_Timeout(self, task, IEC104_Net_Now_Ms(), &remaining) != IEC104_INTERROGATION_FINISH_NONE) {
status->timer_active = true;
status->timeout_remaining_ms = remaining;
}
return true;
}