Files
STM32F4-Base/IEC60870-Slave/test/iec104_slave_test.c
T
STF 7596f3371a
CI / L0 静态门禁 (pre-commit) (push) Failing after 44s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
补充总召唤的广播地址,电度召唤+时钟同步业务的实现
2026-10-29 03:20:19 -07:00

1318 lines
67 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.
/* 共用子站发送接口回归:复用可控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, &parameters, 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, &copy_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, &copy_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(&current, 0);
CHECK(old_event.transport.generation != current.transport.generation);
CHECK(IEC104_Slave_Get_Event_Identity(service, &old_event, &current_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, &current, &current_identity));
CHECK(IEC104_Slave_Check_Request(service, &current_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, &current.asdu) == IEC104_TCP_SUBMIT_ERROR);
CHECK(IEC104_Slave_Try_Send_Response(service, &old_identity, &current.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(&current.connection, &current.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(&current, 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(&current.connection, &current.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(&current, 0);
CHECK(IEC104_Slave_Get_Event_Identity(service, &current, &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, &current.asdu) == IEC104_TCP_SUBMIT_ERROR);
CHECK(IEC104_Slave_Try_Send_Response(service, &old_identity, &current.asdu) == IEC104_TCP_SUBMIT_ERROR);
CHECK(fake.sent_bytes == sent);
CHECK(IEC60870_MasterConnection_Send_ACT_CON(&current.connection, &current.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(&copy) == NULL);
CHECK(IEC104_Slave_StationSnapshot_End(&copy) == 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(&current, 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, &current, 44) == IEC104_TCP_SUBMIT_OK);
service_end(); service_begin(); receive_request(&current, 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;
}