/* * 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 /* 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); }