/** * IEC 60870-5-104无状态编解码:完整APDU的APCI检查及I/S/U报文编码。 * ASDU公共头解析由IEC60870_Codec提供,本模块只处理104传输帧。 * * 仅使用调用方提供的报文、序号和输出缓冲,不保存连接状态, * 不依赖接收状态机、链路状态机、发送队列或日志模块。 */ #include "IEC104_Codec.h" #include #include /** * 功能:根据 IEC-104 控制域首字节识别 I、S 或 U 帧。 * 输入参数:control——控制域首字节。 * 输出参数:无。 * 返回值:对应的 IEC104_FrameType_t 帧类型。 * 说明:仅按控制域低位分类,不验证保留位、U功能码或帧长度。 */ IEC104_FrameType_t IEC104_Get_Frame_Type(uint8_t control) { if ((control & 0x01U) == 0U) { return IEC104_FRAME_TYPE_I; } if ((control & 0x03U) == 0x01U) { return IEC104_FRAME_TYPE_S; } return IEC104_FRAME_TYPE_U; } /** * 功能:解析 I 帧控制域中的发送序号 N(S)。 * 输入参数:frame——至少包含6字节APCI且控制域合法的I帧。 * 输出参数:无。 * 返回值:返回0至32767范围内的N(S);frame为NULL时返回0。 */ static uint16_t Get_Send_Number(const uint8_t *frame) { uint16_t encoded_number; if (frame == NULL) { return 0; } encoded_number = (uint16_t)(((uint16_t)frame[3] << 8) | (uint16_t)frame[2]); return (uint16_t)(encoded_number >> 1); } /** * 功能:解析 I 帧或 S 帧控制域中的接收序号 N(R)。 * 输入参数:frame——至少包含6字节APCI且控制域合法的I帧或S帧。 * 输出参数:无。 * 返回值:返回0至32767范围内的N(R);frame为NULL时返回0。 */ static uint16_t Get_Receive_Number(const uint8_t *frame) { uint16_t encoded_number; if (frame == NULL) { return 0; } encoded_number = (uint16_t)(((uint16_t)frame[5] << 8) | (uint16_t)frame[4]); return (uint16_t)(encoded_number >> 1); } /** * 功能:检查 U 帧功能码是否为标准定义的六种合法值之一。 * 输入参数:function——报文中的控制字节,或编码入口传入的原始功能码。 * 输出参数:无。 * 返回值:功能码合法返回1,否则返回0。 * 说明:与编码入口一样使用int接收原值,避免短枚举在校验前将0x107等非法值截成0x07。 */ static int U_Frame_Function_Valid(int function) { switch (function) { case IEC104_U_STARTDT_ACT: case IEC104_U_STARTDT_CON: case IEC104_U_STOPDT_ACT: case IEC104_U_STOPDT_CON: case IEC104_U_TESTFR_ACT: case IEC104_U_TESTFR_CON: return 1; default: return 0; } } /** * 功能:检查 I 帧控制域和对应的长度约束。 * 输入参数:frame——至少包含6字节APCI的完整帧。 * 输出参数:无。 * 返回值:控制域合法返回1,否则返回0。 */ static int I_Frame_Control_Field_Valid(const uint8_t *frame) { /* I 帧必须容纳完整ASDU公共头,N(S) 和 N(R) 的格式位必须为0。 */ return frame[1] >= IEC104_MIN_LENGTH_FIELD + IEC104_ASDU_HEADER_LEN && (frame[2] & 0x01U) == 0U && (frame[4] & 0x01U) == 0U; } /** * 功能:检查 S 帧控制域和对应的长度约束。 * 输入参数:frame——至少包含6字节APCI的完整帧。 * 输出参数:无。 * 返回值:控制域合法返回1,否则返回0。 */ static int S_Frame_Control_Field_Valid(const uint8_t *frame) { /* S 帧无 ASDU,前两个控制字节固定,N(R) 格式位必须为0。 */ return frame[1] == IEC104_MIN_LENGTH_FIELD && frame[2] == 0x01U && frame[3] == 0x00U && (frame[4] & 0x01U) == 0U; } /** * 功能:检查 U 帧控制域和对应的长度约束。 * 输入参数:frame——至少包含6字节APCI的完整帧。 * 输出参数:无。 * 返回值:控制域合法返回1,否则返回0。 */ static int U_Frame_Control_Field_Valid(const uint8_t *frame) { /* U 帧无 ASDU,只允许六种功能码,后三个控制字节固定为0。 */ return frame[1] == IEC104_MIN_LENGTH_FIELD && U_Frame_Function_Valid(frame[2]) && frame[3] == 0x00U && frame[4] == 0x00U && frame[5] == 0x00U; } /** * 功能:检查完整APDU的外层长度和APCI控制域,解析控制信息及ASDU位置。 * 输入参数:frame——完整APDU首地址;frame_len——完整APDU长度。 * 输出参数:apci——解析成功时写入帧类型、序号、U功能码和ASDU位置。 * 返回值:解析成功返回1,否则返回0且不保证apci中的内容。 * 说明:I帧要求ASDU至少含IEC104_ASDU_HEADER_LEN字节;公共头字段和信息对象由后续解析处理。 */ int IEC104_Parse_APCI(const uint8_t *frame, uint16_t frame_len, IEC104_ApciInfo_t *apci) { IEC104_FrameType_t frame_type; if (frame == NULL || apci == NULL || frame_len < IEC104_APCI_LEN || frame[0] != IEC104_START_BYTE || frame[1] < IEC104_MIN_LENGTH_FIELD || frame[1] > IEC104_MAX_LENGTH_FIELD || frame_len != (uint16_t)(2U + frame[1])) { return 0; } frame_type = IEC104_Get_Frame_Type(frame[2]); apci->frame_type = frame_type; apci->send_number = 0; apci->receive_number = 0; apci->u_function = 0; apci->asdu = NULL; apci->asdu_len = 0; switch (frame_type) { case IEC104_FRAME_TYPE_I: if (!I_Frame_Control_Field_Valid(frame)) { return 0; } apci->send_number = Get_Send_Number(frame); apci->receive_number = Get_Receive_Number(frame); apci->asdu = &frame[IEC104_APCI_LEN]; apci->asdu_len = (uint16_t)(frame[1] - IEC104_MIN_LENGTH_FIELD); break; case IEC104_FRAME_TYPE_S: if (!S_Frame_Control_Field_Valid(frame)) { return 0; } apci->receive_number = Get_Receive_Number(frame); break; case IEC104_FRAME_TYPE_U: if (!U_Frame_Control_Field_Valid(frame)) { return 0; } apci->u_function = frame[2]; break; default: return 0; } return 1; } /** * 功能:根据发送序号、接收序号和ASDU构造一个完整的I帧APDU。 * 输入参数:buf——输出缓冲区;send_seq——N(S);recv_seq——N(R); * asdu——完整ASDU首地址;asdu_len——ASDU长度。 * 输出参数:buf——成功时写入起始字节、长度域、控制域和ASDU。 * 返回值:成功返回生成的APDU总长度,参数非法返回-1。 * 说明:调用方必须保证buf至少具有IEC104_APCI_LEN+asdu_len字节空间; * 本函数只编码报文,不读取或修改链路序号状态;调用方须保存recv_seq供发送完成提交。 */ int IEC104_Build_Iframe(uint8_t *buf, uint16_t send_seq, uint16_t recv_seq, const uint8_t *asdu, int asdu_len) { uint16_t encoded_send; uint16_t encoded_receive; if (buf == NULL || asdu == NULL || asdu_len < (int)IEC104_ASDU_HEADER_LEN || asdu_len > (int)IEC104_MAX_ASDU_LEN || send_seq > IEC104_SEQUENCE_MASK || recv_seq > IEC104_SEQUENCE_MASK || IEC104_MIN_LENGTH_FIELD + asdu_len > IEC104_MAX_LENGTH_FIELD) { return -1; } encoded_send = (uint16_t)(send_seq << 1); encoded_receive = (uint16_t)(recv_seq << 1); buf[0] = IEC104_START_BYTE; buf[1] = (uint8_t)(IEC104_MIN_LENGTH_FIELD + asdu_len); buf[2] = (uint8_t)(encoded_send & 0xFFU); buf[3] = (uint8_t)(encoded_send >> 8); buf[4] = (uint8_t)(encoded_receive & 0xFFU); buf[5] = (uint8_t)(encoded_receive >> 8); memcpy(&buf[IEC104_APCI_LEN], asdu, (size_t)asdu_len); return IEC104_APCI_LEN + asdu_len; } /** * 功能:根据接收序号构造一个完整的S帧APDU。 * 输入参数:buf——输出缓冲区;recv_seq——待编码的N(R)。 * 输出参数:buf——成功时写入6字节S帧。 * 返回值:成功返回IEC104_APCI_LEN,参数非法返回-1。 * 说明:调用方必须保证buf至少具有IEC104_APCI_LEN字节空间; * 本函数只编码报文,不读取或修改链路序号状态;调用方须保存recv_seq供发送完成提交。 */ int IEC104_Build_Sframe(uint8_t *buf, uint16_t recv_seq) { uint16_t encoded_receive; if (buf == NULL || recv_seq > IEC104_SEQUENCE_MASK) { return -1; } encoded_receive = (uint16_t)(recv_seq << 1); buf[0] = IEC104_START_BYTE; buf[1] = IEC104_MIN_LENGTH_FIELD; buf[2] = 0x01U; buf[3] = 0x00U; buf[4] = (uint8_t)(encoded_receive & 0xFFU); buf[5] = (uint8_t)(encoded_receive >> 8); return IEC104_APCI_LEN; } /** * 功能:根据U帧功能码构造一个完整的U帧APDU。 * 输入参数:buf——输出缓冲区;function——待校验的整数功能码,只允许六种U帧功能值。 * 输出参数:buf——成功时写入6字节U帧,参数检查失败时不写入。 * 返回值:成功返回IEC104_APCI_LEN,参数或功能码非法返回-1。 * 说明:调用方必须保证buf至少具有IEC104_APCI_LEN字节空间; * buf非NULL且功能码合法时固定返回6,容量由调用方保证,本函数无法检查。 * function保持int原值直到校验通过,再转为控制字节;调用方不要预先收窄未校验值。 * 本函数只编码报文,不处理U帧状态转换或网络发送。 */ int IEC104_Build_Uframe(uint8_t *buf, int function) { if (buf == NULL || !U_Frame_Function_Valid(function)) { return -1; } buf[0] = IEC104_START_BYTE; buf[1] = IEC104_MIN_LENGTH_FIELD; buf[2] = (uint8_t)function; buf[3] = 0x00U; buf[4] = 0x00U; buf[5] = 0x00U; return IEC104_APCI_LEN; }