Files
STM32F4-Base/IEC60870-Slave/test/iec60870_asdu_extended_test.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

605 lines
35 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.
/* 新增37种类型的独立报文字节向量及边界测试。
* 期望字节手工给出,不通过生产编码器计算;联合体仅用于测试存储。 */
#include "IEC60870_Asdu.h"
#include <limits.h>
#include <stdio.h>
#include <string.h>
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: %s\n", __func__, __LINE__, #c); return 0; } } while (0)
#define COUNT(a) (sizeof(a) / sizeof((a)[0]))
#define SHORT_TIME_BYTES 0xD5, 0xDD, 0xA2
#define LONG_TIME_BYTES 0xD5, 0xDD, 0xA2, 0x8C, 0x9D, 0x02, 0x18
static const IEC60870_CP24Time2a_t short_time = {56789, 34, 1};
static const IEC60870_CP56Time2a_t long_time = {{2024,2,29,12,34,56,789,4},1,1,1};
static const IEC60870_CP16Time2a_t elapsed = {0xBEEF};
static const IEC60870_BinaryCounterReading_t counter = {INT32_MIN,21,1,1,1};
static const uint8_t segment_data[] = {0xDE,0xAD,0xBE,0xEF};
/* 公共测试显式提供布局,不引入104传输头文件。 */
static const IEC60870_AppLayerParameters_t default_parameters = {1, 1, 2, 0, 2, 3, 249};
typedef union {
IEC60870_StepPositionInformation_t v5;
IEC60870_BitString32_t v7;
IEC60870_SinglePointWithCP24Time2a_t v2;
IEC60870_DoublePointWithCP24Time2a_t v4;
IEC60870_StepPositionWithCP24Time2a_t v6;
IEC60870_Bitstring32WithCP24Time2a_t v8;
IEC60870_MeasuredValueNormalizedWithCP24Time2a_t v10;
IEC60870_MeasuredValueScaledWithCP24Time2a_t v12;
IEC60870_MeasuredValueShortWithCP24Time2a_t v14;
IEC60870_IntegratedTotalsWithCP24Time2a_t v16;
IEC60870_StepPositionWithCP56Time2a_t v32;
IEC60870_Bitstring32WithCP56Time2a_t v33;
IEC60870_EventOfProtectionEquipment_t v17;
IEC60870_PackedStartEventsOfProtectionEquipment_t v18;
IEC60870_PackedOutputCircuitInfo_t v19;
IEC60870_EventOfProtectionEquipmentWithCP56Time2a_t v38;
IEC60870_PackedStartEventsOfProtectionEquipmentWithCP56Time2a_t v39;
IEC60870_PackedOutputCircuitInfoWithCP56Time2a_t v40;
IEC60870_PackedSinglePointWithSCD_t v20;
IEC60870_MeasuredValueNormalizedWithoutQuality_t v21;
IEC60870_Bitstring32Command_t v51;
IEC60870_Bitstring32CommandWithCP56Time2a_t v64;
IEC60870_EndOfInitialization_t v70;
IEC60870_TestCommand_t v104;
IEC60870_DelayAcquisitionCommand_t v106;
IEC60870_ParameterNormalizedValue_t v110;
IEC60870_ParameterScaledValue_t v111;
IEC60870_ParameterFloatValue_t v112;
IEC60870_ParameterActivation_t v113;
IEC60870_FileReady_t v120;
IEC60870_SectionReady_t v121;
IEC60870_FileCallOrSelect_t v122;
IEC60870_FileACK_t v124;
IEC60870_FileLastSegmentOrSection_t v123;
IEC60870_FileSegment_t v125;
IEC60870_FileDirectory_t v126;
IEC60870_QueryLog_t v127;
} Object;
typedef struct {
uint8_t type, length, single, sequence;
uint8_t payload[16];
} Vector;
static const Vector vectors[] = {
{2, 4, 0, 1, {0xF1, SHORT_TIME_BYTES}},
{4, 4, 0, 1, {0x93, SHORT_TIME_BYTES}},
{5, 2, 0, 1, {0xFE, 0x81}},
{6, 5, 0, 1, {0xFE, 0x81, SHORT_TIME_BYTES}},
{7, 5, 0, 1, {0xEF, 0xCD, 0xAB, 0x89, 0x51}},
{8, 8, 0, 1, {0xEF, 0xCD, 0xAB, 0x89, 0x51, SHORT_TIME_BYTES}},
{10, 6, 0, 1, {0, 0x80, 0x81, SHORT_TIME_BYTES}},
{12, 6, 0, 1, {0xFE, 0xFF, 0x10, SHORT_TIME_BYTES}},
{14, 8, 0, 1, {0, 0, 0xC0, 0x3F, 0x41, SHORT_TIME_BYTES}},
{16, 8, 0, 1, {0, 0, 0, 0x80, 0xF5, SHORT_TIME_BYTES}},
{17, 6, 0, 1, {0x9A, 0xEF, 0xBE, SHORT_TIME_BYTES}},
{18, 7, 0, 1, {0x21, 0x98, 0xEF, 0xBE, SHORT_TIME_BYTES}},
{19, 7, 0, 1, {9, 0x98, 0xEF, 0xBE, SHORT_TIME_BYTES}},
{20, 5, 0, 1, {0x34, 0x12, 0xCD, 0xAB, 0x81}},
{21, 2, 0, 1, {0, 0x80}},
{32, 9, 0, 1, {0xFE, 0x81, LONG_TIME_BYTES}},
{33, 12, 0, 1, {0xEF, 0xCD, 0xAB, 0x89, 0x51, LONG_TIME_BYTES}},
{38, 10, 0, 1, {0x9A, 0xEF, 0xBE, LONG_TIME_BYTES}},
{39, 11, 0, 1, {0x21, 0x98, 0xEF, 0xBE, LONG_TIME_BYTES}},
{40, 11, 0, 1, {9, 0x98, 0xEF, 0xBE, LONG_TIME_BYTES}},
{51, 4, 1, 0, {0xEF, 0xCD, 0xAB, 0x89}},
{64, 11, 1, 0, {0xEF, 0xCD, 0xAB, 0x89, LONG_TIME_BYTES}},
{70, 1, 1, 0, {0x82}},
{104, 2, 1, 0, {0xAA, 0x55}},
{106, 2, 1, 0, {0xEF, 0xBE}},
{110, 3, 0, 0, {0, 0x80, 0xC3}},
{111, 3, 0, 0, {0xFE, 0xFF, 0xC3}},
{112, 5, 0, 0, {0, 0, 0xC0, 0x3F, 0xC3}},
{113, 1, 0, 0, {0x82}},
{120, 6, 1, 0, {0x34, 0x12, 1, 0xCD, 0xAB, 0x80}},
{121, 7, 1, 0, {0x34, 0x12, 0x56, 1, 0xCD, 0xAB, 0x80}},
{122, 4, 1, 0, {0x34, 0x12, 0x56, 0x12}},
{123, 5, 1, 0, {0x34, 0x12, 0x56, 1, 0xA5}},
{124, 4, 1, 0, {0x34, 0x12, 0x56, 0x12}},
{125, 8, 1, 0, {0x34, 0x12, 0x56, 4, 0xDE, 0xAD, 0xBE, 0xEF}},
{126, 13, 0, 1, {0x34, 0x12, 1, 0xCD, 0xAB, 0xE3, LONG_TIME_BYTES}},
{127, 16, 1, 0, {0x34, 0x12, LONG_TIME_BYTES, LONG_TIME_BYTES}},
};
static IEC60870_AsduResult_t Create_Object(uint8_t type, uint32_t ioa, Object *out)
{
switch (type) {
case 2: return IEC60870_SinglePointWithCP24Time2a_Create(&out->v2, ioa, 1, 0xF0, &short_time);
case 4: return IEC60870_DoublePointWithCP24Time2a_Create(&out->v4, ioa, 3, 0x90, &short_time);
case 5: return IEC60870_StepPositionInformation_Create(&out->v5, ioa, -2, 1, 0x81);
case 6: return IEC60870_StepPositionWithCP24Time2a_Create(&out->v6, ioa, -2, 1, 0x81, &short_time);
case 7: return IEC60870_BitString32_Create(&out->v7, ioa, UINT32_C(0x89ABCDEF), 0x51);
case 8: return IEC60870_Bitstring32WithCP24Time2a_Create(&out->v8, ioa, UINT32_C(0x89ABCDEF), 0x51, &short_time);
case 10: return IEC60870_MeasuredValueNormalizedWithCP24Time2a_Create(&out->v10, ioa, INT16_MIN, 0x81, &short_time);
case 12: return IEC60870_MeasuredValueScaledWithCP24Time2a_Create(&out->v12, ioa, -2, 0x10, &short_time);
case 14: return IEC60870_MeasuredValueShortWithCP24Time2a_Create(&out->v14, ioa, 1.5f, 0x41, &short_time);
case 16: return IEC60870_IntegratedTotalsWithCP24Time2a_Create(&out->v16, ioa, &counter, &short_time);
case 17: return IEC60870_EventOfProtectionEquipment_Create(&out->v17, ioa, 2, 0x98, &elapsed, &short_time);
case 18: return IEC60870_PackedStartEventsOfProtectionEquipment_Create(&out->v18, ioa, 0x21, 0x98, &elapsed, &short_time);
case 19: return IEC60870_PackedOutputCircuitInfo_Create(&out->v19, ioa, 9, 0x98, &elapsed, &short_time);
case 20: return IEC60870_PackedSinglePointWithSCD_Create(&out->v20, ioa, 0x1234, 0xABCD, 0x81);
case 21: return IEC60870_MeasuredValueNormalizedWithoutQuality_Create(&out->v21, ioa, INT16_MIN);
case 32: return IEC60870_StepPositionWithCP56Time2a_Create(&out->v32, ioa, -2, 1, 0x81, &long_time);
case 33: return IEC60870_Bitstring32WithCP56Time2a_Create(&out->v33, ioa, UINT32_C(0x89ABCDEF), 0x51, &long_time);
case 38: return IEC60870_EventOfProtectionEquipmentWithCP56Time2a_Create(&out->v38, ioa, 2, 0x98, &elapsed, &long_time);
case 39: return IEC60870_PackedStartEventsOfProtectionEquipmentWithCP56Time2a_Create(&out->v39, ioa, 0x21, 0x98, &elapsed, &long_time);
case 40: return IEC60870_PackedOutputCircuitInfoWithCP56Time2a_Create(&out->v40, ioa, 9, 0x98, &elapsed, &long_time);
case 51: return IEC60870_Bitstring32Command_Create(&out->v51, ioa, UINT32_C(0x89ABCDEF));
case 64: return IEC60870_Bitstring32CommandWithCP56Time2a_Create(&out->v64, ioa, UINT32_C(0x89ABCDEF), &long_time);
case 70: return IEC60870_EndOfInitialization_Create(&out->v70, ioa, 0x82);
case 104: return IEC60870_TestCommand_Create(&out->v104, ioa);
case 106: return IEC60870_DelayAcquisitionCommand_Create(&out->v106, ioa, &elapsed);
case 110: return IEC60870_ParameterNormalizedValue_Create(&out->v110, ioa, INT16_MIN, 0xC3);
case 111: return IEC60870_ParameterScaledValue_Create(&out->v111, ioa, -2, 0xC3);
case 112: return IEC60870_ParameterFloatValue_Create(&out->v112, ioa, 1.5f, 0xC3);
case 113: return IEC60870_ParameterActivation_Create(&out->v113, ioa, 0x82);
case 120: return IEC60870_FileReady_Create(&out->v120, ioa, 0x1234, UINT32_C(0xABCD01), 0x80);
case 121: return IEC60870_SectionReady_Create(&out->v121, ioa, 0x1234, 0x56, UINT32_C(0xABCD01), 0x80);
case 122: return IEC60870_FileCallOrSelect_Create(&out->v122, ioa, 0x1234, 0x56, 0x12);
case 123: return IEC60870_FileLastSegmentOrSection_Create(&out->v123, ioa, 0x1234, 0x56, 1, 0xA5);
case 124: return IEC60870_FileACK_Create(&out->v124, ioa, 0x1234, 0x56, 0x12);
case 125: return IEC60870_FileSegment_Create(&out->v125, ioa, 0x1234, 0x56, segment_data, sizeof(segment_data));
case 126: return IEC60870_FileDirectory_Create(&out->v126, ioa, 0x1234, UINT32_C(0xABCD01), 0xE3, &long_time);
case 127: return IEC60870_QueryLog_Create(&out->v127, ioa, 0x1234, &long_time, &long_time);
default: return IEC60870_ASDU_UNSUPPORTED;
}
}
static IEC60870_AsduResult_t Parse_Object(uint8_t type, uint32_t ioa, const uint8_t *payload, uint16_t length, Object *out)
{
switch (type) {
case 5: return IEC60870_StepPositionInformation_Get_From_Buffer(&out->v5, ioa, payload, length);
case 7: return IEC60870_BitString32_Get_From_Buffer(&out->v7, ioa, payload, length);
case 2: return IEC60870_SinglePointWithCP24Time2a_Get_From_Buffer(&out->v2, ioa, payload, length);
case 4: return IEC60870_DoublePointWithCP24Time2a_Get_From_Buffer(&out->v4, ioa, payload, length);
case 6: return IEC60870_StepPositionWithCP24Time2a_Get_From_Buffer(&out->v6, ioa, payload, length);
case 8: return IEC60870_Bitstring32WithCP24Time2a_Get_From_Buffer(&out->v8, ioa, payload, length);
case 10: return IEC60870_MeasuredValueNormalizedWithCP24Time2a_Get_From_Buffer(&out->v10, ioa, payload, length);
case 12: return IEC60870_MeasuredValueScaledWithCP24Time2a_Get_From_Buffer(&out->v12, ioa, payload, length);
case 14: return IEC60870_MeasuredValueShortWithCP24Time2a_Get_From_Buffer(&out->v14, ioa, payload, length);
case 16: return IEC60870_IntegratedTotalsWithCP24Time2a_Get_From_Buffer(&out->v16, ioa, payload, length);
case 32: return IEC60870_StepPositionWithCP56Time2a_Get_From_Buffer(&out->v32, ioa, payload, length);
case 33: return IEC60870_Bitstring32WithCP56Time2a_Get_From_Buffer(&out->v33, ioa, payload, length);
case 17: return IEC60870_EventOfProtectionEquipment_Get_From_Buffer(&out->v17, ioa, payload, length);
case 18: return IEC60870_PackedStartEventsOfProtectionEquipment_Get_From_Buffer(&out->v18, ioa, payload, length);
case 19: return IEC60870_PackedOutputCircuitInfo_Get_From_Buffer(&out->v19, ioa, payload, length);
case 38: return IEC60870_EventOfProtectionEquipmentWithCP56Time2a_Get_From_Buffer(&out->v38, ioa, payload, length);
case 39: return IEC60870_PackedStartEventsOfProtectionEquipmentWithCP56Time2a_Get_From_Buffer(&out->v39, ioa, payload, length);
case 40: return IEC60870_PackedOutputCircuitInfoWithCP56Time2a_Get_From_Buffer(&out->v40, ioa, payload, length);
case 20: return IEC60870_PackedSinglePointWithSCD_Get_From_Buffer(&out->v20, ioa, payload, length);
case 21: return IEC60870_MeasuredValueNormalizedWithoutQuality_Get_From_Buffer(&out->v21, ioa, payload, length);
case 51: return IEC60870_Bitstring32Command_Get_From_Buffer(&out->v51, ioa, payload, length);
case 64: return IEC60870_Bitstring32CommandWithCP56Time2a_Get_From_Buffer(&out->v64, ioa, payload, length);
case 70: return IEC60870_EndOfInitialization_Get_From_Buffer(&out->v70, ioa, payload, length);
case 104: return IEC60870_TestCommand_Get_From_Buffer(&out->v104, ioa, payload, length);
case 106: return IEC60870_DelayAcquisitionCommand_Get_From_Buffer(&out->v106, ioa, payload, length);
case 110: return IEC60870_ParameterNormalizedValue_Get_From_Buffer(&out->v110, ioa, payload, length);
case 111: return IEC60870_ParameterScaledValue_Get_From_Buffer(&out->v111, ioa, payload, length);
case 112: return IEC60870_ParameterFloatValue_Get_From_Buffer(&out->v112, ioa, payload, length);
case 113: return IEC60870_ParameterActivation_Get_From_Buffer(&out->v113, ioa, payload, length);
case 120: return IEC60870_FileReady_Get_From_Buffer(&out->v120, ioa, payload, length);
case 121: return IEC60870_SectionReady_Get_From_Buffer(&out->v121, ioa, payload, length);
case 122: return IEC60870_FileCallOrSelect_Get_From_Buffer(&out->v122, ioa, payload, length);
case 124: return IEC60870_FileACK_Get_From_Buffer(&out->v124, ioa, payload, length);
case 123: return IEC60870_FileLastSegmentOrSection_Get_From_Buffer(&out->v123, ioa, payload, length);
case 125: return IEC60870_FileSegment_Get_From_Buffer(&out->v125, ioa, payload, length);
case 126: return IEC60870_FileDirectory_Get_From_Buffer(&out->v126, ioa, payload, length);
case 127: return IEC60870_QueryLog_Get_From_Buffer(&out->v127, ioa, payload, length);
default: return IEC60870_ASDU_UNSUPPORTED;
}
}
static IEC60870_InformationObject_t *Base(Object *object)
{
return (IEC60870_InformationObject_t *)object;
}
/* 独立按协议宽度拼接地址,未调用生产工具。 */
static void Write_Address(uint8_t *out, uint32_t value, int width)
{
int i;
for (i = 0; i < width; ++i) { out[i] = (uint8_t)(value >> (8 * i)); }
}
static uint16_t Expected_Frame(const Vector *v, const IEC60870_AppLayerParameters_t *p,
int sq, int count, uint32_t first, uint8_t *out)
{
uint16_t n = (uint16_t)(2 + p->sizeOfCOT + p->sizeOfCA);
int i;
out[0] = v->type;
out[1] = (uint8_t)((sq << 7) | count);
out[2] = 0xC3;
if (p->sizeOfCOT == 2) { out[3] = 0xA7; }
Write_Address(out + 2 + p->sizeOfCOT, p->sizeOfCA == 2 ? 0x1234U : 0x34U, p->sizeOfCA);
for (i = 0; i < count; ++i) {
if (i == 0 || !sq) {
Write_Address(out + n, first + (uint32_t)(sq ? i : i * 7), p->sizeOfIOA);
n = (uint16_t)(n + p->sizeOfIOA);
}
memcpy(out + n, v->payload, v->length);
n = (uint16_t)(n + v->length);
}
return n;
}
static int Reject_Frame(const IEC60870_AppLayerParameters_t *p, const uint8_t *frame, uint16_t length)
{
IEC60870_AsduInfo_t info;
uint8_t saved[sizeof(info)];
memset(&info, 0xA5, sizeof(info));
memcpy(saved, &info, sizeof(info));
CHECK(IEC60870_Asdu_Parse(p, frame, length, &info) == IEC60870_ASDU_ERROR);
CHECK(memcmp(saved, &info, sizeof(info)) == 0);
return 1;
}
static int test_all_layouts(void)
{
int cot, ca, ioa, sq;
unsigned int cases = 0;
size_t k;
for (cot = 1; cot <= 2; ++cot) for (ca = 1; ca <= 2; ++ca) for (ioa = 1; ioa <= 3; ++ioa) {
IEC60870_AppLayerParameters_t p = default_parameters;
IEC60870_AsduHeader_t header = {3,1,1,0xA7,0};
uint32_t first = ioa == 1 ? 0x40U : ioa == 2 ? 0x3440U : 0x123440U;
p.sizeOfCOT = cot; p.sizeOfCA = ca; p.sizeOfIOA = ioa; p.maxSizeOfASDU = 254;
header.common_address = ca == 1 ? 0x34 : 0x1234;
for (k = 0; k < COUNT(vectors); ++k) for (sq = 0; sq <= vectors[k].sequence; ++sq) {
const Vector *v = &vectors[k];
IEC60870_Asdu_t asdu, saved_asdu;
IEC60870_AsduInfo_t info;
Object object, parsed, saved_object;
uint8_t bytes[260], expected[260], saved_bytes[260], encoded[262];
const uint8_t *data;
uint16_t length, wire_length, object_length;
int count = v->single ? 1 : 2, i;
memset(bytes, 0xA5, sizeof(bytes));
CHECK(IEC60870_Asdu_Create(&asdu, &p, bytes, sizeof(bytes), &header, sq) == IEC60870_ASDU_OK);
for (i = 0; i < count; ++i) {
uint32_t address = first + (uint32_t)(sq ? i : i * 7);
CHECK(Create_Object(v->type, address, &object) == IEC60870_ASDU_OK);
CHECK(IEC60870_InformationObject_Get_Type(Base(&object)) == v->type);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, Base(&object)) == IEC60870_ASDU_OK);
CHECK(IEC60870_InformationObject_Encode(Base(&object), &p, 0, encoded, sizeof(encoded), &object_length) == IEC60870_ASDU_OK);
CHECK(object_length == ioa + v->length);
CHECK(memcmp(encoded + ioa, v->payload, v->length) == 0);
}
CHECK(IEC60870_Asdu_Get_Data(&asdu, &data, &length) == IEC60870_ASDU_OK);
wire_length = Expected_Frame(v, &p, sq, count, first, expected);
CHECK(length == wire_length && memcmp(data, expected, length) == 0);
CHECK(bytes[length] == 0xA5);
CHECK(IEC60870_Asdu_Parse(&p, expected, length, &info) == IEC60870_ASDU_OK);
CHECK(info.object_count == count && info.is_sequence == sq && info.common_address == header.common_address);
for (i = 0; i < count; ++i) {
CHECK(IEC60870_Asdu_Get_Element(&info, (uint8_t)i, &parsed, sizeof(parsed)) == IEC60870_ASDU_OK);
CHECK(Base(&parsed)->ioa == first + (uint32_t)(sq ? i : i * 7));
CHECK(IEC60870_InformationObject_Encode(Base(&parsed), &p, 0, encoded, sizeof(encoded), &object_length) == IEC60870_ASDU_OK);
CHECK(memcmp(encoded + ioa, v->payload, v->length) == 0);
}
memset(&parsed, 0xA5, sizeof(parsed)); memcpy(&saved_object, &parsed, sizeof(parsed));
CHECK(IEC60870_Asdu_Get_Element(&info, 0, &parsed, 1) == IEC60870_ASDU_ERROR);
CHECK(memcmp(&parsed, &saved_object, sizeof(parsed)) == 0);
CHECK(Reject_Frame(&p, expected, (uint16_t)(length - 1)));
expected[length] = 0;
CHECK(Reject_Frame(&p, expected, (uint16_t)(length + 1)));
for (i = 0; i < v->length; ++i) {
CHECK(Parse_Object(v->type, first, v->payload, (uint16_t)i, &parsed) == IEC60870_ASDU_ERROR);
CHECK(memcmp(&parsed, &saved_object, sizeof(parsed)) == 0);
}
CHECK(Parse_Object(v->type, first, v->payload, v->length, &parsed) == IEC60870_ASDU_OK);
/* 原对象存储可复用,加入ASDU后保存的字节必须独立。 */
memset(&object, 0, sizeof(object));
CHECK(memcmp(data, expected, length) == 0);
saved_asdu = asdu; memcpy(saved_bytes, bytes, sizeof(bytes));
CHECK(Create_Object(v->type, first, &object) == IEC60870_ASDU_OK);
if (v->single) {
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, Base(&object)) == IEC60870_ASDU_FULL);
} else if (sq) {
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, Base(&object)) == IEC60870_ASDU_ERROR);
} else {
asdu.capacity = asdu.length; saved_asdu = asdu;
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, Base(&object)) == IEC60870_ASDU_FULL);
}
CHECK(memcmp(&asdu, &saved_asdu, sizeof(asdu)) == 0);
CHECK(memcmp(bytes, saved_bytes, sizeof(bytes)) == 0);
++cases;
}
}
printf("Extended ASDU: %u independent wire layouts verified\n", cases);
return 1;
}
static int test_sequence_and_counts(void)
{
size_t k;
uint8_t frame[260], buffer[260], saved[260];
IEC60870_AsduHeader_t header = {3,1,1,0xA7,0x1234};
IEC60870_Asdu_t asdu, before;
Object object;
for (k = 0; k < COUNT(vectors); ++k) {
const Vector *v = &vectors[k];
uint16_t n = Expected_Frame(v, &default_parameters, 0, 1, 0x40, frame);
frame[1] = 0;
CHECK(Reject_Frame(&default_parameters, frame, n));
if (!v->sequence) {
n = Expected_Frame(v, &default_parameters, 1, 1, 0x40, frame);
CHECK(Reject_Frame(&default_parameters, frame, n));
memset(buffer, 0xA5, sizeof(buffer));
CHECK(IEC60870_Asdu_Create(&asdu, &default_parameters, buffer, sizeof(buffer), &header, 1) == IEC60870_ASDU_OK);
before = asdu; memcpy(saved, buffer, sizeof(buffer));
CHECK(Create_Object(v->type, 0x40, &object) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, Base(&object)) == IEC60870_ASDU_ERROR);
CHECK(memcmp(&asdu, &before, sizeof(asdu)) == 0 && memcmp(buffer, saved, sizeof(buffer)) == 0);
}
if (v->single) {
n = Expected_Frame(v, &default_parameters, 0, 2, 0x40, frame);
CHECK(Reject_Frame(&default_parameters, frame, n));
}
if (v->sequence) {
IEC60870_AppLayerParameters_t p = default_parameters;
p.sizeOfIOA = 1;
n = Expected_Frame(v, &p, 1, 2, 255, frame);
CHECK(Reject_Frame(&p, frame, n));
}
}
return 1;
}
static int test_invalid_fields(void)
{
/* 每项给出独立的非法保留位/状态/时间修改,不依赖生产描述表。 */
static const struct { uint8_t type, offset, mask; } faults[] = {
{2,0,2},{4,0,4},{5,1,2},{6,1,2},{7,4,2},{8,4,2},
{10,2,2},{12,2,2},{14,4,2},{16,7,0x40},
{17,0,4},{18,0,0x40},{19,0,0x10},{20,4,2},
{32,1,2},{33,4,2},{38,0,4},{39,1,4},{40,0,0x10},
{104,0,1},{120,5,1},{121,6,1},{125,3,1},
{126,11,0x80},{127,14,0x80}
};
size_t i, k;
for (i = 0; i < COUNT(faults); ++i) {
Object object, before;
uint8_t payload[17], frame[260];
for (k = 0; k < COUNT(vectors) && vectors[k].type != faults[i].type; ++k) {}
CHECK(k < COUNT(vectors));
memcpy(payload, vectors[k].payload, vectors[k].length);
payload[faults[i].offset] |= faults[i].mask;
memset(&object, 0xA5, sizeof(object)); memcpy(&before, &object, sizeof(object));
CHECK(Parse_Object(vectors[k].type, 1, payload, vectors[k].length, &object) == IEC60870_ASDU_ERROR);
CHECK(memcmp(&object, &before, sizeof(object)) == 0);
{
uint16_t n = Expected_Frame(&vectors[k], &default_parameters, 0, 1, 1, frame);
memcpy(frame + 9, payload, vectors[k].length);
CHECK(Reject_Frame(&default_parameters, frame, n));
}
}
/* 多对象中的第二个非法对象也必须导致整条ASDU解析失败。 */
{
IEC60870_AsduInfo_t info;
uint8_t frame[] = {5,0x82,3,0,1,0,1,0,0,0xFE,0x81,0xFE,0x83};
CHECK(Reject_Frame(&default_parameters, frame, sizeof(frame)));
frame[12] = 0x81;
CHECK(IEC60870_Asdu_Parse(&default_parameters, frame, sizeof(frame), &info) == IEC60870_ASDU_OK);
}
return 1;
}
static int test_short_times(void)
{
IEC60870_CP16Time2a_t cp16, parsed16 = {0};
IEC60870_CP24Time2a_t cp24 = {59999,59,1}, parsed24, before24;
uint8_t bytes[4] = {0xA5,0xA5,0xA5,0xA5}, saved[4];
uint32_t i;
for (i = 0; i <= 65535U; ++i) {
cp16.milliseconds = (uint16_t)i;
CHECK(IEC60870_Time_To_CP16(&cp16, bytes, 2));
CHECK(bytes[0] == (uint8_t)i && bytes[1] == (uint8_t)(i >> 8));
CHECK(IEC60870_Time_From_CP16(bytes, 2, &parsed16) && parsed16.milliseconds == i);
}
CHECK(IEC60870_Time_To_CP24(&cp24, bytes, 3));
CHECK(bytes[0] == 0x5F && bytes[1] == 0xEA && bytes[2] == 0xBB && bytes[3] == 0xA5);
CHECK(IEC60870_Time_From_CP24(bytes, 3, &parsed24));
CHECK(parsed24.milliseconds == 59999 && parsed24.minute == 59 && parsed24.is_invalid == 1);
before24 = parsed24;
memcpy(saved, bytes, sizeof(bytes));
cp24.milliseconds = 60000;
CHECK(!IEC60870_Time_To_CP24(&cp24, bytes, sizeof(bytes)));
CHECK(memcmp(bytes, saved, sizeof(bytes)) == 0);
cp24.milliseconds = 0; cp24.minute = 60;
CHECK(!IEC60870_Time_To_CP24(&cp24, bytes, sizeof(bytes)));
cp24.minute = 0; cp24.is_invalid = 2;
CHECK(!IEC60870_Time_To_CP24(&cp24, bytes, sizeof(bytes)));
bytes[2] |= 0x40;
CHECK(!IEC60870_Time_From_CP24(bytes, 3, &parsed24));
CHECK(memcmp(&parsed24, &before24, sizeof(parsed24)) == 0);
bytes[0] = 0x60; bytes[1] = 0xEA; bytes[2] = 0;
CHECK(!IEC60870_Time_From_CP24(bytes, 3, &parsed24));
bytes[0] = 0; bytes[1] = 0; bytes[2] = 60;
CHECK(!IEC60870_Time_From_CP24(bytes, 3, &parsed24));
CHECK(!IEC60870_Time_From_CP24(bytes, 2, &parsed24));
CHECK(!IEC60870_Time_From_CP24(NULL, 3, &parsed24));
CHECK(!IEC60870_Time_From_CP16(bytes, 1, &parsed16));
CHECK(!IEC60870_Time_To_CP16(&cp16, bytes, 1));
CHECK(!IEC60870_Time_To_CP24(NULL, bytes, 3));
cp24.milliseconds = 0; cp24.minute = 0; cp24.is_invalid = 0;
CHECK(IEC60870_Time_To_CP24(&cp24, bytes, 3));
CHECK(bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0);
return 1;
}
static int test_constructor_atomicity(void)
{
Object object, before;
size_t i;
uint8_t encoded[262], saved_bytes[262];
uint16_t length = 0xA55A;
IEC60870_CP24Time2a_t bad_time = {60000,0,0};
memset(&object, 0xA5, sizeof(object)); memcpy(&before, &object, sizeof(object));
for (i = 0; i < COUNT(vectors); ++i) {
CHECK(Create_Object(vectors[i].type, UINT32_C(0x1000000), &object) == IEC60870_ASDU_ERROR);
CHECK(memcmp(&object, &before, sizeof(object)) == 0);
CHECK(Parse_Object(vectors[i].type, 1, NULL, vectors[i].length, &object) == IEC60870_ASDU_ERROR);
CHECK(memcmp(&object, &before, sizeof(object)) == 0);
}
#define REJECT_CREATE(call) do { CHECK((call) == IEC60870_ASDU_ERROR); CHECK(memcmp(&object, &before, sizeof(object)) == 0); } while (0)
REJECT_CREATE(IEC60870_StepPositionInformation_Create(&object.v5, 1, -65, 0, 0));
REJECT_CREATE(IEC60870_StepPositionInformation_Create(&object.v5, 1, 64, 0, 0));
REJECT_CREATE(IEC60870_StepPositionInformation_Create(&object.v5, 1, 0, 2, 0));
REJECT_CREATE(IEC60870_BitString32_Create(&object.v7, 1, 0, 2));
REJECT_CREATE(IEC60870_SinglePointWithCP24Time2a_Create(&object.v2, 1, 1, 0, &bad_time));
REJECT_CREATE(IEC60870_SinglePointWithCP24Time2a_Create(&object.v2, 1, 1, 0, NULL));
REJECT_CREATE(IEC60870_EventOfProtectionEquipment_Create(&object.v17, 1, 4, 0, &elapsed, &short_time));
REJECT_CREATE(IEC60870_EventOfProtectionEquipment_Create(&object.v17, 1, 1, 4, &elapsed, &short_time));
REJECT_CREATE(IEC60870_PackedStartEventsOfProtectionEquipment_Create(&object.v18, 1, 64, 0, &elapsed, &short_time));
REJECT_CREATE(IEC60870_PackedOutputCircuitInfo_Create(&object.v19, 1, 16, 0, &elapsed, &short_time));
REJECT_CREATE(IEC60870_EndOfInitialization_Create(&object.v70, 1, 256));
REJECT_CREATE(IEC60870_DelayAcquisitionCommand_Create(&object.v106, 1, NULL));
REJECT_CREATE(IEC60870_ParameterNormalizedValue_Create(&object.v110, 1, 0, -1));
REJECT_CREATE(IEC60870_FileReady_Create(&object.v120, 1, 1, UINT32_C(0x1000000), 0));
REJECT_CREATE(IEC60870_SectionReady_Create(&object.v121, 1, 1, 1, 0, 1));
REJECT_CREATE(IEC60870_FileSegment_Create(&object.v125, 1, 1, 1, NULL, 1));
REJECT_CREATE(IEC60870_FileSegment_Create(&object.v125, 1, 1, 1, segment_data, 256));
REJECT_CREATE(IEC60870_FileDirectory_Create(&object.v126, 1, 1, 0, 0, NULL));
REJECT_CREATE(IEC60870_QueryLog_Create(&object.v127, 1, 1, &long_time, NULL));
#undef REJECT_CREATE
CHECK(Create_Object(5, 1, &object) == IEC60870_ASDU_OK);
object.v5.value = 100;
memset(encoded, 0xA5, sizeof(encoded)); memcpy(saved_bytes, encoded, sizeof(encoded));
CHECK(IEC60870_InformationObject_Encode(Base(&object), &default_parameters, 0, encoded, sizeof(encoded), &length) == IEC60870_ASDU_ERROR);
CHECK(memcmp(encoded, saved_bytes, sizeof(encoded)) == 0 && length == 0xA55A);
return 1;
}
static int test_file_segments(void)
{
int cot, ca, ioa, max;
uint8_t input[255], bytes[270], before_bytes[270], encoded[262];
IEC60870_AsduHeader_t header = {13,0,0,0,1};
unsigned int i;
CHECK(IEC60870_FileSegment_Get_Max_Data_Size(&default_parameters) == 236);
CHECK(IEC60870_FileSegment_Get_Max_Data_Size(NULL) == -1);
for (i = 0; i < sizeof(input); ++i) { input[i] = (uint8_t)(i ^ 0x5A); }
for (cot = 1; cot <= 2; ++cot) for (ca = 1; ca <= 2; ++ca) for (ioa = 1; ioa <= 3; ++ioa) {
IEC60870_AppLayerParameters_t p = default_parameters;
IEC60870_FileSegment_t segment, parsed;
IEC60870_Asdu_t asdu, before;
IEC60870_AsduInfo_t info;
const uint8_t *wire;
uint16_t n, object_length;
int h = 2 + cot + ca;
p.sizeOfCOT = cot; p.sizeOfCA = ca; p.sizeOfIOA = ioa; p.maxSizeOfASDU = 254;
max = IEC60870_FileSegment_Get_Max_Data_Size(&p);
CHECK(max == 254 - h - ioa - 4);
CHECK(IEC60870_FileSegment_Create(&segment, 1, 0x1234, 7, input, (uint16_t)max) == IEC60870_ASDU_OK);
memset(bytes, 0xA5, sizeof(bytes));
CHECK(IEC60870_Asdu_Create(&asdu, &p, bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &segment.object) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Get_Data(&asdu, &wire, &n) == IEC60870_ASDU_OK && n == 254);
CHECK(wire[h+ioa] == 0x34 && wire[h+ioa+1] == 0x12 && wire[h+ioa+2] == 7 && wire[h+ioa+3] == max);
CHECK(memcmp(wire+h+ioa+4, input, (size_t)max) == 0 && bytes[254] == 0xA5);
CHECK(IEC60870_Asdu_Parse(&p, wire, n, &info) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Get_Element(&info, 0, &parsed, sizeof(parsed)) == IEC60870_ASDU_OK);
CHECK(parsed.length == max && memcmp(parsed.data, input, (size_t)max) == 0);
memset(bytes+h+ioa+4, 0, (size_t)max);
CHECK(memcmp(parsed.data, input, (size_t)max) == 0); /* 解析结果拥有独立数据。 */
CHECK(IEC60870_FileSegment_Create(&segment, 1, 1, 1, input, (uint16_t)(max+1)) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Create(&asdu, &p, bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK);
before = asdu; memcpy(before_bytes, bytes, sizeof(bytes));
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &segment.object) == IEC60870_ASDU_FULL);
CHECK(memcmp(&asdu, &before, sizeof(asdu)) == 0 && memcmp(bytes, before_bytes, sizeof(bytes)) == 0);
CHECK(IEC60870_FileSegment_Create(&segment, 1, 1, 1, input, 255) == IEC60870_ASDU_OK);
CHECK(IEC60870_InformationObject_Encode(&segment.object, &p, 0, encoded, sizeof(encoded), &object_length) == IEC60870_ASDU_OK);
CHECK(object_length == ioa+259 && encoded[ioa+3] == 255);
CHECK(IEC60870_FileSegment_Get_From_Buffer(&parsed, 1, encoded+ioa, 259) == IEC60870_ASDU_OK);
CHECK(parsed.length == 255 && memcmp(parsed.data, input, sizeof(input)) == 0);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &segment.object) == IEC60870_ASDU_FULL);
CHECK(IEC60870_FileSegment_Create(&segment, 1, 1, 1, NULL, 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &segment.object) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Get_Data(&asdu, &wire, &n) == IEC60870_ASDU_OK && n == h+ioa+4);
CHECK(IEC60870_Asdu_Parse(&p, wire, n, &info) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Get_Element(&info, 0, &parsed, sizeof(parsed)) == IEC60870_ASDU_OK && parsed.length == 0);
p.maxSizeOfASDU = h+ioa+4;
CHECK(IEC60870_FileSegment_Get_Max_Data_Size(&p) == 0);
p.maxSizeOfASDU--;
CHECK(IEC60870_FileSegment_Get_Max_Data_Size(&p) == -1);
}
/* 默认104配置:236字节恰好249;237字节必须返回FULL。 */
{
IEC60870_FileSegment_t segment;
IEC60870_Asdu_t asdu;
const uint8_t *wire;
uint16_t n;
CHECK(IEC60870_Asdu_Create(&asdu, &default_parameters, bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_FileSegment_Create(&segment, 1, 1, 1, input, 237) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &segment.object) == IEC60870_ASDU_FULL);
CHECK(IEC60870_FileSegment_Create(&segment, 1, 1, 1, input, 236) == IEC60870_ASDU_OK);
memset(input, 0, sizeof(input));
CHECK(segment.data[0] == 0x5A); /* 构造结果也不借用源数据。 */
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &segment.object) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Get_Data(&asdu, &wire, &n) == IEC60870_ASDU_OK && n == 249);
bytes[12]++; /* LOS称有237字节,但原文只有236字节。 */
CHECK(Reject_Frame(&default_parameters, bytes, n));
CHECK(IEC60870_Asdu_Get_Data(&asdu, &wire, &n) == IEC60870_ASDU_ERROR);
}
return 1;
}
static int test_numeric_and_parameter_fields(void)
{
IEC60870_StepPositionInformation_t step;
IEC60870_ParameterNormalizedValue_t parameter;
IEC60870_ParameterFloatValue_t floating;
IEC60870_PackedSinglePointWithSCD_t packed;
IEC60870_FileReady_t file;
IEC60870_Asdu_t asdu;
IEC60870_AsduInfo_t info;
IEC60870_AsduHeader_t header = {6,0,0,0,1};
uint8_t bytes[254], payload[16], raw_float[] = {0x23,0x01,0xC0,0x7F,0xFF};
uint16_t n;
const uint8_t *wire;
int v;
for (v = -64; v <= 63; ++v) {
CHECK(IEC60870_StepPositionInformation_Create(&step, 1, v, 0, 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_InformationObject_Encode(&step.object, &default_parameters, 1, payload, sizeof(payload), &n) == IEC60870_ASDU_OK);
CHECK(n == 2 && payload[0] == ((unsigned int)v & 0x7FU) && payload[1] == 0);
CHECK(IEC60870_StepPositionInformation_Get_From_Buffer(&step, 1, payload, 2) == IEC60870_ASDU_OK && step.value == v);
}
CHECK(IEC60870_PackedSinglePointWithSCD_Get_From_Buffer(&packed, 1, (const uint8_t[]){0x34,0x12,0xCD,0xAB,0x81}, 5) == IEC60870_ASDU_OK);
CHECK(packed.status == 0x1234 && packed.change == 0xABCD && packed.quality == 0x81);
CHECK(IEC60870_FileReady_Create(&file, 1, 1, UINT32_C(0xFFFFFF), 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_InformationObject_Encode(&file.object, &default_parameters, 0, payload, sizeof(payload), &n) == IEC60870_ASDU_OK);
CHECK(payload[5] == 255 && payload[6] == 255 && payload[7] == 255);
CHECK(IEC60870_ParameterFloatValue_Get_From_Buffer(&floating, 1, raw_float, sizeof(raw_float)) == IEC60870_ASDU_OK);
CHECK(floating.qualifier == 255);
CHECK(IEC60870_InformationObject_Encode(&floating.object, &default_parameters, 0, payload, sizeof(payload), &n) == IEC60870_ASDU_OK);
CHECK(memcmp(payload+3, raw_float, sizeof(raw_float)) == 0); /* NaN位模式与QPM全部保留。 */
CHECK(IEC60870_Asdu_Create(&asdu, &default_parameters, bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_ParameterNormalizedValue_Create(&parameter, 1, INT16_MIN, 0xFF) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &parameter.object) == IEC60870_ASDU_OK);
CHECK(IEC60870_ParameterNormalizedValue_Create(&parameter, 999, INT16_MAX, 0) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &parameter.object) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Get_Data(&asdu, &wire, &n) == IEC60870_ASDU_OK);
CHECK(IEC60870_Asdu_Parse(&default_parameters, wire, n, &info) == IEC60870_ASDU_OK && info.object_count == 2);
CHECK(IEC60870_Asdu_Get_Element(&info, 1, &parameter, sizeof(parameter)) == IEC60870_ASDU_OK);
CHECK(parameter.object.ioa == 999 && parameter.value == INT16_MAX && parameter.qualifier == 0);
return 1;
}
int main(void)
{
int passed = 0;
#define RUN(test) do { if (!(test)()) { return 1; } ++passed; } while (0)
RUN(test_all_layouts);
RUN(test_sequence_and_counts);
RUN(test_invalid_fields);
RUN(test_short_times);
RUN(test_constructor_atomicity);
RUN(test_file_segments);
RUN(test_numeric_and_parameter_fields);
printf("Extended ASDU: %d groups passed (37 additional types, short times, variable file segments)\n", passed);
return 0;
}