/** * 公共链路逻辑:初始化、完整状态校验、I/S序号、k/w窗口、S确认与TESTFR。 * 主站启停见IEC104_Link_Master_StartStop.c,子站启停见IEC104_Link_Slave_StartStop.c。 * 本文件不执行TCP收发;连接调度层串行提交接收、发送完成和时间事件。 * * 协作边界:本文件定义Internal.h中的跨文件辅助函数,供两个角色实现调用。 * State_Valid按本端角色调用本文件内的static过程校验,不反向依赖角色实现。 * 带_Internal后缀的函数具有外部链接,但只供链路实现使用;其余局部辅助函数 * 采用static。公开入口负责校验,内部步骤按各自前置条件复用已校验的状态。 * 主要提交点:Prepare_I推进vs;Handle_I/S推进va(I另推进vr); * Complete_I_Send/Complete_S_Ack_Send提交实际发送的N(R),其中I帧完成还推进sent_vs。 */ #include "IEC104_Link_Common.h" #include "IEC104_Link_Internal.h" #include /* 仅用于本文件内I/S帧的序号规划;只有READY时才读取快照并编码。 */ typedef enum { IEC104_PLAN_ERROR, IEC104_PLAN_SKIPPED, IEC104_PLAN_READY } IEC104_PlanResult_t; /** * 功能:计算15位IEC-104序号从from前进到to的距离。 * 输入参数:from——起始序号;to——目标序号。 * 输出参数:无。 * 返回值:0至32767范围内的循环距离。 */ static uint16_t Number_Distance(uint16_t from, uint16_t to) { /* 按15位序号环计算距离,使32767到0的距离为1。 */ return (uint16_t)((to - from) & IEC104_SEQUENCE_MASK); } /** * 功能:将15位IEC-104序号循环加1。 * 输入参数:value——当前序号。 * 输出参数:无。 * 返回值:递增后的序号;32767的下一个序号为0。 */ static uint16_t Number_Increment(uint16_t value) { /* 与0x7FFF相与,自动将32768折回0。 */ return (uint16_t)((value + 1U) & IEC104_SEQUENCE_MASK); } /** * 功能:检查序号、稳定状态、标志和TESTFR等公共基础字段。 * 输入参数:state——链路状态。 * 输出参数:无。 * 返回值:公共基础字段合法返回1,否则返回0。 * 说明:S确认、角色和启停过程由后续完整校验检查。 */ static int Link_State_Fields_Valid(const IEC104_LinkState_t *state) { if (state == NULL || state->vs > IEC104_SEQUENCE_MASK || state->sent_vs > IEC104_SEQUENCE_MASK || state->va > IEC104_SEQUENCE_MASK || state->vr > IEC104_SEQUENCE_MASK || state->acked_nr > IEC104_SEQUENCE_MASK || state->s_ack_nr > IEC104_SEQUENCE_MASK) { return 0; } /* 确认位置不得越过本地完成位置;用共同尾端的循环距离检查,兼容序号回绕。 */ if (Number_Distance(state->sent_vs, state->vs) > Number_Distance(state->va, state->vs)) { return 0; } switch (state->dt_state) { case IEC104_DT_STOPPED: case IEC104_DT_STARTED: break; default: return 0; } switch (state->testfr_state) { case IEC104_TESTFR_IDLE: case IEC104_TESTFR_ACT_SEND_PENDING: case IEC104_TESTFR_CON_PENDING: return 1; default: return 0; } } /** * 功能:计算已经接收但本端尚未通过I/S帧确认的I帧数量。 * 输入参数:state——序号字段有效的链路状态。 * 输出参数:无。 * 返回值:acked_nr到V(R)之间的15位循环距离。 * 前置条件:state非NULL,acked_nr和vr为15位序号,未确认区间未占满序号环。 * 调用关系:公共确认逻辑及Slave停止排空共用,因此保留外部链接;不重复校验。 */ uint16_t IEC104_Link_Unconfirmed_Rx_Internal(const IEC104_LinkState_t *state) { return Number_Distance(state->acked_nr, state->vr); } /** * 功能:判断接收确认已完成,或已由在途S帧覆盖当前接收位置。 * 输入参数:state——已通过完整合法性检查的链路状态。 * 输出参数:无,不修改state。 * 返回值:1表示acked_nr或在途S帧已覆盖当前V(R),否则返回0。 * 说明:不要求S完成回调已提交;不能用只覆盖旧V(R)的S帧替代最新确认。 * 调用关系:Master准备STOPDT_ACT和接收STOPDT_CON时分别检查,不能缓存旧结果。 * 返回1不推进acked_nr;在途S仍须按FIFO完成,失败仍须断连。 */ int IEC104_Link_Rx_Ack_Covered_Internal(const IEC104_LinkState_t *state) { return state->acked_nr == state->vr || (state->s_ack_state == IEC104_S_ACK_SEND_PENDING && state->s_ack_nr == state->vr); } /** * 功能:计算已提交发送完成、且尚未被对端确认的I帧数量。 * 输入参数:state——合法链路状态,确认位置没有越过本地完成位置。 * 输出参数:无,不修改state。 * 返回值:需等待对端确认的已完成帧数;仅有排队帧时返回0。 * 说明:ACK必须在对应Complete之后处理,已完成未确认区间为va..sent_vs。 */ static uint16_t Sent_Unacknowledged_I_Count(const IEC104_LinkState_t *state) { return Number_Distance(state->va, state->sent_vs); } /** * 功能:检查普通S确认帧的发送状态和快照组合是否一致。 * 输入参数:state——待检查的链路状态。 * 输出参数:无。 * 返回值:组合一致返回1,否则返回0。 * 说明:调用前必须已经通过Link_State_Fields_Valid()。 */ static int S_Ack_State_Valid(const IEC104_LinkState_t *state) { switch (state->s_ack_state) { case IEC104_S_ACK_IDLE: return state->s_ack_nr == 0U; case IEC104_S_ACK_SEND_PENDING: /* N(R)=0是合法快照,是否在当前确认区间内留到完成时检查。 */ return 1; default: return 0; } } /** * 功能:检查主站dt_state与dt_procedure组合,拒绝子站专用过程。 * 前置条件:state非NULL,公共字段及S状态已合法,role为CONTROLLING。 * 返回值:IDLE允许两种稳定状态;启动过程须STOPPED,停止过程须STARTED; * 满足时返回1,其余返回0。只校验组合,不修改状态或验证报文。 * 调用关系:仅供本文件内State_Valid按角色分派,使用static内部链接。 * 不得反调State_Valid/Role_Valid,否则形成递归。 */ static int Master_State_Consistent(const IEC104_LinkState_t *state) { switch (state->dt_procedure) { case IEC104_DT_PROC_IDLE: return 1; case IEC104_DT_PROC_START_ACT_SEND_PENDING: case IEC104_DT_PROC_START_CON_WAITING: return state->dt_state == IEC104_DT_STOPPED; case IEC104_DT_PROC_STOP_REQUESTED: case IEC104_DT_PROC_STOP_ACT_SEND_PENDING: case IEC104_DT_PROC_STOP_CON_WAITING: return state->dt_state == IEC104_DT_STARTED; default: return 0; } } /** * 子站只使用IDLE和STOP_DRAINING;后者表示已停用、等待确认完成。 * CON发送进度归SendBuffer,不参与启停组合校验。 */ static int Slave_State_Consistent(const IEC104_LinkState_t *state) { return state->dt_procedure == IEC104_DT_PROC_IDLE || (state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING && state->dt_state == IEC104_DT_STOPPED); } /** * 功能:完整检查链路状态的字段定义域和跨流程一致性。 * 输入参数:state——链路状态;允许NULL,返回0。 * 输出参数:无,不修改state。 * 返回值:公共字段、S状态及本端角色的过程组合合法返回1,否则返回0。 * 调用关系:公共入口直接调用,角色入口经Role_Valid调用;本函数依次检查 * Fields、S_Ack,再分派本文件内的Master/Slave_State_Consistent,不会推进过程。 * 说明:角色末级校验不得反调本函数或Role_Valid,避免递归;本检查不证明事件 * 属于当前连接,也不替代本次N(S)/N(R)、发送长度和FIFO顺序检查。 */ int IEC104_Link_State_Valid_Internal(const IEC104_LinkState_t *state) { if (!Link_State_Fields_Valid(state) || !S_Ack_State_Valid(state)) { return 0; } switch (state->role) { case IEC104_ROLE_CONTROLLING: return Master_State_Consistent(state); case IEC104_ROLE_CONTROLLED: return Slave_State_Consistent(state); default: return 0; } } /** * 功能:校验链路后生成只含数值的I帧发送进度快照。 * 输入参数:state——本连接链路状态;所有访问须串行。 * 输出参数:progress——仅返回1时写入,不得与state重叠;失败时保持原值。 * 返回值:1表示成功;0表示指针或完整链路状态非法。 * 说明:由本模块把内部序号映射为公开的进度含义,不提交任何接收或完成事件。 */ int IEC104_Link_Get_Send_Progress( const IEC104_LinkState_t *state, IEC104_IFrameSendProgress_t *progress) { IEC104_IFrameSendProgress_t prepared; if (progress == NULL || !IEC104_Link_State_Valid_Internal(state)) { return 0; } prepared.next_frame_ns = state->vs; prepared.next_completion_ns = state->sent_vs; prepared.peer_ack_nr = state->va; *progress = prepared; return 1; } /** * 功能:生成链路观察快照,由Link统一解释未确认接收数量及启停/探测状态。 * 输入参数:state——当前链路;status——非NULL且不与state重叠的输出。 * 返回值:1成功并写status,0参数或完整状态非法;失败不改输出,不预留或推进链路。 * 说明:连接层可据未确认数量结束t2区间;控制动作准入另用Can_Prepare接口。 */ int IEC104_Link_Get_Status(const IEC104_LinkState_t *state, IEC104_LinkStatus_t *status) { IEC104_LinkStatus_t prepared; if (status == NULL || !IEC104_Link_State_Valid_Internal(state)) { return 0; } prepared.role = state->role; prepared.dt_state = state->dt_state; prepared.dt_procedure = state->dt_procedure; prepared.testfr_state = state->testfr_state; prepared.unconfirmed_rx_count = IEC104_Link_Unconfirmed_Rx_Internal(state); *status = prepared; return 1; } /** * 功能:检查链路是否符合绑定新连接发送管理器的初始状态条件。 * 输入参数:state——待检查状态,允许NULL。 * 输出参数:无,不修改链路。 * 返回值:状态合法、所有序号为0、STOPPED且各过程空闲时返回1,否则0。 * 说明:完整校验同时保证空闲S快照为0;不检查连接历史或实际TCP状态。 */ int IEC104_Link_Is_Initial_State(const IEC104_LinkState_t *state) { return IEC104_Link_State_Valid_Internal(state) && state->vs == 0U && state->sent_vs == 0U && state->va == 0U && state->vr == 0U && state->acked_nr == 0U && state->dt_state == IEC104_DT_STOPPED && state->dt_procedure == IEC104_DT_PROC_IDLE && state->testfr_state == IEC104_TESTFR_IDLE && state->s_ack_state == IEC104_S_ACK_IDLE; } /** * 功能:判断对端确认号是否位于V(A)到已提交本地完成位置sent_vs之间。 * 输入参数:state——有效链路状态;receive_number——对端携带的N(R)。 * 输出参数:无。 * 返回值:N(R)为15位且位于va到sent_vs的前向闭区间时返回1;越过完成位置或退回va之前均返回0。 */ static int Acknowledge_Number_Valid(const IEC104_LinkState_t *state, uint16_t receive_number) { uint16_t outstanding_count; uint16_t acknowledged_count; if (receive_number > IEC104_SEQUENCE_MASK) { return 0; } outstanding_count = Number_Distance(state->va, state->sent_vs); acknowledged_count = Number_Distance(state->va, receive_number); /* Prepare只预留序号;必须先提交对应Complete,才允许ACK覆盖该帧。 */ return acknowledged_count <= outstanding_count; } /** * 功能:提交一份本地已经完整发送的I/S帧所携带的N(R)快照。 * 输入参数:state——已通过完整校验的链路状态;receive_number——实际上线报文携带的N(R)。 * 输出参数:快照合法时推进acked_nr;非法时不修改状态。 * 返回值:OK表示快照已提交;FATAL表示快照非法。 * 说明:这是I/S完成流程共用的内部操作,不判断具体帧类型或发送长度。 * Complete_I_Send和Complete_S_Ack_Send已校验state;这里仅校验确认号定义域 * 和acked_nr..vr区间。返回OK表示本地发送提交,不表示对端已处理该确认。 */ static IEC104_LinkResult_t Commit_Sent_NR( IEC104_LinkState_t *state, uint16_t receive_number) { uint16_t acknowledged_count; uint16_t outstanding_count; if (receive_number > IEC104_SEQUENCE_MASK) { return IEC104_LINK_RESULT_FATAL; } acknowledged_count = Number_Distance(state->acked_nr, receive_number); outstanding_count = IEC104_Link_Unconfirmed_Rx_Internal(state); if (acknowledged_count > outstanding_count) { return IEC104_LINK_RESULT_FATAL; } state->acked_nr = receive_number; return IEC104_LINK_RESULT_OK; } /** * 功能:把链路状态对象设置为指定角色的新连接初始状态。 * 输入参数:state——非NULL状态对象;role——已由调用方校验或规范化的角色。 * 输出参数:保留传入角色,清零序号并复位确认、启停和探测状态。 * 返回值:无。 * 说明:仅供State_Init与State_Reset复用,不负责校验指针和角色。 */ static void Initialize_State_Fields(IEC104_LinkState_t *state, IEC104_LinkRole_t role) { state->role = role; state->vs = 0U; state->sent_vs = 0U; state->va = 0U; state->vr = 0U; state->acked_nr = 0U; state->s_ack_state = IEC104_S_ACK_IDLE; state->s_ack_nr = 0U; state->dt_state = IEC104_DT_STOPPED; state->dt_procedure = IEC104_DT_PROC_IDLE; state->testfr_state = IEC104_TESTFR_IDLE; } /** * 功能:初始化一条连接的链路窗口与超时默认配置。 * 输入参数:config——待初始化的配置对象;允许为NULL。 * 输出参数:有效对象写入k、w、t1、t2和t3默认值。 * 返回值:无。 */ /* 适用端:控制站与被控站。 */ void IEC104_Link_Config_Default(IEC104_LinkConfig_t *config) { if (config == NULL) { return; } config->k = IEC104_DEFAULT_K; config->w = IEC104_DEFAULT_W; config->t1_ms = IEC104_DEFAULT_T1_MS; config->t2_ms = IEC104_DEFAULT_T2_MS; config->t3_ms = IEC104_DEFAULT_T3_MS; } /** * 功能:为新连接初始化链路状态,并固定本端角色。 * 输入参数:state——待初始化的状态对象;role——CONTROLLING或CONTROLLED。 * 输出参数:成功时写入role,清零序号和确认快照,数据传输置STOPPED, * 启停过程、S确认和TESTFR置IDLE。 * 返回值:1表示初始化成功;0表示state为NULL或role非法,此时不修改状态。 * 说明:仅初始化链路算法状态,连接配置由Config_Default单独初始化。 * 新连接处理任何链路事件前须初始化成功,不要求在TCP建连成功后才调用。 * 本模块未实现连接生命周期管理;旧事件隔离和状态内存安全复用由接入方负责。 */ /* 适用端:控制站与被控站,由role参数确定本端角色。 */ int IEC104_Link_State_Init(IEC104_LinkState_t *state, IEC104_LinkRole_t role) { if (state == NULL || (role != IEC104_ROLE_CONTROLLING && role != IEC104_ROLE_CONTROLLED)) { return 0; } Initialize_State_Fields(state, role); return 1; } /** * 功能:复位链路序号、S确认、启停和探测状态,清除待完成过程的算法记录。 * 输入参数:state——待复位的链路状态;允许为NULL。 * 输出参数:state——保留有效角色,序号及S快照清零,回到STOPPED/IDLE; * S确认和TESTFR置IDLE;非法角色规范化为INVALID。 * 返回值:无。 * 说明:本函数只复位链路算法字段,不能用于致命故障后在原连接上继续通信。 * 外部接入要求:调用方负责关闭故障连接,并防止旧事件提交到重新初始化后的状态; * 对应连接管理、任务调度和定时器管理当前未实现,不属于本函数行为。 * Reset会读取原role,非空state须具有确定的字段内容,不能传入未初始化的自动变量。 */ /* 适用端:控制站与被控站。 */ void IEC104_Link_State_Reset(IEC104_LinkState_t *state) { IEC104_LinkRole_t role; if (state == NULL) { return; } role = state->role; if (role != IEC104_ROLE_CONTROLLING && role != IEC104_ROLE_CONTROLLED) { role = IEC104_ROLE_INVALID; } Initialize_State_Fields(state, role); } /* I/S启停策略集中于下方判断函数;只查询当前状态,不缓存收发权限。 */ /* 子站START受理后即可接收I帧;STOP受理即关闭,后到START重新开放。 */ static int I_Frame_Receive_Allowed(const IEC104_LinkState_t *state) { return state->dt_state == IEC104_DT_STARTED && (state->role != IEC104_ROLE_CONTROLLED || state->dt_procedure == IEC104_DT_PROC_IDLE); } /** * 功能:判断当前是否允许业务层预留新的I帧。 * 输入参数:state——已通过完整合法性检查的链路状态。 * 输出参数:无,不修改state。 * 返回值:1表示可预留新I帧,0表示当前方向尚未开放或正在停止。 * 说明:控制站在STARTDT_ACT之后可提前发送命令,无须等待STARTDT_CON。 * ACT待完成时新I帧必须排在ACT后,FIFO发送及完成顺序由调用方保证。 * 被控站受理START即开放准备;发送存储须保证新I帧不越过已有CON。 * 此处只检查启停门控,k窗口和序号环边界由Plan_I_Frame继续检查。 */ static int New_I_Send_Allowed(const IEC104_LinkState_t *state) { if (state->dt_state == IEC104_DT_STARTED) { return state->dt_procedure == IEC104_DT_PROC_IDLE; } return state->role == IEC104_ROLE_CONTROLLING && (state->dt_procedure == IEC104_DT_PROC_START_ACT_SEND_PENDING || state->dt_procedure == IEC104_DT_PROC_START_CON_WAITING); } /* 子站停止等待ACK时允许S;STOP_CON已生成后的STOPPED/IDLE拒绝S。 */ static int S_Frame_Receive_Allowed(const IEC104_LinkState_t *state) { return state->dt_state == IEC104_DT_STARTED || (state->role == IEC104_ROLE_CONTROLLED && state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING); } /* 子站的S和CON共享FIFO;无需再等待START_CON的网络完成。 */ static int Dt_Allows_S_Ack(const IEC104_LinkState_t *state) { return state->role != IEC104_ROLE_CONTROLLED || state->dt_state == IEC104_DT_STARTED || state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING; } /** * 功能:判断停止阶段是否要求普通S确认绕过w门限。 * 输入参数:state——已通过完整合法性检查的链路状态。 * 输出参数:无,不修改state。 * 返回值:停止请求及排空的四个阶段为1,其余为0。 * 说明:只决定确认紧迫性,不绕过在途S或无待确认数据检查。 */ static int Dt_Requires_Immediate_Ack(const IEC104_LinkState_t *state) { return state->dt_procedure == IEC104_DT_PROC_STOP_REQUESTED || state->dt_procedure == IEC104_DT_PROC_STOP_ACT_SEND_PENDING || state->dt_procedure == IEC104_DT_PROC_STOP_CON_WAITING || state->dt_procedure == IEC104_DT_PROC_STOP_DRAINING; } /** * 功能:检查I帧的发送序号和确认号,并更新链路状态。 * 输入参数:state——当前链路状态;apci——已完成基础解析的I帧APCI。 * 输出参数:成功时更新V(A)和V(R);待确认数量由acked_nr到V(R)推导。 * 返回值:OK表示接收方向已经开放、序号合法并提交;FATAL表示接收方向未开放, * 或状态、参数、帧类型、序号非法。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Handle_I_Frame( IEC104_LinkState_t *state, const IEC104_ApciInfo_t *apci) { uint16_t next_receive_number; /* 先完成所有检查;合法状态的V(R)为15位,N(S)==V(R)也保证N(S)未越界。 */ if (!IEC104_Link_State_Valid_Internal(state) || apci == NULL || !I_Frame_Receive_Allowed(state) || apci->frame_type != IEC104_FRAME_TYPE_I || apci->send_number != state->vr || !Acknowledge_Number_Valid(state, apci->receive_number)) { return IEC104_LINK_RESULT_FATAL; } next_receive_number = Number_Increment(state->vr); /* 未确认区间不得占满15位序号环,否则acked_nr==V(R)会被误判为没有积压。 */ if (next_receive_number == state->acked_nr) { return IEC104_LINK_RESULT_FATAL; } /* I帧中的N(R)确认本端发送数据,合法N(S)使本端的期望接收序号V(R)加1。 */ state->va = apci->receive_number; state->vr = next_receive_number; return IEC104_LINK_RESULT_OK; } /** * 功能:检查S帧的确认号并更新已确认发送序号。 * 输入参数:state——当前链路状态;apci——已完成基础解析的S帧APCI。 * 输出参数:成功时更新V(A)。 * 返回值:OK表示接收阶段和确认号合法并提交;其他情况返回FATAL且不修改状态。 * 说明:STARTED下允许接收S;被控站STOPPED下仅STOP_DRAINING允许,以完成停止排空。 * 被控站START受理即STARTED,无须等待START_CON完成;STOPPED/IDLE拒绝S。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Handle_S_Frame( IEC104_LinkState_t *state, const IEC104_ApciInfo_t *apci) { if (!IEC104_Link_State_Valid_Internal(state) || apci == NULL || apci->frame_type != IEC104_FRAME_TYPE_S || !S_Frame_Receive_Allowed(state) || !Acknowledge_Number_Valid(state, apci->receive_number)) { return IEC104_LINK_RESULT_FATAL; } /* S帧不携带用户数据,只用N(R)推进本端的发送确认位置。 */ state->va = apci->receive_number; return IEC104_LINK_RESULT_OK; } /** * 功能:只根据链路状态规划下一帧I帧的序号快照,不编码报文也不修改状态。 * 输入参数:state——当前链路状态;k——发送窗口上限; * tx——调用方提供的非NULL临时快照。 * 输出参数:仅READY时tx写入当前V(S)、V(R),frame_length暂置0,等待编码层填写。 * 返回值:READY表示可生成;SKIPPED表示门控或窗口阻止发送;ERROR表示参数/状态非法。 * 说明:本函数是只读规划阶段。调用方必须在同一串行临界区内完成编码,并且 * 只有编码成功后才能把tx提交给发送队列并推进V(S)。 * Can_Prepare_I_Frame与Prepare_I_Frame共用本函数;tx均指向栈上临时对象,故此处不再判空。 */ static IEC104_PlanResult_t Plan_I_Frame(const IEC104_LinkState_t *state, uint16_t k, IEC104_IFrameTx_t *tx) { uint16_t next_send_number; uint16_t outstanding_count; if (!IEC104_Link_State_Valid_Internal(state) || k == 0U || k > IEC104_SEQUENCE_MASK) { return IEC104_PLAN_ERROR; } if (!New_I_Send_Allowed(state)) { return IEC104_PLAN_SKIPPED; } outstanding_count = Number_Distance(state->va, state->vs); if (outstanding_count >= k) { return IEC104_PLAN_SKIPPED; } next_send_number = Number_Increment(state->vs); /* 未完成区间包含在未确认区间内;保留显式检查,避免完成边界的回绕歧义。 */ if (next_send_number == state->sent_vs) { return IEC104_PLAN_SKIPPED; } tx->send_number = state->vs; tx->receive_number = state->vr; tx->frame_length = 0U; return IEC104_PLAN_READY; } /* 只读复用实际构帧的启停、k窗口及序号边界检查,不编码、不预留N(S)。 */ int IEC104_Link_Can_Prepare_I_Frame(const IEC104_LinkState_t *state, uint16_t k) { IEC104_IFrameTx_t planned_tx; IEC104_PlanResult_t planned = Plan_I_Frame(state, k, &planned_tx); return planned == IEC104_PLAN_READY ? 1 : (planned == IEC104_PLAN_SKIPPED ? 0 : -1); } /** * 功能:检查启停状态和k窗口,构造I帧并立即预留其N(S)。 * 输入参数:state——链路状态;k——发送窗口;asdu/asdu_len——完整ASDU; * frame/capacity——输出缓冲及容量;tx——发送队列保存的构帧快照。 * 输出参数:成功时写入frame和tx,并推进V(S)。 * 返回值:成功返回完整APDU长度;0表示当前不允许新I帧或窗口已满; * -1表示参数或状态非法。 * 说明:成功后N(S)不可撤销;报文须进入严格FIFO队列,发送失败由上层断连。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_Prepare_I_Frame(IEC104_LinkState_t *state, uint16_t k, const uint8_t *asdu, uint16_t asdu_len, uint8_t *frame, uint16_t capacity, IEC104_IFrameTx_t *tx) { IEC104_IFrameTx_t planned_tx; IEC104_PlanResult_t planned; int length; if (asdu == NULL || asdu_len < IEC104_ASDU_HEADER_LEN || asdu_len > IEC104_MAX_ASDU_LEN || frame == NULL || capacity < (uint16_t)(IEC104_APCI_LEN + asdu_len) || tx == NULL) { return -1; } planned = Plan_I_Frame(state, k, &planned_tx); if (planned != IEC104_PLAN_READY) { return planned == IEC104_PLAN_SKIPPED ? 0 : -1; } length = IEC104_Build_Iframe(frame, planned_tx.send_number, planned_tx.receive_number, asdu, (int)asdu_len); if (length < 0) { return -1; } /* 编码成功后才一次性提交状态和发送队列快照。 */ planned_tx.frame_length = (uint16_t)length; *tx = planned_tx; state->vs = Number_Increment(planned_tx.send_number); return length; } /** * 功能:提交一帧由Prepare_I_Frame生成的I帧最终本地发送结果。 * 输入参数:state——链路状态;tx——构帧时保存的I帧快照; * sent_len——最终累计成功发送的字节数,等于快照长度才算成功;发送失败也可传-1。 * 输出参数:完整发送且快照合法时推进acked_nr和sent_vs;失败时不修改状态。 * 返回值:OK表示完整发送并提交;FATAL表示参数、状态、长度、顺序或快照非法。 * 说明:Send限定为本地发送提交;返回OK不表示对端已经确认本帧。 * V(S)已在Prepare阶段推进;本接口按FIFO推进发送完成边界,不再次推进V(S)。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Complete_I_Send( IEC104_LinkState_t *state, const IEC104_IFrameTx_t *tx, int sent_len) { IEC104_LinkResult_t result; if (!IEC104_Link_State_Valid_Internal(state) || tx == NULL || tx->send_number > IEC104_SEQUENCE_MASK || tx->receive_number > IEC104_SEQUENCE_MASK || tx->frame_length < (uint16_t)(IEC104_APCI_LEN + IEC104_ASDU_HEADER_LEN) || tx->frame_length > (uint16_t)(IEC104_APCI_LEN + IEC104_MAX_ASDU_LEN) || sent_len != (int)tx->frame_length) { return IEC104_LINK_RESULT_FATAL; } /* 没有待完成帧或N(S)不匹配时拒绝,避免重复/乱序回调提交到别的帧。 */ if (state->sent_vs == state->vs || tx->send_number != state->sent_vs) { return IEC104_LINK_RESULT_FATAL; } result = Commit_Sent_NR(state, tx->receive_number); if (result != IEC104_LINK_RESULT_OK) { return result; } state->sent_vs = Number_Increment(tx->send_number); return IEC104_LINK_RESULT_OK; } /** * 功能:判断最早已完成发送且尚未被对端确认的I帧是否达到t1超时。 * 输入参数:state——链路状态;elapsed_ms——该帧实际完成发送后经过的毫秒数; * t1_ms——t1配置值。 * 输出参数:无,不修改state。 * 返回值:1表示已经超时;0表示无已完成未确认帧或尚未超时;-1表示参数非法。 * 说明:只预留未Complete的帧不参与t1;时间戳和队列仍由调用方维护。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_T1_I_Ack_Expired(const IEC104_LinkState_t *state, uint32_t elapsed_ms, uint32_t t1_ms) { if (!IEC104_Link_State_Valid_Internal(state) || t1_ms == 0U) { return -1; } if (Sent_Unacknowledged_I_Count(state) == 0U) { return 0; } return elapsed_ms >= t1_ms ? 1 : 0; } /** * 功能:只根据链路状态规划S确认的N(R),不编码报文也不修改状态。 * 输入参数:state——当前链路状态;w——接收确认门限; * immediate——非0表示绕过w门限立即确认; * receive_number——用于接收待编码N(R)的输出地址。 * 输出参数:仅READY时写入当前V(R)。 * 返回值:READY表示应生成;SKIPPED表示当前无需或已有S帧在途。 * 前置条件:state已通过完整校验,w在1..32767内,receive_number为有效输出地址。 * 说明:STOPDT排空半态会自动绕过w门限。实际S帧编码成功后,构帧步骤才把 * receive_number保存到s_ack_nr并进入SEND_PENDING。 */ static IEC104_PlanResult_t Plan_S_Ack(const IEC104_LinkState_t *state, uint16_t w, int immediate, uint16_t *receive_number) { uint16_t unconfirmed_count; if (!Dt_Allows_S_Ack(state)) { return IEC104_PLAN_SKIPPED; } if (state->s_ack_state == IEC104_S_ACK_SEND_PENDING) { return IEC104_PLAN_SKIPPED; } unconfirmed_count = IEC104_Link_Unconfirmed_Rx_Internal(state); if (unconfirmed_count == 0U) { return IEC104_PLAN_SKIPPED; } if (!immediate && unconfirmed_count < w && !Dt_Requires_Immediate_Ack(state)) { return IEC104_PLAN_SKIPPED; } *receive_number = state->vr; return IEC104_PLAN_READY; } /** * 功能:只读查询普通S确认需求,供连接层在取得控制槽前决定是否调用发送准备接口。 * 输入参数:state——当前链路;w——合法接收门限;immediate——非0立即确认。 * 返回值:1需要且允许准备,0当前无需/不能准备,-1参数或状态非法。 * 说明:直接复用构帧使用的Plan_S_Ack;查询不消耗发送编号、不改变S快照或acked_nr。 */ int IEC104_Link_Can_Prepare_S_Ack(const IEC104_LinkState_t *state, uint16_t w, int immediate) { uint16_t receive_number; if (!IEC104_Link_State_Valid_Internal(state) || w == 0U || w > IEC104_SEQUENCE_MASK) { return -1; } return Plan_S_Ack(state, w, immediate, &receive_number) == IEC104_PLAN_READY; } /** * 功能:按已规划的N(R)编码S帧,成功后提交公共S确认状态。 * 输入参数:state——链路状态;response——输出缓冲;receive_number——规划的N(R)。 * 输出参数:成功时写入6字节S帧,将N(R)保存到s_ack_nr并置SEND_PENDING。 * 前置条件:state已通过完整校验且无S在途;response非NULL且至少6字节; * receive_number为本次选定的有效15位确认号。 * 说明:普通S确认和STOPDT共用此步骤;使用传入快照,不重新规划或读取V(R)。 * 本步骤不推进acked_nr,须等Complete_S_Ack_Send确认发送成功后提交。 * 返回值:6表示编码并提交成功;-1表示编码失败,此时不提交状态。 * 调用关系:本文件的普通确认/t2以及Master、Slave的停止准备均调用本函数, * 所以不能直接设为static;是否应发送、容量和角色均由调用入口检查。 */ int IEC104_Link_Prepare_S_Ack_Internal(IEC104_LinkState_t *state, uint8_t *response, uint16_t receive_number) { int length = IEC104_Build_Sframe(response, receive_number); if (length != IEC104_APCI_LEN) { return -1; } /* 编码成功后提交与报文字节完全一致的N(R)快照。 */ state->s_ack_nr = receive_number; state->s_ack_state = IEC104_S_ACK_SEND_PENDING; return length; } /** * 功能:普通S确认与t2共用的规划、编码和提交路径。 * 输入参数:state——链路状态;w——确认门限;immediate——非0立即确认; * response——输出缓冲。 * 输出参数:生成时写入S帧并记录其N(R)快照和在途状态;未生成时保持原值。 * 前置条件:state和w已校验,response至少有IEC104_APCI_LEN字节空间。 * 返回值:6表示生成,0表示当前不生成,-1表示编码失败。 */ static int Prepare_S_Ack_Validated(IEC104_LinkState_t *state, uint16_t w, int immediate, uint8_t *response) { uint16_t receive_number; IEC104_PlanResult_t planned = Plan_S_Ack(state, w, immediate, &receive_number); if (planned != IEC104_PLAN_READY) { return 0; } return IEC104_Link_Prepare_S_Ack_Internal(state, response, receive_number); } /** * 功能:按w门限、显式立即确认或STOPDT排空要求生成普通S帧并保存N(R)快照。 * 输入参数:state——链路状态;w——接收确认门限;immediate——非0跳过w门限,仍受启停及S在途限制; * response——输出缓冲;capacity——至少6字节。 * 输出参数:成功时写入S帧并记录SEND_PENDING及构帧时V(R)。 * 返回值:6表示生成;0表示当前无需或不能由普通流程生成;-1表示参数/状态非法。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_Prepare_S_Ack(IEC104_LinkState_t *state, uint16_t w, int immediate, uint8_t *response, uint16_t capacity) { if (!IEC104_Link_State_Valid_Internal(state) || w == 0U || w > IEC104_SEQUENCE_MASK || response == NULL || capacity < IEC104_APCI_LEN) { return -1; } return Prepare_S_Ack_Validated(state, w, immediate, response); } /** * 功能:提交公共S确认帧的最终发送结果,普通确认与两端停止流程共用。 * 输入参数:state——链路状态;sent_len——最终累计发送字节数。 * 输出参数:成功时按快照推进acked_nr并回IDLE;失败或快照失效时不推进确认。 * 返回值:OK表示发送成功并提交;IGNORED表示没有S帧等待完成; * FATAL表示状态非法、发送失败或快照失效。 * 说明:状态非法直接返回且不清理;状态合法但无S在途时先返回IGNORED, * 不检查sent_len。有S在途时,发送失败或快照失效均清除S状态和快照。 * 成功同样清除S状态/快照,所有结果均保留当前启停过程。 * 普通确认及两端STOPDT中的S帧均由本接口完成。本接口不携带帧身份, * 调用方须按原发送节点恰好分派一次。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Complete_S_Ack_Send( IEC104_LinkState_t *state, int sent_len) { IEC104_LinkResult_t result; if (!IEC104_Link_State_Valid_Internal(state)) { return IEC104_LINK_RESULT_FATAL; } if (state->s_ack_state != IEC104_S_ACK_SEND_PENDING) { return IEC104_LINK_RESULT_IGNORED; } if (sent_len != IEC104_APCI_LEN) { state->s_ack_state = IEC104_S_ACK_IDLE; state->s_ack_nr = 0U; return IEC104_LINK_RESULT_FATAL; } result = Commit_Sent_NR(state, state->s_ack_nr); state->s_ack_state = IEC104_S_ACK_IDLE; state->s_ack_nr = 0U; return result; } /** * 功能:在最早已接收但本端尚未确认的I帧达到t2等待时间后生成S确认帧。 * 输入参数:state——链路状态;elapsed_ms——已经等待的毫秒数; * t2_ms——t2配置值;response——输出缓冲;capacity——至少6字节。 * 输出参数:到期且可生成时写入S帧,并保存普通S帧的N(R)快照。 * 返回值:6表示生成;0表示尚不需要或当前已有流程负责;-1表示参数/状态非法。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_Prepare_T2_Ack(IEC104_LinkState_t *state, uint32_t elapsed_ms, uint32_t t2_ms, uint8_t *response, uint16_t capacity) { if (!IEC104_Link_State_Valid_Internal(state) || response == NULL || capacity < IEC104_APCI_LEN || t2_ms == 0U) { return -1; } if (elapsed_ms < t2_ms) { return 0; } /* 已校验输入;到期后由共用规划判断待确认数据和在途S帧。 */ return Prepare_S_Ack_Validated(state, 1U, 1, response); } /** * 功能:检查链路状态自洽且本端固定角色与调用接口要求一致。 * 输入参数:state——待检查的链路状态对象;role——接口要求的本端角色。 * 输出参数:无,不修改state。 * 返回值:状态自洽且角色匹配返回1,否则返回0。 * 调用关系:Master/Slave角色专用公开入口使用;先调用State_Valid再比较角色, * 短路求值保证state为NULL时不会解引用。末级角色校验不得反调此函数。 */ int IEC104_Link_Role_Valid_Internal(const IEC104_LinkState_t *state, IEC104_LinkRole_t role) { return IEC104_Link_State_Valid_Internal(state) && state->role == role; } /** * 功能:处理TESTFR_ACT,复用U帧构造接口生成TESTFR_CON应答。 * 输入参数:apci——已通过APCI解析的TESTFR_ACT;response——输出缓冲; * capacity——缓冲实际容量,至少6字节。 * 输出参数:response——写入68 04 83 00 00 00。 * 返回值:OK表示生成固定6字节应答;FATAL表示参数或报文非法。 * 说明:不读写链路状态,不执行网络发送,不处理主动探测或超时。 * 此应答没有对应Complete接口;发送队列负责发送结果和失败断连,不能 * 把TESTFR_CON发送完成交给处理本端主动ACT的Complete_Testfr_Act_Send。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Handle_Testfr_Act( const IEC104_ApciInfo_t *apci, uint8_t *response, uint16_t capacity) { if (apci == NULL || response == NULL || capacity < IEC104_APCI_LEN || apci->frame_type != IEC104_FRAME_TYPE_U || apci->u_function != IEC104_U_TESTFR_ACT) { return IEC104_LINK_RESULT_FATAL; } /* 缓冲已验证且功能码固定,编码必定生成6字节;(void)表示有意忽略返回值。 */ (void)IEC104_Build_Uframe(response, IEC104_U_TESTFR_CON); return IEC104_LINK_RESULT_OK; } /* 已通过完整状态检查;查询与构帧共用唯一的主动探测准入条件。 */ static int Testfr_Act_Allowed(const IEC104_LinkState_t *state) { return state->testfr_state == IEC104_TESTFR_IDLE; } /** * 功能:查询当前是否允许新一轮主动TESTFR,不读取时钟或改变状态。 * 输入参数:state——待检查链路;调用方另外判断是否已满足t3空闲时长。 * 返回值:1可准备,0当前已有探测,-1参数或完整状态非法。 * 说明:普通主动探测和t3构帧均复用Testfr_Act_Allowed,不在连接层另判枚举值。 */ int IEC104_Link_Can_Prepare_Testfr_Act(const IEC104_LinkState_t *state) { if (!IEC104_Link_State_Valid_Internal(state)) { return -1; } return Testfr_Act_Allowed(state); } /** * 功能:主动TESTFR与t3共用的状态检查、编码和提交路径。 * 输入参数:state——链路状态;request——输出缓冲。 * 输出参数:生成时写入6字节TESTFR_ACT,并进入TESTFR_ACT_SEND_PENDING。 * 前置条件:state已校验,request至少有IEC104_APCI_LEN字节空间。 * 返回值:6表示生成,0表示已有探测。 * 说明:与只读查询共用Testfr_Act_Allowed,真正发送完成后才开始等待CON; * 本函数不判断链路空闲时间,参数校验和t3时间比较由公开入口负责。 */ static int Prepare_Testfr_Act_Validated(IEC104_LinkState_t *state, uint8_t *request) { if (!Testfr_Act_Allowed(state)) { return 0; } /* * 缓冲已验证且TESTFR_ACT为合法常量,编码器必定写入6字节并返回6。 * (void)只显式丢弃这次调用的返回值,函数仍会执行编码并写入request。 */ (void)IEC104_Build_Uframe(request, IEC104_U_TESTFR_ACT); state->testfr_state = IEC104_TESTFR_ACT_SEND_PENDING; return IEC104_APCI_LEN; } /** * 功能:生成主动TESTFR_ACT请求,并记录等待发送完成的状态。 * 输入参数:state——链路状态;request——输出缓冲;capacity——实际容量,至少6字节。 * 输出参数:request——写入68 04 43 00 00 00;state——探测转为ACT_SEND_PENDING。 * 返回值:6表示生成请求;0表示已有探测进行中;-1表示参数或链路状态非法。 * 说明:返回0或-1时不修改状态和缓冲;不执行发送,不修改启停、序号及确认位置。 * 返回6后发送层必须保证恰好提交一次Complete;发送超时也以失败长度提交。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_Prepare_Testfr_Act(IEC104_LinkState_t *state, uint8_t *request, uint16_t capacity) { if (!IEC104_Link_State_Valid_Internal(state) || request == NULL || capacity < IEC104_APCI_LEN) { return -1; } return Prepare_Testfr_Act_Validated(state, request); } /** * 功能:在链路空闲达到t3后生成主动TESTFR_ACT请求。 * 输入参数:state——链路状态;idle_elapsed_ms——空闲毫秒数; * t3_ms——t3配置值;request——输出缓冲;capacity——至少6字节。 * 输出参数:到期且允许生成时写入TESTFR_ACT,并复用主动探测发送状态。 * 返回值:6表示生成;0表示尚未到期或已有探测;-1表示参数或链路状态非法。 * 说明:先校验完整状态和参数,再比较t3时间;返回6只表示生成报文,尚未发送。 * 连接层统一定义有效链路活动,并更新t3的单调计时起点;本函数不保存时刻。 * t3活动更新不得重置正在等待TESTFR_CON的t1起点。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_Prepare_T3_Testfr(IEC104_LinkState_t *state, uint32_t idle_elapsed_ms, uint32_t t3_ms, uint8_t *request, uint16_t capacity) { if (!IEC104_Link_State_Valid_Internal(state) || request == NULL || capacity < IEC104_APCI_LEN || t3_ms == 0U) { return -1; } if (idle_elapsed_ms < t3_ms) { return 0; } /* 已校验输入;t3到期后直接复用主动探测准备路径。 */ return Prepare_Testfr_Act_Validated(state, request); } /** * 功能:提交TESTFR_ACT最终发送结果,成功后开始等待TESTFR_CON。 * 输入参数:state——链路状态;sent_len——最终累计成功发送字节数,恰好6表示成功。 * 输出参数:有匹配ACT时,完整发送成功转CON_PENDING;发送失败回IDLE并由上层断连。 * 返回值:OK表示完整发送并提交;IGNORED表示没有待完成的ACT; * FATAL表示状态非法或发送失败。 * 说明:部分写入仍在继续时不要调用;状态合法但无匹配ACT时先返回IGNORED,不检查长度。 * 调用方须先提交ACT完整发送结果,再分派对端CON;不缓存提前CON。 * 返回OK后,由连接层以本次ACT实际完整发送时刻为等待CON的t1起点, * 不能用延迟处理Complete时的当前时刻代替;本函数不操作外部定时器。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Complete_Testfr_Act_Send( IEC104_LinkState_t *state, int sent_len) { if (!IEC104_Link_State_Valid_Internal(state)) { return IEC104_LINK_RESULT_FATAL; } if (state->testfr_state != IEC104_TESTFR_ACT_SEND_PENDING) { return IEC104_LINK_RESULT_IGNORED; } if (sent_len != IEC104_APCI_LEN) { state->testfr_state = IEC104_TESTFR_IDLE; return IEC104_LINK_RESULT_FATAL; } /* 完整发送成功只开始等待对端确认;收到CON后才结束本轮探测。 */ state->testfr_state = IEC104_TESTFR_CON_PENDING; return IEC104_LINK_RESULT_OK; } /** * 功能:接受正在等待的TESTFR_CON,结束本端主动探测。 * 输入参数:state——链路状态;apci——已通过APCI解析的TESTFR_CON。 * 输出参数:仅CON_PENDING时回IDLE;非OK时不修改状态。 * 返回值:OK表示接受确认;IGNORED表示当前未等待CON; * FATAL表示参数、报文或链路状态非法。 * 说明:输入校验先于等待状态检查;ACT_SEND_PENDING时返回IGNORED且不缓存CON。 * 调用方须先提交ACT完整发送结果,再处理CON;本接口不修改启停状态、 * 链路序号及确认位置,也不生成应答报文。返回OK后由连接层取消本轮等待计时, * 本接口不操作外部定时器。 */ /* 适用端:控制站与被控站。 */ IEC104_LinkResult_t IEC104_Link_Handle_Testfr_Con( IEC104_LinkState_t *state, const IEC104_ApciInfo_t *apci) { if (!IEC104_Link_State_Valid_Internal(state) || apci == NULL || apci->frame_type != IEC104_FRAME_TYPE_U || apci->u_function != IEC104_U_TESTFR_CON) { return IEC104_LINK_RESULT_FATAL; } if (state->testfr_state != IEC104_TESTFR_CON_PENDING) { return IEC104_LINK_RESULT_IGNORED; } state->testfr_state = IEC104_TESTFR_IDLE; return IEC104_LINK_RESULT_OK; } /** * 功能:判断主动TESTFR流程等待TESTFR_CON是否达到t1超时。 * 输入参数:state——链路状态;elapsed_ms——距当前探测ACT实际完整发送的毫秒数; * t1_ms——t1配置值,必须大于0。 * 输出参数:无,不修改state。 * 返回值:1表示已经超时;0表示未等待CON或尚未超时;-1表示参数或链路状态非法。 * 说明:先校验状态及t1_ms;合法时仅对CON_PENDING判定t1,ACT_SEND_PENDING返回0。 * ACT_SEND_PENDING表示本地发送结果尚未提交,发送层须限制排队和发送等待时间。 * 连接层定时器只负责唤醒;回调须校验连接代次,并在连接串行调度中按当前 * 有效探测的起点重新计算elapsed_ms,不能沿用同一连接上旧探测的时间差。 * 本函数不识别探测轮次,也不操作定时器或连接;只有返回1才按t1超时断连。 */ /* 适用端:控制站与被控站。 */ int IEC104_Link_T1_Testfr_Con_Expired(const IEC104_LinkState_t *state, uint32_t elapsed_ms, uint32_t t1_ms) { if (!IEC104_Link_State_Valid_Internal(state) || t1_ms == 0U) { return -1; } if (state->testfr_state != IEC104_TESTFR_CON_PENDING) { return 0; } return elapsed_ms >= t1_ms ? 1 : 0; }