803 lines
40 KiB
C
803 lines
40 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会话。
|
||
* 事件保存两者,防止延迟应答误发给重连的主站或静态槽复用后的另一服务。
|
||
* 共用站点表只管理CA路由和节点寿命;总召等业务按注册编号保留原站,点表仍由应用管理。
|
||
* 节点的引用寿命与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后不再复用该槽。 */
|
||
IEC104_Station_t *stations; /* 调用方节点按注册顺序排列,含待回收的退役节点;不另分配站点池。 */
|
||
uint32_t station_locks; /* 固定站点集合的嵌套借用计数,不是线程锁。 */
|
||
uint32_t station_snapshots; /* 活动遍历期间不回收退役节点,保护所有游标。 */
|
||
uint64_t station_serial; /* 注册编号不随Destroy清零,避免同CA替换/服务槽复用混淆。 */
|
||
};
|
||
|
||
_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;
|
||
}
|
||
|
||
/* 先在本服务的链表中按地址查找,再读取目标节点;外来节点不借用其_owner字段找服务。
|
||
* 同时接受尚未回收的退役节点,以便原业务继续Acquire/Release;这里不判断CA路由资格。 */
|
||
static IEC104_Station_t *find_owned_station(const IEC104_Slave_t *self,
|
||
const IEC104_Station_t *station)
|
||
{
|
||
IEC104_Station_t *current;
|
||
if (!Slave_Owned(self) || station == NULL) { return NULL; }
|
||
for (current = self->stations; current != NULL; current = current->_next) {
|
||
if (current == station) { return current; }
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* 活动快照可能停在任意节点,因此全部快照End前都不摘除退役节点。
|
||
* 无快照且已删除、引用归零时才摘链并清零;不释放调用方的节点或context存储。
|
||
* Remove/Release/快照End均尝试回收,调用方据Is_Reusable决定何时释放自己的存储。 */
|
||
static void collect_retired_stations(IEC104_Slave_t *self)
|
||
{
|
||
IEC104_Station_t **entry = &self->stations;
|
||
if (self->station_snapshots != 0U) { return; }
|
||
while (*entry != NULL) {
|
||
IEC104_Station_t *station = *entry;
|
||
if (!station->_registered && station->_users == 0U) {
|
||
*entry = station->_next;
|
||
memset(station, 0, sizeof(*station));
|
||
} else { entry = &station->_next; }
|
||
}
|
||
}
|
||
|
||
/* 借用调用方节点,成功前不改链表或节点;不创建通信资源,不分配点表。 */
|
||
IEC104_StationResult_t IEC104_Slave_Add_Station(IEC104_Slave_t *self,
|
||
IEC104_Station_t *station, uint16_t common_address, void *context)
|
||
{
|
||
IEC104_Station_t **tail;
|
||
if (!Slave_Owned(self) || station == NULL || common_address == 0U ||
|
||
common_address == IEC104_CA_BROADCAST) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
if (station->_owner != NULL) { return IEC104_STATION_ALREADY_REGISTERED; }
|
||
if (self->station_locks != 0U) { return IEC104_STATION_BUSY; }
|
||
if (self->station_serial == UINT64_MAX) { return IEC104_STATION_LIMIT; }
|
||
for (tail = &self->stations; *tail != NULL; tail = &(*tail)->_next) {
|
||
if ((*tail)->_registered && (*tail)->_common_address == common_address) {
|
||
return IEC104_STATION_DUPLICATE_CA;
|
||
}
|
||
}
|
||
station->_next = NULL;
|
||
station->_owner = self;
|
||
station->_context = context;
|
||
station->_users = 0U;
|
||
station->_common_address = common_address;
|
||
station->_id = ++self->station_serial;
|
||
station->_registered = true;
|
||
*tail = station;
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
/* 删除先退出路由,引用和快照决定实际回收时机;OK不代表节点已可释放。
|
||
* 保留中的旧节点不会挡住相同CA的新注册,原业务仍通过注册编号找到旧节点。 */
|
||
IEC104_StationResult_t IEC104_Slave_Remove_Station(IEC104_Slave_t *self, IEC104_Station_t *station)
|
||
{
|
||
if (!Slave_Owned(self) || station == NULL) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
if (find_owned_station(self, station) == NULL || !station->_registered) { return IEC104_STATION_NOT_FOUND; }
|
||
if (self->station_locks != 0U) { return IEC104_STATION_BUSY; }
|
||
station->_registered = false;
|
||
collect_retired_stations(self);
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
/* 新请求只匹配仍注册的节点;返回借用指针,不自动增加引用或决定广播范围。 */
|
||
IEC104_Station_t *IEC104_Slave_Get_Station(const IEC104_Slave_t *self, uint16_t common_address)
|
||
{
|
||
IEC104_Station_t *station;
|
||
if (!Slave_Owned(self) || common_address == 0U || common_address == IEC104_CA_BROADCAST) { return NULL; }
|
||
for (station = self->stations; station != NULL; station = station->_next) {
|
||
if (station->_registered && station->_common_address == common_address) { return station; }
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* 不另存可失配的数量;上限仅由可注册CA和调用方存储决定。 */
|
||
size_t IEC104_Slave_Get_Station_Count(const IEC104_Slave_t *self)
|
||
{
|
||
const IEC104_Station_t *station;
|
||
size_t count = 0U;
|
||
if (!Slave_Owned(self)) { return 0U; }
|
||
for (station = self->stations; station != NULL; station = station->_next) {
|
||
if (station->_registered) { ++count; }
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/* 普通遍历读取当前注册集合;跨循环固定集合须Lock,允许增删的遍历须用Snapshot。 */
|
||
IEC104_Station_t *IEC104_Slave_Next_Station(const IEC104_Slave_t *self, const IEC104_Station_t *current)
|
||
{
|
||
IEC104_Station_t *station;
|
||
if (!Slave_Owned(self)) { return NULL; }
|
||
station = current == NULL ? self->stations : find_owned_station(self, current);
|
||
if (current != NULL && station != NULL) { station = station->_next; }
|
||
while (station != NULL && !station->_registered) { station = station->_next; }
|
||
return station;
|
||
}
|
||
|
||
/* 固定遍历集合,不锁点值;串行事件循环中的嵌套借用必须显式配对。 */
|
||
IEC104_StationResult_t IEC104_Slave_Lock_Stations(IEC104_Slave_t *self)
|
||
{
|
||
if (!Slave_Owned(self)) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
if (self->station_locks == UINT32_MAX) { return IEC104_STATION_LIMIT; }
|
||
++self->station_locks;
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
IEC104_StationResult_t IEC104_Slave_Unlock_Stations(IEC104_Slave_t *self)
|
||
{
|
||
if (!Slave_Owned(self) || self->station_locks == 0U) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
--self->station_locks;
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
/* 持有具体节点,不重新按CA路由;尚被保留的退役节点也能取得引用,供原业务继续处理。 */
|
||
IEC104_StationResult_t IEC104_Slave_Acquire_Station(IEC104_Slave_t *self, IEC104_Station_t *station)
|
||
{
|
||
if (!Slave_Owned(self) || station == NULL) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
if (find_owned_station(self, station) == NULL) { return IEC104_STATION_NOT_FOUND; }
|
||
if (station->_users == UINT32_MAX) { return IEC104_STATION_LIMIT; }
|
||
++station->_users;
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
/* 归还自己的引用后可能立即摘链清零;没有其他寿命保护时,不再使用该节点或context。 */
|
||
IEC104_StationResult_t IEC104_Slave_Release_Station(IEC104_Slave_t *self, IEC104_Station_t *station)
|
||
{
|
||
if (!Slave_Owned(self) || station == NULL) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
if (find_owned_station(self, station) == NULL) { return IEC104_STATION_NOT_FOUND; }
|
||
if (station->_users == 0U) { return IEC104_STATION_INVALID_ARGUMENT; }
|
||
--station->_users;
|
||
collect_retired_stations(self);
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
/* 延迟事件按原注册编号找回节点,包括被引用/快照保留的退役节点,避免同CA替换串站。
|
||
* 与按CA查找一样只返回借用指针;需要独立持有时仍须Acquire。 */
|
||
IEC104_Station_t *IEC104_Slave_Get_Station_By_Id(const IEC104_Slave_t *self, uint64_t station_id)
|
||
{
|
||
IEC104_Station_t *station;
|
||
if (!Slave_Owned(self) || station_id == 0U) { return NULL; }
|
||
for (station = self->stations; station != NULL; station = station->_next) {
|
||
if (station->_id == station_id) { return station; }
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
uint64_t IEC104_Station_Get_Id(const IEC104_Station_t *station)
|
||
{
|
||
return station != NULL && station->_owner != NULL ? station->_id : 0U;
|
||
}
|
||
|
||
bool IEC104_Station_Is_Registered(const IEC104_Station_t *station)
|
||
{
|
||
return station != NULL && station->_owner != NULL && station->_registered;
|
||
}
|
||
|
||
bool IEC104_Station_Is_Reusable(const IEC104_Station_t *station)
|
||
{
|
||
return station != NULL && station->_owner == NULL;
|
||
}
|
||
|
||
/* 只保存注册编号上界和遍历位置,不冻结IOA或点值,也不阻止站点增删。
|
||
* 注册编号单调递增,后来加入的同CA替代节点也会落在本次上界之外。 */
|
||
IEC104_StationResult_t IEC104_Slave_StationSnapshot_Begin(IEC104_Slave_t *self,
|
||
IEC104_StationSnapshot_t *snapshot)
|
||
{
|
||
if (!Slave_Owned(self) || snapshot == NULL || snapshot->_owner != NULL) {
|
||
return IEC104_STATION_INVALID_ARGUMENT;
|
||
}
|
||
if (self->station_snapshots == UINT32_MAX) { return IEC104_STATION_LIMIT; }
|
||
snapshot->_owner = self;
|
||
snapshot->_next = self->stations;
|
||
snapshot->_last_id = self->station_serial;
|
||
snapshot->_self = snapshot;
|
||
++self->station_snapshots;
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
/* 先推进游标再返回当前仍注册的节点;删除的未来节点跳过,新增节点不纳入。
|
||
* 快照保护节点存储但不自动Acquire;返回NULL只表示遍历完毕,仍须End归还保护。 */
|
||
IEC104_Station_t *IEC104_Slave_StationSnapshot_Next(IEC104_StationSnapshot_t *snapshot)
|
||
{
|
||
if (snapshot == NULL || snapshot->_self != snapshot || !Slave_Owned(snapshot->_owner)) { return NULL; }
|
||
while (snapshot->_next != NULL) {
|
||
IEC104_Station_t *station = snapshot->_next;
|
||
snapshot->_next = station->_next;
|
||
if (station->_id > snapshot->_last_id) { snapshot->_next = NULL; return NULL; }
|
||
if (station->_registered) { return station; }
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* 与Begin配对一次;最后一个快照结束会尝试回收所有无引用的退役节点。
|
||
* 业务若要跨End继续使用Next返回的节点,必须提前Acquire。 */
|
||
IEC104_StationResult_t IEC104_Slave_StationSnapshot_End(IEC104_StationSnapshot_t *snapshot)
|
||
{
|
||
IEC104_Slave_t *self;
|
||
if (snapshot == NULL || snapshot->_self != snapshot || !Slave_Owned(snapshot->_owner)) {
|
||
return IEC104_STATION_INVALID_ARGUMENT;
|
||
}
|
||
self = snapshot->_owner;
|
||
--self->station_snapshots;
|
||
memset(snapshot, 0, sizeof(*snapshot));
|
||
collect_retired_stations(self);
|
||
return IEC104_STATION_OK;
|
||
}
|
||
|
||
uint16_t IEC104_Station_Get_Common_Address(const IEC104_Station_t *station)
|
||
{
|
||
return station != NULL && station->_owner != NULL ? station->_common_address : 0U;
|
||
}
|
||
|
||
void *IEC104_Station_Get_Context(const IEC104_Station_t *station)
|
||
{
|
||
return station != NULL && station->_owner != NULL ? station->_context : NULL;
|
||
}
|
||
|
||
/**
|
||
* 功能:返回本层校验失败的结果,同时完整填写可选的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;
|
||
}
|
||
|
||
/* 完整事件和紧凑身份共用发送校验,不把旧身份替换成当前连接。
|
||
* self只在确认仍被静态池占用后解引用;队列策略、发送期限和FATAL语义保持由TcpServer处理。 */
|
||
static IEC104_TcpSubmitResult_t Submit_Response(IEC104_Slave_t *self,
|
||
const IEC104_SlaveEventIdentity_t *identity, const IEC60870_Asdu_t *asdu, int bypass_queue)
|
||
{
|
||
const uint8_t *data;
|
||
uint16_t length;
|
||
if (!Slave_Owned(self) || identity == NULL || identity->slave != self ||
|
||
identity->instance_id != self->serial || identity->generation == 0U ||
|
||
asdu == NULL || !Parameters_Compatible(asdu->parameters) ||
|
||
IEC60870_Asdu_Get_Data(asdu, &data, &length) != IEC60870_ASDU_OK) {
|
||
return IEC104_TCP_SUBMIT_ERROR;
|
||
}
|
||
return bypass_queue ? IEC104_TcpServer_Try_Send_Response(self->server, identity->generation, data, length) :
|
||
IEC104_TcpServer_Send_Response(self->server, identity->generation, data, length);
|
||
}
|
||
|
||
IEC104_TcpSubmitResult_t IEC104_Slave_Send_Response(IEC104_Slave_t *self,
|
||
const IEC104_SlaveEventIdentity_t *identity, const IEC60870_Asdu_t *asdu)
|
||
{
|
||
return Submit_Response(self, identity, asdu, 0);
|
||
}
|
||
|
||
IEC104_TcpSubmitResult_t IEC104_Slave_Try_Send_Response(IEC104_Slave_t *self,
|
||
const IEC104_SlaveEventIdentity_t *identity, const IEC60870_Asdu_t *asdu)
|
||
{
|
||
return Submit_Response(self, identity, asdu, 1);
|
||
}
|
||
|
||
IEC104_TcpSubmitResult_t IEC104_Slave_Reject_Request(IEC104_Slave_t *self,
|
||
const IEC104_SlaveEvent_t *event, int cause)
|
||
{
|
||
uint8_t bytes[IEC104_MAX_ASDU_LEN];
|
||
const uint8_t *data;
|
||
uint16_t length, reply_length;
|
||
IEC60870_Asdu_t reply;
|
||
IEC104_SlaveEventIdentity_t identity;
|
||
IEC104_TcpServerStatus_t status;
|
||
if (cause < IEC60870_ASDU_COT_UNKNOWN_TYPE || cause > IEC60870_ASDU_COT_UNKNOWN_IOA ||
|
||
!IEC104_Slave_Get_Event_Identity(self, event, &identity) ||
|
||
event->transport.type != IEC104_TCP_EVENT_I_FRAME || event->asdu_result != IEC60870_ASDU_OK ||
|
||
IEC104_TcpServer_Get_Status(self->server, &status, NULL) != IEC104_TCP_OK) {
|
||
return IEC104_TCP_SUBMIT_ERROR;
|
||
}
|
||
/* 本次事件之后尚未消费的启停通知表明链路已停用或曾停用,不能跨启停提交应答。 */
|
||
if (status.pending_state_event_count != 0U ||
|
||
IEC60870_Asdu_Get_Data(&event->asdu, &data, &length) != IEC60870_ASDU_OK ||
|
||
IEC60870_Asdu_Build_Response(¶meters104, data, length, cause, 1,
|
||
bytes, sizeof(bytes), &reply_length) != IEC60870_ASDU_OK ||
|
||
IEC60870_Asdu_Create_From_Buffer(&reply, ¶meters104, bytes, reply_length) != IEC60870_ASDU_OK) {
|
||
return IEC104_TCP_SUBMIT_ERROR;
|
||
}
|
||
return Submit_Response(self, &identity, &reply, 0);
|
||
}
|
||
|
||
/**
|
||
* 功能:把普通/不排队应答交给产生该事件的原服务、原会话。
|
||
* 输入参数:connection——事件内已绑定的接口;asdu——布局兼容的完整ASDU,可独立于接收视图。
|
||
* 返回值:透传OK/WAIT/ERROR/FATAL并记录在原事件中;普通send_asdu包装为bool。
|
||
* 说明:先检查Slave所有权和serial,再由TcpServer检查generation、启停状态及发送容量。
|
||
* OK表示已复制接手,不代表写完或收到ACK;WAIT未接手,业务保存原事件/ASDU后择机重试。
|
||
* 不查找当前新连接来替换旧身份;FATAL可能已经写出字节,不能作为未发送直接重放。
|
||
*/
|
||
static IEC104_TcpSubmitResult_t Submit_ASDU(IEC60870_MasterConnection_t *connection,
|
||
const IEC60870_Asdu_t *asdu, int bypass_queue)
|
||
{
|
||
IEC104_SlaveEvent_t *event = Get_Context(connection);
|
||
IEC104_SlaveEventIdentity_t identity;
|
||
if (event == NULL) { return IEC104_TCP_SUBMIT_ERROR; }
|
||
identity.slave = event->_owner;
|
||
identity.instance_id = event->_owner_serial;
|
||
identity.generation = event->_generation;
|
||
event->_last_submit_result = Submit_Response(event->_owner, &identity, asdu, bypass_queue);
|
||
return event->_last_submit_result;
|
||
}
|
||
|
||
static bool Send_ASDU(IEC60870_MasterConnection_t *connection, IEC60870_Asdu_t *asdu)
|
||
{
|
||
return Submit_ASDU(connection, asdu, 0) == IEC104_TCP_SUBMIT_OK;
|
||
}
|
||
|
||
/* 104专用不排队入口;不改变共用连接接口的函数指针或普通应答排队约定。 */
|
||
IEC104_TcpSubmitResult_t IEC104_Slave_Try_Send_ASDU(
|
||
IEC60870_MasterConnection_t *connection, const IEC60870_Asdu_t *asdu)
|
||
{
|
||
return Submit_ASDU(connection, asdu, 1);
|
||
}
|
||
|
||
/**
|
||
* 功能:修改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); }
|
||
if (self->stations != NULL || self->station_locks != 0U || self->station_snapshots != 0U) {
|
||
return Slave_Error(IEC104_TCP_INVALID_STATE, 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);
|
||
}
|
||
|
||
/* 紧凑业务上下文只需保留来源身份即可关闭原会话;校验与完整事件关闭入口一致。 */
|
||
IEC104_TcpResult_t IEC104_Slave_Close_Connection_By_Identity(IEC104_Slave_t *self,
|
||
const IEC104_SlaveEventIdentity_t *identity, IEC104_TcpError_t *error)
|
||
{
|
||
if (!Slave_Owned(self)) { return Slave_Error(IEC104_TCP_INVALID_HANDLE, error); }
|
||
if (identity == NULL || identity->slave != self || identity->instance_id != self->serial ||
|
||
identity->generation == 0U) {
|
||
return Slave_Error(IEC104_TCP_INVALID_ARGUMENT, error);
|
||
}
|
||
return IEC104_TcpServer_Close_Connection(self->server, identity->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);
|
||
}
|