498 lines
26 KiB
C
498 lines
26 KiB
C
/*
|
||
* 104子站业务入口:拥有一个TcpServer,并把底层I帧通知转换为ASDU视图和应答接口。
|
||
* 职责分工:Slave管理业务入口和事件绑定;TcpServer管理socket、调度、队列及协议会话。
|
||
* 本层只保存不透明的TcpServer指针,通过公开函数操作它,不访问其成员或复制底层对象。
|
||
*
|
||
* 生命周期:Create -> Config_Default/Init -> Poll或Prepare/Process -> Close -> 读空事件 -> Destroy。
|
||
* 接收路径:TcpServer_NextEvent -> 复制原文及来源身份 -> Bind_Event -> 应用处理。
|
||
* 应答路径:IEC60870_MasterConnection_Send_* -> 本文件的发送适配 -> TcpServer_Send_Response。
|
||
* 主动上报:Slave_Enqueue_Event -> 服务级NORMAL队列,不借用某个请求的连接身份。
|
||
* 本层不自动执行业务命令或发送确认;ASDU检查及业务许可仍由应用负责。
|
||
*
|
||
* 两种身份分别解决不同的复用问题:serial标识本次Slave_Create,generation标识原TCP会话。
|
||
* 事件保存两者,防止延迟应答误发给重连的主站或静态槽复用后的另一服务。
|
||
* 所有调用在所属事件循环线程串行执行;静态池没有锁,Create/Destroy也须跨服务串行。
|
||
*/
|
||
#include "IEC104_Slave.h"
|
||
#include "IEC104_TcpServer.h"
|
||
#include "IEC104_TcpServer_Config.h"
|
||
#include <string.h>
|
||
|
||
/* 104专用默认布局来自IEC104_Types.h;共用ASDU模块通过参数工作,不硬编码104布局。
|
||
* 静态只读存储供接收视图和主动上报共用,返回的参数指针不依赖某个服务或事件的寿命。 */
|
||
static const IEC60870_AppLayerParameters_t parameters104 = IEC104_APP_LAYER_PARAMETERS_DEFAULT;
|
||
|
||
/* 完整类型仅在本文件可见;业务只保存Create返回的指针。 */
|
||
struct IEC104_Slave {
|
||
IEC104_TcpServer_t *server; /* 私有TCP服务;NULL表示该Slave槽空闲,Close不会清除此成员。 */
|
||
uint64_t serial; /* 每次成功Create递增,Destroy保留;到UINT64_MAX后不再复用该槽。 */
|
||
};
|
||
|
||
_Static_assert(IEC104_TCP_SERVER_CAPACITY > 0U, "Slave needs at least one service slot");
|
||
/* Slave和TcpServer各有对象池;每个Slave占用一个底层名额,直接创建TCP服务也占该名额。 */
|
||
static IEC104_Slave_t slave_pool[IEC104_TCP_SERVER_CAPACITY];
|
||
|
||
static bool Send_ASDU(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu);
|
||
|
||
/**
|
||
* 功能:判断句柄是否指向当前占用的Slave静态槽,NULL和池外地址返回false。
|
||
* 说明:先按地址查池再解引用;server非空只表示拥有底层对象,不代表已Init或已连接。
|
||
* 槽复用后,单独的旧句柄副本无法与新句柄区分;Destroy成功后业务必须停止使用旧句柄。
|
||
*/
|
||
static bool Slave_Owned(const IEC104_Slave_t *self)
|
||
{
|
||
size_t index;
|
||
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
|
||
if (self == &slave_pool[index]) { return self->server != NULL; }
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 功能:返回本层校验失败的结果,同时完整填写可选的error。
|
||
* 输入参数:result——本层检测出的错误;error——调用方诊断存储,允许NULL。
|
||
* 说明:阶段统一为VALIDATE,socket_error为0;底层操作的诊断直接透传,不在这里改写。
|
||
*/
|
||
static IEC104_TcpResult_t Slave_Error(IEC104_TcpResult_t result, IEC104_TcpError_t *error)
|
||
{
|
||
if (error != NULL) {
|
||
error->result = result;
|
||
error->stage = IEC104_TCP_STAGE_VALIDATE;
|
||
error->socket_error = 0;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 功能:从共用连接接口取回它所属的Slave事件;接口不匹配时返回NULL。
|
||
* 说明:同时核对发送函数和事件内接口的地址,拒绝赋值/memcpy产生的未修复副本。
|
||
* 此处只检查接口绑定,服务所有权、serial和连接代次由发送路径继续检查。
|
||
* connection及其object必须仍指向有效存储,不能用此检查代替业务的对象寿命管理。
|
||
*/
|
||
static IEC104_SlaveEvent_t *Get_Context(const IEC60870_MasterConnection_t *connection)
|
||
{
|
||
IEC104_SlaveEvent_t *event;
|
||
if (connection == NULL || connection->send_asdu != Send_ASDU || connection->object == NULL) {
|
||
return NULL;
|
||
}
|
||
event = (IEC104_SlaveEvent_t *)connection->object;
|
||
return &event->connection == connection ? event : NULL;
|
||
}
|
||
|
||
/**
|
||
* 功能:确认待发送ASDU使用可由本接入层处理的104应用层布局。
|
||
* 返回值:基础参数合法、COT/CA/IOA为2/2/3字节且ASDU上限不超过249时为true。
|
||
* 说明:允许业务采用更小的最大长度或不同默认OA;这里不检查具体信息对象和业务权限。
|
||
*/
|
||
static bool Parameters_Compatible(const IEC60870_AppLayerParameters_t *parameters)
|
||
{
|
||
return IEC60870_AppLayerParameters_Valid(parameters) &&
|
||
parameters->sizeOfCOT == 2 && parameters->sizeOfCA == 2 && parameters->sizeOfIOA == 3 &&
|
||
parameters->maxSizeOfASDU <= IEC104_MAX_ASDU_LEN;
|
||
}
|
||
|
||
/**
|
||
* 功能:实现共用连接的send_asdu,把应答交给产生该事件的原服务、原会话。
|
||
* 输入参数:connection——事件内已绑定的接口;asdu——布局兼容的完整ASDU,可独立于接收视图。
|
||
* 返回值:仅底层SUBMIT_OK转换为true;WAIT/ERROR/FATAL返回false并记录在原事件中。
|
||
* 说明:先检查Slave所有权和serial,再由TcpServer检查generation、启停状态及发送容量。
|
||
* OK表示已复制接手,不代表写完或收到ACK;WAIT未接手,业务保存原事件/ASDU后择机重试。
|
||
* 不查找当前新连接来替换旧身份;FATAL可能已经写出字节,不能作为未发送直接重放。
|
||
*/
|
||
static bool Send_ASDU(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu)
|
||
{
|
||
IEC104_SlaveEvent_t *event = Get_Context(connection);
|
||
const uint8_t *data;
|
||
uint16_t length;
|
||
if (event == NULL) { return false; }
|
||
event->_last_submit_result = IEC104_TCP_SUBMIT_ERROR;
|
||
if (!Slave_Owned(event->_owner) || event->_owner->serial != event->_owner_serial ||
|
||
asdu == NULL || !Parameters_Compatible(asdu->parameters) ||
|
||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK) {
|
||
return false;
|
||
}
|
||
event->_last_submit_result = IEC104_TcpServer_Send_Response(
|
||
event->_owner->server, event->_generation, data, length);
|
||
return event->_last_submit_result == IEC104_TCP_SUBMIT_OK;
|
||
}
|
||
|
||
/**
|
||
* 功能:修改ASDU的传送原因和否定标志,再沿原连接提交确认或终止。
|
||
* 输入参数:cause——目标COT;negative——目标P/N;asdu——允许原地修改的ASDU。
|
||
* 返回值:true已接手;false表示字段修改或提交失败,详细提交结果保存在事件中。
|
||
* 说明:按COT、P/N顺序修改,后续失败不回滚已经成功的修改;不执行业务,也不自动重试。
|
||
*/
|
||
static bool Send_Confirmation(IEC60870_MasterConnection_t *connection,
|
||
IEC60870_Asdu_t *asdu, int cause, bool negative)
|
||
{
|
||
IEC104_SlaveEvent_t *event = Get_Context(connection);
|
||
if (event == NULL) { return false; }
|
||
event->_last_submit_result = IEC104_TCP_SUBMIT_ERROR;
|
||
if (IEC60870_Asdu_Set_COT(asdu, cause) != IEC60870_ASDU_OK ||
|
||
IEC60870_Asdu_Set_Negative(asdu, negative ? 1 : 0) != IEC60870_ASDU_OK) {
|
||
return false;
|
||
}
|
||
return Send_ASDU(connection, asdu);
|
||
}
|
||
|
||
/* 共用激活确认入口:COT=7,P/N由业务指定;沿用Send_Confirmation的修改及提交契约。 */
|
||
static bool Send_ACT_CON(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu, bool negative)
|
||
{
|
||
return Send_Confirmation(connection, asdu, IEC60870_ASDU_COT_ACTIVATION_CON, negative);
|
||
}
|
||
|
||
/* 共用激活终止入口:COT=10、P/N=0;由业务处理完成后调用,不自动跟随ACT_CON发送。 */
|
||
static bool Send_ACT_TERM(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu)
|
||
{
|
||
return Send_Confirmation(connection, asdu, IEC60870_ASDU_COT_ACTIVATION_TERMINATION, false);
|
||
}
|
||
|
||
/* 共用参数查询入口:有效事件接口返回静态只读参数;不要求原连接仍在线,也不表示可以发送。 */
|
||
static const IEC60870_AppLayerParameters_t *Get_Parameters(IEC60870_MasterConnection_t *connection)
|
||
{
|
||
return Get_Context(connection) != NULL ? ¶meters104 : NULL;
|
||
}
|
||
|
||
/**
|
||
* 功能:让目标事件的ASDU视图和共用发送接口指向目标自身的存储。
|
||
* 前提:transport原文已复制完毕,事件地址在后续使用期间保持稳定。
|
||
* 输出参数:event->asdu/connection/asdu_result;不改来源身份、原文和历史提交结果。
|
||
* 说明:非I帧不提供ASDU接口;I帧视图构造失败时保留原通知供诊断,connection保持清零。
|
||
* 视图借用本事件中APCI之后的ASDU字节,不再借用TcpServer或Receiver的内部缓冲。
|
||
*/
|
||
static void Bind_Event(IEC104_SlaveEvent_t *event)
|
||
{
|
||
uint16_t length;
|
||
memset(&event->asdu, 0, sizeof(event->asdu));
|
||
memset(&event->connection, 0, sizeof(event->connection));
|
||
event->asdu_result = IEC60870_ASDU_UNSUPPORTED;
|
||
if (event->transport.type != IEC104_TCP_EVENT_I_FRAME) { return; }
|
||
event->asdu_result = IEC60870_ASDU_ERROR;
|
||
length = event->transport.data.i_frame.apdu_length;
|
||
if (length < IEC104_APCI_LEN || length > IEC104_MAX_APDU_LEN) { return; }
|
||
event->asdu_result = IEC60870_Asdu_Create_From_Buffer(&event->asdu, ¶meters104,
|
||
event->transport.data.i_frame.apdu + IEC104_APCI_LEN, (uint16_t)(length - IEC104_APCI_LEN));
|
||
if (event->asdu_result != IEC60870_ASDU_OK) { return; }
|
||
event->connection.object = event;
|
||
event->connection.send_asdu = Send_ASDU;
|
||
event->connection.send_act_con = Send_ACT_CON;
|
||
event->connection.send_act_term = Send_ACT_TERM;
|
||
event->connection.get_application_layer_parameters = Get_Parameters;
|
||
}
|
||
|
||
/**
|
||
* 功能:从静态池取得一个Slave,并为它取得一个私有TcpServer。
|
||
* 返回值:成功返回不透明句柄;任一池无可用槽或身份编号耗尽时返回NULL。
|
||
* 说明:不创建socket、不分配堆内存;只有取得底层服务后才占用Slave槽并递增serial。
|
||
* 成功后由业务配对Destroy;不能自行分配、复制、清零或free句柄指向的对象。
|
||
*/
|
||
IEC104_Slave_t *IEC104_Slave_Create(void)
|
||
{
|
||
size_t index;
|
||
for (index = 0U; index < IEC104_TCP_SERVER_CAPACITY; ++index) {
|
||
IEC104_Slave_t *self = &slave_pool[index];
|
||
if (self->server == NULL && self->serial != UINT64_MAX) {
|
||
IEC104_TcpServer_t *server = IEC104_TcpServer_Create();
|
||
if (server == NULL) { return NULL; }
|
||
self->server = server;
|
||
++self->serial;
|
||
return self;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* 功能:归还已关闭且通知已读空的Slave及其底层服务。
|
||
* 输入参数:self——Create取得且未Destroy的句柄;error——可选的本次调用诊断。
|
||
* 返回值:OK时句柄失效;运行中为INVALID_STATE,通知未读空为EVENTS_PENDING,失败保留对象。
|
||
* 说明:先由TcpServer检查并归还底层槽,再释放Slave槽;保留serial隔离旧事件。
|
||
* 常规顺序为Close -> NextEvent读空 -> Destroy;尚未Init或Init失败后也可直接归还。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Destroy(IEC104_Slave_t *self, IEC104_TcpError_t *error)
|
||
{
|
||
IEC104_TcpResult_t result;
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
result = IEC104_TcpServer_Destroy(self->server, error);
|
||
if (result == IEC104_TCP_OK) { self->server = NULL; }
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 功能:填写默认监听地址0.0.0.0、端口2404及协议参数。
|
||
* 输出参数:config——调用方可继续调整的配置;NULL时无操作。
|
||
* 说明:复用TcpServer默认值,不占用服务槽;ASDU布局另由parameters104提供。
|
||
*/
|
||
void IEC104_Slave_Config_Default(IEC104_SlaveConfig_t *config)
|
||
{
|
||
IEC104_TcpServer_Config_Default(config);
|
||
}
|
||
|
||
/**
|
||
* 功能:初始化所拥有的TCP服务,开始监听并建立服务级发送队列。
|
||
* 输入参数:self——有效Slave句柄;config——本次监听和协议配置,底层复制后使用。
|
||
* 输出参数:error——本次诊断,允许NULL;底层返回的阶段和网络错误保持原样。
|
||
* 返回值:OK已监听,其他结果区分参数、配置、生命周期或网络失败。
|
||
* 说明:成功不表示已有主站,之后必须驱动Poll或Prepare/Process;失败不销毁句柄。
|
||
* 再次Init前须Close并读空旧通知,旧事件的连接身份不会因重新初始化而更新。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Init(IEC104_Slave_t *self,
|
||
const IEC104_SlaveConfig_t *config, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Init(self->server, config, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:为外部事件循环查询socket关注集合、等待期限及本地待处理工作。
|
||
* 输入参数:self——已Init的服务;info——非NULL快照存储;error——可选诊断。
|
||
* 返回值:OK时写出info;失败保持info不变,不进行收发或协议推进。
|
||
* 说明:有待消费事件或need_process时不得阻塞等待;状态改变后须重新Prepare。
|
||
* 快照中的socket仅供注册,所有权仍属于内部TcpServer,业务不能直接读写或关闭。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Prepare(const IEC104_Slave_t *self,
|
||
IEC104_TcpWaitInfo_t *info, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Prepare(self->server, info, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:把外部事件循环的一批就绪、定时或本地通知交给底层有界推进。
|
||
* 输入参数:self——已Init的服务;ready——必填就绪批次,身份来自对应Prepare/注册快照。
|
||
* 输出参数:error——可选诊断;返回值与TcpServer_Process一致,OK也包括没有进展。
|
||
* 说明:不等待;返回后消费NextEvent,再Prepare更新关注集合。旧通知不得替换为新token。
|
||
* 客户端故障通过收尾事件交付,服务级失败则由调用方保存诊断后Close。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Process(IEC104_Slave_t *self,
|
||
const IEC104_TcpReady_t *ready, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Process(self->server, ready, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:独立主循环使用的准备、至多一次网络等待和有界推进组合入口。
|
||
* 输入参数:self——已Init的服务;max_wait_ms——本轮网络等待上限,0不等待;error可为NULL。
|
||
* 返回值:OK表示服务可继续;客户端断线不一定使本函数失败,须读取NextEvent。
|
||
* 说明:等待还受协议期限和本地工作约束;max_wait_ms不保证整个函数的执行耗时。
|
||
* 不创建后台线程、不调用业务处理函数;每轮返回后由应用消费通知。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Poll(IEC104_Slave_t *self,
|
||
uint32_t max_wait_ms, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Poll(self->server, max_wait_ms, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:取出最早的TCP通知,并为有效I帧建立ASDU视图和原连接的发送接口。
|
||
* 输入参数:self——有效句柄,Close后仍可读取;event——地址稳定的非NULL输出存储。
|
||
* 返回值:READY替换整个event;EMPTY/ERROR保持上次事件及其视图不变。
|
||
* 说明:先读入临时量,只有取得通知后才覆盖输出;保存原服务serial和连接generation。
|
||
* READY仅表示取得事件,I帧还须检查asdu_result;视图失败仍保留原文,不自动确认。
|
||
* 不进行I/O;下一次READY会覆盖同一event,延迟处理须另存并调用Copy_Event修复自引用。
|
||
*/
|
||
IEC104_TcpEventResult_t IEC104_Slave_NextEvent(IEC104_Slave_t *self, IEC104_SlaveEvent_t *event)
|
||
{
|
||
IEC104_TcpEvent_t received;
|
||
IEC104_TcpEventResult_t result;
|
||
if (!Slave_Owned(self) || event == NULL) { return IEC104_TCP_EVENT_ERROR; }
|
||
result = IEC104_TcpServer_NextEvent(self->server, &received);
|
||
if (result != IEC104_TCP_EVENT_READY) { return result; }
|
||
memset(event, 0, sizeof(*event));
|
||
event->transport = received;
|
||
event->_owner = self;
|
||
event->_owner_serial = self->serial;
|
||
event->_generation = received.generation;
|
||
event->_last_submit_result = IEC104_TCP_SUBMIT_ERROR;
|
||
Bind_Event(event);
|
||
return IEC104_TCP_EVENT_READY;
|
||
}
|
||
|
||
/* 只校验事件自身布局和自引用,不要求原服务或TCP会话仍存活。
|
||
* Copy_Event允许保存历史原文;Get_Event_Identity在此基础上额外校验当前服务实例。
|
||
* 解析失败的I帧仍是有效通知,由业务处理其asdu_result,不在此丢弃诊断信息。 */
|
||
static bool Event_Valid(const IEC104_SlaveEvent_t *event)
|
||
{
|
||
const uint8_t *data;
|
||
uint16_t length;
|
||
if (event == NULL || event->_owner == NULL || event->_owner_serial == 0U ||
|
||
event->_generation == 0U || event->_generation != event->transport.generation ||
|
||
event->transport.type < IEC104_TCP_EVENT_CONNECTED ||
|
||
event->transport.type > IEC104_TCP_EVENT_DEACTIVATED) {
|
||
return false;
|
||
}
|
||
if (event->transport.type == IEC104_TCP_EVENT_I_FRAME && event->asdu_result == IEC60870_ASDU_OK) {
|
||
if (event->connection.object != event || Get_Context(&event->connection) != event ||
|
||
event->asdu.parameters != ¶meters104 ||
|
||
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK ||
|
||
data != event->transport.data.i_frame.apdu + IEC104_APCI_LEN ||
|
||
(uint32_t)length + IEC104_APCI_LEN != event->transport.data.i_frame.apdu_length) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 功能:把事件复制到稳定的目标存储,同时重建目标ASDU视图及发送接口的自引用。
|
||
* 输入参数:source——NextEvent或本函数产生的有效事件;destination——目标事件存储。
|
||
* 返回值:true已复制或源/目标为同一合法对象;false校验失败且不修改目标。
|
||
* 说明:先核对源视图仍借用源原文,避免传播普通赋值形成的悬空绑定。
|
||
* 保留原服务、代次和最近提交结果,不查询当前主站、不发送;历史事件也可复制保存。
|
||
* 复制只延长原文和接口存储的寿命,不能使已关闭或已被替换的原连接重新可用。
|
||
*/
|
||
bool IEC104_Slave_Copy_Event(IEC104_SlaveEvent_t *destination, const IEC104_SlaveEvent_t *source)
|
||
{
|
||
if (destination == NULL || !Event_Valid(source)) { return false; }
|
||
if (destination != source) { *destination = *source; Bind_Event(destination); }
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 功能:验证业务收到的Slave事件,交付可保存的服务实例和原连接身份。
|
||
* 参数:self为所属服务;event为原事件或Copy_Event副本;identity为非NULL输出。
|
||
* 返回:true写出身份;false为非法来源/布局/视图或参数,保持输出不变。
|
||
* 说明:只匹配服务实例,不用当前TCP代次拒绝历史通知;是否仍可推进另由Check_Request判断。
|
||
*/
|
||
bool IEC104_Slave_Get_Event_Identity(const IEC104_Slave_t *self,
|
||
const IEC104_SlaveEvent_t *event, IEC104_SlaveEventIdentity_t *identity)
|
||
{
|
||
IEC104_SlaveEventIdentity_t copy;
|
||
if (!Slave_Owned(self) || identity == NULL || !Event_Valid(event) ||
|
||
event->_owner != self || event->_owner_serial != self->serial) {
|
||
return false;
|
||
}
|
||
memset(©, 0, sizeof(copy));
|
||
copy.slave = self;
|
||
copy.instance_id = event->_owner_serial;
|
||
copy.generation = event->_generation;
|
||
*identity = copy;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 功能:统一判断业务是否应先处理通知、放弃失效请求,或继续当前会话的工作。
|
||
* 参数:identity为Get_Event_Identity的只读结果,允许零初始化表示尚未绑定会话。
|
||
* 返回:ERROR为参数/查询失败;WAIT_EVENTS优先于会话判断;STALE为原身份不再可用;READY可继续。
|
||
* 说明:检查通知FIFO、服务实例、TCP代次和传输激活状态,不执行I/O或保留发送窗口。
|
||
* 先读空通知可避免同一代次STOP后又START时,仅凭最终激活状态误续旧业务。
|
||
*/
|
||
IEC104_SlaveRequestState_t IEC104_Slave_Check_Request(const IEC104_Slave_t *self,
|
||
const IEC104_SlaveEventIdentity_t *identity)
|
||
{
|
||
IEC104_TcpServerStatus_t status;
|
||
if (!Slave_Owned(self) || identity == NULL ||
|
||
IEC104_TcpServer_Get_Status(self->server, &status, NULL) != IEC104_TCP_OK) {
|
||
return IEC104_SLAVE_REQUEST_ERROR;
|
||
}
|
||
if (status.pending_event_count != 0U) { return IEC104_SLAVE_REQUEST_WAIT_EVENTS; }
|
||
if (identity->slave != self || identity->instance_id != self->serial ||
|
||
identity->generation == 0U || identity->generation != status.generation ||
|
||
!status.connected || !status.connection_status_valid || !status.connection.data_transfer_started) {
|
||
return IEC104_SLAVE_REQUEST_STALE;
|
||
}
|
||
return IEC104_SLAVE_REQUEST_READY;
|
||
}
|
||
|
||
/**
|
||
* 功能:补充共用发送接口bool结果,查询这个事件连接最近一次提交的具体状态。
|
||
* 输入参数:connection——event.connection的地址;不是Slave句柄,也不是底层socket。
|
||
* 返回值:OK/WAIT/ERROR/FATAL;尚未发送或接口绑定无效时返回ERROR。
|
||
* 说明:记录只在提交时更新,不随后续Poll或主站ACK更新;发送完成事实应查看RECORD事件。
|
||
*/
|
||
IEC104_TcpSubmitResult_t IEC104_Slave_Get_Last_Submit_Result(
|
||
const IEC60870_MasterConnection_t *connection)
|
||
{
|
||
const IEC104_SlaveEvent_t *event = Get_Context(connection);
|
||
return event != NULL ? event->_last_submit_result : IEC104_TCP_SUBMIT_ERROR;
|
||
}
|
||
|
||
/**
|
||
* 功能:取得本接入使用的104应用层参数,供业务构造主动上报或独立应答ASDU。
|
||
* 输入参数:self——有效Slave句柄,不要求已Init或当前在线。
|
||
* 返回值:静态只读参数地址;无效句柄返回NULL,调用方不能修改或释放该参数。
|
||
*/
|
||
const IEC60870_AppLayerParameters_t *IEC104_Slave_Get_Application_Layer_Parameters(
|
||
const IEC104_Slave_t *self)
|
||
{
|
||
return Slave_Owned(self) ? ¶meters104 : NULL;
|
||
}
|
||
|
||
/**
|
||
* 功能:校验ASDU布局后复制到服务级NORMAL队列,用于主动上报。
|
||
* 输入参数:self——已Init的服务;asdu——合法且与104布局兼容的ASDU,不包含APCI。
|
||
* 返回值:OK已复制,WAIT未接手可重试,ERROR参数/状态非法,FATAL底层队列异常。
|
||
* 说明:允许首次接入前及断线期间缓存,本次不直接写socket;空间不足可覆盖最旧NORMAL。
|
||
* Close清空缓存;重连恢复可能重复,业务应按自身语义处理。请求应答使用事件connection。
|
||
*/
|
||
IEC104_TcpSubmitResult_t IEC104_Slave_Enqueue_Event(IEC104_Slave_t *self, IEC60870_Asdu_t *asdu)
|
||
{
|
||
const uint8_t *data;
|
||
uint16_t length;
|
||
if (!Slave_Owned(self) || asdu == NULL || !Parameters_Compatible(asdu->parameters) ||
|
||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK) {
|
||
return IEC104_TCP_SUBMIT_ERROR;
|
||
}
|
||
return IEC104_TcpServer_Enqueue_Event(self->server, data, length);
|
||
}
|
||
|
||
/**
|
||
* 功能:读取当前主站的IPv4、源端口和连接代次副本,不执行网络查询。
|
||
* 输入参数:self——已Init的服务;peer——非NULL输出存储;error——可选诊断。
|
||
* 返回值:仅OK写peer;无当前连接为NOT_CONNECTED,其他失败也保持peer原值。
|
||
* 说明:这是当前状态;历史事件的地址应从事件自身读取,不能用新主站地址替代旧来源。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Get_Remote_Address(const IEC104_Slave_t *self,
|
||
IEC104_TcpPeerInfo_t *peer, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Get_Remote_Address(self->server, peer, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:按事件捕获的原连接身份主动断开主站,保留监听和服务级NORMAL。
|
||
* 输入参数:self——所属Slave;event——本服务事件或Copy_Event副本;error——可选诊断。
|
||
* 返回值:OK已断开;其他服务/已复用Slave的事件为INVALID_ARGUMENT;当前无连接为NOT_CONNECTED;
|
||
* 仍有连接但代次不同为STALE_GENERATION;底层服务异常通过原结果和error返回。
|
||
* 说明:来源serial先由本层检查,原generation再由TcpServer核对;不用当前代次代替旧身份。
|
||
* 不等待STOPDT或ACK,成功后继续读取收尾事件,再Prepare更新监听条件。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Close_Connection(IEC104_Slave_t *self,
|
||
const IEC104_SlaveEvent_t *event, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
if (event == NULL || event->_owner != self || event->_owner_serial != self->serial ||
|
||
event->_generation == 0U) {
|
||
return Slave_Error(IEC104_TCP_INVALID_ARGUMENT, error);
|
||
}
|
||
return IEC104_TcpServer_Close_Connection(self->server, event->_generation, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:取得内部TCP服务、队列和最近会话的状态副本,不推进协议或消费队列。
|
||
* 输入参数:self——有效句柄;status——非NULL输出存储;error——可选诊断。
|
||
* 返回值:OK写入status,失败保持输出;Create后、Init失败后和Close后均可查询。
|
||
* 说明:会话和队列各自有有效标志,connected与data_transfer_started含义不同。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Get_Status(const IEC104_Slave_t *self,
|
||
IEC104_TcpServerStatus_t *status, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Get_Status(self->server, status, error);
|
||
}
|
||
|
||
/**
|
||
* 功能:关闭监听和当前连接,清空待发ASDU,但保留已生成的业务及收尾通知。
|
||
* 输入参数:self——尚未Destroy的句柄;error——可选的本次调用诊断。
|
||
* 返回值:OK已收尾或本来未初始化;无效句柄、重入按底层约定返回错误。
|
||
* 说明:不等待业务发送/ACK,不归还对象池名额;读空事件后可重新Init或Destroy。
|
||
* 自建事件循环还须撤销旧socket注册和定时器,不能将其通知用于下一次Init。
|
||
*/
|
||
IEC104_TcpResult_t IEC104_Slave_Close(IEC104_Slave_t *self, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
return IEC104_TcpServer_Close(self->server, error);
|
||
}
|