Files
STM32F4-Base/IEC60870-Slave/common/IEC60870_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

191 lines
9.4 KiB
C

/*
* 101/104共用的无状态ASDU基础解析:参数校验、公共头及最低载荷长度检查。
* 只处理调用方提供的字节和参数,不依赖APCI、TCP、串口或连接状态。
* 具体信息对象的完整校验由IEC60870_Asdu和InformationObjects完成。
*/
#include "IEC60870_Codec.h"
#include <stddef.h>
#include <string.h>
/* ASDU公共头中各组合字段的位掩码。 */
#define IEC60870_ASDU_OBJECT_COUNT_MASK 0x7FU
#define IEC60870_ASDU_SEQUENCE_MASK 0x80U
#define IEC60870_ASDU_CAUSE_MASK 0x3FU
#define IEC60870_ASDU_NEGATIVE_MASK 0x40U
#define IEC60870_ASDU_TEST_MASK 0x80U
#define IEC60870_ASDU_IOA_LEN 3U
/* 已知固定长度类型的单个信息对象总长,包含3字节IOA;0表示未在本表检查。
* 保留IOA使读命令C_RD_NA_1的零数据长度与未知类型可区分。
* Type ID是线上的协议值;变长文件段F_SG_NA_1及其他未列类型由业务解析。 */
static const uint8_t asdu_object_lengths[] = {
[1] = IEC60870_ASDU_IOA_LEN + 1U, /* M_SP_NA_1 */
[2] = IEC60870_ASDU_IOA_LEN + 4U, /* M_SP_TA_1 */
[3] = IEC60870_ASDU_IOA_LEN + 1U, /* M_DP_NA_1 */
[4] = IEC60870_ASDU_IOA_LEN + 4U, /* M_DP_TA_1 */
[5] = IEC60870_ASDU_IOA_LEN + 2U, /* M_ST_NA_1 */
[6] = IEC60870_ASDU_IOA_LEN + 5U, /* M_ST_TA_1 */
[7] = IEC60870_ASDU_IOA_LEN + 5U, /* M_BO_NA_1 */
[8] = IEC60870_ASDU_IOA_LEN + 8U, /* M_BO_TA_1 */
[9] = IEC60870_ASDU_IOA_LEN + 3U, /* M_ME_NA_1 */
[10] = IEC60870_ASDU_IOA_LEN + 6U, /* M_ME_TA_1 */
[11] = IEC60870_ASDU_IOA_LEN + 3U, /* M_ME_NB_1 */
[12] = IEC60870_ASDU_IOA_LEN + 6U, /* M_ME_TB_1 */
[13] = IEC60870_ASDU_IOA_LEN + 5U, /* M_ME_NC_1 */
[14] = IEC60870_ASDU_IOA_LEN + 8U, /* M_ME_TC_1 */
[15] = IEC60870_ASDU_IOA_LEN + 5U, /* M_IT_NA_1 */
[16] = IEC60870_ASDU_IOA_LEN + 8U, /* M_IT_TA_1 */
[17] = IEC60870_ASDU_IOA_LEN + 6U, /* M_EP_TA_1 */
[18] = IEC60870_ASDU_IOA_LEN + 7U, /* M_EP_TB_1 */
[19] = IEC60870_ASDU_IOA_LEN + 7U, /* M_EP_TC_1 */
[20] = IEC60870_ASDU_IOA_LEN + 5U, /* M_PS_NA_1 */
[21] = IEC60870_ASDU_IOA_LEN + 2U, /* M_ME_ND_1 */
[30] = IEC60870_ASDU_IOA_LEN + 8U, /* M_SP_TB_1 */
[31] = IEC60870_ASDU_IOA_LEN + 8U, /* M_DP_TB_1 */
[32] = IEC60870_ASDU_IOA_LEN + 9U, /* M_ST_TB_1 */
[33] = IEC60870_ASDU_IOA_LEN + 12U, /* M_BO_TB_1 */
[34] = IEC60870_ASDU_IOA_LEN + 10U, /* M_ME_TD_1 */
[35] = IEC60870_ASDU_IOA_LEN + 10U, /* M_ME_TE_1 */
[36] = IEC60870_ASDU_IOA_LEN + 12U, /* M_ME_TF_1 */
[37] = IEC60870_ASDU_IOA_LEN + 12U, /* M_IT_TB_1 */
[38] = IEC60870_ASDU_IOA_LEN + 10U, /* M_EP_TD_1 */
[39] = IEC60870_ASDU_IOA_LEN + 11U, /* M_EP_TE_1 */
[40] = IEC60870_ASDU_IOA_LEN + 11U, /* M_EP_TF_1 */
[41] = IEC60870_ASDU_IOA_LEN + 14U, /* S_IT_TC_1 */
[45] = IEC60870_ASDU_IOA_LEN + 1U, /* C_SC_NA_1 */
[46] = IEC60870_ASDU_IOA_LEN + 1U, /* C_DC_NA_1 */
[47] = IEC60870_ASDU_IOA_LEN + 1U, /* C_RC_NA_1 */
[48] = IEC60870_ASDU_IOA_LEN + 3U, /* C_SE_NA_1 */
[49] = IEC60870_ASDU_IOA_LEN + 3U, /* C_SE_NB_1 */
[50] = IEC60870_ASDU_IOA_LEN + 5U, /* C_SE_NC_1 */
[51] = IEC60870_ASDU_IOA_LEN + 4U, /* C_BO_NA_1 */
[58] = IEC60870_ASDU_IOA_LEN + 8U, /* C_SC_TA_1 */
[59] = IEC60870_ASDU_IOA_LEN + 8U, /* C_DC_TA_1 */
[60] = IEC60870_ASDU_IOA_LEN + 8U, /* C_RC_TA_1 */
[61] = IEC60870_ASDU_IOA_LEN + 10U, /* C_SE_TA_1 */
[62] = IEC60870_ASDU_IOA_LEN + 10U, /* C_SE_TB_1 */
[63] = IEC60870_ASDU_IOA_LEN + 12U, /* C_SE_TC_1 */
[64] = IEC60870_ASDU_IOA_LEN + 11U, /* C_BO_TA_1 */
[70] = IEC60870_ASDU_IOA_LEN + 1U, /* M_EI_NA_1 */
[100] = IEC60870_ASDU_IOA_LEN + 1U, /* C_IC_NA_1 */
[101] = IEC60870_ASDU_IOA_LEN + 1U, /* C_CI_NA_1 */
[102] = IEC60870_ASDU_IOA_LEN, /* C_RD_NA_1 */
[103] = IEC60870_ASDU_IOA_LEN + 7U, /* C_CS_NA_1 */
[104] = IEC60870_ASDU_IOA_LEN + 2U, /* C_TS_NA_1 */
[105] = IEC60870_ASDU_IOA_LEN + 1U, /* C_RP_NA_1 */
[106] = IEC60870_ASDU_IOA_LEN + 2U, /* C_CD_NA_1 */
[107] = IEC60870_ASDU_IOA_LEN + 9U, /* C_TS_TA_1 */
[110] = IEC60870_ASDU_IOA_LEN + 3U, /* P_ME_NA_1 */
[111] = IEC60870_ASDU_IOA_LEN + 3U, /* P_ME_NB_1 */
[112] = IEC60870_ASDU_IOA_LEN + 5U, /* P_ME_NC_1 */
[113] = IEC60870_ASDU_IOA_LEN + 1U, /* P_AC_NA_1 */
[120] = IEC60870_ASDU_IOA_LEN + 6U, /* F_FR_NA_1 */
[121] = IEC60870_ASDU_IOA_LEN + 7U, /* F_SR_NA_1 */
[122] = IEC60870_ASDU_IOA_LEN + 4U, /* F_SC_NA_1 */
[123] = IEC60870_ASDU_IOA_LEN + 5U, /* F_LS_NA_1 */
[124] = IEC60870_ASDU_IOA_LEN + 4U, /* F_AF_NA_1 */
[126] = IEC60870_ASDU_IOA_LEN + 13U, /* F_DR_TA_1 */
[127] = IEC60870_ASDU_IOA_LEN + 16U /* F_SC_NB_1 */
};
/* 调用前已检查参数、可变公共头及ASDU总长。表中仍以3字节IOA为基准,
* 计算时先减去基准IOA再加上配置宽度。仅检查已知固定长度对象是否被截短;
* 不要求长度恰好相等,不解释对象内容,也不在这里判定零对象或SQ的业务合法性。 */
static int ASDU_Payload_Length_Valid(const IEC60870_AppLayerParameters_t *parameters,
const uint8_t *asdu, uint16_t asdu_len, uint16_t header_length)
{
uint32_t object_count = asdu[1] & IEC60870_ASDU_OBJECT_COUNT_MASK;
uint32_t object_length;
uint32_t required_length;
if (object_count == 0U || asdu[0] >= sizeof(asdu_object_lengths)) {
return 1;
}
object_length = asdu_object_lengths[asdu[0]];
if (object_length == 0U) {
return 1;
}
if ((asdu[1] & IEC60870_ASDU_SEQUENCE_MASK) != 0U) {
/* 连续地址只传首个IOA;非连续地址则每个对象均携带IOA。 */
required_length = (uint32_t)parameters->sizeOfIOA +
object_count * (object_length - IEC60870_ASDU_IOA_LEN);
} else {
required_length = object_count *
(object_length - IEC60870_ASDU_IOA_LEN + (uint32_t)parameters->sizeOfIOA);
}
return (uint32_t)(asdu_len - header_length) >= required_length;
}
/**
* 功能:检查101/104共用应用层参数的字段宽度、源发地址和总长上限。
* 输入参数:parameters——待检查参数,允许NULL。
* 输出参数:无。
* 返回值:合法返回1,NULL或非法字段返回0。
* 说明:先校验小范围字段再计算头长,不选择默认配置或修改参数。
*/
int IEC60870_AppLayerParameters_Valid(const IEC60870_AppLayerParameters_t *parameters)
{
return parameters != NULL && parameters->sizeOfTypeId == 1 &&
parameters->sizeOfVSQ == 1 &&
(parameters->sizeOfCOT == 1 || parameters->sizeOfCOT == 2) &&
(parameters->sizeOfCA == 1 || parameters->sizeOfCA == 2) &&
parameters->sizeOfIOA >= 1 && parameters->sizeOfIOA <= 3 &&
parameters->originatorAddress >= 0 && parameters->originatorAddress <= 255 &&
parameters->maxSizeOfASDU >= 2 + parameters->sizeOfCOT + parameters->sizeOfCA &&
parameters->maxSizeOfASDU <= IEC60870_MAX_ASDU_LEN;
}
/**
* 功能:检查ASDU公共头及已知固定长度对象的最低载荷长度,解析公共头。
* 输入参数:parameters——双方约定的非空应用层参数;asdu/asdu_len——完整ASDU。
* 输出参数:asdu_info——解析成功时写入公共头字段和信息对象区位置。
* 返回值:解析成功返回1,参数或长度非法返回0且不修改asdu_info。
* 说明:按Type ID、VSQ数量及SQ位检查最低载荷长度;未知/变长类型及零对象不做此检查。
* 不检查多余尾字节、SQ/数量的类型约束、传送原因、地址或信息对象内容是否合法。
*/
int IEC60870_Parse_ASDU_Header(const IEC60870_AppLayerParameters_t *parameters,
const uint8_t *asdu,
uint16_t asdu_len,
IEC60870_AsduInfo_t *asdu_info)
{
IEC60870_AsduInfo_t result;
uint16_t header_length;
unsigned int ca_index;
if (asdu == NULL || asdu_info == NULL || !IEC60870_AppLayerParameters_Valid(parameters)) {
return 0;
}
header_length = (uint16_t)(2 + parameters->sizeOfCOT + parameters->sizeOfCA);
if (asdu_len < header_length || asdu_len > parameters->maxSizeOfASDU ||
!ASDU_Payload_Length_Valid(parameters, asdu, asdu_len, header_length)) {
return 0;
}
/* COT为1字节时CA紧跟COT,不存在OA;先组装局部结果,再一次性发布。 */
memset(&result, 0, sizeof(result));
ca_index = (unsigned int)(2 + parameters->sizeOfCOT);
result.common_address = asdu[ca_index];
if (parameters->sizeOfCA == 2) {
result.common_address |= (uint16_t)((uint16_t)asdu[ca_index + 1U] << 8);
}
result.type_id = asdu[0];
result.object_count =
(uint8_t)(asdu[1] & IEC60870_ASDU_OBJECT_COUNT_MASK);
result.is_sequence =
(uint8_t)((asdu[1] & IEC60870_ASDU_SEQUENCE_MASK) != 0U);
result.cause_of_transmission =
(uint8_t)(asdu[2] & IEC60870_ASDU_CAUSE_MASK);
result.is_negative_confirm =
(uint8_t)((asdu[2] & IEC60870_ASDU_NEGATIVE_MASK) != 0U);
result.is_test =
(uint8_t)((asdu[2] & IEC60870_ASDU_TEST_MASK) != 0U);
result.originator_address = parameters->sizeOfCOT == 2 ? asdu[3] : 0;
result.information_objects = &asdu[header_length];
result.information_objects_len = (uint16_t)(asdu_len - header_length);
result.parameters = parameters;
*asdu_info = result;
return 1;
}