Files
STM32F4-Base/IEC60870-Slave/104/IEC104_Codec.c
T
STF 4c55544f05
CI / L0 静态门禁 (pre-commit) (push) Failing after 45s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
通信协议代码
2026-10-26 08:31:48 -04:00

301 lines
9.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* IEC 60870-5-104无状态编解码:完整APDU的APCI检查及I/S/U报文编码。
* ASDU公共头解析由IEC60870_Codec提供,本模块只处理104传输帧。
*
* 仅使用调用方提供的报文、序号和输出缓冲,不保存连接状态,
* 不依赖接收状态机、链路状态机、发送队列或日志模块。
*/
#include "IEC104_Codec.h"
#include <stddef.h>
#include <string.h>
/**
* 功能:根据 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;
}