301 lines
9.9 KiB
C
301 lines
9.9 KiB
C
/**
|
||
* 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;
|
||
}
|