1318 lines
67 KiB
C
1318 lines
67 KiB
C
/* 共用子站发送接口回归:复用可控Net后端,验证真实104提交路径。
|
||
* 不另行链接TcpServer.c、Net后端或事件环测试翻译单元。 */
|
||
#define main event_ring_suite_main
|
||
#include "iec104_tcp_event_ring_test.c"
|
||
#undef main
|
||
#include "IEC104_Slave.h"
|
||
|
||
static const IEC60870_AppLayerParameters_t slave_parameters = IEC104_APP_LAYER_PARAMETERS_DEFAULT;
|
||
/* Type100、TEST=1、OA=0x5A、CA=0x1234、IOA=0、QOI=20。 */
|
||
static const uint8_t interrogation[] = {100, 1, 0x86, 0x5A, 0x34, 0x12, 0, 0, 0, 20};
|
||
|
||
/**
|
||
* 功能:检查期望报文只改动COT首字节。
|
||
* 输入参数:actual——实际ASDU;original——输入副本;length——长度;cot——期望COT字节。
|
||
* 输出参数:无。
|
||
* 返回值:无,断言失败时终止测试。
|
||
*/
|
||
static void check_reply(const uint8_t *actual, const uint8_t *original, size_t length, uint8_t cot)
|
||
{
|
||
size_t i;
|
||
for (i = 0; i < length; ++i) { CHECK(actual[i] == (i == 2U ? cot : original[i])); }
|
||
}
|
||
|
||
/**
|
||
* 功能:验证12种字段宽度下的原文借用、原地修改和失败原子性。
|
||
* 输入参数:无。
|
||
* 输出参数:无。
|
||
* 返回值:无。
|
||
*/
|
||
static void test_mutable_views(void)
|
||
{
|
||
int cot, ca, ioa;
|
||
for (cot = 1; cot <= 2; ++cot) {
|
||
for (ca = 1; ca <= 2; ++ca) {
|
||
for (ioa = 1; ioa <= 3; ++ioa) {
|
||
IEC60870_AppLayerParameters_t p = {1, 1, cot, 0, ca, ioa, 254};
|
||
uint8_t bytes[16] = {100, 1, 0xC6}, original[16];
|
||
uint16_t length = (uint16_t)(2 + cot + ca + ioa + 1), exported = 0;
|
||
const uint8_t *data = NULL;
|
||
IEC60870_Asdu_t object, snapshot;
|
||
IEC60870_InterrogationCommand_t command;
|
||
if (cot == 2) { bytes[3] = 0x5A; }
|
||
bytes[2 + cot] = 0x34;
|
||
if (ca == 2) { bytes[3 + cot] = 0x12; }
|
||
bytes[length - 1U] = 20;
|
||
memcpy(original, bytes, sizeof(bytes));
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, length) == IEC60870_ASDU_OK);
|
||
CHECK(object.buffer == bytes && object.capacity == length && object.is_from_buffer);
|
||
CHECK(memcmp(bytes, original, sizeof(bytes)) == 0);
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 7) == IEC60870_ASDU_OK);
|
||
check_reply(bytes, original, length, 0xC7);
|
||
CHECK(IEC60870_Asdu_Set_Negative(&object, 0) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Get_Data(&object, &data, &exported) == IEC60870_ASDU_OK);
|
||
CHECK(data == bytes && exported == length);
|
||
check_reply(data, original, length, 0x87);
|
||
snapshot = object;
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, -1) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 64) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Set_Negative(&object, 2) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, length - 1U) == IEC60870_ASDU_ERROR);
|
||
CHECK(memcmp(&object, &snapshot, sizeof(object)) == 0);
|
||
CHECK(IEC60870_InterrogationCommand_Create(&command, 0, 20) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Add_InformationObject(&object, &command.object) == IEC60870_ASDU_ERROR);
|
||
CHECK(memcmp(&object, &snapshot, sizeof(object)) == 0);
|
||
check_reply(bytes, original, length, 0x87);
|
||
CHECK(memcmp(bytes + length, original + length, sizeof(bytes) - length) == 0);
|
||
}
|
||
}
|
||
}
|
||
{
|
||
/* 未知类型及零对象可原文回显,但不能作为已校验业务对象执行。 */
|
||
IEC60870_AppLayerParameters_t p = {1, 1, 1, 0, 1, 1, 254};
|
||
uint8_t bytes[254] = {200, 0, 0x86, 1};
|
||
IEC60870_Asdu_t object, snapshot;
|
||
IEC60870_AsduInfo_t info;
|
||
const uint8_t *data = NULL;
|
||
uint16_t length = 0;
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, 4) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Parse(&p, bytes, 4, &info) == IEC60870_ASDU_UNSUPPORTED);
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 44) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Get_Data(&object, &data, &length) == IEC60870_ASDU_OK && length == 4);
|
||
CHECK(data == bytes && bytes[2] == 0xAC);
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, 254) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Get_Data(&object, &data, &length) == IEC60870_ASDU_OK && length == 254);
|
||
snapshot = object;
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, 255) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, NULL, bytes, 4) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, NULL, 4) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(NULL, &p, bytes, 4) == IEC60870_ASDU_ERROR);
|
||
CHECK(memcmp(&object, &snapshot, sizeof(object)) == 0);
|
||
CHECK(IEC60870_Asdu_Set_COT(NULL, 7) == IEC60870_ASDU_ERROR);
|
||
CHECK(IEC60870_Asdu_Set_Negative(NULL, 0) == IEC60870_ASDU_ERROR);
|
||
object.is_from_buffer = 2;
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 7) == IEC60870_ASDU_ERROR && bytes[2] == 0xAC);
|
||
}
|
||
}
|
||
|
||
typedef struct {
|
||
IEC60870_MasterConnection_t connection;
|
||
IEC60870_AppLayerParameters_t parameters;
|
||
IEC60870_Asdu_t *seen;
|
||
unsigned calls;
|
||
bool negative;
|
||
bool result;
|
||
} TestConnection;
|
||
|
||
/* 测试接入只记录分派参数和返回值,用来确认共用层不替代协议实现。 */
|
||
static bool record_asdu(IEC60870_MasterConnection_t *self, IEC60870_Asdu_t *object)
|
||
{
|
||
TestConnection *test = (TestConnection *)self->object;
|
||
CHECK(self == &test->connection);
|
||
test->seen = object;
|
||
test->calls += 1U;
|
||
return test->result;
|
||
}
|
||
static bool record_con(IEC60870_MasterConnection_t *self, IEC60870_Asdu_t *object, bool negative)
|
||
{
|
||
TestConnection *test = (TestConnection *)self->object;
|
||
test->negative = negative;
|
||
test->calls += 10U;
|
||
return record_asdu(self, object);
|
||
}
|
||
static bool record_term(IEC60870_MasterConnection_t *self, IEC60870_Asdu_t *object)
|
||
{
|
||
TestConnection *test = (TestConnection *)self->object;
|
||
test->calls += 100U;
|
||
return record_asdu(self, object);
|
||
}
|
||
static const IEC60870_AppLayerParameters_t *record_parameters(IEC60870_MasterConnection_t *self)
|
||
{
|
||
return &((TestConnection *)self->object)->parameters;
|
||
}
|
||
|
||
/**
|
||
* 功能:验证共用接口的回调分派、布尔返回和非104参数传递。
|
||
* 输入参数:无。
|
||
* 输出参数:无。
|
||
* 返回值:无。
|
||
*/
|
||
static void test_common_dispatch(void)
|
||
{
|
||
TestConnection test = {0};
|
||
IEC60870_MasterConnection_t empty = {0};
|
||
IEC60870_Asdu_t object = {0};
|
||
test.parameters = (IEC60870_AppLayerParameters_t){1, 1, 1, 0, 1, 2, 100};
|
||
test.connection.object = &test;
|
||
test.connection.send_asdu = record_asdu;
|
||
test.connection.send_act_con = record_con;
|
||
test.connection.send_act_term = record_term;
|
||
test.connection.get_application_layer_parameters = record_parameters;
|
||
test.result = true;
|
||
CHECK(IEC60870_MasterConnection_Send_ASDU(&test.connection, &object));
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(&test.connection, &object, true));
|
||
CHECK(test.negative);
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_TERM(&test.connection, &object));
|
||
CHECK(test.calls == 113U && test.seen == &object);
|
||
CHECK(IEC60870_MasterConnection_Get_Application_Layer_Parameters(&test.connection) == &test.parameters);
|
||
test.result = false;
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&test.connection, &object, false));
|
||
CHECK(!test.negative && test.calls == 124U);
|
||
CHECK(!IEC60870_MasterConnection_Send_ASDU(NULL, &object));
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(NULL, &object, false));
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_TERM(NULL, &object));
|
||
CHECK(!IEC60870_MasterConnection_Send_ASDU(&empty, &object));
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&empty, &object, false));
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_TERM(&empty, &object));
|
||
CHECK(IEC60870_MasterConnection_Get_Application_Layer_Parameters(NULL) == NULL);
|
||
CHECK(IEC60870_MasterConnection_Get_Application_Layer_Parameters(&empty) == NULL);
|
||
}
|
||
|
||
static IEC104_Slave_t *service;
|
||
|
||
/* 仅测试为了注入底层故障取得池中对象;正常业务测试只调用Slave公开接口。 */
|
||
static void locate_test_server(void)
|
||
{
|
||
size_t i;
|
||
sut = NULL;
|
||
for (i = 0U; i < IEC104_TCP_SERVER_CAPACITY; ++i) {
|
||
if (server_pool[i].in_use != 0U) { sut = &server_pool[i].server; break; }
|
||
}
|
||
CHECK(sut != NULL);
|
||
}
|
||
|
||
static void service_poll(void) { CHECK(IEC104_Slave_Poll(service, 0, NULL) == IEC104_TCP_OK); }
|
||
|
||
static unsigned service_drain(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_TcpEventResult_t result;
|
||
unsigned count = 0;
|
||
while ((result = IEC104_Slave_NextEvent(service, &event)) == IEC104_TCP_EVENT_READY) { ++count; }
|
||
CHECK(result == IEC104_TCP_EVENT_EMPTY);
|
||
return count;
|
||
}
|
||
|
||
static void service_open_with_window(uint16_t window)
|
||
{
|
||
IEC104_SlaveConfig_t config;
|
||
memset(&fake, 0, sizeof(fake));
|
||
fake.now = 1000;
|
||
service = IEC104_Slave_Create();
|
||
CHECK(service != NULL);
|
||
locate_test_server();
|
||
IEC104_Slave_Config_Default(&config);
|
||
config.protocol.link.k = window;
|
||
CHECK(IEC104_Slave_Init(service, &config, NULL) == IEC104_TCP_OK);
|
||
}
|
||
|
||
static void service_open(void) { service_open_with_window(IEC104_TCP_RECORD_CAPACITY); }
|
||
|
||
static void service_connect(void)
|
||
{
|
||
fake.accept_pending = 1;
|
||
service_poll();
|
||
CHECK(service_drain() == 1);
|
||
push(start_act, sizeof(start_act));
|
||
service_poll();
|
||
CHECK(service_drain() == 1);
|
||
}
|
||
|
||
static void service_begin(void) { service_open(); service_connect(); }
|
||
|
||
static void service_end(void)
|
||
{
|
||
CHECK(IEC104_Slave_Close(service, NULL) == IEC104_TCP_OK);
|
||
service_drain();
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
service = NULL;
|
||
}
|
||
|
||
static void receive_request(IEC104_SlaveEvent_t *event, uint16_t ns)
|
||
{
|
||
uint8_t input[16];
|
||
CHECK(IEC104_Build_Iframe(input, ns, 0, interrogation, sizeof(interrogation)) == 16);
|
||
push(input, sizeof(input));
|
||
service_poll();
|
||
CHECK(IEC104_Slave_NextEvent(service, event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event->transport.type == IEC104_TCP_EVENT_I_FRAME);
|
||
CHECK(event->asdu_result == IEC60870_ASDU_OK);
|
||
}
|
||
|
||
/* 无需手动Init连接或解析ASDU,直接确认;检查完整报文及出队失败的输出保留。 */
|
||
static void test_event_replies(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, snapshot;
|
||
const IEC60870_AppLayerParameters_t *parameters;
|
||
size_t offset;
|
||
unsigned i;
|
||
static const uint8_t expected_cot[] = {0x87, 0xC7, 0x8A};
|
||
service_begin();
|
||
receive_request(&event, 0);
|
||
CHECK(event.asdu.buffer == event.transport.data.i_frame.apdu + IEC104_APCI_LEN);
|
||
parameters = IEC60870_MasterConnection_Get_Application_Layer_Parameters(&event.connection);
|
||
CHECK(parameters != NULL && parameters->sizeOfCOT == 2 && parameters->sizeOfCA == 2 &&
|
||
parameters->sizeOfIOA == 3 && parameters->maxSizeOfASDU == 249);
|
||
CHECK(parameters == IEC104_Slave_Get_Application_Layer_Parameters(service));
|
||
offset = fake.sent_bytes;
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(&event.connection, &event.asdu, false));
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(&event.connection, &event.asdu, true));
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_TERM(&event.connection, &event.asdu));
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(fake.sent_bytes == offset + 48U);
|
||
for (i = 0; i < 3; ++i) {
|
||
const uint8_t *frame = fake.output + offset + i * 16U;
|
||
CHECK(frame[0] == 0x68 && frame[1] == 14 && frame[2] == 2U * i && frame[3] == 0);
|
||
CHECK(frame[4] == 2 && frame[5] == 0);
|
||
check_reply(frame + 6, interrogation, 10, expected_cot[i]);
|
||
}
|
||
check_reply(event.asdu.buffer, interrogation, 10, 0x8A);
|
||
memcpy(&snapshot, &event, sizeof(snapshot));
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_EMPTY);
|
||
CHECK(memcmp(&snapshot, &event, sizeof(event)) == 0);
|
||
CHECK(IEC104_Slave_NextEvent(NULL, &event) == IEC104_TCP_EVENT_ERROR);
|
||
CHECK(memcmp(&snapshot, &event, sizeof(event)) == 0);
|
||
CHECK(IEC104_Slave_NextEvent(service, NULL) == IEC104_TCP_EVENT_ERROR);
|
||
service_end();
|
||
}
|
||
|
||
/* 同一自动绑定的连接也可提交业务另行构造的ASDU。 */
|
||
static void test_constructed_asdu(void)
|
||
{
|
||
uint8_t buffer[249];
|
||
IEC104_SlaveEvent_t event;
|
||
IEC60870_Asdu_t object;
|
||
IEC60870_AsduHeader_t header = {3, 0, 0, 0, 1};
|
||
IEC60870_SinglePointInformation_t point;
|
||
const uint8_t *data = NULL;
|
||
uint16_t length = 0;
|
||
service_begin();
|
||
receive_request(&event, 0);
|
||
CHECK(IEC60870_Asdu_Create(&object, &slave_parameters, buffer, sizeof(buffer), &header, 0) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 20) == IEC60870_ASDU_OK);
|
||
CHECK(!IEC60870_MasterConnection_Send_ASDU(&event.connection, &object));
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC60870_SinglePointInformation_Create(&point, 1, 1, 0) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Add_InformationObject(&object, &point.object) == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_Asdu_Get_Data(&object, &data, &length) == IEC60870_ASDU_OK && length == 10);
|
||
CHECK(IEC60870_MasterConnection_Send_ASDU(&event.connection, &object));
|
||
CHECK(fake.sent_bytes == 22);
|
||
check_reply(fake.output + 12, asdu, 10, 20);
|
||
CHECK(memcmp(fake.output + 12, data, length) == 0);
|
||
service_end();
|
||
}
|
||
|
||
/* HIGH满后的WAIT不接手,复制事件保留已修改字段后可在容量恢复时重试。 */
|
||
static void test_wait_and_retry(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, pending;
|
||
IEC104_TcpServerStatus_t before, after;
|
||
unsigned accepted;
|
||
service_begin();
|
||
receive_request(&event, 0);
|
||
fake.send_blocked = 1;
|
||
for (accepted = 0; accepted < 200; ++accepted) {
|
||
if (!IEC60870_MasterConnection_Send_ASDU(&event.connection, &event.asdu)) { break; }
|
||
}
|
||
CHECK(accepted > 0 && accepted < 200);
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_WAIT);
|
||
CHECK(IEC104_Slave_Get_Status(service, &before, NULL) == IEC104_TCP_OK);
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&event.connection, &event.asdu, true));
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_WAIT);
|
||
check_reply(event.asdu.buffer, interrogation, 10, 0xC7);
|
||
CHECK(IEC104_Slave_Get_Status(service, &after, NULL) == IEC104_TCP_OK);
|
||
CHECK(before.high_queue.pending_asdu_count == after.high_queue.pending_asdu_count);
|
||
CHECK(IEC104_Slave_Copy_Event(&pending, &event));
|
||
memset(&event, 0, sizeof(event));
|
||
fake.send_blocked = 0;
|
||
fake.now += 100;
|
||
service_poll();
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(&pending.connection, &pending.asdu, true));
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&pending.connection) == IEC104_TCP_SUBMIT_OK);
|
||
service_end();
|
||
}
|
||
|
||
/* 保留101布局/254长度/NULL拒绝,以及复制自引用接口后的明确失败。 */
|
||
static void test_invalid_inputs(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, snapshot, destination, before;
|
||
IEC60870_AppLayerParameters_t p = {1, 1, 1, 0, 1, 1, 254};
|
||
IEC60870_Asdu_t object;
|
||
uint8_t bytes[254] = {200, 0, 0x86, 1};
|
||
size_t sent;
|
||
service_begin();
|
||
receive_request(&event, 0);
|
||
snapshot = event; /* 故意模拟错误的按值复制。 */
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(NULL) == IEC104_TCP_SUBMIT_ERROR);
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, 4) == IEC60870_ASDU_OK);
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&event.connection, &object, false));
|
||
CHECK(bytes[2] == 0x87);
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Enqueue_Event(service, &object) == IEC104_TCP_SUBMIT_ERROR);
|
||
p = slave_parameters;
|
||
p.maxSizeOfASDU = 254;
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &p, bytes, 254) == IEC60870_ASDU_OK);
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_TERM(&event.connection, &object));
|
||
CHECK(bytes[2] == 0x8A);
|
||
CHECK(!IEC60870_MasterConnection_Send_ASDU(&event.connection, NULL));
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&event.connection, NULL, false));
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_TERM(&event.connection, NULL));
|
||
CHECK(IEC104_Slave_Enqueue_Event(service, NULL) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(fake.sent_bytes == sent);
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_TERM(&snapshot.connection, &object));
|
||
CHECK(IEC60870_MasterConnection_Get_Application_Layer_Parameters(&snapshot.connection) == NULL);
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&snapshot.connection) == IEC104_TCP_SUBMIT_ERROR);
|
||
memset(&destination, 0x5A, sizeof(destination));
|
||
memcpy(&before, &destination, sizeof(before));
|
||
CHECK(!IEC104_Slave_Copy_Event(&destination, &snapshot));
|
||
CHECK(!IEC104_Slave_Copy_Event(&destination, NULL));
|
||
CHECK(!IEC104_Slave_Copy_Event(NULL, &event));
|
||
CHECK(memcmp(&destination, &before, sizeof(destination)) == 0);
|
||
service_end();
|
||
}
|
||
|
||
/* 原事件可以立即复用;普通/不排队提交只依赖独立身份及本次ASDU,接手后不借用输入。 */
|
||
static void test_identity_response_storage(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_SlaveEventIdentity_t identity;
|
||
IEC60870_Asdu_t object;
|
||
uint8_t bytes[sizeof(interrogation)];
|
||
size_t sent;
|
||
service_begin(); receive_request(&event, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &event, &identity));
|
||
memcpy(bytes, interrogation, sizeof(bytes));
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, &slave_parameters, bytes, sizeof(bytes)) == IEC60870_ASDU_OK);
|
||
memset(&event, 0xCC, sizeof(event));
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 7) == IEC60870_ASDU_OK);
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Send_Response(service, &identity, &object) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(fake.sent_bytes == sent + 16U);
|
||
check_reply(fake.output + sent + 6U, interrogation, 10, 0x87);
|
||
CHECK(IEC60870_Asdu_Set_COT(&object, 10) == IEC60870_ASDU_OK);
|
||
fake.send_blocked = 1;
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &object) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(fake.sent_bytes == sent);
|
||
memset(bytes, 0xCC, sizeof(bytes));
|
||
fake.send_blocked = 0; service_poll();
|
||
CHECK(fake.sent_bytes == sent + 16U);
|
||
check_reply(fake.output + sent + 6U, interrogation, 10, 0x8A);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, &identity, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, &identity, NULL) == IEC104_TCP_NOT_CONNECTED);
|
||
service_end();
|
||
}
|
||
|
||
/* 压缩请求不缩小HIGH容量;普通入口容量与原接口相同,Try遇待发NORMAL不入队/覆盖。 */
|
||
static void test_identity_response_wait(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_SlaveEventIdentity_t identity;
|
||
IEC104_TcpServerStatus_t before, after;
|
||
unsigned accepted;
|
||
service_begin(); receive_request(&event, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &event, &identity));
|
||
fake.send_blocked = 1;
|
||
for (accepted = 0; accepted < 200; ++accepted) {
|
||
IEC104_TcpSubmitResult_t result = IEC104_Slave_Send_Response(service, &identity, &event.asdu);
|
||
if (result == IEC104_TCP_SUBMIT_WAIT) { break; }
|
||
CHECK(result == IEC104_TCP_SUBMIT_OK);
|
||
}
|
||
CHECK(accepted == 1U + IEC104_TCP_HIGH_CAPACITY / (IEC104_ASDU_HIGH_ENTRY_HEADER_LEN + sizeof(interrogation)));
|
||
CHECK(IEC104_Slave_Get_Status(service, &before, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_WAIT);
|
||
CHECK(IEC104_Slave_Get_Status(service, &after, NULL) == IEC104_TCP_OK);
|
||
CHECK(before.high_queue.used_bytes == after.high_queue.used_bytes &&
|
||
before.connection.pending_send_records == after.connection.pending_send_records);
|
||
fake.send_blocked = 0; service_poll(); service_drain();
|
||
CHECK(IEC104_Slave_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_OK);
|
||
service_end();
|
||
|
||
service_begin(); receive_request(&event, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &event, &identity));
|
||
CHECK(IEC104_Slave_Enqueue_Event(service, &event.asdu) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(IEC104_Slave_Get_Status(service, &before, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_WAIT);
|
||
CHECK(IEC104_Slave_Get_Status(service, &after, NULL) == IEC104_TCP_OK);
|
||
CHECK(after.high_queue.used_bytes == before.high_queue.used_bytes &&
|
||
after.normal_queue.used_bytes == before.normal_queue.used_bytes &&
|
||
after.normal_queue.pending_asdu_count == before.normal_queue.pending_asdu_count);
|
||
service_poll(); service_drain();
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_OK);
|
||
service_end();
|
||
}
|
||
|
||
/* 紧凑身份入口保留原实例/代次校验、参数拒绝和FATAL语义,不因省去事件而放宽准入。 */
|
||
static void test_identity_response_validation(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_SlaveEventIdentity_t identity, invalid;
|
||
IEC60870_Asdu_t object;
|
||
IEC60870_AppLayerParameters_t parameters = {1, 1, 1, 0, 1, 1, 249};
|
||
uint8_t bytes[] = {200, 0, 3, 1};
|
||
size_t sent;
|
||
service_begin(); receive_request(&event, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &event, &identity));
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Send_Response(NULL, &identity, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Send_Response(service, NULL, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, NULL) == IEC104_TCP_SUBMIT_ERROR);
|
||
invalid = identity; invalid.slave = NULL;
|
||
CHECK(IEC104_Slave_Send_Response(service, &invalid, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
invalid = identity; ++invalid.instance_id;
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &invalid, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, &invalid, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
invalid = identity; invalid.generation = 0;
|
||
CHECK(IEC104_Slave_Send_Response(service, &invalid, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, &invalid, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(NULL, &identity, NULL) == IEC104_TCP_INVALID_HANDLE);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, NULL, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object, ¶meters, bytes, sizeof(bytes)) == IEC60870_ASDU_OK);
|
||
CHECK(IEC104_Slave_Send_Response(service, &identity, &object) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &object) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(fake.sent_bytes == sent);
|
||
sut->queue_initialized = 0;
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_FATAL);
|
||
sut->queue_initialized = 1;
|
||
push(stop_act, sizeof(stop_act)); service_poll(); service_drain();
|
||
CHECK(IEC104_Slave_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &identity, &event.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
service_end();
|
||
}
|
||
|
||
/* 身份输出独立于事件存储;非法来源、自引用和元数据不得污染输出或复制目标。 */
|
||
static void test_event_identity(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, copied, moved;
|
||
IEC104_SlaveEventIdentity_t identity, before, copy_identity;
|
||
size_t sent;
|
||
service_begin();
|
||
receive_request(&event, 0);
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &event, &identity));
|
||
CHECK(identity.slave == service && identity.instance_id != 0U &&
|
||
identity.generation == event.transport.generation);
|
||
before = identity;
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(NULL, &event, &identity));
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(service, NULL, &identity));
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(service, &event, NULL));
|
||
moved = event; /* 未修复自引用的按值副本必须拒绝。 */
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(service, &moved, &identity));
|
||
CHECK(memcmp(&identity, &before, sizeof(identity)) == 0);
|
||
CHECK(IEC104_Slave_Copy_Event(&copied, &event));
|
||
++copied.transport.generation; /* 两份连接代次不一致,不能接受或继续传播。 */
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(service, &copied, &identity));
|
||
CHECK(!IEC104_Slave_Copy_Event(&moved, &copied));
|
||
CHECK(memcmp(&identity, &before, sizeof(identity)) == 0);
|
||
CHECK(IEC104_Slave_Copy_Event(&copied, &event));
|
||
memset(&event, 0, sizeof(event));
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &copied, ©_identity));
|
||
CHECK(copy_identity.slave == identity.slave && copy_identity.instance_id == identity.instance_id &&
|
||
copy_identity.generation == identity.generation);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_READY);
|
||
#if IEC104_TCP_SERVER_CAPACITY > 1
|
||
{
|
||
IEC104_Slave_t *other = IEC104_Slave_Create();
|
||
CHECK(other != NULL);
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(other, &copied, &identity));
|
||
CHECK(memcmp(&identity, &before, sizeof(identity)) == 0);
|
||
CHECK(IEC104_Slave_Send_Response(other, &identity, &copied.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Try_Send_Response(other, &identity, &copied.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(other, &identity, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
copy_identity.slave = other; /* 即使局部编号相同,其他服务身份也不能用于当前会话。 */
|
||
CHECK(IEC104_Slave_Check_Request(service, ©_identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
CHECK(IEC104_Slave_Destroy(other, NULL) == IEC104_TCP_OK);
|
||
}
|
||
#endif
|
||
CHECK(fake.sent_bytes == sent);
|
||
service_end();
|
||
}
|
||
|
||
/* 通知FIFO优先于当前传输状态:不能跨过尚未消费的STOP/START继续旧业务。 */
|
||
static void test_request_state(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_SlaveEventIdentity_t identity = {0};
|
||
size_t sent;
|
||
service_open();
|
||
CHECK(IEC104_Slave_Check_Request(NULL, &identity) == IEC104_SLAVE_REQUEST_ERROR);
|
||
CHECK(IEC104_Slave_Check_Request(service, NULL) == IEC104_SLAVE_REQUEST_ERROR);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
fake.accept_pending = 1;
|
||
service_poll();
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_WAIT_EVENTS);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event.transport.type == IEC104_TCP_EVENT_CONNECTED);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &event, &identity));
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
push(start_act, sizeof(start_act)); service_poll();
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_WAIT_EVENTS);
|
||
CHECK(service_drain() == 1);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_READY);
|
||
push(stop_act, sizeof(stop_act)); service_poll();
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_WAIT_EVENTS);
|
||
CHECK(service_drain() == 1);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
push(start_act, sizeof(start_act)); service_poll();
|
||
CHECK(service_drain() == 1);
|
||
push(stop_act, sizeof(stop_act)); service_poll();
|
||
push(start_act, sizeof(start_act)); service_poll();
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_WAIT_EVENTS);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event.transport.type == IEC104_TCP_EVENT_DEACTIVATED);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_WAIT_EVENTS);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event.transport.type == IEC104_TCP_EVENT_ACTIVATED);
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_READY);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_READY);
|
||
CHECK(fake.sent_bytes == sent && service_drain() == 0U);
|
||
CHECK(IEC104_Slave_Close_Connection(service, &event, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_WAIT_EVENTS);
|
||
CHECK(service_drain() == 1);
|
||
CHECK(IEC104_Slave_Check_Request(service, &identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
service_end();
|
||
}
|
||
|
||
/* 旧事件的发送和关闭操作均不能作用于新连接,业务无须提取或回传generation。 */
|
||
static void test_stale_generation(void)
|
||
{
|
||
IEC104_SlaveEvent_t old_event, current;
|
||
IEC104_SlaveEventIdentity_t old_identity, current_identity;
|
||
size_t sent;
|
||
service_begin();
|
||
receive_request(&old_event, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &old_event, &old_identity));
|
||
CHECK(IEC104_Slave_Close_Connection(service, &old_event, NULL) == IEC104_TCP_OK);
|
||
service_drain();
|
||
service_connect();
|
||
receive_request(¤t, 0);
|
||
CHECK(old_event.transport.generation != current.transport.generation);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &old_event, ¤t_identity));
|
||
CHECK(current_identity.generation == old_identity.generation); /* 历史事件仍可识别,但不能继续业务。 */
|
||
CHECK(IEC104_Slave_Check_Request(service, &old_identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, ¤t, ¤t_identity));
|
||
CHECK(IEC104_Slave_Check_Request(service, ¤t_identity) == IEC104_SLAVE_REQUEST_READY);
|
||
sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Close_Connection(service, &old_event, NULL) == IEC104_TCP_STALE_GENERATION);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, &old_identity, NULL) == IEC104_TCP_STALE_GENERATION);
|
||
CHECK(IEC104_Slave_Send_Response(service, &old_identity, ¤t.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &old_identity, ¤t.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&old_event.connection, &old_event.asdu, false));
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&old_event.connection) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(fake.sent_bytes == sent && old_event.asdu.buffer[2] == 0x87);
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(¤t.connection, ¤t.asdu, false));
|
||
CHECK(fake.sent_bytes == sent + 16U);
|
||
service_end();
|
||
}
|
||
|
||
static void test_fatal_result(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
service_begin();
|
||
receive_request(&event, 0);
|
||
/* 故障注入只在测试中访问底层,不为业务增加内部句柄访问接口。 */
|
||
sut->queue_initialized = 0;
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&event.connection, &event.asdu, false));
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_FATAL);
|
||
CHECK(event.asdu.buffer[2] == 0x87);
|
||
sut->queue_initialized = 1;
|
||
service_end();
|
||
}
|
||
|
||
/* 源事件被后续请求覆盖后,任务副本仍持有自己的原文与应答接口。 */
|
||
static void test_deferred_event_copy(void)
|
||
{
|
||
IEC104_SlaveEvent_t source, pending;
|
||
uint64_t original_serial;
|
||
service_begin();
|
||
receive_request(&source, 0);
|
||
original_serial = source.transport.data.i_frame.receive_serial;
|
||
CHECK(IEC60870_Asdu_Set_COT(&source.asdu, 7) == IEC60870_ASDU_OK);
|
||
CHECK(IEC104_Slave_Copy_Event(&pending, &source));
|
||
CHECK(IEC104_Slave_Copy_Event(&pending, &pending));
|
||
CHECK(pending.asdu.buffer != source.asdu.buffer);
|
||
CHECK(pending.connection.object == &pending);
|
||
receive_request(&source, 1);
|
||
CHECK(source.transport.data.i_frame.receive_serial != original_serial);
|
||
CHECK(pending.transport.data.i_frame.receive_serial == original_serial);
|
||
CHECK(source.asdu.buffer[2] == 0x86 && pending.asdu.buffer[2] == 0x87);
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_TERM(&pending.connection, &pending.asdu));
|
||
CHECK(source.asdu.buffer[2] == 0x86 && pending.asdu.buffer[2] == 0x8A);
|
||
service_end();
|
||
}
|
||
|
||
/* 服务重启、销毁和池槽复用后,仍存活的任务副本都不得向新实例发送。 */
|
||
static void test_service_reuse(void)
|
||
{
|
||
IEC104_SlaveEvent_t old_event, current;
|
||
IEC104_SlaveEventIdentity_t old_identity, identity;
|
||
IEC104_SlaveConfig_t config;
|
||
IEC104_Slave_t *old_service;
|
||
size_t sent;
|
||
service_begin();
|
||
receive_request(&old_event, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, &old_event, &old_identity));
|
||
CHECK(IEC104_Slave_Close(service, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Init(service, NULL, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
service_drain();
|
||
IEC104_Slave_Config_Default(&config);
|
||
CHECK(IEC104_Slave_Init(service, &config, NULL) == IEC104_TCP_OK);
|
||
service_connect();
|
||
receive_request(¤t, 0);
|
||
CHECK(IEC104_Slave_Check_Request(service, &old_identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
sent = fake.sent_bytes;
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&old_event.connection, &old_event.asdu, false));
|
||
CHECK(fake.sent_bytes == sent);
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(¤t.connection, ¤t.asdu, false));
|
||
old_service = service;
|
||
service_end();
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_TERM(&old_event.connection, &old_event.asdu));
|
||
service_begin();
|
||
CHECK(service == old_service); /* 默认先复用门面第一个槽。 */
|
||
receive_request(¤t, 0);
|
||
CHECK(IEC104_Slave_Get_Event_Identity(service, ¤t, &identity));
|
||
CHECK(old_identity.instance_id != identity.instance_id);
|
||
CHECK(!IEC104_Slave_Get_Event_Identity(service, &old_event, &identity));
|
||
CHECK(IEC104_Slave_Check_Request(service, &old_identity) == IEC104_SLAVE_REQUEST_STALE);
|
||
sent = fake.sent_bytes;
|
||
CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&old_event.connection, &old_event.asdu, false));
|
||
CHECK(IEC104_Slave_Close_Connection(service, &old_event, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Close_Connection_By_Identity(service, &old_identity, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Send_Response(service, &old_identity, ¤t.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Try_Send_Response(service, &old_identity, ¤t.asdu) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(fake.sent_bytes == sent);
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(¤t.connection, ¤t.asdu, false));
|
||
service_end();
|
||
}
|
||
|
||
/* 两层池共享容量;创建失败不泄漏,Close后的收尾通知不能被Destroy悄悄丢弃。 */
|
||
static void test_lifecycle(void)
|
||
{
|
||
IEC104_Slave_t *slots[IEC104_TCP_SERVER_CAPACITY];
|
||
IEC104_TcpServer_t *tcp_slots[IEC104_TCP_SERVER_CAPACITY];
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_TcpError_t error;
|
||
IEC104_SlaveConfig_t config;
|
||
IEC104_TcpServerStatus_t status;
|
||
size_t i;
|
||
for (i = 0; i < IEC104_TCP_SERVER_CAPACITY; ++i) {
|
||
slots[i] = IEC104_Slave_Create();
|
||
CHECK(slots[i] != NULL);
|
||
}
|
||
CHECK(IEC104_Slave_Create() == NULL && IEC104_TcpServer_Create() == NULL);
|
||
for (i = 0; i < IEC104_TCP_SERVER_CAPACITY; ++i) {
|
||
CHECK(IEC104_Slave_Destroy(slots[i], NULL) == IEC104_TCP_OK);
|
||
}
|
||
for (i = 0; i < IEC104_TCP_SERVER_CAPACITY; ++i) {
|
||
tcp_slots[i] = IEC104_TcpServer_Create();
|
||
CHECK(tcp_slots[i] != NULL);
|
||
}
|
||
CHECK(IEC104_Slave_Create() == NULL);
|
||
for (i = 0; i < IEC104_TCP_SERVER_CAPACITY; ++i) {
|
||
CHECK(IEC104_TcpServer_Destroy(tcp_slots[i], NULL) == IEC104_TCP_OK);
|
||
}
|
||
service = IEC104_Slave_Create();
|
||
CHECK(service != NULL);
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK && !status.listening);
|
||
IEC104_Slave_Config_Default(&config);
|
||
config.protocol.link.k = 0;
|
||
CHECK(IEC104_Slave_Init(service, &config, &error) == IEC104_TCP_INVALID_CONFIG);
|
||
CHECK(error.result == IEC104_TCP_INVALID_CONFIG && error.stage == IEC104_TCP_STAGE_VALIDATE);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Destroy(service, &error) == IEC104_TCP_INVALID_HANDLE);
|
||
CHECK(error.result == IEC104_TCP_INVALID_HANDLE);
|
||
service_begin();
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_INVALID_STATE);
|
||
CHECK(IEC104_Slave_Close(service, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_EVENTS_PENDING);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event.transport.type == IEC104_TCP_EVENT_DISCONNECTED);
|
||
CHECK(event.asdu_result == IEC60870_ASDU_UNSUPPORTED && event.connection.send_asdu == NULL);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Get_Application_Layer_Parameters(NULL) == NULL);
|
||
}
|
||
|
||
/* 仅通过公开门面接入外部事件循环,取得请求、对端地址和只读状态。 */
|
||
static void test_external_scheduler(void)
|
||
{
|
||
IEC104_TcpWaitInfo_t wait;
|
||
IEC104_TcpReady_t ready;
|
||
IEC104_TcpPeerInfo_t peer;
|
||
IEC104_TcpServerStatus_t status;
|
||
IEC104_TcpError_t error;
|
||
IEC104_SlaveEvent_t event;
|
||
uint8_t input[16];
|
||
uint32_t i;
|
||
service_begin();
|
||
CHECK(IEC104_Slave_Get_Remote_Address(service, &peer, NULL) == IEC104_TCP_OK);
|
||
CHECK(strcmp(peer.address, "127.0.0.1") == 0 && peer.port == 12000);
|
||
CHECK(IEC104_Slave_Prepare(service, &wait, NULL) == IEC104_TCP_OK);
|
||
memset(&ready, 0, sizeof(ready));
|
||
ready.service_token = wait.service_token;
|
||
for (i = 0; i < wait.socket_count; ++i) {
|
||
ready.sockets[i] = wait.sockets[i];
|
||
ready.sockets[i].events = IEC104_TCP_IO_READ;
|
||
}
|
||
ready.socket_count = wait.socket_count;
|
||
CHECK(IEC104_Build_Iframe(input, 0, 0, interrogation, sizeof(interrogation)) == 16);
|
||
push(input, sizeof(input));
|
||
CHECK(IEC104_Slave_Process(service, &ready, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event.asdu_result == IEC60870_ASDU_OK);
|
||
CHECK(IEC60870_MasterConnection_Send_ACT_CON(&event.connection, &event.asdu, false));
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.connected && status.connection.data_transfer_started);
|
||
CHECK(IEC104_Slave_Process(service, NULL, &error) == IEC104_TCP_INVALID_ARGUMENT);
|
||
CHECK(error.result == IEC104_TCP_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Prepare(NULL, &wait, &error) == IEC104_TCP_INVALID_HANDLE);
|
||
CHECK(error.stage == IEC104_TCP_STAGE_VALIDATE && error.socket_error == 0);
|
||
CHECK(IEC104_Slave_Close_Connection(service, NULL, NULL) == IEC104_TCP_INVALID_ARGUMENT);
|
||
service_end();
|
||
}
|
||
|
||
/* 主动上报只需Slave和ASDU;离线入NORMAL后在连接激活时正常发送。 */
|
||
static void test_offline_enqueue(void)
|
||
{
|
||
IEC60870_Asdu_t object;
|
||
IEC104_TcpServerStatus_t status;
|
||
uint8_t bytes[sizeof(asdu)];
|
||
service_open();
|
||
memcpy(bytes, asdu, sizeof(bytes));
|
||
CHECK(IEC60870_Asdu_Create_From_Buffer(&object,
|
||
IEC104_Slave_Get_Application_Layer_Parameters(service), bytes, sizeof(bytes)) == IEC60870_ASDU_OK);
|
||
CHECK(IEC104_Slave_Enqueue_Event(service, &object) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(fake.sent_bytes == 0);
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.normal_queue.pending_asdu_count == 1);
|
||
service_connect();
|
||
service_poll();
|
||
CHECK(fake.sent_bytes == 22);
|
||
CHECK(memcmp(fake.output + 12, asdu, sizeof(asdu)) == 0);
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.normal_queue.pending_asdu_count == 0 && status.normal_queue.stored_asdu_count == 1);
|
||
service_end();
|
||
}
|
||
|
||
/* 多于原任务池容量的站点共享一个Slave;同IOA的数据源由CA定位到不同context。 */
|
||
static void test_station_registry(void)
|
||
{
|
||
IEC104_Station_t stations[13] = {0}, duplicate = {0}, boundary = {0};
|
||
struct { uint32_t ioa; int value; } points[13];
|
||
IEC104_Station_t *current = NULL;
|
||
unsigned i;
|
||
service = IEC104_Slave_Create();
|
||
CHECK(service != NULL && IEC104_Slave_Get_Station_Count(service) == 0U);
|
||
for (i = 0U; i < 13U; ++i) {
|
||
points[i].ioa = 100U;
|
||
points[i].value = (int)i;
|
||
CHECK(IEC104_Slave_Add_Station(service, &stations[i], (uint16_t)(i + 1U), &points[i]) == IEC104_STATION_OK);
|
||
}
|
||
CHECK(IEC104_Slave_Get_Station_Count(service) == 13U);
|
||
for (i = 0U; i < 13U; ++i) {
|
||
current = IEC104_Slave_Next_Station(service, current);
|
||
CHECK(current == &stations[i]);
|
||
CHECK(IEC104_Slave_Get_Station(service, (uint16_t)(i + 1U)) == current);
|
||
CHECK(IEC104_Station_Get_Common_Address(current) == i + 1U);
|
||
CHECK(IEC104_Station_Get_Context(current) == &points[i]);
|
||
}
|
||
CHECK(IEC104_Slave_Next_Station(service, current) == NULL);
|
||
CHECK(IEC104_Slave_Next_Station(service, &duplicate) == NULL);
|
||
CHECK(IEC104_Slave_Add_Station(service, &duplicate, 7U, NULL) == IEC104_STATION_DUPLICATE_CA);
|
||
CHECK(IEC104_Station_Get_Common_Address(&duplicate) == 0U);
|
||
CHECK(IEC104_Slave_Add_Station(service, &stations[0], 100U, NULL) == IEC104_STATION_ALREADY_REGISTERED);
|
||
CHECK(IEC104_Station_Get_Context(&stations[0]) == &points[0]);
|
||
CHECK(IEC104_Slave_Add_Station(service, &boundary, 65534U, NULL) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Get_Station(service, 65534U) == &boundary);
|
||
CHECK(IEC104_Slave_Get_Station(service, IEC104_CA_BROADCAST) == NULL);
|
||
CHECK(IEC104_Slave_Get_Station(service, 0U) == NULL);
|
||
CHECK(IEC104_Slave_Get_Station(service, 100U) == NULL);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &stations[6]) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Next_Station(service, &stations[5]) == &stations[7]);
|
||
CHECK(IEC104_Slave_Add_Station(service, &duplicate, 7U, &points[6]) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Get_Station(service, 7U) == &duplicate);
|
||
CHECK(IEC104_Slave_Next_Station(service, &boundary) == &duplicate);
|
||
for (i = 0U; i < 13U; ++i) {
|
||
if (i != 6U) { CHECK(IEC104_Slave_Remove_Station(service, &stations[i]) == IEC104_STATION_OK); }
|
||
}
|
||
CHECK(IEC104_Slave_Remove_Station(service, &boundary) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &duplicate) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Station_Get_Common_Address(&duplicate) == 0U && IEC104_Station_Get_Context(&duplicate) == NULL);
|
||
CHECK(IEC104_Slave_Get_Station_Count(service) == 0U && IEC104_Slave_Next_Station(service, NULL) == NULL);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
}
|
||
|
||
/* 引用覆盖延迟业务寿命;不占用其他站点,不允许零计数释放或计数回绕。 */
|
||
static void test_station_references(void)
|
||
{
|
||
IEC104_Station_t first = {0}, second = {0};
|
||
service = IEC104_Slave_Create();
|
||
CHECK(service != NULL);
|
||
CHECK(IEC104_Slave_Acquire_Station(service, &first) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Add_Station(service, &first, 1U, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Acquire_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Acquire_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(!IEC104_Station_Is_Registered(&first) && !IEC104_Station_Is_Reusable(&first));
|
||
CHECK(IEC104_Slave_Get_Station(service, 1U) == NULL);
|
||
CHECK(IEC104_Slave_Get_Station_By_Id(service, IEC104_Station_Get_Id(&first)) == &first);
|
||
CHECK(IEC104_Station_Get_Context(&first) == &first);
|
||
CHECK(IEC104_Slave_Add_Station(service, &second, 2U, NULL) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &second) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(!IEC104_Station_Is_Reusable(&first));
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Station_Is_Reusable(&first));
|
||
CHECK(IEC104_Slave_Add_Station(service, &first, 1U, &first) == IEC104_STATION_OK);
|
||
first._users = UINT32_MAX; /* 仅测试注入溢出边界,不需要执行数十亿次Acquire。 */
|
||
CHECK(IEC104_Slave_Acquire_Station(service, &first) == IEC104_STATION_LIMIT);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_OK);
|
||
first._users = 0U;
|
||
CHECK(IEC104_Slave_Acquire_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
}
|
||
|
||
/* 固定集合允许嵌套读者及站点引用,但在最后一个读者离开前不能增删或销毁。 */
|
||
static void test_station_iteration_lock(void)
|
||
{
|
||
IEC104_Station_t first = {0}, second = {0};
|
||
service = IEC104_Slave_Create();
|
||
CHECK(service != NULL);
|
||
CHECK(IEC104_Slave_Unlock_Stations(service) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Lock_Stations(service) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_INVALID_STATE);
|
||
CHECK(IEC104_Slave_Add_Station(service, &first, 1U, NULL) == IEC104_STATION_BUSY);
|
||
CHECK(IEC104_Slave_Unlock_Stations(service) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Add_Station(service, &first, 1U, NULL) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Lock_Stations(service) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Lock_Stations(service) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Get_Station(service, 1U) == &first);
|
||
CHECK(IEC104_Slave_Next_Station(service, NULL) == &first);
|
||
CHECK(IEC104_Slave_Add_Station(service, &second, 2U, NULL) == IEC104_STATION_BUSY);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_BUSY);
|
||
CHECK(IEC104_Slave_Acquire_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Release_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Unlock_Stations(service) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_BUSY);
|
||
CHECK(IEC104_Slave_Unlock_Stations(service) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Unlock_Stations(service) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
}
|
||
|
||
/* 站点属于服务,不属于某次TCP连接;关闭/重启保留,销毁前必须显式解除关联。 */
|
||
static void test_station_lifecycle(void)
|
||
{
|
||
IEC104_Station_t station = {0};
|
||
IEC104_TcpError_t error;
|
||
IEC104_SlaveConfig_t config;
|
||
service_begin();
|
||
CHECK(IEC104_Slave_Add_Station(service, &station, 0U, NULL) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Add_Station(service, &station, IEC104_CA_BROADCAST, NULL) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Add_Station(NULL, &station, 1U, NULL) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Add_Station(service, NULL, 1U, NULL) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Add_Station(service, &station, 1U, &config) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Close(service, NULL) == IEC104_TCP_OK);
|
||
service_drain();
|
||
CHECK(IEC104_Slave_Get_Station(service, 1U) == &station);
|
||
CHECK(IEC104_Slave_Destroy(service, &error) == IEC104_TCP_INVALID_STATE);
|
||
CHECK(error.result == IEC104_TCP_INVALID_STATE && error.stage == IEC104_TCP_STAGE_VALIDATE);
|
||
IEC104_Slave_Config_Default(&config);
|
||
CHECK(IEC104_Slave_Init(service, &config, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Station_Get_Context(IEC104_Slave_Get_Station(service, 1U)) == &config);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &station) == IEC104_STATION_OK);
|
||
service_end();
|
||
CHECK(IEC104_Slave_Remove_Station(NULL, &station) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Acquire_Station(NULL, &station) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Release_Station(NULL, &station) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Lock_Stations(NULL) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Unlock_Stations(NULL) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_Get_Station(NULL, 1U) == NULL && IEC104_Slave_Get_Station_Count(NULL) == 0U);
|
||
CHECK(IEC104_Slave_Next_Station(NULL, NULL) == NULL);
|
||
CHECK(IEC104_Station_Get_Common_Address(NULL) == 0U && IEC104_Station_Get_Context(NULL) == NULL);
|
||
#if IEC104_TCP_SERVER_CAPACITY >= 2U
|
||
{
|
||
IEC104_Slave_t *other;
|
||
IEC104_Station_t foreign = {0};
|
||
service = IEC104_Slave_Create();
|
||
other = IEC104_Slave_Create();
|
||
CHECK(service != NULL && other != NULL);
|
||
CHECK(IEC104_Slave_Add_Station(service, &station, 7U, &station) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Add_Station(other, &station, 8U, NULL) == IEC104_STATION_ALREADY_REGISTERED);
|
||
CHECK(IEC104_Slave_Add_Station(other, &foreign, 7U, &foreign) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Get_Station(service, 7U) == &station && IEC104_Slave_Get_Station(other, 7U) == &foreign);
|
||
CHECK(IEC104_Slave_Acquire_Station(other, &station) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Release_Station(other, &station) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Remove_Station(other, &station) == IEC104_STATION_NOT_FOUND);
|
||
CHECK(IEC104_Slave_Next_Station(other, &station) == NULL);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &station) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(other, &foreign) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Destroy(other, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/* 遍历不冻结增删;旧引用可用,新注册编号不会混入进行中的广播,最后一个快照归还才回收。 */
|
||
static void test_station_snapshot(void)
|
||
{
|
||
IEC104_Station_t first = {0}, second = {0}, replacement = {0}, added = {0};
|
||
IEC104_StationSnapshot_t scan = {0}, nested = {0}, copy;
|
||
uint64_t old_id;
|
||
service = IEC104_Slave_Create();
|
||
CHECK(service != NULL);
|
||
CHECK(IEC104_Slave_StationSnapshot_Begin(NULL, &scan) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_StationSnapshot_End(&scan) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&scan) == NULL);
|
||
CHECK(IEC104_Slave_Add_Station(service, &first, 1U, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Add_Station(service, &second, 2U, &second) == IEC104_STATION_OK);
|
||
old_id = IEC104_Station_Get_Id(&second);
|
||
CHECK(IEC104_Slave_StationSnapshot_Begin(service, &scan) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_StationSnapshot_Begin(service, &scan) == IEC104_STATION_INVALID_ARGUMENT);
|
||
copy = scan;
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(©) == NULL);
|
||
CHECK(IEC104_Slave_StationSnapshot_End(©) == IEC104_STATION_INVALID_ARGUMENT);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&scan) == &first);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &second) == IEC104_STATION_OK);
|
||
CHECK(!IEC104_Station_Is_Reusable(&second));
|
||
CHECK(IEC104_Slave_Add_Station(service, &replacement, 2U, &replacement) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Station_Get_Id(&replacement) > old_id);
|
||
CHECK(IEC104_Slave_Get_Station_By_Id(service, old_id) == &second);
|
||
CHECK(IEC104_Slave_Get_Station(service, 2U) == &replacement);
|
||
CHECK(IEC104_Slave_Add_Station(service, &added, 3U, NULL) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_StationSnapshot_Begin(service, &nested) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&scan) == NULL);
|
||
CHECK(IEC104_Slave_StationSnapshot_End(&scan) == IEC104_STATION_OK);
|
||
CHECK(!IEC104_Station_Is_Reusable(&second));
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&nested) == &first);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&nested) == &replacement);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&nested) == &added);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&nested) == NULL);
|
||
CHECK(IEC104_Slave_StationSnapshot_End(&nested) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Station_Is_Reusable(&second) && IEC104_Slave_Get_Station_By_Id(service, old_id) == NULL);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &first) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &replacement) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Remove_Station(service, &added) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_StationSnapshot_Begin(service, &scan) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_StationSnapshot_Next(&scan) == NULL);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_INVALID_STATE);
|
||
CHECK(IEC104_Slave_StationSnapshot_End(&scan) == IEC104_STATION_OK);
|
||
CHECK(IEC104_Slave_Destroy(service, NULL) == IEC104_TCP_OK);
|
||
}
|
||
|
||
/* Rejection helpers retain the real transport, queue and sequence handling. */
|
||
static const uint8_t reject_clock[] = {103, 1, 6, 0x31, 1, 0, 0, 0, 0, 0, 0, 0, 12, 28, 10, 26};
|
||
static const uint8_t reject_read[] = {102, 1, 5, 0x32, 1, 0, 0xe9, 3, 0};
|
||
|
||
static void reject_push(uint16_t ns, uint16_t nr, const uint8_t *bytes, size_t length)
|
||
{
|
||
uint8_t frame[IEC104_MAX_APDU_LEN];
|
||
int built = IEC104_Build_Iframe(frame, ns, nr, bytes, (uint16_t)length);
|
||
CHECK(built == (int)(length + IEC104_APCI_LEN));
|
||
push(frame, (size_t)built);
|
||
}
|
||
|
||
static void reject_take(IEC104_SlaveEvent_t *event)
|
||
{
|
||
CHECK(IEC104_Slave_NextEvent(service, event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event->transport.type == IEC104_TCP_EVENT_I_FRAME && event->asdu_result == IEC60870_ASDU_OK);
|
||
}
|
||
|
||
static unsigned reject_output(const uint8_t *frames[], size_t lengths[], unsigned capacity)
|
||
{
|
||
size_t offset = 0U;
|
||
unsigned count = 0U;
|
||
while (offset < fake.sent_bytes) {
|
||
const uint8_t *frame = fake.output + offset;
|
||
size_t length = frame[1] + 2U;
|
||
CHECK(frame[0] == 0x68 && length >= IEC104_APCI_LEN && offset + length <= fake.sent_bytes);
|
||
if ((frame[2] & 1U) == 0U) {
|
||
CHECK(count < capacity);
|
||
frames[count] = frame + IEC104_APCI_LEN;
|
||
lengths[count++] = length - IEC104_APCI_LEN;
|
||
}
|
||
offset += length;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
static void reject_settle(void)
|
||
{
|
||
unsigned i;
|
||
for (i = 0U; i < 8U; ++i) { service_poll(); service_drain(); }
|
||
}
|
||
|
||
/* Every error cause preserves the request, including unknown object bytes and TEST. */
|
||
static void test_reject_echo(void)
|
||
{
|
||
unsigned cause;
|
||
for (cause = 44U; cause <= 47U; ++cause) {
|
||
IEC104_SlaveEvent_t event, before;
|
||
uint8_t request[IEC104_MAX_ASDU_LEN];
|
||
const uint8_t *frames[2];
|
||
size_t lengths[2];
|
||
memset(request, 0x5a, sizeof(request));
|
||
request[0] = 200U; request[1] = 0x82U; request[2] = 0x86U;
|
||
service_begin();
|
||
reject_push(0, 0, request, sizeof(request)); service_poll(); reject_take(&event);
|
||
/* Seed OK so an implementation that resets last-submit to ERROR is caught. */
|
||
CHECK(IEC60870_MasterConnection_Send_ASDU(&event.connection, &event.asdu));
|
||
before = event;
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, (int)cause) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(memcmp(&event, &before, sizeof(event)) == 0);
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(reject_output(frames, lengths, 2) == 2U && lengths[1] == sizeof(request));
|
||
check_reply(frames[1], request, sizeof(request), (uint8_t)(0xc0U | cause));
|
||
service_end();
|
||
}
|
||
}
|
||
|
||
static void test_reject_invalid(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, before, invalid;
|
||
static const int causes[] = {-1, 7, 43, 48, 256};
|
||
size_t sent, i;
|
||
service_begin(); receive_request(&event, 0); before = event; sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Reject_Request(NULL, &event, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Reject_Request(service, NULL, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
for (i = 0U; i < sizeof(causes) / sizeof(causes[0]); ++i) {
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, causes[i]) == IEC104_TCP_SUBMIT_ERROR);
|
||
}
|
||
invalid = event; /* Deliberately invalid self-references. */
|
||
CHECK(IEC104_Slave_Reject_Request(service, &invalid, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Copy_Event(&invalid, &event));
|
||
invalid.asdu_result = IEC60870_ASDU_ERROR;
|
||
CHECK(IEC104_Slave_Reject_Request(service, &invalid, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Copy_Event(&invalid, &event)); ++invalid._owner_serial;
|
||
CHECK(IEC104_Slave_Reject_Request(service, &invalid, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
#if IEC104_TCP_SERVER_CAPACITY > 1
|
||
{
|
||
IEC104_Slave_t *other = IEC104_Slave_Create();
|
||
CHECK(other != NULL);
|
||
CHECK(IEC104_Slave_Reject_Request(other, &event, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(IEC104_Slave_Destroy(other, NULL) == IEC104_TCP_OK);
|
||
}
|
||
#endif
|
||
CHECK(fake.sent_bytes == sent && memcmp(&event, &before, sizeof(event)) == 0);
|
||
service_end();
|
||
}
|
||
|
||
/* Ordinary unread I events must not block an immediate rejection. */
|
||
static void test_reject_batch(void)
|
||
{
|
||
unsigned mode, i;
|
||
for (mode = 0U; mode < 3U; ++mode) {
|
||
const uint8_t *requests[] = {reject_clock, reject_read};
|
||
size_t sizes[] = {sizeof(reject_clock), sizeof(reject_read)};
|
||
const uint8_t *frames[2]; size_t lengths[2];
|
||
IEC104_SlaveEvent_t event, before;
|
||
IEC104_TcpServerStatus_t status;
|
||
if (mode == 1U) { requests[0] = reject_read; sizes[0] = sizeof(reject_read);
|
||
requests[1] = reject_clock; sizes[1] = sizeof(reject_clock); }
|
||
if (mode == 2U) { requests[1] = reject_clock; sizes[1] = sizeof(reject_clock); }
|
||
service_begin();
|
||
for (i = 0U; i < 2U; ++i) { reject_push((uint16_t)i, 0, requests[i], sizes[i]); }
|
||
service_poll();
|
||
for (i = 0U; i < 2U; ++i) {
|
||
reject_take(&event); before = event;
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.pending_state_event_count == 0U && status.pending_event_count == 1U - i);
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(memcmp(&event, &before, sizeof(event)) == 0);
|
||
}
|
||
reject_settle();
|
||
CHECK(reject_output(frames, lengths, 2) == 2U);
|
||
for (i = 0U; i < 2U; ++i) {
|
||
CHECK(lengths[i] == sizes[i]); check_reply(frames[i], requests[i], sizes[i], 0x6c);
|
||
}
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK && status.connected);
|
||
service_end();
|
||
}
|
||
}
|
||
|
||
/* k=1 queues the second response; reusing the source cannot change the saved bytes. */
|
||
static void test_reject_window(void)
|
||
{
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_TcpServerStatus_t status;
|
||
const uint8_t *frames[2]; size_t lengths[2];
|
||
uint8_t unknown[IEC104_MAX_ASDU_LEN];
|
||
memset(unknown, 0x5a, sizeof(unknown));
|
||
unknown[0] = 200U; unknown[1] = 0U; unknown[2] = 0x86U;
|
||
service_open_with_window(1U); service_connect();
|
||
reject_push(0, 0, reject_clock, sizeof(reject_clock));
|
||
reject_push(1, 0, unknown, sizeof(unknown)); service_poll();
|
||
reject_take(&event); CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
reject_take(&event); CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
memset(&event, 0xa5, sizeof(event));
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.high_queue.pending_asdu_count == 1U && reject_output(frames, lengths, 2) == 1U);
|
||
push_ack(1); reject_settle();
|
||
CHECK(reject_output(frames, lengths, 2) == 2U && lengths[1] == sizeof(unknown));
|
||
check_reply(frames[1], unknown, sizeof(unknown), 0xec);
|
||
service_end();
|
||
}
|
||
|
||
/* Real HIGH exhaustion returns WAIT, retaining existing entries and the input event. */
|
||
static void test_reject_full(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, before_event;
|
||
IEC104_TcpServerStatus_t before, after;
|
||
unsigned accepted;
|
||
uint8_t high_before[IEC104_TCP_HIGH_CAPACITY];
|
||
service_begin(); receive_request(&event, 0); fake.send_blocked = 1;
|
||
for (accepted = 0U; accepted < 200U; ++accepted) {
|
||
if (!IEC60870_MasterConnection_Send_ASDU(&event.connection, &event.asdu)) { break; }
|
||
}
|
||
CHECK(accepted > 0U && accepted < 200U);
|
||
CHECK(IEC104_Slave_Get_Last_Submit_Result(&event.connection) == IEC104_TCP_SUBMIT_WAIT);
|
||
before_event = event; memcpy(high_before, sut->high_bytes, sizeof(high_before));
|
||
CHECK(IEC104_Slave_Get_Status(service, &before, NULL) == IEC104_TCP_OK);
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_WAIT);
|
||
CHECK(memcmp(&event, &before_event, sizeof(event)) == 0);
|
||
CHECK(memcmp(high_before, sut->high_bytes, sizeof(high_before)) == 0);
|
||
CHECK(IEC104_Slave_Get_Status(service, &after, NULL) == IEC104_TCP_OK && after.connected);
|
||
CHECK(before.high_queue.used_bytes == after.high_queue.used_bytes &&
|
||
before.high_queue.pending_asdu_count == after.high_queue.pending_asdu_count);
|
||
fake.send_blocked = 0; reject_settle();
|
||
service_end();
|
||
}
|
||
|
||
/* State counts exclude CONNECTED; unread STOP/START blocks an old immediate response. */
|
||
static void test_reject_state_gate(void)
|
||
{
|
||
unsigned restart;
|
||
for (restart = 0U; restart < 2U; ++restart) {
|
||
IEC104_SlaveEvent_t event, before;
|
||
IEC104_TcpServerStatus_t status;
|
||
const uint8_t *frames[1]; size_t lengths[1];
|
||
service_open(); fake.accept_pending = 1; service_poll();
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.pending_event_count == 1U && status.pending_state_event_count == 0U);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
push(start_act, sizeof(start_act)); service_poll();
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.pending_state_event_count == 1U); CHECK(service_drain() == 1U);
|
||
reject_push(0, 0, reject_clock, sizeof(reject_clock)); push(stop_act, sizeof(stop_act));
|
||
if (restart) { push(start_act, sizeof(start_act)); }
|
||
service_poll(); reject_take(&event); before = event;
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.pending_state_event_count == 1U + restart);
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(memcmp(&event, &before, sizeof(event)) == 0 && reject_output(frames, lengths, 1) == 0U);
|
||
CHECK(IEC104_Slave_NextEvent(service, &event) == IEC104_TCP_EVENT_READY);
|
||
CHECK(event.transport.type == IEC104_TCP_EVENT_DEACTIVATED);
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.pending_state_event_count == restart);
|
||
service_drain();
|
||
if (!restart) { push(start_act, sizeof(start_act)); service_poll(); service_drain(); }
|
||
reject_push(1, 0, reject_read, sizeof(reject_read)); service_poll(); reject_take(&event);
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
CHECK(reject_output(frames, lengths, 1) == 1U);
|
||
service_end();
|
||
}
|
||
}
|
||
|
||
/* STOP invalidates queued replies; a repeated START preserves them without a state event. */
|
||
static void test_reject_queued_lifecycle(void)
|
||
{
|
||
unsigned stop;
|
||
for (stop = 0U; stop < 2U; ++stop) {
|
||
IEC104_SlaveEvent_t event;
|
||
IEC104_TcpServerStatus_t status;
|
||
const uint8_t *frames[2]; size_t lengths[2];
|
||
service_open_with_window(1U); service_connect();
|
||
reject_push(0, 0, reject_clock, sizeof(reject_clock));
|
||
reject_push(1, 0, reject_read, sizeof(reject_read)); service_poll();
|
||
reject_take(&event); CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
reject_take(&event); CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
push(stop ? stop_act : start_act, sizeof(start_act)); service_poll();
|
||
CHECK(IEC104_Slave_Get_Status(service, &status, NULL) == IEC104_TCP_OK);
|
||
CHECK(status.pending_state_event_count == stop && status.high_queue.pending_asdu_count == 1U - stop);
|
||
service_drain(); push_ack(1); reject_settle();
|
||
CHECK(reject_output(frames, lengths, 2) == 2U - stop);
|
||
if (stop) {
|
||
push(start_act, sizeof(start_act)); service_poll(); service_drain();
|
||
reject_push(2, 1, reject_read, sizeof(reject_read)); service_poll(); reject_take(&event);
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_OK);
|
||
}
|
||
CHECK(reject_output(frames, lengths, 2) == 2U);
|
||
check_reply(frames[1], reject_read, sizeof(reject_read), 0x6c);
|
||
service_end();
|
||
}
|
||
}
|
||
|
||
static void test_reject_stale(void)
|
||
{
|
||
IEC104_SlaveEvent_t old_event, current;
|
||
size_t sent;
|
||
service_begin(); receive_request(&old_event, 0);
|
||
CHECK(IEC104_Slave_Close_Connection(service, &old_event, NULL) == IEC104_TCP_OK);
|
||
service_drain(); service_connect(); receive_request(¤t, 0); sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Reject_Request(service, &old_event, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(fake.sent_bytes == sent);
|
||
CHECK(IEC104_Slave_Reject_Request(service, ¤t, 44) == IEC104_TCP_SUBMIT_OK);
|
||
service_end(); service_begin(); receive_request(¤t, 0); sent = fake.sent_bytes;
|
||
CHECK(IEC104_Slave_Reject_Request(service, &old_event, 44) == IEC104_TCP_SUBMIT_ERROR);
|
||
CHECK(fake.sent_bytes == sent);
|
||
service_end();
|
||
}
|
||
|
||
static void test_reject_fatal(void)
|
||
{
|
||
IEC104_SlaveEvent_t event, before;
|
||
size_t sent;
|
||
service_begin(); receive_request(&event, 0); before = event; sent = fake.sent_bytes;
|
||
sut->queue_initialized = 0U;
|
||
CHECK(IEC104_Slave_Reject_Request(service, &event, 44) == IEC104_TCP_SUBMIT_FATAL);
|
||
CHECK(memcmp(&event, &before, sizeof(event)) == 0 && fake.sent_bytes == sent);
|
||
sut->queue_initialized = 1U; /* Restore the injected fault only for cleanup; do not retry. */
|
||
service_end();
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
test_mutable_views();
|
||
test_common_dispatch();
|
||
test_event_replies();
|
||
test_constructed_asdu();
|
||
test_wait_and_retry();
|
||
test_invalid_inputs();
|
||
test_identity_response_storage();
|
||
test_identity_response_wait();
|
||
test_identity_response_validation();
|
||
test_event_identity();
|
||
test_request_state();
|
||
test_stale_generation();
|
||
test_fatal_result();
|
||
test_deferred_event_copy();
|
||
test_service_reuse();
|
||
test_lifecycle();
|
||
test_external_scheduler();
|
||
test_offline_enqueue();
|
||
test_station_registry();
|
||
test_station_references();
|
||
test_station_iteration_lock();
|
||
test_station_lifecycle();
|
||
test_station_snapshot();
|
||
test_reject_echo();
|
||
test_reject_invalid();
|
||
test_reject_batch();
|
||
test_reject_window();
|
||
test_reject_full();
|
||
test_reject_state_gate();
|
||
test_reject_queued_lifecycle();
|
||
test_reject_stale();
|
||
test_reject_fatal();
|
||
puts("Slave service: 32 groups passed (replies, identities, stations, lifecycle, backpressure, rejection)");
|
||
return 0;
|
||
}
|