/* 纯事件总召模块及示例接入回归:真实Slave/TCP提交路径 + 可控Net,无系统socket。 * 仅链接scripts/build.ps1中的公共依赖,不再链接TcpServer.c或Net后端。 * HIGH缩到251字节,通过应用应答和真实发送背压覆盖各业务阶段及自动推进的停止条件。 */ #define main existing_event_ring_main #define IEC104_Net_Now_Ms example_test_base_now #include "iec104_tcp_event_ring_test.c" #undef IEC104_Net_Now_Ms #undef main /* Advance only when armed: exercise two module time reads crossing a deadline. */ static uint64_t cs_test_now_step; uint64_t IEC104_Net_Now_Ms(void) { uint64_t now = example_test_base_now(); fake.now += cs_test_now_step; return now; } #define IEC104_SLAVE_EXAMPLE_NO_MAIN #include "../examples/iec104_slave_example.c" #include "IEC104_ClockSynchronization.h" static ExampleApp app; static IEC104_InterrogationStationState_t broadcast_states[16]; static unsigned master_ns; static IEC104_SlaveEvent_t last_request; static const IEC104_InterrogationConfig_t *custom_config; static const IEC104_InterrogationConfig_t *counter_custom_config; static bool counter_enabled; static bool clock_enabled; static const IEC104_ClockSynchronizationConfig_t *clock_custom_config; static IEC104_InterrogationStationState_t counter_broadcast_states[16]; static uint16_t custom_window = 32; static const uint8_t gi_request[] = {100, 1, 0x86, 0x5a, 1, 0, 0, 0, 0, 20}; static const uint8_t fallback_request[] = {106, 1, 0x86, 0x61, 1, 0, 0, 0, 0, 0x39, 0x30}; static const uint8_t read_request[] = {102, 1, 5, 0x62, 1, 0, 0xe9, 3, 0}; static void request_unchanged(const uint8_t *request, size_t size); static void app_poll(void) { CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); { IEC104_SlaveEvent_t event; IEC104_TcpEventResult_t result; while ((result = IEC104_Slave_NextEvent(app.slave, &event)) == IEC104_TCP_EVENT_READY) { if (event.transport.type == IEC104_TCP_EVENT_I_FRAME) { CHECK(IEC104_Slave_Copy_Event(&last_request, &event)); } CHECK(Example_Handle_Event(&app, &event)); } CHECK(result == IEC104_TCP_EVENT_EMPTY); } } static void app_connect(void) { fake.accept_pending = 1; app_poll(); CHECK(!app.gi.active); push(start_act, sizeof(start_act)); app_poll(); CHECK(app.gi.active); master_ns = 0; } static void app_open(void) { IEC104_SlaveConfig_t config; IEC104_Slave_t *slave; memset(&fake, 0, sizeof(fake)); fake.now = 1000; slave = IEC104_Slave_Create(); CHECK(slave != NULL); IEC104_Slave_Config_Default(&config); config.protocol.link.k = custom_window; CHECK(IEC104_Slave_Init(slave, &config, NULL) == IEC104_TCP_OK); /* 可控后端仅用此指针调整窗口或注入底层故障,不替换发送路径。 */ { unsigned slot; sut = NULL; for (slot = 0; slot < IEC104_TCP_SERVER_CAPACITY; ++slot) { if (server_pool[slot].in_use != 0U) { sut = &server_pool[slot].server; break; } } CHECK(sut != NULL); } CHECK(Example_Init(&app, slave, custom_config == NULL ? 1U : custom_config->common_address, broadcast_states, sizeof(broadcast_states) / sizeof(broadcast_states[0]))); if (custom_config != NULL) { CHECK(IEC104_Interrogation_Destroy(app.interrogation)); app.interrogation = IEC104_Interrogation_Create(slave, custom_config); CHECK(app.interrogation != NULL); } if (counter_enabled) { CHECK(Example_Init_Counters(&app, counter_broadcast_states, 16U)); if (counter_custom_config != NULL) { CHECK(IEC104_Interrogation_Destroy(app.counter)); app.counter = IEC104_CounterInterrogation_Create(slave, counter_custom_config); CHECK(app.counter != NULL); } } if (clock_enabled) { CHECK(Example_Init_Clock(&app, clock_custom_config)); } app_connect(); } static void app_close(void) { IEC104_Slave_t *slave = app.slave; Example_Cancel(&app); CHECK(Example_Drain_Business(&app)); CHECK(Example_Drain_Counter(&app)); CHECK(IEC104_Slave_Close(slave, NULL) == IEC104_TCP_OK); CHECK(Example_Drain(&app)); CHECK(IEC104_Interrogation_Destroy(app.interrogation)); if (app.counter != NULL) { CHECK(IEC104_Interrogation_Destroy(app.counter)); } if (app.clock != NULL) { CHECK(IEC104_ClockSynchronization_Destroy(app.clock)); } if (IEC104_Station_Is_Registered(&app.station)) { CHECK(IEC104_Slave_Remove_Station(slave, &app.station) == IEC104_STATION_OK); } CHECK(IEC104_Station_Is_Reusable(&app.station)); CHECK(IEC104_Slave_Destroy(slave, NULL) == IEC104_TCP_OK); memset(&app, 0, sizeof(app)); } /* 独立拼接主站I帧,避免用待测ASDU构造器生成输入或期望结果。 */ static void queue_request(const uint8_t *payload, size_t length) { uint8_t frame[255] = {0x68}; CHECK(length <= 249); frame[1] = (uint8_t)(length + 4U); frame[2] = (uint8_t)(master_ns << 1); frame[3] = (uint8_t)(master_ns >> 7); ++master_ns; memcpy(frame + 6, payload, length); push(frame, length + 6); } static void app_request(const uint8_t *payload, size_t length) { queue_request(payload, length); app_poll(); } static void app_finish(void) { unsigned i; for (i = 0; i < 128; ++i) { app_poll(); CHECK(Example_Step(&app)); } CHECK(app.gi.pending_requests == 0 && !app.gi.gi_pending); } /* 跳过U/S帧和用于制造HIGH拥塞的Type200填充帧,保留真实业务输出顺序。 */ static unsigned collect_asdus(const uint8_t **out, size_t *lengths) { size_t offset = 0; unsigned count = 0; while (offset < fake.sent_bytes) { const uint8_t *frame = fake.output + offset; size_t length; CHECK(fake.sent_bytes - offset >= 6 && frame[0] == 0x68); length = (size_t)frame[1] + 2U; CHECK(length >= 6 && offset + length <= fake.sent_bytes); if ((frame[2] & 1U) == 0U && frame[6] != 200) { CHECK(count < 32); out[count] = frame + 6; lengths[count++] = length - 6; } offset += length; } return count; } static void check_gi(unsigned extra_reply, uint8_t oa) { static const uint8_t values[] = {0, 1, 0, 1, 1, 0, 1, 0}; static const int16_t measures[] = {2300, 2310, 2290, 5000, -125, 0, 1234, 42}; const uint8_t *frames[32]; size_t lengths[32]; unsigned f, p, indexes[8], gi_count = 0U; unsigned total = collect_asdus(frames, lengths); CHECK(total == 8 + extra_reply); for (f = 0U; f < total; ++f) { if (frames[f][3] == oa && (frames[f][2] & 0x40U) == 0U) { CHECK(gi_count < 8U); indexes[gi_count++] = f; } } CHECK(gi_count == 8U); /* 其他请求的否定应答可穿插,但本次总召内容及自身顺序不变。 */ for (f = 0; f < 8; ++f) { const uint8_t *a = frames[indexes[f]]; CHECK(a[3] == oa && a[4] == 1 && a[5] == 0); if (f == 0 || f == 7) { CHECK(lengths[indexes[f]] == 10 && a[0] == 100 && a[1] == 1); CHECK(a[2] == (f == 0 ? 0x87 : 0x8a)); CHECK(a[6] == 0 && a[7] == 0 && a[8] == 0 && a[9] == 20); } else { unsigned is_signal = f < 5; unsigned is_single = f < 3; size_t width = is_signal ? 4U : 6U; unsigned first = (is_single ? f - 1U : is_signal ? f - 3U : f - 5U) * 4U; CHECK(a[0] == (is_single ? 1 : is_signal ? 3 : 11) && a[1] == 4 && a[2] == 0x94); CHECK(lengths[indexes[f]] == 6 + width * 4); for (p = 0; p < 4; ++p) { const uint8_t *object = a + 6 + width * p; unsigned ioa = object[0] | (unsigned)object[1] << 8 | (unsigned)object[2] << 16; CHECK(ioa == (is_single ? 1001U : is_signal ? 1101U : 2001U) + first + p); if (is_single) { CHECK(object[3] == values[first + p]); } else if (is_signal) { CHECK(object[3] == 1U); } else { CHECK((int16_t)(object[3] | (uint16_t)object[4] << 8) == measures[first + p]); CHECK(object[5] == 0); } } } } CHECK(app.gi.gi_submitted == 1); } static void test_total_call(void) { app_open(); fake.send_limit = 3; /* 每帧分多次write,仍只有一次提交且原字节顺序不变。 */ app_request(gi_request, sizeof(gi_request)); CHECK(app.gi.pending_requests == 1); app_finish(); check_gi(0, 0x5a); app_close(); } /* 把实际HIGH填满,并保留一个尚未写出的当前帧;失败必须是WAIT而不是测试桩伪造。 */ static void congest(IEC60870_MasterConnection_t *connection) { uint8_t bytes[249] = {200, 0, 3, 0, 1, 0}; IEC60870_Asdu_t filler; unsigned i; CHECK(IEC60870_Asdu_Create_From_Buffer(&filler, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes)) == IEC60870_ASDU_OK); fake.send_blocked = 1; for (i = 0; i < 32; ++i) { if (!IEC60870_MasterConnection_Send_ASDU(connection, &filler)) { CHECK(IEC104_Slave_Get_Last_Submit_Result(connection) == IEC104_TCP_SUBMIT_WAIT); return; } } CHECK(0); } /* 保留一个阻塞的当前帧,HIGH中只留下一个10字节应答及其2字节记录头的空间。 * 通过真实容量限制在第一条REPLY后产生WAIT,不依赖业务层的处理步数参数。 */ static void congest_except_one_reply(IEC60870_MasterConnection_t *connection) { uint8_t bytes[249] = {200, 0, 3, 0, 1, 0}; IEC60870_Asdu_t filler; fake.send_blocked = 1; CHECK(IEC60870_Asdu_Create_From_Buffer(&filler, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes)) == IEC60870_ASDU_OK); CHECK(IEC60870_MasterConnection_Send_ASDU(connection, &filler)); CHECK(IEC60870_Asdu_Create_From_Buffer(&filler, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes) - 12U) == IEC60870_ASDU_OK); CHECK(IEC60870_MasterConnection_Send_ASDU(connection, &filler)); } static void test_wait_each_phase(void) { static const struct { IEC104_InterrogationPhase_t phase; size_t batch; } phases[] = { {IEC104_INTERROGATION_CONFIRM, 0}, {IEC104_INTERROGATION_DATA, 0}, {IEC104_INTERROGATION_DATA, 2}, {IEC104_INTERROGATION_DATA, 4}, {IEC104_INTERROGATION_TERMINATE, 6} }; unsigned i; for (i = 0; i < sizeof(phases) / sizeof(phases[0]); ++i) { size_t point_index; unsigned steps; app_open(); app_request(gi_request, sizeof(gi_request)); for (steps = 0; steps < 32; ++steps) { if (app.gi.phase == phases[i].phase && app.gi.batch_index == phases[i].batch) { break; } CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); if (app.gi.phase == phases[i].phase && app.gi.batch_index == phases[i].batch) { break; } CHECK(Example_Drain_Business(&app)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); } CHECK(steps < 32); congest(&last_request.connection); point_index = app.gi.batch_index; /* DATA阶段由应用应答当场尝试发送;WAIT不得推进应用或协议游标。 */ CHECK(Example_Step(&app)); if (!app.gi.waiting) { CHECK(Example_Step(&app)); } CHECK(app.gi.waiting && app.gi.phase == phases[i].phase); CHECK(Example_Step(&app) && app.gi.waiting && app.gi.batch_index == point_index); fake.send_blocked = 0; fake.send_limit = 3; app_finish(); CHECK(app.gi.waits >= 2); check_gi(0, 0x5a); app_close(); } } static void test_validation(void) { static const struct { unsigned offset; uint8_t value, cot; } cases[] = { {0, 200, 44}, {2, 0x83, 45}, {4, 2, 46}, {6, 1, 47}, {9, 19, 7}, {9, 37, 7}, {2, 0xc6, 7} }; unsigned i; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { const uint8_t *out[32]; size_t lengths[32], j; uint8_t request[sizeof(gi_request)]; app_open(); memcpy(request, gi_request, sizeof(request)); request[cases[i].offset] = cases[i].value; app_request(request, sizeof(request)); CHECK(!app.gi.gi_pending && app.gi.pending_requests == 0); CHECK((i == 0 && app.other_submitted == 1) || (i != 0 && app.gi.negative_submitted == 1)); app_finish(); /* Type200既用作填充也用作未知类型请求;本案例直接检查唯一输出帧。 */ if (i == 0) { out[0] = fake.output + 12; lengths[0] = fake.output[7] - 4U; } else { CHECK(collect_asdus(out, lengths) == 1); } CHECK(lengths[0] == sizeof(request)); for (j = 0; j < sizeof(request); ++j) { CHECK(out[0][j] == (j == 2 ? (uint8_t)(0xc0 | cases[i].cot) : request[j])); } CHECK((app.gi.rejected + app.other_rejected) == 1 && app.gi.gi_submitted == 0); app_close(); } } static void test_busy(void) { const uint8_t *out[32]; size_t lengths[32]; uint8_t second[sizeof(gi_request)]; app_open(); app_request(gi_request, sizeof(gi_request)); memcpy(second, gi_request, sizeof(second)); second[3] = 0x6b; app_request(second, sizeof(second)); CHECK(app.gi.pending_requests == 1 && app.gi.negative_submitted == 1); app_finish(); check_gi(1, 0x5a); CHECK(collect_asdus(out, lengths) == 9); CHECK(lengths[0] == 10 && out[0][2] == 0xc7 && out[0][3] == 0x6b); app_close(); } static void test_stop(void) { uint8_t second[sizeof(gi_request)]; app_open(); app_request(gi_request, sizeof(gi_request)); push(stop_act, sizeof(stop_act)); app_poll(); CHECK(app.gi.pending_requests == 0 && !app.gi.active && app.gi.cancelled == 1); push(start_act, sizeof(start_act)); app_poll(); memcpy(second, gi_request, sizeof(second)); second[3] = 0x6b; app_request(second, sizeof(second)); app_finish(); check_gi(0, 0x6b); app_close(); } static void test_disconnect(void) { IEC104_SlaveEvent_t old; uint64_t generation; uint8_t second[sizeof(gi_request)]; app_open(); app_request(gi_request, sizeof(gi_request)); generation = app.gi.generation; CHECK(IEC104_Slave_Copy_Event(&old, &last_request)); CHECK(IEC104_Slave_Close_Connection(app.slave, &old, NULL) == IEC104_TCP_OK); CHECK(Example_Drain(&app) && app.gi.pending_requests == 0 && !app.gi.active); app_connect(); CHECK(app.gi.generation != generation); CHECK(!IEC60870_MasterConnection_Send_ACT_CON(&old.connection, &old.asdu, false)); memcpy(second, gi_request, sizeof(second)); second[3] = 0x6b; app_request(second, sizeof(second)); app_finish(); check_gi(0, 0x6b); app_close(); } static void test_malformed(void) { static const struct { size_t length; uint8_t vsq, cot, ca, ioa, qoi, reply; } cases[] = { {10, 0x81, 0x86, 1, 0, 20, 7}, {11, 1, 0x86, 1, 0, 20, 7}, {249, 0x81, 0x86, 1, 0, 36, 7}, {11, 1, 0x88, 1, 0, 20, 9}, {10, 0x81, 0x88, 1, 0, 20, 9}, {11, 1, 6, 1, 0, 20, 7}, {11, 1, 0x83, 2, 1, 19, 45}, {11, 1, 0x86, 2, 1, 19, 46}, {11, 1, 0x88, 1, 1, 19, 47}, {11, 1, 0xc6, 1, 0, 19, 7} }; unsigned i; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { IEC104_TcpServerStatus_t status; const uint8_t *frames[32]; size_t lengths[32]; uint8_t request[249], expected[10]; app_open(); memset(request, 0xa5, sizeof(request)); memcpy(request, gi_request, sizeof(gi_request)); request[1] = cases[i].vsq; request[2] = cases[i].cot; request[3] = 0x67; request[4] = cases[i].ca; request[6] = cases[i].ioa; request[9] = cases[i].qoi; request_unchanged(request, cases[i].length); app_finish(); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); CHECK(!app.gi.gi_pending && app.gi.gi_submitted == 0 && app.gi.rejected == 1 && app.gi.cancelled == 0); CHECK(app.gi.negative_submitted == 1 && app.gi.dropped_requests == 0 && app.other_rejected == 0); memcpy(expected, request, sizeof(expected)); expected[1] = 1; expected[2] = (uint8_t)((request[2] & 0x80U) | 0x40U | cases[i].reply); CHECK(collect_asdus(frames, lengths) == 1 && lengths[0] == sizeof(expected)); CHECK(memcmp(frames[0], expected, sizeof(expected)) == 0); app_request(gi_request, sizeof(gi_request)); app_finish(); check_gi(1, 0x5a); app_close(); } } /* 头部/最低载荷合法但对象不唯一:总召接手后丢弃,不能猜测首对象或进入UNKNOWN_TYPE兜底。 */ static void test_malformed_drop(void) { static const struct { size_t length; uint8_t vsq; } cases[] = { {6, 0}, {10, 0}, {6, 0x80}, {14, 2}, {11, 0x82} }; unsigned i; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { uint8_t request[14] = {0}; IEC104_TcpServerStatus_t status; const uint8_t *frames[32]; size_t lengths[32]; app_open(); memcpy(request, gi_request, sizeof(gi_request)); request[1] = cases[i].vsq; request_unchanged(request, cases[i].length); app_finish(); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); CHECK(collect_asdus(frames, lengths) == 0 && app.gi.rejected == 1 && app.gi.cancelled == 0); CHECK(app.gi.negative_submitted == 0 && app.gi.dropped_requests == 0 && app.other_rejected == 0); app_request(gi_request, sizeof(gi_request)); app_finish(); check_gi(0, 0x5a); app_close(); } } /* 真正截短的公共头/载荷仍由Receiver和连接层拒绝;不能放宽为业务层的畸形单对象回复。 */ static void test_receiver_truncated(void) { static const struct { size_t length; uint8_t type, vsq; } cases[] = { {5, 100, 1}, {9, 100, 1}, {10, 100, 2}, {10, 100, 0x82}, {5, 101, 1} }; unsigned i; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { uint8_t request[sizeof(gi_request)]; IEC104_TcpServerStatus_t status; app_open(); memcpy(request, gi_request, sizeof(request)); request[0] = cases[i].type; request[1] = cases[i].vsq; app_request(request, cases[i].length); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && !status.connected); CHECK(app.gi.rejected == 0 && app.gi.submitted == 0 && app.gi.pending_requests == 0); app_close(); } } static void test_overload(void) { unsigned i; IEC104_TcpServerStatus_t status; uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); memcpy(request, gi_request, sizeof(request)); request[9] = 19; /* 仅有短应答也能占满池;新总召不得挤掉旧应答。 */ for (i = 0; i < 4; ++i) { request[3] = (uint8_t)(0x60U + i); queue_request(request, sizeof(request)); } app_poll(); app_request(gi_request, sizeof(gi_request)); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); CHECK(app.gi.overloads == 1 && app.gi.pending_requests == 4 && !app.gi.gi_pending); CHECK(app.gi.negative_submitted == 0 && app.gi.dropped_requests == 1 && app.gi.cancelled == 0); app_finish(); CHECK(collect_asdus(frames, lengths) == 4 && app.gi.negative_submitted == 4); for (i = 0; i < 4; ++i) { CHECK(frames[i][2] == 0xc7 && frames[i][3] == 0x60U + i); } app_close(); } /* STOP/START排在一轮通知预算之后时,旧总召也不能在同一TCP重新激活后继续发送。 */ static void test_event_budget_stop_start(void) { IEC60870_Asdu_t filler; IEC104_TcpServerStatus_t status; uint8_t bytes[] = {200, 0, 3, 0, 1, 0}; unsigned i; app_open(); app_request(gi_request, sizeof(gi_request)); CHECK(IEC60870_Asdu_Create_From_Buffer(&filler, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes)) == IEC60870_ASDU_OK); for (i = 0; i < 32; ++i) { CHECK(IEC60870_MasterConnection_Send_ASDU(&last_request.connection, &filler)); } push_ack(32); for (i = 0; i < 8; ++i) { CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); } CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK); CHECK(status.pending_event_count == 32); push(stop_act, sizeof(stop_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); push(start_act, sizeof(start_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); /* 直接验证库入口也会让出,不仅依靠示例自己的事件预算标志。 */ CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.events_pending && app.gi.submitted == 0U && app.gi.pending_requests == 1U); CHECK(Example_Drain(&app) && app.events_pending && app.gi.pending_requests == 1); CHECK(Example_Step(&app) && (app.gi.submitted + app.other_submitted) == 0); CHECK(Example_Drain(&app) && !app.events_pending && app.gi.active && app.gi.pending_requests == 0); CHECK(Example_Step(&app) && (app.gi.submitted + app.other_submitted) == 0); app_close(); } static void test_non_gi_ignored(void) { unsigned busy; for (busy = 0; busy < 4; ++busy) { IEC104_SlaveEvent_t event; uint8_t input[] = {101, 1, 6, 0x5a, 1, 0, 0, 0, 0, 5}; app_open(); if (busy & 1U) { app_request(gi_request, sizeof(gi_request)); } if (busy & 2U) { input[4] = input[5] = 0xff; } queue_request(input, sizeof(input)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_Slave_NextEvent(app.slave, &event) == IEC104_TCP_EVENT_READY); CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &event) == IEC104_INTERROGATION_EVENT_IGNORED); CHECK(memcmp(event.transport.data.i_frame.apdu + 6, input, sizeof(input)) == 0); CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi) && app.gi.pending_requests == (busy & 1U)); CHECK(app.gi.rejected == 0 && app.gi.submitted == 0); /* 总召忙不能替总电度作答。 */ app_close(); } } static void test_module_contracts(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationStatus_t status, before; IEC104_Slave_t *slave = IEC104_Slave_Create(); IEC104_Interrogation_t *gi; CHECK(slave != NULL); IEC104_Interrogation_Config_Default(NULL); IEC104_Interrogation_Config_Default(&config); CHECK(IEC104_Interrogation_Create(NULL, &config) == NULL); CHECK(IEC104_Interrogation_Create(slave, NULL) == NULL); config.common_address = 0U; CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); config.common_address = IEC104_CA_BROADCAST; CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); config.common_address = 1U; config.max_objects_per_asdu = 0U; CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); config.max_objects_per_asdu = 128U; CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); config.max_objects_per_asdu = 60U; gi = IEC104_Interrogation_Create(slave, &config); CHECK(gi != NULL && IEC104_Interrogation_Create(slave, &config) == NULL); CHECK(!IEC104_Interrogation_Process(NULL)); CHECK(IEC104_Interrogation_Handle_Event(gi, NULL) == IEC104_INTERROGATION_EVENT_ERROR); memset(&status, 0xa5, sizeof(status)); memcpy(&before, &status, sizeof(status)); CHECK(!IEC104_Interrogation_Get_Status(NULL, &status) && memcmp(&status, &before, sizeof(status)) == 0); CHECK(IEC104_Interrogation_Get_Status(gi, &status) && status.phase == IEC104_INTERROGATION_IDLE); IEC104_Interrogation_Cancel(gi); CHECK(IEC104_Interrogation_Destroy(gi) && !IEC104_Interrogation_Destroy(gi)); CHECK(!IEC104_Interrogation_Process(gi)); CHECK(IEC104_Interrogation_Create(slave, &config) == gi); /* 销毁后可重新创建。 */ CHECK(IEC104_Interrogation_Destroy(gi)); CHECK(IEC104_Slave_Destroy(slave, NULL) == IEC104_TCP_OK); } /* 以下直接驱动公开业务事件API,不经过示例的自动事件应答。 */ static void gi_process(void) { app_poll(); CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); } static IEC104_InterrogationEvent_t gi_take(IEC104_InterrogationEventType_t type) { IEC104_InterrogationEvent_t event; CHECK(IEC104_Interrogation_NextEvent(app.interrogation, &event) == IEC104_INTERROGATION_NEXT_READY); CHECK(event.type == type && event.instance_id != 0U && event.request_id != 0U); return event; } static void gi_empty(void) { IEC104_InterrogationEvent_t event, before; memset(&event, 0xa5, sizeof(event)); memcpy(&before, &event, sizeof(before)); CHECK(IEC104_Interrogation_NextEvent(app.interrogation, &event) == IEC104_INTERROGATION_NEXT_EMPTY); CHECK(memcmp(&event, &before, sizeof(event)) == 0); CHECK(IEC104_Interrogation_NextEvent(NULL, &event) == IEC104_INTERROGATION_NEXT_ERROR); CHECK(memcmp(&event, &before, sizeof(event)) == 0); } static IEC104_InterrogationEvent_t gi_begin(void) { app_request(gi_request, sizeof(gi_request)); gi_process(); return gi_take(IEC104_INTERROGATION_BEGIN); } static IEC104_InterrogationEvent_t gi_data(void) { IEC104_InterrogationEvent_t event = gi_begin(); CHECK(event.request.common_address == 1 && event.request.originator_address == 0x5a && event.request.qualifier == 20 && event.request.is_test && event.request.receive_serial != 0U); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_STALE); gi_process(); return gi_take(IEC104_INTERROGATION_NEED_DATA); } static void gi_done(const IEC104_InterrogationEvent_t *event, size_t batches, size_t objects) { IEC104_InterrogationEvent_t finish; CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, event) == IEC104_INTERROGATION_REPLY_OK); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, event) == IEC104_INTERROGATION_REPLY_STALE); gi_process(); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_COMPLETED && finish.request_id == event->request_id && finish.batch_index == batches && finish.objects_submitted == objects); gi_empty(); app_poll(); } /* 以应用自己的参数/缓冲构造单点ASDU;Submit成功后这些存储可全部修改。 */ static void single_batch(IEC60870_Asdu_t *asdu, IEC60870_AppLayerParameters_t *parameters, uint8_t *bytes, unsigned count) { IEC60870_AsduHeader_t header = {20, 0, 1, 0x5a, 1}; unsigned i; *parameters = *IEC104_Slave_Get_Application_Layer_Parameters(app.slave); CHECK(IEC60870_Asdu_Create(asdu, parameters, bytes, 249, &header, 0) == IEC60870_ASDU_OK); for (i = 0; i < count; ++i) { IEC60870_SinglePointInformation_t point; CHECK(IEC60870_SinglePointInformation_Create(&point, 1001 + i, 1, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_Asdu_Add_InformationObject(asdu, &point.object) == IEC60870_ASDU_OK); } } /* 仅推进当前业务任务,不隐式Poll或消费Slave通知,便于检查自动推进的停止条件。 */ static void gi_process_only(void) { CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); } /* 同一IOA的突发原文采用独立报文字节,避免由总召构造逻辑生成预期结果。 */ static void spontaneous_value(uint8_t value) { uint8_t bytes[] = {1, 1, 3, 0, 1, 0, 0xe9, 3, 0, 0}; IEC60870_Asdu_t asdu; bytes[9] = value; CHECK(IEC60870_Asdu_Create_From_Buffer(&asdu, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes)) == IEC60870_ASDU_OK); CHECK(IEC104_Slave_Enqueue_Event(app.slave, &asdu) == IEC104_TCP_SUBMIT_OK); } /* 旧突发不能晚于新总召;新突发先发后,WAIT的旧总召必须重新取值,且不阻挡突发。 */ static void test_gi_spontaneous_order(void) { unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; IEC104_TcpServerStatus_t before, after; uint8_t bytes[249]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); single_batch(&batch, ¶meters, bytes, 1); bytes[9] = mode == 0 ? 1 : 0; if (mode == 2) { congest(&last_request.connection); } spontaneous_value(mode == 0 ? 0 : 1); CHECK(IEC104_Slave_Get_Status(app.slave, &before, NULL) == IEC104_TCP_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); CHECK(IEC104_Slave_Get_Status(app.slave, &after, NULL) == IEC104_TCP_OK); CHECK(after.high_queue.stored_asdu_count == before.high_queue.stored_asdu_count && after.normal_queue.pending_asdu_count == before.normal_queue.pending_asdu_count && after.connection.pending_send_records == before.connection.pending_send_records); gi_process_only(); CHECK(app.gi.data_send_waiting && app.gi.batch_index == 0 && !app.gi.need_process); memset(bytes, 0xcc, sizeof(bytes)); /* WAIT没有借用或缓存旧批次。 */ fake.send_blocked = 0; gi_process(); CHECK(collect_asdus(frames, lengths) == 2 && frames[1][2] == 3 && frames[1][9] == (mode == 0 ? 0 : 1)); single_batch(&batch, ¶meters, bytes, 1); /* 重新取到当前值1。 */ CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); CHECK(collect_asdus(frames, lengths) == 4 && frames[2][2] == 0x94 && frames[2][9] == 1); CHECK(frames[3][2] == 0x8a); app_close(); } } /* 总召已接手后,即使只写了部分APDU,后来的新突发也必须排在它后面。 */ static void test_gi_accepted_order(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; const uint8_t *frames[32]; size_t lengths[32], written; app_open(); event = gi_data(); single_batch(&batch, ¶meters, bytes, 1); bytes[9] = 0; written = fake.sent_bytes; fake.send_limit = 3; CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); CHECK(fake.sent_bytes > written && fake.sent_bytes < written + 16); memset(bytes, 0xcc, sizeof(bytes)); spontaneous_value(1); fake.send_limit = 0; gi_process(); CHECK(collect_asdus(frames, lengths) == 3); CHECK(frames[1][2] == 0x94 && frames[1][9] == 0 && frames[2][2] == 3 && frames[2][9] == 1); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); app_close(); } /* 首次有效数据及时交付后,发送WAIT不耗尽取数期限;下一批仍受新的取数期限保护。 */ static void test_gi_send_wait_timeout(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event, finish; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; IEC104_Interrogation_Config_Default(&config); config.data_timeout_ms = 20; custom_config = &config; app_open(); event = gi_data(); congest(&last_request.connection); single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC60870_Asdu_Set_CA(&batch, 2) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_ERROR); fake.now += 19; gi_process_only(); CHECK(app.gi.application_waiting && !app.gi.data_send_waiting && app.gi.timeout_remaining_ms == 1); CHECK(IEC60870_Asdu_Set_CA(&batch, 1) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); fake.now += 100; gi_process_only(); CHECK(app.gi.timeouts == 0 && !app.gi.timer_active && !app.gi.application_waiting && app.gi.data_send_waiting); fake.send_blocked = 0; gi_process(); single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(app.gi.application_waiting && !app.gi.data_send_waiting && app.gi.timeout_remaining_ms == 20); fake.now += 20; gi_process_only(); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_DATA_TIMEOUT && finish.batch_index == 1); app_close(); custom_config = NULL; } /* k=1窗口满时不转入HIGH;ACK释放窗口后先发NORMAL,下一次ACK后才接手总召。 */ static void test_gi_window_wait(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; IEC104_TcpServerStatus_t status; uint8_t bytes[249]; const uint8_t *frames[32]; size_t lengths[32]; custom_window = 1; app_open(); event = gi_data(); single_batch(&batch, ¶meters, bytes, 1); bytes[9] = 0; CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.high_queue.stored_asdu_count == 0); spontaneous_value(1); push_ack(1); gi_process(); CHECK(collect_asdus(frames, lengths) == 2 && frames[1][2] == 3); single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); push_ack(2); gi_process(); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); push_ack(3); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); CHECK(collect_asdus(frames, lengths) == 4 && frames[2][2] == 0x94 && frames[2][9] == 1); app_close(); custom_window = 32; } /* 对端确认期限已到时,不排队入口先让正常接收处理已经到达的ACK。 */ static void test_gi_due_ack_wait(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; IEC104_Interrogation_Config_Default(&config); config.data_timeout_ms = 0; custom_config = &config; app_open(); event = gi_data(); single_batch(&batch, ¶meters, bytes, 1); fake.now += 15000; push_ack(1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); gi_process(); CHECK(app.gi.active && app.gi.batch_index == 0); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); app_close(); custom_config = NULL; } /* 不排队入口的FATAL使交互失效,不能把可能已发送的批次当成WAIT重试。 */ static void test_gi_submit_fatal(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; app_open(); event = gi_data(); single_batch(&batch, ¶meters, bytes, 1); sut->queue_initialized = 0; CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_STALE); sut->queue_initialized = 1; CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_SEND_ERROR); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_STALE); app_close(); } /* 真实U帧填满启停通知槽后,新的GI及STOP同批到达;读空FIFO不自动受理保留的STOP。 */ static void test_stop_in_process_after_state_backpressure(void) { IEC104_InterrogationEvent_t event, finish; IEC104_ConnectionStatus_t connection; IEC104_TcpServerStatus_t service; unsigned i; app_open(); CHECK(IEC104_TCP_EVENT_STATE_RESERVE % 2U == 0U); for (i = 0; i < IEC104_TCP_EVENT_STATE_RESERVE; ++i) { push(i % 2U == 0U ? stop_act : start_act, sizeof(start_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); } queue_request(gi_request, sizeof(gi_request)); push(stop_act, sizeof(stop_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK && sut->rx_blocked); CHECK(Example_Drain(&app)); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.pending_event_count == 0); CHECK(IEC104_Connection_Get_Status(&sut->connection, sut->generation, &connection) == IEC104_CONNECTION_OK); CHECK(connection.has_unhandled_frame && app.gi.gi_pending && app.gi.active); gi_process_only(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process_only(); gi_empty(); CHECK(app.gi.events_pending && !app.gi.need_process && app.gi.gi_pending && app.gi.cancelled == 0); CHECK(app.gi.submitted == 0 && app.gi.waits == 0); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); CHECK(!service.connection.data_transfer_started && service.pending_event_count != 0); CHECK(Example_Drain(&app)); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_STOPPED); gi_empty(); app_close(); } static int hold_transfer_change(void *context) { (void)context; return 0; } /* 注入一次通知容量背压以覆盖任意业务阶段;STOP仍经真实Receiver/Drive受理,不伪造发送结果。 */ static void retain_stop_for_submit(void) { IEC104_ConnectionTransferObserver_t observer = sut->connection.transfer_observer; IEC104_TcpServerStatus_t service; sut->connection.transfer_observer.can_accept = hold_transfer_change; push(stop_act, sizeof(stop_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); sut->connection.transfer_observer = observer; CHECK(sut->rx_blocked && !sut->connection.receive_batch_pending); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK); CHECK(service.pending_event_count == 0 && service.connection.data_transfer_started); } /* 准备待交给Handle_Event的真实忙请求,测试立即应答入口;不调用业务处理器提前提交。 */ static void read_busy_request(void) { uint8_t request[sizeof(gi_request)]; memcpy(request, gi_request, sizeof(request)); request[3] = 0x61; queue_request(request, sizeof(request)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_Slave_NextEvent(app.slave, &last_request) == IEC104_TCP_EVENT_READY); CHECK(last_request.transport.type == IEC104_TCP_EVENT_I_FRAME); } /* Process、立即应答、Submit_Batch均须先交付STOP;不停止取数计时、不重发、可在START后重新总召。 */ static void test_stop_during_submit(void) { unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t event, finish; IEC104_InterrogationStatus_t before; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; app_open(); event = mode == 0 ? gi_begin() : gi_data(); if (mode == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } else if (mode == 1) { read_busy_request(); } else { single_batch(&batch, ¶meters, bytes, 1); } retain_stop_for_submit(); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &before)); if (mode == 0) { CHECK(IEC104_Interrogation_Process(app.interrogation)); } else if (mode == 1) { CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &last_request) == IEC104_INTERROGATION_EVENT_HANDLED); } else { CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.events_pending && !app.gi.need_process && app.gi.gi_pending && app.gi.cancelled == before.cancelled); CHECK(app.gi.submitted == before.submitted && app.gi.waits == before.waits && app.gi.phase == before.phase); CHECK(app.gi.batch_index == before.batch_index && app.gi.objects_submitted == before.objects_submitted); CHECK(app.gi.data_send_waiting == before.data_send_waiting && app.gi.timer_active == before.timer_active); CHECK(!before.timer_active || app.gi.timeout_remaining_ms == before.timeout_remaining_ms); gi_empty(); gi_process_only(); gi_empty(); /* 通知未读时重复推进也不能抢先定终结原因。 */ CHECK(Example_Drain(&app)); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_STOPPED); if (mode == 2) { CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_STALE); } gi_empty(); push(start_act, sizeof(start_act)); app_poll(); app_request(gi_request, sizeof(gi_request)); app_finish(); CHECK(app.gi.gi_submitted == 1 && app.gi.negative_submitted == 0); app_close(); } } /* 通知已成功交付后注入内部故障,真实FATAL即使伴随STOP也不能降为WAIT。 */ static int notify_stop_then_fail(void *context, IEC104_DataTransferState_t state) { CHECK(Notify_Transfer_State(context, state)); return 0; } static void test_fatal_during_stop_submit(void) { unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t event, finish; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; IEC104_TcpServerStatus_t service; uint8_t bytes[249]; app_open(); event = mode == 0 ? gi_begin() : gi_data(); if (mode == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } else if (mode == 1) { read_busy_request(); } else { single_batch(&batch, ¶meters, bytes, 1); } retain_stop_for_submit(); sut->connection.transfer_observer.notify = notify_stop_then_fail; if (mode == 0) { CHECK(IEC104_Interrogation_Process(app.interrogation)); } else if (mode == 1) { CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &last_request) == IEC104_INTERROGATION_EVENT_ERROR); } else { CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_STALE); } CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK); CHECK(!service.connected && service.pending_event_count != 0 && service.connection.fault == IEC104_TCP_FAULT_INTERNAL); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_SEND_ERROR); CHECK(Example_Drain(&app)); gi_empty(); app_close(); } } /* EOF后的提交属于ERROR;连接仍存在且传输快照仍为STARTED,不能误认成STOP。 */ static void test_submit_error_without_stop(void) { unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t event, finish; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; IEC104_TcpServerStatus_t service; uint8_t bytes[249]; app_open(); event = mode == 0 ? gi_begin() : gi_data(); if (mode == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } else if (mode == 1) { read_busy_request(); } else { single_batch(&batch, ¶meters, bytes, 1); } sut->rx_eof = 1; /* 保留连接以完成EOF收尾,但不再允许新应答。 */ if (mode == 0) { CHECK(IEC104_Interrogation_Process(app.interrogation)); } else if (mode == 1) { CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &last_request) == IEC104_INTERROGATION_EVENT_ERROR); } else { CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_STALE); } CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK); CHECK(service.connected && service.connection.data_transfer_started && service.pending_event_count == 0); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_SEND_ERROR); gi_empty(); app_close(); } } /* OK及WAIT也能顺带交付RECORD;成功只提交一次,后续应答必须等新通知被消费。 */ static void test_submit_with_new_record(void) { static const uint8_t ack[] = {0x68, 4, 1, 0, 2, 0}; unsigned wait, i; for (wait = 0; wait < 2; ++wait) { IEC104_InterrogationEvent_t event; IEC104_TcpServerStatus_t service; IEC104_SlaveEvent_t record; uint8_t request[sizeof(gi_request)]; uint32_t consumed; app_open(); event = gi_data(); memcpy(request, gi_request, sizeof(request)); for (i = 0; i < 2; ++i) { request[3] = (uint8_t)(0x60U + i); queue_request(request, sizeof(request)); } app_poll(); CHECK(app.gi.pending_requests == 3 && app.gi.negative_submitted == 0); if (wait) { congest(&last_request.connection); } /* ACK已由连接层处理,回执尚未转入Slave FIFO,由Send_Response内的Consume_Records交付。 */ CHECK(IEC104_Connection_Feed(&sut->connection, sut->generation, ack, sizeof(ack), fake.now, &consumed) == IEC104_CONNECTION_OK && consumed == sizeof(ack)); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.pending_event_count == 0); gi_process_only(); CHECK(app.gi.events_pending && app.gi.negative_submitted == (wait ? 0U : 1U)); CHECK(app.gi.pending_requests == (wait ? 3U : 2U) && app.gi.gi_pending && app.gi.cancelled == 0); gi_process_only(); /* 即使再次调用,未读回执也阻止第二个应答提交。 */ CHECK(app.gi.negative_submitted == (wait ? 0U : 1U)); CHECK(IEC104_Slave_NextEvent(app.slave, &record) == IEC104_TCP_EVENT_READY); CHECK(record.transport.type == IEC104_TCP_EVENT_RECORD && Example_Handle_Event(&app, &record)); gi_empty(); fake.send_blocked = 0; for (i = 0; i < 8; ++i) { gi_process(); } CHECK(app.gi.negative_submitted == 2 && app.gi.pending_requests == 1); gi_done(&event, 0, 0); app_close(); } } static void queue_busy(uint8_t oa) { uint8_t request[sizeof(gi_request)]; memcpy(request, gi_request, sizeof(request)); request[3] = oa; queue_request(request, sizeof(request)); } static void request_busy(uint8_t oa) { queue_busy(oa); app_poll(); } /* 本地决定/数据一旦接手,无需新网络事件即可续跑;查询本身不提交帧或消费事件。 */ static void test_need_process_business(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; app_open(); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi) && !app.gi.need_process); app_request(gi_request, sizeof(gi_request)); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process && app.gi.pending_events == 0); gi_process_only(); CHECK(!app.gi.need_process && app.gi.pending_events == 1 && app.gi.submitted == 0); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(Example_Wait_Time(&app, 50) == 50 && !app.gi.need_process && app.gi.application_waiting); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process && app.gi.submitted == 0); gi_process_only(); CHECK(!app.gi.need_process && app.gi.submitted == 1); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(Example_Wait_Time(&app, 50) == 50 && !app.gi.need_process); single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process && app.gi.batch_index == 1); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.submitted == 2 && app.gi.pending_events == 0); gi_process_only(); CHECK(!app.gi.need_process && app.gi.batch_index == 1 && app.gi.objects_submitted == 1); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process && app.gi.submitted == 2); gi_process_only(); CHECK(!app.gi.need_process && app.gi.pending_requests == 0 && app.gi.pending_events == 1); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); CHECK(Example_Wait_Time(&app, 50) == 50 && !app.gi.need_process); app_close(); } /* CONFIRM/批次/终止/短应答的真实发送背压均停止本地续跑,网络推进后显式重试恢复。 */ static void test_need_process_send_wait(void) { unsigned mode, i; for (mode = 0; mode < 4; ++mode) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; unsigned long waits; app_open(); event = mode == 0 ? gi_begin() : gi_data(); congest(&last_request.connection); if (mode == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } else if (mode == 1) { single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); } else if (mode == 2) { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } else { request_busy(0x60); CHECK(!app.gi.need_process && app.gi.waits == 1); /* 入口已尝试并得到WAIT。 */ } if (mode == 0 || mode == 2) { CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process); } gi_process_only(); CHECK(app.gi.waiting && !app.gi.need_process && app.gi.batch_index == 0); waits = app.gi.waits; for (i = 0; i < 3; ++i) { CHECK(Example_Wait_Time(&app, 50) == 50 && !app.gi.need_process && app.gi.waits == waits); } fake.send_blocked = 0; gi_process(); CHECK(!app.gi.need_process && app.gi.waits == waits); if (mode == 1) { single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); } if (mode == 2) { CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); } else { if (mode < 2) { event = gi_take(IEC104_INTERROGATION_NEED_DATA); } gi_done(&event, mode == 1 ? 1U : 0U, mode == 1 ? 1U : 0U); } app_close(); } } /* 总召等应用不遮蔽后面的短应答;未读STOP/START始终先于本地续跑。 */ static void test_need_process_reply_and_transport(void) { IEC104_InterrogationEvent_t event; app_open(); event = gi_data(); queue_busy(0x60); queue_busy(0x61); app_poll(); CHECK(app.gi.pending_requests == 3 && app.gi.application_waiting && app.gi.waiting); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process); gi_process_only(); CHECK(app.gi.pending_requests == 1 && app.gi.negative_submitted == 2 && !app.gi.need_process); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process); push(stop_act, sizeof(stop_act)); push(start_act, sizeof(start_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.events_pending && !app.gi.need_process && app.gi.pending_requests == 1); gi_process_only(); CHECK(app.gi.events_pending && !app.gi.need_process && app.gi.gi_submitted == 0); app_poll(); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(!app.gi.events_pending && !app.gi.need_process && app.gi.pending_requests == 0); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_STOPPED); gi_empty(); app_close(); } /* BEGIN和NEED_DATA均可长期留在应用手中,忙拒绝/错误应答不依赖应用先完成总召。 */ static void test_reply_while_waiting(void) { unsigned mode; for (mode = 0U; mode < 2U; ++mode) { IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; uint8_t invalid[sizeof(gi_request)]; app_open(); event = mode == 0U ? gi_begin() : gi_data(); request_busy(0x61); memcpy(invalid, gi_request, sizeof(invalid)); invalid[4] = 2; app_request(invalid, sizeof(invalid)); CHECK(collect_asdus(frames, lengths) == mode + 2U); /* 安全的短应答已在入口提交,不等待Process。 */ gi_process_only(); CHECK(collect_asdus(frames, lengths) == mode + 2U); CHECK(frames[mode][2] == 0xc7 && frames[mode][3] == 0x61); invalid[2] = 0xee; /* TEST + P/N + UNKNOWN_COMMON_ADDRESS。 */ CHECK(lengths[mode + 1U] == sizeof(invalid) && memcmp(frames[mode + 1U], invalid, sizeof(invalid)) == 0); CHECK(app.gi.gi_pending && app.gi.pending_requests == 1 && app.gi.waiting && app.gi.application_waiting); CHECK(app.gi.phase == (mode == 0U ? IEC104_INTERROGATION_START : IEC104_INTERROGATION_DATA)); gi_process_only(); CHECK(collect_asdus(frames, lengths) == mode + 2U); /* 等待应用时立即返回,不重发。 */ gi_empty(); /* 原工作事件不被重复交付或替换。 */ if (mode == 0U) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } gi_done(&event, 0, 0); app_close(); } } /* 旧FINISHED占满最小业务FIFO时,新总召不能生成BEGIN,单帧应答仍可发送。 */ static void test_reply_event_backpressure(void) { IEC104_InterrogationEvent_t old, event; const uint8_t *frames[32]; size_t lengths[32]; app_open(); old = gi_begin(); IEC104_Interrogation_Cancel(app.interrogation); app_request(gi_request, sizeof(gi_request)); request_busy(0x61); gi_process(); CHECK(collect_asdus(frames, lengths) == 1 && frames[0][2] == 0xc7 && frames[0][3] == 0x61); CHECK(app.gi.pending_requests == 1 && app.gi.pending_events == 1 && app.gi.waiting && !app.gi.application_waiting); event = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(event.request_id == old.request_id && event.reason == IEC104_INTERROGATION_FINISH_CANCELLED); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request_id != old.request_id); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &old, true) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 0, 0); app_close(); } /* 持续释放并复用非队首槽:总召未完成也能接收后续请求,应答自身仍保持FIFO。 */ static void test_reply_slot_reuse(void) { IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); event = gi_data(); for (i = 0U; i < 3U; ++i) { queue_busy((uint8_t)(0x60U + i)); } app_poll(); /* 同批通知未读空时先保存,后到应答不能越过已排队应答。 */ CHECK(app.gi.pending_requests == 4); for (i = 0U; i < 6U; ++i) { congest_except_one_reply(&last_request.connection); gi_process_only(); fake.send_blocked = 0; app_poll(); CHECK(collect_asdus(frames, lengths) == i + 2U); CHECK(lengths[i + 1U] == 10 && frames[i + 1U][2] == 0xc7 && frames[i + 1U][3] == 0x60U + i); CHECK(app.gi.phase == IEC104_INTERROGATION_DATA && app.gi.application_waiting && app.gi.overloads == 0); if (i < 3U) { request_busy((uint8_t)(0x63U + i)); CHECK(app.gi.pending_requests == 4); } } CHECK(app.gi.pending_requests == 1 && app.gi.batch_index == 0); gi_done(&event, 0, 0); app_close(); } /* 总召在旧应答之后建立:BEGIN出队及应用应答必须定位该请求,不能误操作索引队首。 */ static void test_reply_non_head_events(void) { IEC104_InterrogationEvent_t event; uint8_t invalid[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); memcpy(invalid, gi_request, sizeof(invalid)); invalid[9] = 19; invalid[3] = 0x61; queue_request(invalid, sizeof(invalid)); invalid[3] = 0x62; queue_request(invalid, sizeof(invalid)); queue_request(gi_request, sizeof(gi_request)); app_poll(); congest_except_one_reply(&last_request.connection); gi_process_only(); /* 第一条应答、非队首总召BEGIN,再因第二条应答的WAIT返回。 */ CHECK(app.gi.phase == IEC104_INTERROGATION_START && app.gi.pending_requests == 2); event = gi_take(IEC104_INTERROGATION_BEGIN); request_busy(0x63); /* 复用刚释放的槽,不能改变工作事件所指的总召。 */ CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); fake.send_blocked = 0; gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(collect_asdus(frames, lengths) == 4); CHECK(frames[0][3] == 0x61 && frames[1][3] == 0x62 && frames[2][2] == 0x87 && frames[3][3] == 0x63); gi_done(&event, 0, 0); app_close(); } /* 一次调用在两类就绪任务之间轮转,自动提交当前批次并索取下一批;后续应答仍可穿插。 */ static void test_reply_round_robin(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); queue_busy(0x60); queue_busy(0x61); queue_busy(0x62); app_poll(); gi_process_only(); CHECK(app.gi.batch_index == 1 && app.gi.pending_requests == 1 && app.gi.application_waiting); CHECK(collect_asdus(frames, lengths) == 5); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.batch_index == 1); request_busy(0x63); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process_only(); CHECK(app.gi.batch_index == 2 && app.gi.application_waiting); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.batch_index == 2 && app.gi.pending_requests == 1); gi_done(&event, 2, 2); CHECK(collect_asdus(frames, lengths) == 8); CHECK(frames[0][2] == 0x87 && frames[1][0] == 1 && frames[6][0] == 1 && frames[7][2] == 0x8a); CHECK(frames[2][3] == 0x60 && frames[3][3] == 0x61 && frames[4][3] == 0x62 && frames[5][3] == 0x63); app_close(); } /* HIGH满时保留最早应答;后来的应答不能越过它,恢复后每条只提交一次。 */ static void test_reply_send_wait(void) { IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); congest(&last_request.connection); request_busy(0x60); gi_process_only(); CHECK(app.gi.pending_requests == 2 && app.gi.waits == 2); /* 入口和Process各尝试一次,均未接手。 */ request_busy(0x61); gi_process_only(); CHECK(app.gi.pending_requests == 3 && app.gi.waits == 3); CHECK(collect_asdus(frames, lengths) == 1); fake.send_blocked = 0; gi_process(); CHECK(app.gi.pending_requests == 1 && app.gi.application_waiting); CHECK(collect_asdus(frames, lengths) == 3 && frames[1][3] == 0x60 && frames[2][3] == 0x61); gi_process(); CHECK(collect_asdus(frames, lengths) == 3); gi_done(&event, 0, 0); app_close(); } /* 新应答只能越过尚未提交的总召工作,不能越过发送层已经接手的数据帧。 */ static void test_reply_preserves_submitted_data(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); fake.send_blocked = 1; single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process_only(); CHECK(app.gi.batch_index == 1); request_busy(0x60); gi_process_only(); CHECK(collect_asdus(frames, lengths) == 1); fake.send_blocked = 0; gi_process(); CHECK(collect_asdus(frames, lengths) == 3 && frames[1][0] == 1 && frames[2][2] == 0xc7); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); app_close(); } /* 短应答已完成一部分后,STOP/断线/超时仍须清掉其余任务,FINISHED恰好一次。 */ static void test_reply_cleanup(void) { IEC104_InterrogationConfig_t config; unsigned mode; IEC104_Interrogation_Config_Default(&config); config.data_timeout_ms = 20; custom_config = &config; for (mode = 0U; mode < 3U; ++mode) { IEC104_InterrogationEvent_t event, finish; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); queue_busy(0x60); queue_busy(0x61); app_poll(); congest_except_one_reply(&last_request.connection); gi_process_only(); fake.send_blocked = 0; app_poll(); CHECK(app.gi.pending_requests == 2 && collect_asdus(frames, lengths) == 2); fake.now += 20; if (mode == 0U) { push(stop_act, sizeof(stop_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); } else if (mode == 1U) { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); } gi_process_only(); if (mode < 2U) { CHECK(app.gi.events_pending); } app_poll(); CHECK(collect_asdus(frames, lengths) == 2 && app.gi.pending_requests == 0); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == (mode == 0U ? IEC104_INTERROGATION_FINISH_STOPPED : (mode == 1U ? IEC104_INTERROGATION_FINISH_DISCONNECTED : IEC104_INTERROGATION_FINISH_DATA_TIMEOUT))); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); gi_process(); gi_empty(); app_close(); } custom_config = NULL; } /* 仅有单帧任务时,一次调用提交整个固定池中的就绪应答并回到空闲;再次调用无副作用。 */ static void test_process_reply_only(void) { uint8_t invalid[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); memcpy(invalid, gi_request, sizeof(invalid)); invalid[9] = 19; for (i = 0U; i < 4U; ++i) { invalid[3] = (uint8_t)(0x60U + i); queue_request(invalid, sizeof(invalid)); } app_poll(); CHECK(app.gi.pending_requests == 4 && app.gi.submitted == 0); gi_process_only(); CHECK(app.gi.pending_requests == 0 && app.gi.phase == IEC104_INTERROGATION_IDLE && !app.gi.waiting); CHECK(collect_asdus(frames, lengths) == 4); for (i = 0U; i < 4U; ++i) { CHECK(lengths[i] == 10 && frames[i][2] == 0xc7 && frames[i][3] == 0x60U + i); } gi_process_only(); gi_empty(); CHECK(app.gi.submitted == 4 && collect_asdus(frames, lengths) == 4); app_close(); } /* 短应答入口不得越过尚未交付的STOP/START;满槽时也不能用最后激活状态发送旧应答。 */ static void test_immediate_reply_guard(void) { unsigned mode, i; for (mode = 0; mode < 2; ++mode) { IEC104_InterrogationEvent_t event = {0}; uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); memcpy(request, gi_request, sizeof(request)); if (mode == 0) { event = gi_data(); queue_busy(0x61); } else { for (i = 0; i < 4; ++i) { request[9] = (uint8_t)(20U + i); queue_request(request, sizeof(request)); } app_poll(); /* 一个总召及三个REPLY,未读通知/旧REPLY阻止提前发送。 */ request[9] = 24; queue_request(request, sizeof(request)); } push(stop_act, sizeof(stop_act)); push(start_act, sizeof(start_act)); for (i = 0; i < 3; ++i) { CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); } app_poll(); CHECK(app.gi.active && app.gi.pending_requests == 0 && app.gi.negative_submitted == 0); CHECK(collect_asdus(frames, lengths) == (mode == 0 ? 1U : 0U)); if (mode == 0) { IEC104_InterrogationEvent_t finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_STOPPED); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); } else { CHECK(app.gi.overloads == 1 && app.gi.dropped_requests == 1); } gi_empty(); memcpy(request, gi_request, sizeof(request)); request[4] = 2; app_request(request, sizeof(request)); CHECK(app.gi.negative_submitted == 1); /* 新启用的会话可立即应答。 */ CHECK(memcmp(last_request.transport.data.i_frame.apdu + 6, request, sizeof(request)) == 0); app_close(); } } /* 旧REPLY不能被新拒绝越过;发送也满时只丢新请求,原NEED_DATA保持有效。 */ static void test_overload_drop_preserves_active(void) { unsigned mode, i; for (mode = 0; mode < 2; ++mode) { IEC104_InterrogationEvent_t event; IEC104_TcpServerStatus_t status; uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); if (mode == 0) { for (i = 0; i < 4; ++i) { queue_busy((uint8_t)(0x60U + i)); } app_poll(); } else { memcpy(request, gi_request, sizeof(request)); congest(&last_request.connection); for (i = 21; i <= 23; ++i) { request[9] = (uint8_t)i; app_request(request, sizeof(request)); } request[9] = 24; app_request(request, sizeof(request)); } CHECK(app.gi.pending_requests == 4 && app.gi.overloads == 1 && app.gi.dropped_requests == 1); CHECK(app.gi.cancelled == 0 && app.gi.negative_submitted == 0 && app.gi.application_waiting); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); gi_empty(); fake.send_blocked = 0; gi_process(); if (mode == 0) { CHECK(collect_asdus(frames, lengths) == 4 && app.gi.negative_submitted == 3); for (i = 1; i < 4; ++i) { CHECK(frames[i][2] == 0xc7 && frames[i][3] == 0x5fU + i); } } else { app_poll(); CHECK(app.gi.pending_requests == 1 && app.gi.negative_submitted == 3); CHECK(collect_asdus(frames, lengths) == 4); for (i = 1; i < 4; ++i) { CHECK(frames[i][2] == 0xc7 && frames[i][9] == 20U + i); } } gi_done(&event, 0, 0); /* 过载没有使原应用事件失效。 */ CHECK(app.gi.dropped_requests == 1 && app.gi.overloads == 1); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); app_close(); } } /* 忙时不同组也拒绝且不自动重放;主站在结束后重新请求,每组保留自己的OA/TEST。 */ static void test_qoi_sequential_retry(void) { static const uint8_t qualifiers[] = {22, 20, 21}; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_SinglePointInformation_t point; uint8_t request[sizeof(gi_request)], bytes[249]; const uint8_t *frames[32]; size_t lengths[32]; uint64_t previous_id = 0; unsigned i; app_open(); for (i = 0; i < 3; ++i) { IEC60870_AsduHeader_t header = {0}; memcpy(request, gi_request, sizeof(request)); request[9] = qualifiers[i]; request[3] = (uint8_t)(0x70U + i); request[2] = i == 1 ? 0x86 : 6; app_request(request, sizeof(request)); if (i == 0) { unsigned later; for (later = 1; later < 3; ++later) { request[9] = qualifiers[later]; request[3] = (uint8_t)(0x70U + later); request[2] = later == 1 ? 0x86 : 6; app_request(request, sizeof(request)); } CHECK(app.gi.negative_submitted == 2 && app.gi.pending_requests == 1); } gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.qualifier == qualifiers[i] && event.request.originator_address == 0x70U + i); CHECK(event.request.is_test == (i == 1) && event.request_id > previous_id); previous_id = event.request_id; gi_empty(); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); header.cause_of_transmission = qualifiers[i]; header.common_address = 1; header.originator_address = event.request.originator_address; header.is_test = event.request.is_test; CHECK(IEC60870_Asdu_Create(&batch, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_SinglePointInformation_Create(&point, 1001U + i, 1, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_Asdu_Add_InformationObject(&batch, &point.object) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); gi_process(); gi_empty(); CHECK(!app.gi.gi_pending && app.gi.pending_requests == 0); } CHECK(collect_asdus(frames, lengths) == 11 && app.gi.gi_submitted == 3 && !app.gi.gi_pending); CHECK(frames[0][2] == 0xc7 && frames[0][9] == 20 && frames[0][3] == 0x71); CHECK(frames[1][2] == 0x47 && frames[1][9] == 21 && frames[1][3] == 0x72); for (i = 0; i < 3; ++i) { unsigned first = 2U + i * 3U, test = i == 1 ? 0x80U : 0U; CHECK(frames[first][2] == (test | 7U) && frames[first][9] == qualifiers[i]); CHECK(frames[first + 1][0] == 1 && frames[first + 1][2] == (test | qualifiers[i])); CHECK(frames[first + 2][2] == (test | 10U) && frames[first + 2][9] == qualifiers[i]); CHECK(frames[first][3] == 0x70U + i && frames[first + 1][3] == 0x70U + i && frames[first + 2][3] == 0x70U + i); } app_close(); } /* BEGIN生成前、交付前后、确认/数据/终止发送等待期间,均拒绝相同及不同QOI。 */ static void test_busy_all_phases(void) { unsigned mode, qoi; for (mode = 0; mode < 8; ++mode) { IEC104_InterrogationEvent_t event = {0}, finish; IEC104_InterrogationPhase_t phase; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t request[sizeof(gi_request)], bytes[249]; app_open(); app_request(gi_request, sizeof(gi_request)); if (mode > 0) { gi_process(); } if (mode > 1) { event = gi_take(IEC104_INTERROGATION_BEGIN); } if (mode > 2) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } if (mode > 3) { gi_process(); } if (mode > 4) { event = gi_take(IEC104_INTERROGATION_NEED_DATA); } if (mode == 6) { single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); } else if (mode == 7) { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } if (mode == 3 || mode >= 6) { congest(&last_request.connection); gi_process_only(); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); phase = app.gi.phase; memcpy(request, gi_request, sizeof(request)); request[3] = 0x71; request[2] = 6; for (qoi = 20; qoi <= 21; ++qoi) { request[9] = (uint8_t)qoi; app_request(request, sizeof(request)); } CHECK(app.gi.rejected == 2 && app.gi.gi_pending && app.gi.phase == phase); CHECK(app.gi.pending_requests == ((mode == 3 || mode >= 6) ? 3U : 1U)); IEC104_Interrogation_Cancel(app.interrogation); if (mode > 1) { finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.request.qualifier == 20); CHECK(finish.request.originator_address == 0x5a && finish.request.is_test); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_CANCELLED); } gi_empty(); app_close(); } } /* 被忙拒绝的QOI没有撤销对象;匹配原请求仍可立即撤销,之后主站可重新发起其他组。 */ static void test_busy_cancel_match(void) { IEC104_InterrogationEvent_t event, next, finish; uint8_t request[sizeof(gi_request)]; app_open(); event = gi_data(); memcpy(request, gi_request, sizeof(request)); request[9] = 21; app_request(request, sizeof(request)); request[2] = 0x88; app_request(request, sizeof(request)); CHECK(app.gi.pending_requests == 1 && app.gi.application_waiting && app.gi.deactivated == 0); CHECK(app.gi.negative_submitted == 2); gi_empty(); request[9] = 20; app_request(request, sizeof(request)); CHECK(!app.gi.gi_pending && app.gi.pending_requests == 0 && app.gi.deactivated == 1); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_DEACTIVATED); gi_empty(); app_request(request, sizeof(request)); CHECK(app.gi.negative_submitted == 3); request[9] = 21; request[2] = 0x86; app_request(request, sizeof(request)); gi_process(); next = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(next.request.qualifier == 21 && next.request_id != event.request_id); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &next, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); next = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&next, 0, 0); CHECK(app.gi.gi_submitted == 1); app_close(); } /* 清理当前总召和积压的忙应答,仅已交付BEGIN的原请求产生FINISHED。 */ static void test_busy_stop_cleanup(void) { unsigned mode, qoi; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t event, finish; uint8_t request[sizeof(gi_request)]; app_open(); event = gi_data(); memcpy(request, gi_request, sizeof(request)); for (qoi = 21; qoi <= 22; ++qoi) { request[9] = (uint8_t)qoi; queue_request(request, sizeof(request)); } app_poll(); CHECK(app.gi.pending_requests == 3); if (mode == 0) { push(stop_act, sizeof(stop_act)); app_poll(); } else if (mode == 1) { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); app_poll(); } else { IEC104_Interrogation_Cancel(app.interrogation); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.pending_requests == 0 && !app.gi.gi_pending && app.gi.cancelled == 3); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == (mode == 0 ? IEC104_INTERROGATION_FINISH_STOPPED : (mode == 1 ? IEC104_INTERROGATION_FINISH_DISCONNECTED : IEC104_INTERROGATION_FINISH_CANCELLED))); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); gi_empty(); app_close(); } } /* 后来的忙应答不会重置原总召的执行期限。 */ static void test_busy_preserves_deadline(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event, finish; IEC104_TcpServerStatus_t status; uint8_t request[sizeof(gi_request)]; IEC104_Interrogation_Config_Default(&config); config.data_timeout_ms = 500; config.total_timeout_ms = 100; custom_config = &config; app_open(); event = gi_data(); fake.now += 99; memcpy(request, gi_request, sizeof(request)); request[9] = 21; app_request(request, sizeof(request)); request[9] = 22; app_request(request, sizeof(request)); CHECK(app.gi.timeout_remaining_ms == 1 && app.gi.negative_submitted == 2 && app.gi.pending_requests == 1); ++fake.now; gi_process_only(); CHECK(app.gi.timeouts == 1 && app.gi.pending_requests == 0 && !app.gi.gi_pending); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT && finish.request_id == event.request_id); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && !status.connected); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); gi_empty(); app_close(); custom_config = NULL; } /* 主站重新发起的下一个组获得完整执行期限,不继承此前被拒绝请求的等待时间。 */ static void test_next_request_deadline(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; uint8_t request[sizeof(gi_request)]; IEC104_Interrogation_Config_Default(&config); config.total_timeout_ms = 100; config.begin_timeout_ms = config.data_timeout_ms = 0; custom_config = &config; app_open(); event = gi_data(); memcpy(request, gi_request, sizeof(request)); request[9] = 21; app_request(request, sizeof(request)); fake.now += 90; gi_done(&event, 0, 0); gi_process(); gi_empty(); app_request(request, sizeof(request)); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.qualifier == 21 && app.gi.timeout_remaining_ms == 100); fake.now += 99; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 0, 0); CHECK(app.gi.timeouts == 0 && app.gi.gi_submitted == 2); app_close(); custom_config = NULL; } /* 历史FINISHED占据业务FIFO时,仅当前START等待空位;没有排队计时,但仍拒绝新总召。 */ static void test_begin_capacity_wait(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t old, next, finish; uint8_t request[sizeof(gi_request)]; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = 10; config.data_timeout_ms = 20; config.total_timeout_ms = 100; custom_config = &config; app_open(); old = gi_begin(); IEC104_Interrogation_Cancel(app.interrogation); memcpy(request, gi_request, sizeof(request)); request[9] = 21; app_request(request, sizeof(request)); gi_process_only(); CHECK(app.gi.gi_pending && app.gi.waiting && !app.gi.timer_active && !app.gi.need_process); request[9] = 22; app_request(request, sizeof(request)); CHECK(app.gi.negative_submitted == 1 && app.gi.pending_requests == 1); fake.now += 60001; gi_process_only(); CHECK(app.gi.pending_requests == 1 && !app.gi.timer_active && app.gi.timeouts == 0); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == old.request_id && finish.reason == IEC104_INTERROGATION_FINISH_CANCELLED); CHECK(Example_Wait_Time(&app, 50) == 0 && app.gi.need_process && app.gi.waiting && app.gi.pending_events == 0); gi_process_only(); next = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(next.request.qualifier == 21 && next.request_id != old.request_id); CHECK(app.gi.timer_active && app.gi.timeout_remaining_ms == 10); IEC104_Interrogation_Cancel(app.interrogation); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request_id == next.request_id); gi_empty(); app_close(); custom_config = NULL; } /* BEGIN生成前也可匹配撤销;Slave中已到达的撤销先于业务推进,不交付多余BEGIN。 */ static void test_cancel_before_begin(void) { uint8_t request[sizeof(gi_request)]; IEC104_InterrogationEvent_t event; app_open(); app_request(gi_request, sizeof(gi_request)); memcpy(request, gi_request, sizeof(request)); request[9] = 21; queue_request(request, sizeof(request)); request[9] = 20; request[2] = 0x88; queue_request(request, sizeof(request)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); gi_process_only(); CHECK(app.gi.events_pending && app.gi.pending_events == 0); app_poll(); gi_process(); gi_empty(); CHECK(app.gi.deactivated == 1 && app.gi.negative_submitted == 1 && !app.gi.gi_pending); CHECK(app.gi.pending_requests == 0 && app.gi.gi_submitted == 0); request[9] = 21; request[2] = 0x86; app_request(request, sizeof(request)); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.qualifier == 21); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 0, 0); app_close(); } static void test_event_empty_and_wait(void) { IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); event = gi_data(); for (i = 0; i < 20; ++i) { gi_process(); CHECK(app.gi.application_waiting && app.gi.waiting && app.gi.batch_index == 0 && app.gi.submitted == 1); gi_empty(); /* 已取出的NEED_DATA只交付一次,数据源未就绪不重复请求。 */ } gi_done(&event, 0, 0); CHECK(collect_asdus(frames, lengths) == 2); CHECK(frames[0][2] == 0x87 && frames[1][2] == 0x8a); app_close(); } static void test_event_copy_and_send_wait(void) { IEC104_InterrogationEvent_t event, next; IEC60870_Asdu_t asdu; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249], expected[10]; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); event = gi_data(); single_batch(&asdu, ¶meters, bytes, 1); memcpy(expected, bytes, sizeof(expected)); congest(&last_request.connection); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_WAIT); for (i = 0; i < 3; ++i) { gi_process(); CHECK(app.gi.waiting && app.gi.data_send_waiting && !app.gi.application_waiting && app.gi.batch_index == 0); gi_empty(); } fake.send_blocked = 0; gi_process(); single_batch(&asdu, ¶meters, bytes, 1); fake.send_blocked = 1; /* 成功接手但未写出时仍须复制原文,不能借用应用缓冲。 */ CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); memset(bytes, 0xcc, sizeof(bytes)); memset(¶meters, 0, sizeof(parameters)); memset(&asdu, 0, sizeof(asdu)); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); fake.send_blocked = 0; gi_process(); next = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(next.batch_index == 1 && next.objects_submitted == 1); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); gi_done(&next, 1, 1); CHECK(collect_asdus(frames, lengths) == 3 && lengths[1] == sizeof(expected)); CHECK(memcmp(frames[1], expected, sizeof(expected)) == 0); app_close(); } static void test_event_batch_validation(void) { unsigned mode; for (mode = 0; mode < 12; ++mode) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t asdu; IEC60870_AppLayerParameters_t parameters; IEC104_TcpServerStatus_t status; uint8_t bytes[249]; app_open(); event = gi_data(); single_batch(&asdu, ¶meters, bytes, mode == 6 ? 5 : mode == 7 ? 0 : 1); switch (mode) { case 0: bytes[2] = 0x83; break; /* COT */ case 1: bytes[2] = 0xd4; break; /* P/N */ case 2: bytes[2] = 0x14; break; /* TEST */ case 3: bytes[3] = 0; break; /* OA */ case 4: bytes[4] = 2; break; /* CA */ case 5: bytes[0] = 100; bytes[9] = 20; break; /* 命令类型不作为总召数据 */ case 6: break; /* 超过配置的四点上限 */ case 7: break; /* 空容器 */ case 8: parameters.sizeOfIOA = 2; break; case 9: bytes[1] = 2; break; /* 不完整载荷 */ case 10: asdu.length = 250; break; default: asdu.parameters = NULL; break; } CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); single_batch(&asdu, ¶meters, bytes, 1); /* 同一事件修正后仍可提交。 */ CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_STALE); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); app_close(); } } /* 不同类型、SQ及容量边界通过独立线缆字节断言,不依赖应用示例点表。 */ static void test_event_mixed_types(void) { static const uint8_t data[][14] = { {3, 0x82, 0x94, 0x5a, 1, 0, 0x56, 0x34, 0x12, 0x81, 0x82}, {13, 1, 0x94, 0x5a, 1, 0, 200, 0, 0, 0, 0, 0xc0, 0x3f, 0x10}, {9, 1, 0x94, 0x5a, 1, 0, 0x2c, 1, 0, 0, 0xc0, 0} }; static const uint16_t sizes[] = {11, 14, 12}; IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); event = gi_data(); for (i = 0; i < 3; ++i) { IEC60870_Asdu_t asdu; uint8_t bytes[14]; memcpy(bytes, data[i], sizes[i]); CHECK(IEC60870_Asdu_Create_From_Buffer(&asdu, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizes[i]) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } gi_done(&event, 3, 4); CHECK(collect_asdus(frames, lengths) == 5); for (i = 0; i < 3; ++i) { CHECK(lengths[i + 1] == sizes[i] && memcmp(frames[i + 1], data[i], sizes[i]) == 0); } app_close(); } static void test_event_capacity_batches(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; IEC60870_AppLayerParameters_t parameters; IEC60870_Asdu_t asdu; uint8_t bytes[249]; const uint8_t *frames[32]; size_t lengths[32]; unsigned batch, index, counts[] = {60, 60, 2, 40, 40, 1}; IEC104_Interrogation_Config_Default(&config); custom_config = &config; app_open(); event = gi_data(); parameters = *IEC104_Slave_Get_Application_Layer_Parameters(app.slave); for (batch = 0; batch < 6; ++batch) { IEC60870_AsduHeader_t header = {20, 0, 1, 0x5a, 1}; CHECK(IEC60870_Asdu_Create(&asdu, ¶meters, bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK); for (index = 0; index < counts[batch]; ++index) { if (batch < 3) { IEC60870_SinglePointInformation_t point; CHECK(IEC60870_SinglePointInformation_Create(&point, 1001 + (batch * 60) + index, 1, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &point.object) == IEC60870_ASDU_OK); } else { IEC60870_MeasuredValueScaled_t point; CHECK(IEC60870_MeasuredValueScaled_Create(&point, 2001 + ((batch - 3) * 40) + index, -125, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_Asdu_Add_InformationObject(&asdu, &point.object) == IEC60870_ASDU_OK); } } CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); /* 大批次可能填满实际HIGH,循环Poll后再请求下一批。 */ do { gi_process(); } while (app.gi.batch_index == batch); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } gi_done(&event, 6, 203); CHECK(collect_asdus(frames, lengths) == 8); for (batch = 0; batch < 6; ++batch) { CHECK(frames[batch + 1][1] == counts[batch] && lengths[batch + 1] == 6 + counts[batch] * (batch < 3 ? 4 : 6)); } app_close(); custom_config = NULL; } static void test_event_reject_and_fail(void) { unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t event, finish; IEC104_TcpServerStatus_t service; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = mode == 2 ? gi_data() : gi_begin(); if (mode == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, false) == IEC104_INTERROGATION_REPLY_OK); } else { CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == (mode == 0 ? IEC104_INTERROGATION_FINISH_REJECTED : IEC104_INTERROGATION_FINISH_SOURCE_ERROR)); CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); gi_process(); app_poll(); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK); if (mode < 2) { CHECK(service.connected && collect_asdus(frames, lengths) == 1 && frames[0][2] == 0xc7); } else { CHECK(!service.connected && app.gi.gi_submitted == 0); CHECK(collect_asdus(frames, lengths) == (mode == 2 ? 1U : 0U)); } gi_empty(); app_close(); } } static void test_event_cancellation(void) { static const IEC104_InterrogationFinishReason_t reasons[] = { IEC104_INTERROGATION_FINISH_CANCELLED, IEC104_INTERROGATION_FINISH_STOPPED, IEC104_INTERROGATION_FINISH_DISCONNECTED }; unsigned mode; for (mode = 0; mode < sizeof(reasons) / sizeof(reasons[0]); ++mode) { IEC104_InterrogationEvent_t event, finish; app_open(); event = gi_data(); if (mode == 0) { IEC104_Interrogation_Cancel(app.interrogation); } if (mode == 1) { push(stop_act, sizeof(stop_act)); app_poll(); } if (mode == 2) { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); app_poll(); } CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); CHECK(!IEC104_Interrogation_Destroy(app.interrogation)); /* 终结事件尚未读取。 */ IEC104_Interrogation_Cancel(app.interrogation); /* 重复取消不覆盖已排队的终结原因。 */ finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == reasons[mode] && finish.request_id == event.request_id); gi_empty(); app_close(); } } /* 最小两槽FIFO:旧FINISHED未读时暂停新BEGIN,为将来的终结保留容量。 */ static void test_event_queue_and_destroy(void) { IEC104_InterrogationEvent_t first, second, finish; app_open(); first = gi_begin(); CHECK(!IEC104_Interrogation_Destroy(app.interrogation)); /* 正在等待应用决定。 */ IEC104_Interrogation_Cancel(app.interrogation); app_request(gi_request, sizeof(gi_request)); gi_process(); CHECK(app.gi.pending_events == 1 && app.gi.waiting && !app.gi.application_waiting); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == first.request_id); gi_process(); second = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(second.request_id != first.request_id); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &first, true) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &second, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); /* NEED_DATA已入队,取消必须移除它,只保留FINISHED。 */ IEC104_Interrogation_Cancel(app.interrogation); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == second.request_id); gi_empty(); app_request(gi_request, sizeof(gi_request)); gi_process(); /* BEGIN未取出便取消,不通知未建立资源的应用。 */ IEC104_Interrogation_Cancel(app.interrogation); gi_empty(); app_close(); } static void test_event_pending_stop_start(void) { IEC104_InterrogationEvent_t event, finish, next, changed; uint64_t generation; app_open(); event = gi_data(); generation = event.request.generation; push(stop_act, sizeof(stop_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); push(start_act, sizeof(start_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_WAIT); CHECK(IEC104_Interrogation_Process(app.interrogation)); app_poll(); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_STOPPED); next = gi_begin(); CHECK(next.request.generation == generation && next.request_id != event.request_id); changed = next; ++changed.instance_id; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &changed, true) == IEC104_INTERROGATION_REPLY_STALE); changed = next; ++changed.batch_index; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &changed, true) == IEC104_INTERROGATION_REPLY_STALE); changed = next; changed.type = IEC104_INTERROGATION_FINISHED; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &changed, true) == IEC104_INTERROGATION_REPLY_STALE); app_close(); } static void test_event_instance_reuse(void) { IEC104_InterrogationEvent_t old, current; IEC104_Interrogation_t *slot; app_open(); old = gi_begin(); slot = app.interrogation; app_close(); app_open(); current = gi_begin(); CHECK(app.interrogation == slot && current.request_id == old.request_id && current.instance_id != old.instance_id); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &old, true) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &old) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, ¤t, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); current = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(¤t, 0, 0); app_close(); } /* 总召入口通过Slave统一拒绝错误副本/身份,不能改变已交付BEGIN的任务。 */ static void test_slave_event_validation(void) { IEC104_InterrogationEvent_t begin, data; IEC104_InterrogationStatus_t before, after; IEC104_SlaveEvent_t invalid; unsigned mode; app_open(); begin = gi_begin(); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &before)); invalid = last_request; CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &invalid) == IEC104_INTERROGATION_EVENT_ERROR); CHECK(IEC104_Slave_Copy_Event(&invalid, &last_request)); ++invalid.transport.generation; CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &invalid) == IEC104_INTERROGATION_EVENT_ERROR); /* 仅注入公共视图失败;正常线上截短报文在Receiver中被挡住,不会产生这种I帧事件。 */ for (mode = 0; mode < 4; ++mode) { CHECK(IEC104_Slave_Copy_Event(&invalid, &last_request)); invalid.asdu_result = IEC60870_ASDU_ERROR; invalid.transport.data.i_frame.apdu[6] = mode == 0 ? 101 : 100; if (mode == 2) { invalid.transport.data.i_frame.apdu_length = 6; } if (mode == 3) { invalid.transport.data.i_frame.apdu_length = 7; } CHECK(IEC104_Interrogation_Handle_Event(app.interrogation, &invalid) == IEC104_INTERROGATION_EVENT_IGNORED); CHECK(Example_Handle_Event(&app, &invalid) && app.other_submitted == 0 && app.other_rejected == 0); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &after)); CHECK(after.pending_requests == before.pending_requests && after.gi_pending && after.phase == before.phase && after.cancelled == before.cancelled && after.rejected == before.rejected); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &begin, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); data = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&data, 0, 0); app_close(); } /* 所有16组都由应用通过同一事件API接受;用独立请求/输出字节核对QOI和数据COT。 */ static void test_group_interrogation(void) { unsigned qoi; for (qoi = 20; qoi <= 36; ++qoi) { uint8_t request[sizeof(gi_request)], bytes[249]; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; const uint8_t *frames[32]; size_t lengths[32]; app_open(); memcpy(request, gi_request, sizeof(request)); request[9] = (uint8_t)qoi; app_request(request, sizeof(request)); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.qualifier == qoi); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); single_batch(&batch, ¶meters, bytes, 1); if (qoi != 20) { CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_ERROR); } CHECK(IEC60870_Asdu_Set_COT(&batch, (uint8_t)qoi) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1, 1); CHECK(collect_asdus(frames, lengths) == 3); request[2] = 0x87; CHECK(lengths[0] == 10 && memcmp(frames[0], request, 10) == 0); CHECK(lengths[1] == 10 && frames[1][0] == 1 && frames[1][2] == (0x80 | qoi)); CHECK(frames[1][3] == 0x5a && frames[1][4] == 1 && frames[1][6] == 0xe9); request[2] = 0x8a; CHECK(lengths[2] == 10 && memcmp(frames[2], request, 10) == 0); app_close(); } } /* 点表属于应用:示例仅配置组1遥信和组2遥测,其余合法组交给应用否定确认。 */ static void test_example_groups(void) { static const uint8_t groups[] = {21, 22, 23, 36}; unsigned i, f; for (i = 0; i < sizeof(groups); ++i) { uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; unsigned count; app_open(); memcpy(request, gi_request, sizeof(request)); request[9] = groups[i]; app_request(request, sizeof(request)); app_finish(); count = collect_asdus(frames, lengths); CHECK(count == (groups[i] == 21 ? 6U : groups[i] == 22 ? 4U : 1U)); if (count == 1) { CHECK(frames[0][2] == 0xc7 && app.gi.gi_submitted == 0); } else { CHECK(frames[0][2] == 0x87 && frames[count - 1U][2] == 0x8a); for (f = 1; f < count - 1U; ++f) { const uint8_t *a = frames[f]; unsigned is_double = groups[i] == 21 && f >= 3U; unsigned first = groups[i] == 22 ? 2001U : is_double ? 1101U : 1001U; unsigned batch = is_double ? f - 3U : f - 1U; CHECK(a[0] == (groups[i] == 22 ? 11 : is_double ? 3 : 1) && a[1] == 4 && a[2] == (0x80 | groups[i])); CHECK((unsigned)(a[6] | (unsigned)a[7] << 8 | (unsigned)a[8] << 16) == first + batch * 4U); if (is_double) { unsigned point; CHECK(lengths[f] == 22U); for (point = 0U; point < 4U; ++point) { CHECK(a[9U + point * 4U] == 1U); } } } } CHECK(frames[0][9] == groups[i] && app.session_request == 0); app_close(); } } /* 站总召和遥信分组均包含双点实时值:首批双点遭发送WAIT时保持游标, * 下一次尝试重新读取变化后的double_values;最终每个双点恰好交接一次。 */ static void test_example_double_wait_values(void) { unsigned qualifier; for (qualifier = 20U; qualifier <= 21U; ++qualifier) { uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; unsigned step, point; app_open(); memcpy(request, gi_request, sizeof(request)); request[9] = (uint8_t)qualifier; app_request(request, sizeof(request)); for (step = 0U; step < 32U; ++step) { CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); if (app.gi.phase == IEC104_INTERROGATION_DATA && app.gi.batch_index == 2U) { break; } CHECK(Example_Drain_Business(&app)); } CHECK(step < 32U && app.gi_data.single_index == 8U && app.gi_data.double_index == 0U); congest(&last_request.connection); CHECK(Example_Step(&app) && app.business_pending && app.gi.waiting); CHECK(app.gi_data.double_index == 0U && app.gi.batch_index == 2U && app.batch_bytes[9] == 1U); app.double_values[0] = app.double_values[3] = app.double_values[7] = 2U; CHECK(Example_Step(&app) && app.business_pending && app.gi.waiting); CHECK(app.gi_data.double_index == 0U && app.gi.batch_index == 2U && app.batch_bytes[9] == 2U); fake.send_blocked = 0; app_finish(); CHECK(collect_asdus(frames, lengths) == (qualifier == 20U ? 8U : 6U)); for (point = 0U; point < 8U; ++point) { unsigned batch = 3U + point / 4U; const uint8_t *a = frames[batch]; const uint8_t *object = a + 6U + (point % 4U) * 4U; CHECK(lengths[batch] == 22U && a[0] == 3U && a[1] == 4U && a[2] == (0x80U | qualifier)); CHECK((unsigned)(object[0] | (unsigned)object[1] << 8 | (unsigned)object[2] << 16) == 1101U + point); CHECK(object[3] == (point == 0U || point == 3U || point == 7U ? 2U : 1U)); } CHECK(app.gi.gi_submitted == 1U && app.session_request == 0U && app.gi_station == NULL); app_close(); } } /* 撤销覆盖所有阶段,包含未读事件、已取事件、HIGH满、已提交部分数据和待发终止。 * 已提交帧保留顺序;尚未提交批次及ACT_TERM不能越过肯定撤销确认。 */ static void test_remote_deactivation_phases(void) { unsigned phase; for (phase = 0; phase <= 8; ++phase) { uint8_t request[sizeof(gi_request)], bytes[249]; IEC104_InterrogationEvent_t event = {0}, finish; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; const uint8_t *frames[32]; size_t lengths[32]; unsigned prior = phase >= 4 ? 1U : 0U; app_open(); app_request(gi_request, sizeof(gi_request)); if (phase >= 1) { gi_process(); } if (phase >= 2) { event = gi_take(IEC104_INTERROGATION_BEGIN); } if (phase >= 3) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } if (phase >= 4) { gi_process(); } if (phase >= 5) { event = gi_take(IEC104_INTERROGATION_NEED_DATA); } if (phase == 6 || phase == 8) { single_batch(&batch, ¶meters, bytes, 1); if (phase == 6) { congest(&last_request.connection); } CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == (phase == 6 ? IEC104_INTERROGATION_REPLY_WAIT : IEC104_INTERROGATION_REPLY_OK)); } if (phase == 7) { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } if (phase == 8) { gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); ++prior; } if (phase == 3 || phase == 6 || phase == 7) { if (phase != 6) { congest(&last_request.connection); } gi_process(); CHECK(app.gi.waiting); } memcpy(request, gi_request, sizeof(request)); request[2] = 0x88; app_request(request, sizeof(request)); CHECK(!app.gi.gi_pending && app.gi.active && app.gi.deactivated == 1 && app.gi.pending_requests == (fake.send_blocked ? 1U : 0U)); if (phase >= 2) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, NULL) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_DEACTIVATED && finish.request_id == event.request_id); CHECK(finish.batch_index == (phase == 8 ? 1U : 0U)); } gi_empty(); if (fake.send_blocked) { gi_process(); CHECK(app.gi.waiting && app.gi.phase == IEC104_INTERROGATION_REPLY); fake.send_blocked = 0; } app_finish(); CHECK(collect_asdus(frames, lengths) == prior + 1 && app.gi.gi_submitted == 0); request[2] = 0x89; CHECK(lengths[prior] == 10 && memcmp(frames[prior], request, 10) == 0); app_request(gi_request, sizeof(gi_request)); app_finish(); CHECK(app.gi.gi_submitted == 1 && collect_asdus(frames, lengths) == prior + 9); app_close(); } } static void test_remote_deactivation_validation(void) { static const struct { unsigned offset; uint8_t value, cot; } cases[] = { {9, 21, 9}, {3, 0x6b, 9}, {2, 8, 9}, {9, 19, 9}, {9, 37, 9}, {2, 0xc8, 9}, {4, 2, 46}, {6, 1, 47}, {2, 0x83, 45} }; unsigned i; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { IEC104_InterrogationEvent_t event; uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); event = gi_data(); memcpy(request, gi_request, sizeof(request)); request[2] = 0x88; request[cases[i].offset] = cases[i].value; app_request(request, sizeof(request)); CHECK(app.gi.gi_pending && app.gi.deactivated == 0); gi_done(&event, 0, 0); /* 不匹配撤销不终结当前请求,原任务仍能正常完成。 */ CHECK(collect_asdus(frames, lengths) == 3); request[2] = (request[2] & 0x80) | 0x40 | cases[i].cot; CHECK(lengths[1] == 10 && memcmp(frames[1], request, 10) == 0 && frames[2][2] == 0x8a); memcpy(request, gi_request, sizeof(request)); request[2] = 0x88; app_request(request, sizeof(request)); app_finish(); /* 已完成,无可撤销任务。 */ CHECK(collect_asdus(frames, lengths) == 4 && frames[3][2] == 0xc9); app_close(); } } /* 满队列中撤销非队首的分组总召,不能因缺少额外槽而断线;重复撤销回否定COT=9。 */ static void test_remote_deactivation_full_queue(void) { uint8_t invalid[sizeof(gi_request)], request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; app_open(); memcpy(invalid, gi_request, sizeof(invalid)); invalid[9] = 19; memcpy(request, gi_request, sizeof(request)); request[9] = 36; queue_request(invalid, sizeof(invalid)); queue_request(request, sizeof(request)); queue_request(invalid, sizeof(invalid)); queue_request(invalid, sizeof(invalid)); app_poll(); CHECK(app.gi.pending_requests == 4 && app.gi.gi_pending); request[2] = 0x88; app_request(request, sizeof(request)); CHECK(app.gi.active && app.gi.deactivated == 1 && app.gi.overloads == 0 && app.gi.pending_requests == 4); app_finish(); gi_empty(); CHECK(collect_asdus(frames, lengths) == 4); CHECK(frames[0][2] == 0xc7 && frames[1][2] == 0xc7 && frames[2][2] == 0xc7 && frames[3][2] == 0x89 && frames[3][9] == 36); app_request(request, sizeof(request)); app_finish(); CHECK(collect_asdus(frames, lengths) == 5 && frames[4][2] == 0xc9); app_close(); } static void check_timeout(IEC104_InterrogationFinishReason_t reason, unsigned delivered, uint64_t id, bool keep_connection) { IEC104_TcpServerStatus_t service; CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.timeouts == 1 && app.gi.last_finish_reason == reason && !app.gi.gi_pending && !app.gi.timer_active); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected == keep_connection); if (delivered) { IEC104_InterrogationEvent_t finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == reason && finish.request_id == id); } gi_process_only(); gi_empty(); /* 重复Process不再产生第二次FINISHED;独立验证业务时钟,不推进链路期限。 */ CHECK(app.gi.timeouts == 1 && app.gi.gi_submitted == 0); } static void test_begin_timeout(void) { IEC104_InterrogationConfig_t config; unsigned mode; IEC104_Interrogation_Config_Default(&config); CHECK(config.begin_timeout_ms == 15000 && config.data_timeout_ms == 15000 && config.total_timeout_ms == 0); config.begin_timeout_ms = 100; config.total_timeout_ms = 1000; custom_config = &config; for (mode = 0; mode < 5; ++mode) { IEC104_InterrogationEvent_t event = {0}; app_open(); app_request(gi_request, sizeof(gi_request)); gi_process(); if (mode != 0) { event = gi_take(IEC104_INTERROGATION_BEGIN); } fake.now += 99; gi_process(); CHECK(app.gi.timer_active && app.gi.timeout_remaining_ms == 1 && app.gi.timeouts == 0); CHECK(Example_Wait_Time(&app, 50) == (mode == 0 ? 0U : 1U)); ++fake.now; CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi) && app.gi.timeout_remaining_ms == 0 && app.gi.timeouts == 0); CHECK(app.gi.need_process && Example_Wait_Time(&app, 50) == 0); if (mode == 1 || mode == 4) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, mode == 1) == IEC104_INTERROGATION_REPLY_STALE); } else if (mode == 2) { CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); } else { CHECK(IEC104_Interrogation_Process(app.interrogation)); } check_timeout(IEC104_INTERROGATION_FINISH_BEGIN_TIMEOUT, mode != 0, event.request_id, true); { const uint8_t *frames[32]; size_t lengths[32]; uint8_t expected[sizeof(gi_request)]; memcpy(expected, gi_request, sizeof(expected)); expected[2] = 0xc7; app_poll(); CHECK(collect_asdus(frames, lengths) == 1 && lengths[0] == sizeof(expected)); CHECK(memcmp(frames[0], expected, sizeof(expected)) == 0); CHECK(app.gi.negative_submitted == 1 && app.gi.rejected == 1 && app.gi.cancelled == 0); } app_close(); } custom_config = NULL; } static void test_data_timeout(void) { IEC104_InterrogationConfig_t config; unsigned mode; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = 50; config.data_timeout_ms = 100; config.total_timeout_ms = 1000; custom_config = &config; for (mode = 0; mode < 5; ++mode) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; app_open(); if (mode == 4) { /* NEED_DATA已排队但应用从未取出。 */ event = gi_begin(); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); } else { event = gi_data(); } single_batch(&batch, ¶meters, bytes, 1); fake.now += 99; gi_process(); CHECK(app.gi.timeout_remaining_ms == 1 && app.gi.timeouts == 0); ++fake.now; if (mode == 0) { CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_STALE); } else if (mode == 1) { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); } else if (mode == 2) { CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); } else { CHECK(IEC104_Interrogation_Process(app.interrogation)); } check_timeout(IEC104_INTERROGATION_FINISH_DATA_TIMEOUT, 1, event.request_id, false); app_close(); } custom_config = NULL; } /* 新一批重启取数期限,但不会延长整个请求的总期限。 */ static void test_timeout_progress(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = config.data_timeout_ms = 100; config.total_timeout_ms = 250; custom_config = &config; app_open(); event = gi_begin(); fake.now += 99; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); fake.now += 99; single_batch(&batch, ¶meters, bytes, 1); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); fake.now += 49; gi_process(); CHECK(app.gi.timeouts == 0 && app.gi.timeout_remaining_ms == 3 && app.gi.objects_submitted == 1); fake.now += 3; CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); check_timeout(IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT, 1, event.request_id, false); app_close(); custom_config = NULL; } /* BEGIN前等业务FIFO空位不计期限;BEGIN生成后,确认/数据/终止的发送等待均受总期限约束。 */ static void test_total_timeout_waits(void) { IEC104_InterrogationConfig_t config; unsigned mode; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = config.data_timeout_ms = 0; config.total_timeout_ms = 100; custom_config = &config; for (mode = 0; mode < 5; ++mode) { IEC104_InterrogationEvent_t event = {0}; uint8_t invalid[sizeof(gi_request)], bytes[249]; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; app_open(); if (mode == 0) { memcpy(invalid, gi_request, sizeof(invalid)); invalid[9] = 19; app_request(invalid, sizeof(invalid)); app_request(gi_request, sizeof(gi_request)); } else if (mode == 1) { event = gi_begin(); IEC104_Interrogation_Cancel(app.interrogation); app_request(gi_request, sizeof(gi_request)); } else if (mode == 2) { event = gi_begin(); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } else { event = gi_data(); if (mode == 3) { single_batch(&batch, ¶meters, bytes, 1); congest(&last_request.connection); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); } else { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } } if (mode != 1 && mode != 3) { congest(&last_request.connection); } gi_process(); CHECK(app.gi.waiting); CHECK(mode == 1 ? !app.gi.timer_active : (app.gi.timer_active && app.gi.timeout_remaining_ms == 100)); fake.now += 100; CHECK(IEC104_Interrogation_Process(app.interrogation)); if (mode == 1) { IEC104_InterrogationEvent_t old = gi_take(IEC104_INTERROGATION_FINISHED); IEC104_TcpServerStatus_t status; CHECK(old.reason == IEC104_INTERROGATION_FINISH_CANCELLED && old.request_id == event.request_id); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.pending_requests == 1 && app.gi.timeouts == 0 && app.gi.negative_submitted == 0); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); gi_empty(); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(app.gi.timer_active && app.gi.timeout_remaining_ms == 100); IEC104_Interrogation_Cancel(app.interrogation); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request_id == event.request_id); } else { check_timeout(IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT, mode >= 2, event.request_id, mode < 3); } app_close(); } custom_config = NULL; } static void test_timeout_disabled_and_large(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = config.data_timeout_ms = config.total_timeout_ms = 0; custom_config = &config; app_open(); event = gi_begin(); fake.now += 10000; gi_process(); CHECK(!app.gi.timer_active && app.gi.timeouts == 0 && Example_Wait_Time(&app, 50) == 50); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 0, 0); app_close(); config.total_timeout_ms = UINT32_MAX; app_open(); event = gi_begin(); fake.now += (uint64_t)UINT32_MAX - 1U; /* 只推进业务时钟,不运行链路定时器,用于独立验证32位最大配置与64位时钟差。 */ CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi) && app.gi.timeout_remaining_ms == 1); ++fake.now; CHECK(IEC104_Interrogation_Process(app.interrogation)); check_timeout(IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT, 1, event.request_id, true); app_close(); custom_config = NULL; } /* 到期时先消费已经排队的传输通知,STOP/主站撤销优先,应用应答先得到WAIT再得到STALE。 */ static void test_timeout_transport_priority(void) { IEC104_InterrogationConfig_t config; unsigned mode; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = config.data_timeout_ms = config.total_timeout_ms = 100; custom_config = &config; for (mode = 0; mode < 2; ++mode) { IEC104_InterrogationEvent_t event, finish; uint8_t request[sizeof(gi_request)]; app_open(); event = gi_data(); fake.now += 100; if (mode == 0) { push(stop_act, sizeof(stop_act)); } else { memcpy(request, gi_request, sizeof(request)); request[2] = 0x88; queue_request(request, sizeof(request)); } CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_Interrogation_Process(app.interrogation)); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_WAIT); app_poll(); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == (mode == 0 ? IEC104_INTERROGATION_FINISH_STOPPED : IEC104_INTERROGATION_FINISH_DEACTIVATED)); gi_process(); gi_empty(); CHECK(app.gi.timeouts == 0); app_close(); } custom_config = NULL; } /* 五种确认前拒绝共用原任务槽;真实HIGH和任务池同时满时,旧REPLY保持顺序,恢复后可重新总召。 */ static void test_preconfirm_failure_reply_order(void) { static const IEC104_InterrogationFinishReason_t reasons[] = { IEC104_INTERROGATION_FINISH_REJECTED, IEC104_INTERROGATION_FINISH_SOURCE_ERROR, IEC104_INTERROGATION_FINISH_BEGIN_TIMEOUT, IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT, IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT }; unsigned mode; for (mode = 0; mode < 10; ++mode) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t begin, finish, next; IEC104_TcpServerStatus_t service; const uint8_t *frames[32]; size_t lengths[32]; uint8_t request[sizeof(gi_request)], expected[sizeof(gi_request)]; unsigned i, scenario = mode % 5; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = scenario == 2 ? 50 : 0; config.total_timeout_ms = 100; custom_config = &config; app_open(); memcpy(request, gi_request, sizeof(request)); request[2] = 6; request[3] = 0x71; request[9] = 36; if (mode >= 5) { request[4] = request[5] = 0xff; } app_request(request, sizeof(request)); gi_process(); begin = gi_take(IEC104_INTERROGATION_BEGIN); congest(&last_request.connection); if (scenario == 4) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &begin, true) == IEC104_INTERROGATION_REPLY_OK); gi_process_only(); CHECK(app.gi.phase == IEC104_INTERROGATION_CONFIRM && app.gi.waiting && app.gi.submitted == 0); } for (i = 0; i < 3; ++i) { request_busy((uint8_t)(0x60U + i)); } CHECK(app.gi.pending_requests == 4 && app.gi.gi_pending && app.gi.negative_submitted == 0); if (scenario == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &begin, false) == IEC104_INTERROGATION_REPLY_OK); } else if (scenario == 1) { CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &begin) == IEC104_INTERROGATION_REPLY_OK); } else { fake.now += scenario == 2 ? 50U : 100U; } gi_process_only(); CHECK(app.gi.pending_requests == 4 && !app.gi.gi_pending && app.gi.phase == IEC104_INTERROGATION_REPLY); CHECK(app.gi.active && app.gi.waiting && !app.gi.timer_active && !app.gi.application_waiting); CHECK(app.gi.rejected == 4 && app.gi.overloads == 0 && app.gi.dropped_requests == 0 && app.gi.cancelled == 0); CHECK(app.gi.timeouts == (scenario >= 2 ? 1U : 0U) && app.gi.last_finish_reason == reasons[scenario]); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == reasons[scenario] && finish.instance_id == begin.instance_id && finish.request_id == begin.request_id); CHECK(finish.request.common_address == 1 && finish.request.qualifier == 36 && finish.request.originator_address == 0x71 && !finish.request.is_test); CHECK(finish.batch_index == 0 && finish.objects_submitted == 0); CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &begin) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &begin, true) == IEC104_INTERROGATION_REPLY_STALE); fake.now += 101; gi_process_only(); gi_empty(); CHECK(app.gi.timeouts == (scenario >= 2 ? 1U : 0U) && app.gi.pending_requests == 4); fake.send_blocked = 0; gi_process(); app_poll(); CHECK(collect_asdus(frames, lengths) == 4); for (i = 0; i < 3; ++i) { memcpy(expected, gi_request, sizeof(expected)); expected[2] = 0xc7; expected[3] = (uint8_t)(0x60U + i); CHECK(lengths[i] == sizeof(expected) && memcmp(frames[i], expected, sizeof(expected)) == 0); } memcpy(expected, request, sizeof(expected)); expected[2] = 0x47; expected[4] = 1; expected[5] = 0; CHECK(lengths[3] == sizeof(expected) && memcmp(frames[3], expected, sizeof(expected)) == 0); CHECK(app.gi.pending_requests == 0 && app.gi.negative_submitted == 4 && app.gi.gi_submitted == 0); app_request(request, sizeof(request)); gi_process(); next = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(next.request_id != begin.request_id && next.request.generation == begin.request.generation); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &next, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); next = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&next, 0, 0); CHECK(collect_asdus(frames, lengths) == 6 && frames[4][2] == 7 && frames[5][2] == 10); CHECK(app.gi.gi_submitted == 1 && app.gi.negative_submitted == 4); app_close(); custom_config = NULL; } } /* BEGIN失败和超时不得跳过Slave中未读的STOP、撤销或断线,原终结原因和单次FINISHED保持不变。 */ static void test_preconfirm_failure_transport_priority(void) { static const IEC104_InterrogationFinishReason_t reasons[] = { IEC104_INTERROGATION_FINISH_STOPPED, IEC104_INTERROGATION_FINISH_DEACTIVATED, IEC104_INTERROGATION_FINISH_DISCONNECTED }; IEC104_InterrogationConfig_t config; unsigned mode; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = config.total_timeout_ms = 50; custom_config = &config; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationEvent_t begin, finish; uint8_t request[sizeof(gi_request)]; app_open(); begin = gi_begin(); fake.now += 50; if (mode == 0) { push(stop_act, sizeof(stop_act)); } else if (mode == 1) { memcpy(request, gi_request, sizeof(request)); request[2] = 0x88; queue_request(request, sizeof(request)); } else { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); } CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); gi_process_only(); CHECK(app.gi.events_pending && app.gi.timeouts == 0); CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &begin) == IEC104_INTERROGATION_REPLY_WAIT); app_poll(); CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &begin) == IEC104_INTERROGATION_REPLY_STALE); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == reasons[mode] && finish.request_id == begin.request_id); gi_process(); gi_empty(); CHECK(app.gi.timeouts == 0 && app.gi.negative_submitted == 0 && app.gi.rejected == 0); app_close(); } custom_config = NULL; } /* 肯定ACT_CON已被发送层接手但TCP尚未写出时,已经进入确认后阶段,失败不能改发否定ACT_CON。 */ static void test_confirm_submission_boundary(void) { static const IEC104_InterrogationFinishReason_t reasons[] = { IEC104_INTERROGATION_FINISH_SOURCE_ERROR, IEC104_INTERROGATION_FINISH_DATA_TIMEOUT, IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT }; unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event, finish; IEC104_TcpServerStatus_t service; const uint8_t *frames[32]; size_t lengths[32]; IEC104_Interrogation_Config_Default(&config); config.data_timeout_ms = mode == 1 ? 50 : 100; config.total_timeout_ms = mode == 2 ? 50 : 200; custom_config = &config; app_open(); event = gi_begin(); fake.send_blocked = 1; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(app.gi.phase == IEC104_INTERROGATION_DATA && app.gi.submitted == 1); CHECK(collect_asdus(frames, lengths) == 0); if (mode == 0) { CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } else { fake.now += 50; gi_process_only(); } CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && !service.connected); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == reasons[mode] && finish.request_id == event.request_id); CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); gi_process_only(); gi_empty(); CHECK(app.gi.negative_submitted == 0 && app.gi.rejected == 0 && app.gi.pending_requests == 0); CHECK(app.gi.timeouts == (mode == 0 ? 0U : 1U) && app.gi.gi_submitted == 0); CHECK(collect_asdus(frames, lengths) == 0); app_close(); custom_config = NULL; } } /* 直接检查传给业务层的输入事件:立即应答、排队、撤销和丢弃都不能改写原请求字节。 */ static void request_unchanged(const uint8_t *request, size_t size) { IEC104_SlaveEvent_t event, copy; IEC104_TcpEventResult_t result; unsigned received = 0; queue_request(request, size); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); while ((result = IEC104_Slave_NextEvent(app.slave, &event)) == IEC104_TCP_EVENT_READY) { CHECK(Example_Handle_Event(&app, &event)); if (event.transport.type == IEC104_TCP_EVENT_I_FRAME) { const uint8_t *data; uint16_t length; CHECK(IEC60870_Asdu_Get_Data(&event.asdu, &data, &length) == IEC60870_ASDU_OK); CHECK(length == size && memcmp(data, request, size) == 0); CHECK(IEC104_Slave_Copy_Event(©, &event) && IEC104_Slave_Copy_Event(&last_request, ©)); ++received; } } CHECK(result == IEC104_TCP_EVENT_EMPTY && received == 1); } /* BEGIN/NEED_DATA已取出时,错误激活/撤销不取消、刷新期限或使原业务事件失效。 */ static void test_malformed_preserves_active(void) { unsigned stage, deactivation, shape, broadcast; for (stage = 0; stage < 2; ++stage) { for (deactivation = 0; deactivation < 2; ++deactivation) { for (shape = 0; shape < 4; ++shape) { for (broadcast = 0; broadcast < 2; ++broadcast) { IEC104_InterrogationEvent_t event; IEC104_InterrogationStatus_t before; IEC104_TcpServerStatus_t service; uint8_t request[14] = {0}; const uint8_t *frames[32]; size_t lengths[32], length = shape == 0 ? 11U : shape == 3 ? 14U : 10U; app_open(); event = stage == 0 ? gi_begin() : gi_data(); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &before) && before.timer_active); fake.now += 7U; memcpy(request, gi_request, sizeof(gi_request)); request[1] = shape == 1 ? 0x81 : shape == 2 ? 0 : shape == 3 ? 2 : 1; request[2] = deactivation ? 0x88 : 0x86; if (broadcast) { request[4] = request[5] = 0xff; } request_unchanged(request, length); gi_process_only(); gi_empty(); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); CHECK(app.gi.phase == before.phase && app.gi.pending_requests == 1 && app.gi.gi_pending); CHECK(app.gi.cancelled == 0 && app.gi.deactivated == 0 && app.gi.rejected == 1); CHECK(app.gi.timer_active && app.gi.timeout_remaining_ms + 7U == before.timeout_remaining_ms); CHECK(app.gi.negative_submitted == (shape < 2 ? 1U : 0U)); if (stage == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } gi_done(&event, 0, 0); CHECK(app.gi.gi_submitted == 1 && collect_asdus(frames, lengths) == (shape < 2 ? 3U : 2U)); if (shape < 2) { unsigned index = stage == 0 ? 0U : 1U; CHECK(lengths[index] == 10 && frames[index][1] == 1); CHECK(frames[index][2] == (deactivation ? 0xc9 : 0xc7) && frames[index][4] == 1); } app_close(); } } } } } /* 畸形单对象否定沿原HIGH/FIFO背压路径,满槽只丢新请求,原BEGIN仍能完成。 */ static void test_malformed_backpressure(void) { IEC104_InterrogationEvent_t event; IEC104_TcpServerStatus_t service; uint8_t request[11] = {0}; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); event = gi_begin(); congest(&last_request.connection); memcpy(request, gi_request, sizeof(gi_request)); for (i = 0; i < 4; ++i) { request[3] = (uint8_t)(0x60U + i); request[2] = i & 1U ? 0x88 : 0x86; request_unchanged(request, sizeof(request)); } CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); CHECK(app.gi.pending_requests == 4 && app.gi.dropped_requests == 1 && app.gi.overloads == 1); CHECK(app.gi.rejected == 4 && app.gi.negative_submitted == 0 && app.gi.cancelled == 0); gi_empty(); fake.send_blocked = 0; for (i = 0; i < 8; ++i) { gi_process(); } CHECK(app.gi.pending_requests == 1 && app.gi.negative_submitted == 3 && app.gi.waits != 0); CHECK(collect_asdus(frames, lengths) == 3); for (i = 0; i < 3; ++i) { CHECK(lengths[i] == 10 && frames[i][1] == 1 && frames[i][3] == 0x60U + i); CHECK(frames[i][2] == (i & 1U ? 0xc9 : 0xc7)); } CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 0, 0); CHECK(app.gi.gi_submitted == 1 && app.gi.cancelled == 0); app_close(); } /* 广播全站及全部16组,非默认本站地址;确认/数据/终止WAIT及批次CA校验。 */ static void test_broadcast_interrogation(void) { IEC104_InterrogationConfig_t config; unsigned qoi; IEC104_Interrogation_Config_Default(&config); config.common_address = 0x1234; custom_config = &config; for (qoi = 20; qoi <= 36; ++qoi) { IEC104_InterrogationEvent_t event, finish; IEC60870_Asdu_t batch; IEC60870_AsduHeader_t header = {(uint8_t)qoi, 0, (uint8_t)(qoi & 1U), 0x67, UINT16_MAX}; IEC60870_SinglePointInformation_t point; const uint8_t *frames[32]; size_t lengths[32]; uint8_t request[sizeof(gi_request)], expected[sizeof(gi_request)], bytes[249]; bool wait = qoi == 20 || qoi == 36; app_open(); memcpy(request, gi_request, sizeof(request)); request[2] = (uint8_t)(6U | ((qoi & 1U) << 7)); request[3] = 0x67; request[4] = request[5] = 0xff; request[9] = (uint8_t)qoi; request_unchanged(request, sizeof(request)); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 0x1234 && event.request.qualifier == qoi); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); if (wait) { congest(&last_request.connection); gi_process_only(); CHECK(app.gi.phase == IEC104_INTERROGATION_CONFIRM && app.gi.waiting); fake.send_blocked = 0; } gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request.common_address == 0x1234); CHECK(IEC60870_Asdu_Create(&batch, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes), &header, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_SinglePointInformation_Create(&point, 0x10203, 1, 0) == IEC60870_ASDU_OK); CHECK(IEC60870_Asdu_Add_InformationObject(&batch, &point.object) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_ERROR); CHECK(IEC60870_Asdu_Set_CA(&batch, 1) == IEC60870_ASDU_OK); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_ERROR); CHECK(IEC60870_Asdu_Set_CA(&batch, 0x1234) == IEC60870_ASDU_OK); if (wait) { congest(&last_request.connection); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); gi_process_only(); CHECK(app.gi.phase == IEC104_INTERROGATION_DATA && app.gi.waiting && app.gi.batch_index == 0); fake.send_blocked = 0; gi_process(); } CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); if (wait) { congest(&last_request.connection); gi_process_only(); CHECK(app.gi.phase == IEC104_INTERROGATION_TERMINATE && app.gi.waiting); fake.send_blocked = 0; } gi_process(); finish = gi_take(IEC104_INTERROGATION_FINISHED); app_poll(); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_COMPLETED && finish.request.common_address == 0x1234); CHECK(finish.batch_index == 1 && finish.objects_submitted == 1 && app.gi.gi_submitted == 1); CHECK(collect_asdus(frames, lengths) == 3); memcpy(expected, request, sizeof(expected)); expected[4] = 0x34; expected[5] = 0x12; expected[2] = (uint8_t)(7U | (request[2] & 0x80U)); CHECK(lengths[0] == sizeof(expected) && memcmp(frames[0], expected, sizeof(expected)) == 0); expected[2] = (uint8_t)(10U | (request[2] & 0x80U)); CHECK(lengths[2] == sizeof(expected) && memcmp(frames[2], expected, sizeof(expected)) == 0); CHECK(lengths[1] == 10 && frames[1][0] == 1 && frames[1][1] == 1 && frames[1][2] == (qoi | (request[2] & 0x80U))); CHECK(frames[1][3] == 0x67 && frames[1][4] == 0x34 && frames[1][5] == 0x12); CHECK(memcmp(frames[1] + 6, (const uint8_t[]){3, 2, 1, 1}, 4) == 0); gi_empty(); app_close(); } custom_config = NULL; } /* 广播错误回复的立即/排队路径都改为本站CA;普通错误站地址保持原值。 */ static void test_broadcast_error_replies(void) { static const struct { unsigned offset; uint8_t value, cot; } cases[] = { {9, 19, 7}, {9, 37, 7}, {6, 1, 47}, {2, 0x83, 45}, {2, 0xc6, 7}, {2, 0x88, 9}, {4, 2, 46}, {4, 0, 46} }; IEC104_InterrogationConfig_t config; unsigned wait, i; IEC104_Interrogation_Config_Default(&config); config.common_address = 0x1234; custom_config = &config; for (wait = 0; wait < 2; ++wait) { for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { uint8_t request[sizeof(gi_request)], expected[sizeof(gi_request)]; IEC104_TcpServerStatus_t service; const uint8_t *frames[32]; size_t lengths[32]; app_open(); if (wait) { /* 先取得有效的连接接口,取消尚未交付BEGIN的请求再填满HIGH。 */ memcpy(request, gi_request, sizeof(request)); request[4] = 0x34; request[5] = 0x12; app_request(request, sizeof(request)); IEC104_Interrogation_Cancel(app.interrogation); congest(&last_request.connection); } memcpy(request, gi_request, sizeof(request)); request[4] = request[5] = 0xff; request[cases[i].offset] = cases[i].value; if (i >= 6) { request[5] = 0; } request_unchanged(request, sizeof(request)); if (wait) { gi_process_only(); CHECK(app.gi.pending_requests == 1 && app.gi.waiting); } fake.send_blocked = 0; gi_process(); app_poll(); memcpy(expected, request, sizeof(expected)); expected[2] = (uint8_t)(0xc0U | cases[i].cot); if (i < 6) { expected[4] = 0x34; expected[5] = 0x12; } CHECK(collect_asdus(frames, lengths) == 1 && lengths[0] == sizeof(expected)); CHECK(memcmp(frames[0], expected, sizeof(expected)) == 0 && app.gi.negative_submitted == 1); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); gi_empty(); app_close(); } } custom_config = NULL; } /* 广播仍受单总召与满槽规则约束,忙应答使用本站CA,不能修改当前交互或断链。 */ static void test_broadcast_busy(void) { unsigned broadcast; for (broadcast = 0; broadcast < 2; ++broadcast) { IEC104_InterrogationEvent_t begin; IEC104_TcpServerStatus_t service; const uint8_t *frames[32]; size_t lengths[32]; uint8_t request[sizeof(gi_request)]; unsigned i; app_open(); memcpy(request, gi_request, sizeof(request)); if (broadcast) { request[4] = request[5] = 0xff; } request_unchanged(request, sizeof(request)); gi_process(); begin = gi_take(IEC104_INTERROGATION_BEGIN); request[4] = request[5] = 0xff; request[3] = 0x60; request[9] = 21; request_unchanged(request, sizeof(request)); gi_process_only(); /* 多站应答保存遍历任务,再由Process有界提交。 */ CHECK(app.gi.gi_pending && app.gi.negative_submitted == 1 && app.gi.pending_requests == 1); congest(&last_request.connection); for (i = 0; i < 4; ++i) { request[3] = (uint8_t)(0x61U + i); request_unchanged(request, sizeof(request)); } CHECK(app.gi.gi_pending && app.gi.pending_requests == 4 && app.gi.dropped_requests == 1 && app.gi.overloads == 1); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &begin, false) == IEC104_INTERROGATION_REPLY_OK); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_REJECTED); fake.send_blocked = 0; app_finish(); gi_empty(); CHECK(collect_asdus(frames, lengths) == 5); for (i = 0; i < 5; ++i) { CHECK(lengths[i] == 10 && frames[i][2] == 0xc7 && frames[i][4] == 1 && frames[i][5] == 0); CHECK(frames[i][3] == (i < 4 ? 0x60U + i : 0x5aU)); } app_close(); } } /* 广播/定向激活与撤销的四种组合,覆盖BEGIN前、确认后以及满任务池撤销。 */ static void test_broadcast_deactivation(void) { unsigned phase, combination; for (phase = 0; phase < 3; ++phase) { for (combination = 0; combination < 4; ++combination) { uint8_t activation[sizeof(gi_request)], deactivation[sizeof(gi_request)]; IEC104_InterrogationEvent_t event = {0}, finish; const uint8_t *frames[32]; size_t lengths[32]; unsigned count, i; app_open(); memcpy(activation, gi_request, sizeof(activation)); activation[9] = 36; if (combination & 1U) { activation[4] = activation[5] = 0xff; } request_unchanged(activation, sizeof(activation)); if (phase != 0) { gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 1); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } if (phase == 2) { congest(&last_request.connection); for (i = 0; i < 3; ++i) { request_busy((uint8_t)(0x60U + i)); } CHECK(app.gi.pending_requests == 4); } memcpy(deactivation, activation, sizeof(deactivation)); deactivation[2] = 0x88; deactivation[4] = combination & 2U ? 0xff : 1; deactivation[5] = combination & 2U ? 0xff : 0; request_unchanged(deactivation, sizeof(deactivation)); if (phase != 0) { finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_DEACTIVATED && finish.request_id == event.request_id); CHECK(finish.request.common_address == 1 && finish.request.qualifier == 36); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); } gi_empty(); fake.send_blocked = 0; app_finish(); count = collect_asdus(frames, lengths); CHECK(count == (phase == 0 ? 1U : phase == 1 ? 2U : 5U)); CHECK(frames[count - 1U][2] == 0x89 && frames[count - 1U][4] == 1 && frames[count - 1U][5] == 0); CHECK(frames[count - 1U][9] == 36 && app.gi.deactivated == 1); deactivation[4] = deactivation[5] = 0xff; request_unchanged(deactivation, sizeof(deactivation)); app_finish(); CHECK(collect_asdus(frames, lengths) == count + 1U && frames[count][2] == 0xc9); CHECK(frames[count][4] == 1 && frames[count][5] == 0); gi_empty(); app_close(); } } } /* 总召绑定必须属于本Slave;配置错误、重复创建和失败销毁均不能破坏站点引用。 */ static void test_station_binding_lifetime(void) { IEC104_Station_t station = {0}; IEC104_InterrogationConfig_t config; IEC104_Slave_t *slave = IEC104_Slave_Create(); IEC104_Interrogation_t *gi; IEC104_InterrogationEvent_t event; CHECK(slave != NULL); IEC104_Interrogation_Config_Default(&config); CHECK(config.station == NULL); config.station = &station; config.common_address = 0U; /* 绑定站点时不用兼容地址,避免两处配置不同CA。 */ CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); CHECK(IEC104_Slave_Add_Station(slave, &station, 0x1234U, &config) == IEC104_STATION_OK); config.max_objects_per_asdu = 0U; CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); CHECK(IEC104_Slave_Remove_Station(slave, &station) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(slave, &station, 0x1234U, &config) == IEC104_STATION_OK); config.max_objects_per_asdu = 4U; config.use_station_registry = true; CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); /* 两种站点选择方式不能同时配置。 */ config.use_station_registry = false; gi = IEC104_Interrogation_Create(slave, &config); CHECK(gi != NULL && IEC104_Interrogation_Create(slave, &config) == NULL); CHECK(IEC104_Slave_Remove_Station(slave, &station) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&station) && IEC104_Slave_Get_Station(slave, 0x1234U) == NULL); CHECK(IEC104_Slave_Destroy(slave, NULL) == IEC104_TCP_INVALID_STATE); CHECK(IEC104_Interrogation_Destroy(gi)); CHECK(IEC104_Station_Is_Reusable(&station)); #if IEC104_TCP_SERVER_CAPACITY >= 2U { IEC104_Slave_t *other = IEC104_Slave_Create(); CHECK(other != NULL); CHECK(IEC104_Slave_Add_Station(other, &station, 0x1234U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Interrogation_Create(slave, &config) == NULL); CHECK(IEC104_Slave_Remove_Station(other, &station) == IEC104_STATION_OK); CHECK(IEC104_Slave_Destroy(other, NULL) == IEC104_TCP_OK); } #endif CHECK(IEC104_Slave_Destroy(slave, NULL) == IEC104_TCP_OK); app_open(); event = gi_begin(); CHECK(!IEC104_Interrogation_Destroy(app.interrogation)); CHECK(IEC104_Slave_Acquire_Station(app.slave, &app.station) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); IEC104_Interrogation_Cancel(app.interrogation); CHECK(!IEC104_Interrogation_Destroy(app.interrogation)); /* 未读FINISHED仍禁止销毁总召实例。 */ CHECK(!IEC104_Station_Is_Reusable(&app.station)); /* 应用持有到清理完成。 */ CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request_id == event.request_id); CHECK(IEC104_Slave_Release_Station(app.slave, &app.station) == IEC104_STATION_OK); app_close(); /* 退出必须能归还唯一引用并成功删除节点,避免重复创建/销毁泄漏。 */ } /* 定向和广播回复均使用绑定站点的实际CA;注册其他站点不会自动展开新的总召。 */ static void test_station_binding_wire(void) { IEC104_InterrogationConfig_t config; unsigned broadcast; IEC104_Interrogation_Config_Default(&config); config.common_address = 0x1234U; config.station = &app.station; config.max_objects_per_asdu = 4U; custom_config = &config; for (broadcast = 0U; broadcast < 2U; ++broadcast) { IEC104_Station_t other = {0}; uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &other, 7U, &other) == IEC104_STATION_OK); memcpy(request, gi_request, sizeof(request)); request[4] = broadcast ? 0xffU : 0x34U; request[5] = broadcast ? 0xffU : 0x12U; app_request(request, sizeof(request)); app_finish(); CHECK(collect_asdus(frames, lengths) == 8U); CHECK(frames[0][2] == 0x87U && frames[7][2] == 0x8aU); for (i = 0U; i < 8U; ++i) { CHECK(frames[i][4] == 0x34U && frames[i][5] == 0x12U); } request[4] = 7U; request[5] = 0U; app_request(request, sizeof(request)); app_finish(); CHECK(collect_asdus(frames, lengths) == 9U); CHECK(frames[8][2] == 0xeeU && frames[8][4] == 7U && frames[8][5] == 0U); CHECK(IEC104_Slave_Remove_Station(app.slave, &other) == IEC104_STATION_OK); app_close(); } custom_config = NULL; } static void registry_request(uint16_t ca, uint8_t cot) { uint8_t request[sizeof(gi_request)]; memcpy(request, gi_request, sizeof(request)); request[4] = (uint8_t)ca; request[5] = (uint8_t)(ca >> 8); request[2] = (uint8_t)(cot | 0x80U); app_request(request, sizeof(request)); } /* 同一IOA属于不同CA时按站点context取数;提交数据必须匹配当前站,而不是默认CA。 */ static void test_registry_routing(void) { IEC104_Station_t second = {0}; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; int station_value = 0; const uint8_t *frames[32]; size_t lengths[32]; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 0x1234U, &station_value) == IEC104_STATION_OK); registry_request(0x1234U, 6U); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 0x1234U && event.request.station_id == IEC104_Station_Get_Id(&second)); CHECK(IEC104_Station_Get_Context(IEC104_Slave_Get_Station_By_Id(app.slave, event.request.station_id)) == &station_value); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); single_batch(&batch, ¶meters, bytes, 1U); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_ERROR); CHECK(IEC60870_Asdu_Set_CA(&batch, 0x1234U) == IEC60870_ASDU_OK); bytes[9] = (uint8_t)station_value; CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); gi_done(&event, 1U, 1U); CHECK(collect_asdus(frames, lengths) == 3U); CHECK(frames[0][2] == 0x87U && frames[1][2] == 0x94U && frames[2][2] == 0x8aU); CHECK(frames[1][4] == 0x34U && frames[1][5] == 0x12U && frames[1][9] == 0U); CHECK(frames[1][6] == 0xe9U && frames[1][7] == 3U); /* IOA仍由应用指定。 */ registry_request(99U, 6U); app_finish(); CHECK(collect_asdus(frames, lengths) == 4U && frames[3][2] == 0xeeU && frames[3][4] == 99U); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); app_close(); } /* 13站超过任务池容量;每站空数据也有独立确认/终止、事件身份和计时,不丢站不扩队列。 */ static void test_registry_broadcast_many(void) { IEC104_Station_t stations[12] = {{0}}; IEC104_InterrogationEvent_t event, finish; uint64_t ids[13]; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Slave_Add_Station(app.slave, &stations[i], (uint16_t)(i + 2U), &stations[i]) == IEC104_STATION_OK); } registry_request(IEC104_CA_BROADCAST, 6U); for (i = 0U; i < 13U; ++i) { gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == i + 1U && (i == 0U || event.request_id > ids[i - 1U])); CHECK(event.batch_index == 0U && event.objects_submitted == 0U && app.gi.pending_requests == 1U); CHECK(event.request.originator_address == 0x5aU && event.request.is_test && event.request.qualifier == 20U); ids[i] = event.request_id; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } for (i = 0U; i < 13U; ++i) { gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request_id == ids[i] && event.request.common_address == i + 1U); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } gi_process(); gi_process(); /* 终止有步数预算,但不需要先消费FINISHED才继续发送。 */ CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(!app.gi.gi_pending && app.gi.phase == IEC104_INTERROGATION_FINISHING && app.gi.pending_events == 13U); CHECK(!app.gi.need_process && !IEC104_Interrogation_Destroy(app.interrogation)); for (i = 0U; i < 13U; ++i) { finish = gi_take(IEC104_INTERROGATION_FINISHED); /* 无需Process也能读完超过FIFO容量的终结记录。 */ CHECK(finish.request_id == ids[i] && finish.reason == IEC104_INTERROGATION_FINISH_COMPLETED); CHECK(finish.batch_index == 0U && finish.objects_submitted == 0U); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); app_poll(); CHECK(collect_asdus(frames, lengths) == 26U && app.gi.gi_submitted == 13U); CHECK(!app.gi.gi_pending && app.gi.pending_requests == 0U); for (i = 0U; i < 26U; ++i) { CHECK(lengths[i] == 10U && frames[i][4] == i % 13U + 1U && frames[i][5] == 0U); CHECK(frames[i][2] == (i < 13U ? 0x87U : 0x8aU)); } for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Slave_Remove_Station(app.slave, &stations[i]) == IEC104_STATION_OK); } app_close(); } /* 删除当前站并以同CA注册新context,旧总召仍读原站;示例仅在取数阶段额外持有引用。 */ static void test_registry_delete_current(void) { IEC104_Station_t replacement = {0}; uint64_t old_id; app_open(); old_id = IEC104_Station_Get_Id(&app.station); registry_request(1U, 6U); CHECK(Example_Step(&app)); CHECK(app.gi_station == NULL); /* BEGIN只判断接受,不占用取数游标。 */ CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&app.station)); CHECK(IEC104_Slave_Get_Station(app.slave, 1U) == NULL); CHECK(IEC104_Slave_Add_Station(app.slave, &replacement, 1U, &replacement) == IEC104_STATION_OK); CHECK(IEC104_Slave_Get_Station_By_Id(app.slave, old_id) == &app.station); CHECK(IEC104_Station_Get_Id(&replacement) != old_id); app_finish(); check_gi(0U, 0x5aU); CHECK(IEC104_Station_Is_Reusable(&app.station) && app.gi_station == NULL); CHECK(IEC104_Slave_Get_Station_By_Id(app.slave, old_id) == NULL); CHECK(IEC104_Slave_Remove_Station(app.slave, &replacement) == IEC104_STATION_OK); registry_request(1U, 6U); app_finish(); /* 删除后新请求必须收到未知CA。 */ CHECK(app.gi.negative_submitted == 1U); app_close(); } /* 当前站完成,尚未选入BEGIN的已删除站跳过,替代/新增站下一轮才参加。 */ static void test_registry_broadcast_mutation(void) { IEC104_Station_t second = {0}, third = {0}, replacement = {0}, added = {0}; IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, NULL) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &replacement, 2U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &added, 4U, NULL) == IEC104_STATION_OK); for (i = 0U; i < 2U; ++i) { gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == (i == 0U ? 1U : 3U)); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } for (i = 0U; i < 2U; ++i) { gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request.common_address == (i == 0U ? 1U : 3U)); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } gi_process(); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == 1U); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == 3U); app_poll(); CHECK(collect_asdus(frames, lengths) == 4U); CHECK(frames[0][4] == 1U && frames[1][4] == 3U && frames[2][4] == 1U && frames[3][4] == 3U); CHECK(frames[0][2] == 0x87U && frames[1][2] == 0x87U && frames[2][2] == 0x8aU && frames[3][2] == 0x8aU); CHECK(IEC104_Station_Is_Reusable(&app.station) && IEC104_Station_Is_Reusable(&second)); CHECK(IEC104_Slave_Remove_Station(app.slave, &replacement) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &added) == IEC104_STATION_OK); app_close(); } /* 广播忙/错误应答按各站实际CA展开;轮次有界且余下工作可见,不越过更早应答。 */ static void test_registry_reply_fanout(void) { IEC104_Station_t stations[12] = {{0}}; IEC104_InterrogationEvent_t begin; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Slave_Add_Station(app.slave, &stations[i], (uint16_t)(i + 2U), NULL) == IEC104_STATION_OK); } begin = gi_begin(); registry_request(IEC104_CA_BROADCAST, 6U); gi_process_only(); CHECK(app.gi.negative_submitted == 8U && app.gi.need_process && app.gi.gi_pending); gi_process(); app_poll(); CHECK(collect_asdus(frames, lengths) == 13U && app.gi.pending_requests == 1U); for (i = 0U; i < 13U; ++i) { CHECK(frames[i][2] == 0xc7U && frames[i][4] == i + 1U); } CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &begin, false) == IEC104_INTERROGATION_REPLY_OK); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request_id == begin.request_id); app_finish(); for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Slave_Remove_Station(app.slave, &stations[i]) == IEC104_STATION_OK); } CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); app_finish(); CHECK(collect_asdus(frames, lengths) == 15U); CHECK(frames[14][2] == 0xeeU && frames[14][4] == 0xffU && frames[14][5] == 0xffU); app_close(); } /* 定向撤销已确认但尚未取数的站;广播撤销剩余站,各自有肯定COT9且不发送ACT_TERM。 */ static void test_registry_deactivation(void) { IEC104_Station_t second = {0}, third = {0}; IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, NULL) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); for (i = 0U; i < 3U; ++i) { gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == i + 1U); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&second)); registry_request(2U, 8U); /* 删除后仍能撤销原活动事务;CA1的NEED_DATA继续有效。 */ CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == 2U); gi_process(); registry_request(IEC104_CA_BROADCAST, 8U); for (i = 0U; i < 2U; ++i) { IEC104_InterrogationEvent_t finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.reason == IEC104_INTERROGATION_FINISH_DEACTIVATED); CHECK(finish.request.common_address == (i == 0U ? 1U : 3U)); } CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); app_finish(); CHECK(collect_asdus(frames, lengths) == 6U && app.gi.deactivated == 2U && app.gi.gi_submitted == 0U); for (i = 0U; i < 3U; ++i) { CHECK(frames[i][2] == 0x87U && frames[i][4] == i + 1U); } for (i = 3U; i < 6U; ++i) { CHECK(frames[i][2] == 0x89U); } CHECK(frames[3][4] == 2U && frames[4][4] == 1U && frames[5][4] == 3U); CHECK(IEC104_Station_Is_Reusable(&second)); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); app_close(); } /* 首站BEGIN拒绝/超时后继续下一站;STOP取消整轮,快照/节点引用均归还。 */ static void test_registry_reject_timeout_stop(void) { unsigned mode; for (mode = 0U; mode < 4U; ++mode) { IEC104_Station_t second = {0}; IEC104_InterrogationEvent_t event; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); if (mode == 0U) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, false) == IEC104_INTERROGATION_REPLY_OK); registry_request(1U, 8U); /* 已拒绝的当前站无活动事务,不能改成肯定撤销并丢失否定确认。 */ CHECK(app.gi.deactivated == 0U); } else if (mode == 1U) { fake.now += 15000U; } else if (mode == 2U) { push(stop_act, sizeof(stop_act)); app_poll(); } else { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 2U); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); fake.now += 15000U; } if (mode == 3U) { gi_process_only(); } /* 单独触发业务期限,避免同点到期的链路t1先收尾。 */ else { gi_process(); } CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == (mode == 0U ? IEC104_INTERROGATION_FINISH_REJECTED : mode == 1U ? IEC104_INTERROGATION_FINISH_BEGIN_TIMEOUT : mode == 2U ? IEC104_INTERROGATION_FINISH_STOPPED : IEC104_INTERROGATION_FINISH_DATA_TIMEOUT)); if (mode == 3U) { CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_DATA_TIMEOUT); } if (mode < 2U) { gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 2U); } CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); IEC104_Interrogation_Cancel(app.interrogation); if (mode < 2U) { CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request_id == event.request_id); } CHECK(IEC104_Station_Is_Reusable(&app.station) && IEC104_Station_Is_Reusable(&second)); app_close(); } } /* 在广播的确认/数据/终止遇到发送WAIT,不能提前换CA;恢复后仍逐站有序完成。 */ static void test_registry_wait_each_phase(void) { unsigned phase; for (phase = 0U; phase < 3U; ++phase) { IEC104_Station_t second = {0}; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t batch; IEC60870_AppLayerParameters_t parameters; uint8_t bytes[249]; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); if (phase == 0U) { congest(&last_request.connection); gi_process_only(); CHECK(app.gi.waiting && !app.gi.need_process && app.gi.phase == IEC104_INTERROGATION_CONFIRM); fake.send_blocked = 0; } gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 2U); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); if (phase == 1U) { single_batch(&batch, ¶meters, bytes, 1U); congest(&last_request.connection); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_WAIT); gi_process_only(); CHECK(app.gi.data_send_waiting && app.gi.pending_requests == 1U); fake.send_blocked = 0; gi_process(); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request.common_address == 2U); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); if (phase == 2U) { congest(&last_request.connection); gi_process_only(); CHECK(app.gi.waiting && app.gi.phase == IEC104_INTERROGATION_TERMINATE && app.gi.gi_submitted == 0U); fake.send_blocked = 0; } gi_process(); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == 1U); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == 2U); app_finish(); CHECK(app.gi.gi_submitted == 2U); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); app_close(); } } static void registry_accept(unsigned count) { unsigned i; registry_request(IEC104_CA_BROADCAST, 6U); for (i = 0U; i < count; ++i) { IEC104_InterrogationEvent_t event; gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == i + 1U); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } gi_process(); } /* 广播畸形撤销只能逐站否定,不能改动已确认各站的活动总召/终结记录。 */ static void test_registry_malformed(void) { IEC104_Station_t second = {0}; IEC104_InterrogationEvent_t first, next, finish; IEC104_TcpServerStatus_t service; uint8_t request[sizeof(gi_request)]; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); registry_accept(2); first = gi_take(IEC104_INTERROGATION_NEED_DATA); memcpy(request, gi_request, sizeof(request)); request[1] = 0x81; request[2] = 0x88; request[4] = request[5] = 0xff; request_unchanged(request, sizeof(request)); gi_process_only(); gi_empty(); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); CHECK(app.gi.pending_requests == 1 && app.gi.gi_pending && app.gi.negative_submitted == 2); CHECK(app.gi.cancelled == 0 && app.gi.deactivated == 0 && app.gi.rejected == 1); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &first) == IEC104_INTERROGATION_REPLY_OK); gi_process(); next = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(next.request.common_address == 2 && next.request_id != first.request_id); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &next) == IEC104_INTERROGATION_REPLY_OK); gi_process(); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == first.request_id && finish.reason == IEC104_INTERROGATION_FINISH_COMPLETED); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == next.request_id && finish.reason == IEC104_INTERROGATION_FINISH_COMPLETED); gi_empty(); app_poll(); CHECK(collect_asdus(frames, lengths) == 6 && app.gi.gi_submitted == 2); for (i = 0; i < 6; ++i) { CHECK(lengths[i] == 10 && frames[i][1] == 1 && frames[i][4] == i % 2U + 1U); CHECK(frames[i][2] == (i < 2 ? 0x87 : i < 4 ? 0xc9 : 0x8a)); } CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); app_close(); } /* 示例只需一套取数游标;线上三段顺序包含非空、多批、混合类型数据。 */ static void test_registry_example_order(void) { IEC104_Station_t second = {0}, third = {0}; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, &app) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); app_finish(); CHECK(collect_asdus(frames, lengths) == 24U && app.gi.gi_submitted == 3U); for (i = 0U; i < 24U; ++i) { unsigned ca = i < 3U ? i + 1U : i < 21U ? (i - 3U) / 6U + 1U : i - 20U; CHECK(frames[i][4] == ca && frames[i][5] == 0U); CHECK(frames[i][2] == (i < 3U ? 0x87U : i < 21U ? 0x94U : 0x8aU)); if (i >= 3U && i < 21U) { unsigned batch = (i - 3U) % 6U; CHECK((frames[i][1] & 0x7fU) == 4U); CHECK(frames[i][0] == (batch < 2U ? 1U : batch < 4U ? 3U : 11U)); } } CHECK(app.gi_station == NULL && app.session_request == 0U); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); app_close(); } /* 广播容量不足必须在任何肯定确认之前整体拒绝;单站和定向总召不需要状态区。 */ static void test_registry_capacity(void) { size_t capacity; for (capacity = 0U; capacity < 3U; ++capacity) { IEC104_InterrogationConfig_t config; IEC104_Station_t second = {0}, third = {0}; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; IEC104_Interrogation_Config_Default(&config); config.use_station_registry = true; config.broadcast_states = capacity != 0U ? broadcast_states : NULL; config.broadcast_capacity = capacity; custom_config = &config; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, &app) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); app_finish(); gi_empty(); CHECK(collect_asdus(frames, lengths) == 3U && app.gi.gi_submitted == 0U && app.gi.overloads == 1U); for (i = 0U; i < 3U; ++i) { CHECK(frames[i][2] == 0xc7U && frames[i][4] == i + 1U); } registry_request(2U, 6U); app_finish(); CHECK(app.gi.gi_submitted == 1U); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); app_finish(); CHECK(app.gi.gi_submitted == 2U); app_close(); custom_config = NULL; } } static void test_registry_workspace_contract(void) { IEC104_InterrogationConfig_t config; app_open(); CHECK(IEC104_Interrogation_Destroy(app.interrogation)); IEC104_Interrogation_Config_Default(&config); CHECK(config.broadcast_states == NULL && config.broadcast_capacity == 0U); config.broadcast_capacity = 1U; CHECK(IEC104_Interrogation_Create(app.slave, &config) == NULL); config.broadcast_states = broadcast_states; config.broadcast_capacity = 0U; CHECK(IEC104_Interrogation_Create(app.slave, &config) == NULL); config.broadcast_capacity = SIZE_MAX / sizeof(*broadcast_states) + 1U; CHECK(IEC104_Interrogation_Create(app.slave, &config) == NULL); config.broadcast_capacity = 8U; app.interrogation = IEC104_Interrogation_Create(app.slave, &config); CHECK(app.interrogation != NULL); #if IEC104_TCP_SERVER_CAPACITY >= 2 { IEC104_Slave_t *other = IEC104_Slave_Create(); IEC104_Interrogation_t *other_gi; CHECK(other != NULL); CHECK(IEC104_Interrogation_Create(other, &config) == NULL); /* 完全重叠。 */ config.broadcast_states = broadcast_states + 4U; CHECK(IEC104_Interrogation_Create(other, &config) == NULL); /* 部分重叠。 */ config.broadcast_states = broadcast_states + 8U; other_gi = IEC104_Interrogation_Create(other, &config); CHECK(other_gi != NULL && IEC104_Interrogation_Destroy(other_gi)); CHECK(IEC104_Slave_Destroy(other, NULL) == IEC104_TCP_OK); } #endif app_close(); } /* 已确认的未来站退役后,仍用原注册身份取值并发送终止,不转向同CA的新节点。 */ static void test_registry_confirmed_deleted(void) { IEC104_Station_t second = {0}, third = {0}, replacement = {0}; IEC104_InterrogationEvent_t event; uint64_t original; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, &app) == IEC104_STATION_OK); original = IEC104_Station_Get_Id(&second); registry_accept(3U); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &replacement, 2U, &replacement) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&second)); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request.common_address == 2U && event.request.station_id == original); app.business_event = event; app.business_pending = true; app_finish(); CHECK(app.gi.gi_submitted == 3U && app.gi.cancelled == 0U && IEC104_Station_Is_Reusable(&second)); CHECK(IEC104_Slave_Get_Station_By_Id(app.slave, original) == NULL); CHECK(IEC104_Slave_Remove_Station(app.slave, &replacement) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); app_close(); } /* 超过FIFO容量的BEGIN都必须有且仅有一次FINISHED;取消后无需Process即可逐个清理。 */ static void test_registry_finish_delivery(void) { static const IEC104_InterrogationFinishReason_t reasons[] = { IEC104_INTERROGATION_FINISH_CANCELLED, IEC104_INTERROGATION_FINISH_STOPPED, IEC104_INTERROGATION_FINISH_DISCONNECTED, IEC104_INTERROGATION_FINISH_SOURCE_ERROR }; unsigned mode; for (mode = 0U; mode < 4U; ++mode) { IEC104_Station_t stations[12] = {{0}}; IEC104_InterrogationEvent_t event, finish; uint64_t ids[13]; unsigned i; app_open(); for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Slave_Add_Station(app.slave, &stations[i], (uint16_t)(i + 2U), &app) == IEC104_STATION_OK); } registry_request(IEC104_CA_BROADCAST, 6U); for (i = 0U; i < 13U; ++i) { gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); ids[i] = event.request_id; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); } gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); if (mode == 0U) { IEC104_Interrogation_Cancel(app.interrogation); } if (mode == 1U) { push(stop_act, sizeof(stop_act)); app_poll(); } if (mode == 2U) { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); app_poll(); } if (mode == 3U) { CHECK(IEC104_Interrogation_Fail_Request(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_STALE); IEC104_Interrogation_Cancel(app.interrogation); /* 不覆盖已有原因。 */ if (mode == 0U) { registry_request(1U, 6U); /* 状态区尚未清理,新总召忙拒绝,不能覆盖旧FINISHED。 */ CHECK(app.gi.negative_submitted == 1U); } CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Slave_Remove_Station(app.slave, &stations[i]) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&stations[i])); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.pending_events == 13U && !app.gi.gi_pending && !app.gi.need_process); CHECK(!IEC104_Interrogation_Destroy(app.interrogation)); for (i = 0U; i < 13U; ++i) { finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == ids[i] && finish.reason == reasons[mode]); } gi_empty(); CHECK(IEC104_Station_Is_Reusable(&app.station)); for (i = 0U; i < 12U; ++i) { CHECK(IEC104_Station_Is_Reusable(&stations[i])); } app_close(); } } /* 当前取数站、未来已确认站、已取完数据等终止站,都支持按CA撤销而不损害其他站。 */ static void test_registry_directed_deactivation(void) { unsigned mode; for (mode = 0U; mode < 3U; ++mode) { IEC104_Station_t second = {0}, third = {0}; IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; unsigned count, i; uint16_t target = mode == 1U ? 2U : 1U; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, &app) == IEC104_STATION_OK); registry_accept(3U); event = gi_take(IEC104_INTERROGATION_NEED_DATA); if (mode == 2U) { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } registry_request(target, 8U); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == target); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == (mode == 0U ? IEC104_INTERROGATION_REPLY_STALE : IEC104_INTERROGATION_REPLY_OK)); app_finish(); CHECK(app.gi.gi_submitted == 2U && app.gi.deactivated == 1U); count = collect_asdus(frames, lengths); for (i = 0U; i < count; ++i) { CHECK(frames[i][2] != 0x8aU || frames[i][4] != target); } CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); app_close(); } } /* 满槽广播撤销复用原任务,按先前应答顺序提交每站COT9;不额外占用事件预留或任务槽。 */ static void test_registry_deactivation_full(void) { enum { task_capacity = 4 }; /* 本测试构建采用模块默认任务容量。 */ IEC104_Station_t second = {0}, third = {0}; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, NULL) == IEC104_STATION_OK); registry_accept(3U); (void)gi_take(IEC104_INTERROGATION_NEED_DATA); congest(&last_request.connection); for (i = 0U; i < task_capacity - 1U; ++i) { registry_request(1U, 6U); } CHECK(app.gi.pending_requests == task_capacity); registry_request(IEC104_CA_BROADCAST, 8U); CHECK(app.gi.deactivated == 1U && app.gi.pending_requests == task_capacity); registry_request(IEC104_CA_BROADCAST, 8U); /* 不覆盖首条撤销;此时也没有错误应答空槽。 */ CHECK(app.gi.deactivated == 1U && app.gi.dropped_requests == 1U); for (i = 0U; i < 3U; ++i) { CHECK(gi_take(IEC104_INTERROGATION_FINISHED).request.common_address == i + 1U); } gi_process_only(); CHECK(app.gi.waiting && !app.gi.need_process); fake.send_blocked = 0; app_finish(); CHECK(collect_asdus(frames, lengths) == 6U + task_capacity - 1U); for (i = 3U; i < 3U + task_capacity - 1U; ++i) { CHECK(frames[i][2] == 0xc7U); } for (; i < 6U + task_capacity - 1U; ++i) { CHECK(frames[i][2] == 0x89U); } CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); app_close(); } /* 另一个站的撤销等待发送时,当前BEGIN被拒绝的ACT_CON不能被撤销应答覆盖。 */ static void test_registry_reject_during_deactivation(void) { unsigned mode; for (mode = 0U; mode < 2U; ++mode) { IEC104_Station_t second = {0}, third = {0}; IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, &app) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, &app) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); if (mode == 0U) { registry_request(1U, 8U); } CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, false) == IEC104_INTERROGATION_REPLY_OK); if (mode == 1U) { registry_request(IEC104_CA_BROADCAST, 8U); } (void)gi_take(IEC104_INTERROGATION_FINISHED); (void)gi_take(IEC104_INTERROGATION_FINISHED); app_finish(); CHECK(collect_asdus(frames, lengths) == (mode == 0U ? 11U : 3U)); CHECK(frames[0][2] == 0x87U && frames[0][4] == 1U); CHECK(frames[1][2] == 0xc7U && frames[1][4] == 2U); CHECK(frames[2][2] == 0x89U && frames[2][4] == 1U); CHECK(app.gi.gi_submitted == (mode == 0U ? 1U : 0U)); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); app_close(); } } /* 显式启用总期限时,已确认但不在当前取数位置的站仍受期限约束,不能遗漏。 */ static void test_registry_total_deadline(void) { unsigned mode; for (mode = 0U; mode < 2U; ++mode) { IEC104_InterrogationConfig_t config; IEC104_Station_t second = {0}; IEC104_InterrogationEvent_t event; IEC104_Interrogation_Config_Default(&config); config.use_station_registry = true; config.broadcast_states = broadcast_states; config.broadcast_capacity = 16U; config.total_timeout_ms = 100U; custom_config = &config; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); fake.now += 50U; gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); if (mode == 1U) { CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); } CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.timer_active && app.gi.timeout_remaining_ms == 50U); fake.now += 50U; gi_process_only(); CHECK(!app.gi.active && app.gi.timeouts == 1U && app.gi.gi_submitted == 0U); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT); gi_empty(); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); app_close(); custom_config = NULL; } } /* 当前站BEGIN超时不能误关尚未到总期限的已确认站;同时到期时总期限优先。 */ static void test_registry_timeout_priority(void) { unsigned mode; for (mode = 0U; mode < 3U; ++mode) { IEC104_InterrogationConfig_t config; IEC104_Station_t second = {0}; IEC104_InterrogationEvent_t event, finish; IEC104_TcpServerStatus_t service; uint64_t first_request_id; IEC104_Interrogation_Config_Default(&config); config.use_station_registry = true; config.broadcast_states = broadcast_states; config.broadcast_capacity = 16U; config.total_timeout_ms = mode == 0U ? 0U : 100U; config.begin_timeout_ms = mode == 2U ? 50U : 25U; custom_config = &config; app_open(); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); registry_request(IEC104_CA_BROADCAST, 6U); gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); first_request_id = event.request_id; CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_OK); fake.now += 50U; gi_process(); event = gi_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == 2U); fake.now += config.begin_timeout_ms - 1U; gi_process_only(); CHECK(app.gi.active && app.gi.timeouts == 0U && app.gi.timer_active && app.gi.timeout_remaining_ms == 1U); ++fake.now; CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi)); CHECK(app.gi.active && app.gi.timeouts == 0U && app.gi.timer_active && app.gi.timeout_remaining_ms == 0U); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); CHECK(IEC104_Interrogation_Reply_Begin(app.interrogation, &event, true) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Get_Status(app.interrogation, &app.gi) && app.gi.timeouts == 1U); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK); if (mode == 2U) { CHECK(!app.gi.active && !service.connected && app.gi.gi_submitted == 0U); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_TOTAL_TIMEOUT); } else { CHECK(app.gi.active && service.connected); finish = gi_take(IEC104_INTERROGATION_FINISHED); CHECK(finish.request_id == event.request_id && finish.reason == IEC104_INTERROGATION_FINISH_BEGIN_TIMEOUT); gi_process(); event = gi_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request_id == first_request_id && event.request.common_address == 1U); CHECK(app.gi.timer_active && app.gi.timeout_remaining_ms == (mode == 0U ? config.data_timeout_ms : 25U)); gi_done(&event, 0U, 0U); } gi_empty(); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); app_close(); custom_config = NULL; } } /* 计数量复用可控传输夹具,但使用独立的引擎实例和广播状态区。 */ static const uint8_t ci_request[] = {101, 1, 6, 0x5a, 1, 0, 0, 0, 0, 5}; static void ci_open(void) { counter_enabled = true; app_open(); } static void ci_close(void) { app_close(); counter_enabled = false; counter_custom_config = NULL; } /* 先推进并读空Slave通知,再推进计数量;测试自行取出业务事件并决定应答时机。 */ static void ci_process(void) { app_poll(); CHECK(IEC104_Interrogation_Process(app.counter)); CHECK(IEC104_Interrogation_Get_Status(app.counter, &app.ci)); } static IEC104_InterrogationEvent_t ci_take(IEC104_InterrogationEventType_t type) { IEC104_InterrogationEvent_t event; CHECK(IEC104_Interrogation_NextEvent(app.counter, &event) == IEC104_INTERROGATION_NEXT_READY); CHECK(event.type == type); return event; } static void ci_empty(void) { IEC104_InterrogationEvent_t event; CHECK(IEC104_Interrogation_NextEvent(app.counter, &event) == IEC104_INTERROGATION_NEXT_EMPTY); } static void ci_send(uint8_t qcc, uint16_t ca) { uint8_t request[10]; memcpy(request, ci_request, sizeof(request)); request[9] = qcc; request[4] = (uint8_t)ca; request[5] = (uint8_t)(ca >> 8); request_unchanged(request, sizeof(request)); } static IEC104_InterrogationEvent_t ci_begin(uint8_t qcc) { ci_send(qcc, 1); ci_process(); return ci_take(IEC104_INTERROGATION_BEGIN); } /* 接受读取请求并取得首个NEED_DATA;冻结/复位没有数据阶段,不使用此辅助函数。 */ static IEC104_InterrogationEvent_t ci_data(uint8_t qcc) { IEC104_InterrogationEvent_t event = ci_begin(qcc); CHECK(IEC104_Interrogation_Reply_Begin(app.counter, &event, true) == IEC104_INTERROGATION_REPLY_OK); ci_process(); return ci_take(IEC104_INTERROGATION_NEED_DATA); } static void ci_done(const IEC104_InterrogationEvent_t *event) { CHECK(IEC104_Interrogation_Complete_Data(app.counter, event) == IEC104_INTERROGATION_REPLY_OK); ci_process(); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); ci_empty(); app_poll(); } /* 独立拼接数据字节,覆盖SQ寻址和CP56时标,避免用待测对象编码器生成预期布局。 */ static void ci_batch(IEC60870_Asdu_t *asdu, uint8_t bytes[249], const IEC104_InterrogationEvent_t *event, uint8_t type, unsigned count, bool sq) { unsigned i, width = type == 1 ? 1U : type == 15 ? 5U : type == 16 ? 8U : 12U; size_t offset = 6U; memset(bytes, 0, 249); bytes[0] = type; bytes[1] = (uint8_t)(count | (sq ? 0x80U : 0U)); bytes[2] = (uint8_t)(event->request.data_cause | (event->request.is_test ? 0x80U : 0U)); bytes[3] = event->request.originator_address; bytes[4] = (uint8_t)event->request.common_address; bytes[5] = (uint8_t)(event->request.common_address >> 8); CHECK(6U + (sq ? 3U : 3U * count) + width * count <= 249U); for (i = 0; i < count; ++i) { if (!sq || i == 0) { bytes[offset++] = (uint8_t)(3001U + i); bytes[offset++] = (uint8_t)((3001U + i) >> 8); bytes[offset++] = 0; } bytes[offset] = 1; if (type == 37) { bytes[offset + 9] = 1; bytes[offset + 10] = 1; } offset += width; } CHECK(IEC60870_Asdu_Create_From_Buffer(asdu, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, (uint16_t)offset) == IEC60870_ASDU_OK); } /* 同一Slave允许两种召唤并存;同种实例重复创建或广播状态区重叠必须失败,销毁后可重建。 */ static void test_counter_module_contracts(void) { IEC104_InterrogationConfig_t config; IEC104_Interrogation_t *counter; app_open(); IEC104_Interrogation_Config_Default(&config); config.use_station_registry = true; config.broadcast_states = broadcast_states; config.broadcast_capacity = 16; CHECK(IEC104_CounterInterrogation_Create(app.slave, &config) == NULL); config.broadcast_states = counter_broadcast_states; counter = IEC104_CounterInterrogation_Create(app.slave, &config); CHECK(counter != NULL); CHECK(IEC104_CounterInterrogation_Create(app.slave, &config) == NULL); CHECK(IEC104_Interrogation_Destroy(counter)); counter = IEC104_CounterInterrogation_Create(app.slave, &config); CHECK(counter != NULL); CHECK(IEC104_Interrogation_Destroy(counter)); app_close(); } /* 校验错误原因优先级、畸形布局的规范化回显和无法可靠应答时的丢弃,输入原文保持不变。 */ static void test_counter_validation(void) { static const struct { unsigned offset; uint8_t value, cot; unsigned length; } cases[] = { {2, 8, 45, 10}, {2, 3, 45, 10}, {4, 2, 46, 10}, {6, 1, 47, 10}, {1, 0x81, 7, 10}, {9, 5, 7, 11}, {9, 0, 7, 10}, {9, 6, 7, 10}, {9, 63, 7, 10}, {2, 0x46, 7, 10}, {1, 0, 0, 10}, {1, 2, 0, 14} }; unsigned i; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { uint8_t request[14] = {0}, expected[10]; const uint8_t *out[32]; size_t lengths[32]; ci_open(); memcpy(request, ci_request, sizeof(ci_request)); request[cases[i].offset] = cases[i].value; request[2] |= 0x80; request_unchanged(request, cases[i].length); app_finish(); CHECK(IEC104_Interrogation_Get_Status(app.counter, &app.ci)); CHECK(app.ci.rejected == 1 && app.other_rejected == 0 && !app.ci.gi_pending); CHECK(collect_asdus(out, lengths) == (cases[i].cot != 0)); if (cases[i].cot != 0) { memcpy(expected, request, sizeof(expected)); expected[1] = 1; expected[2] = (uint8_t)(0xc0 | cases[i].cot); CHECK(lengths[0] == 10 && memcmp(out[0], expected, 10) == 0); } ci_close(); } } /* 检查249字节下的三种最大批次,以及类型、SQ、头部、地址宽度和配置数量限制。 * 错误批次保留当前交互供修正;总召实例仍拒绝计数量数据。 */ static void test_counter_batch_validation(void) { static const struct { uint8_t type; bool sq; unsigned count; } valid[] = {{15, false, 30}, {15, true, 48}, {37, false, 16}}; IEC104_InterrogationConfig_t wide_config; unsigned i; IEC104_Interrogation_Config_Default(&wide_config); counter_custom_config = &wide_config; ci_open(); for (i = 0; i < sizeof(valid) / sizeof(valid[0]); ++i) { IEC104_InterrogationEvent_t event = ci_data(5); IEC60870_Asdu_t asdu; uint8_t bytes[249]; ci_batch(&asdu, bytes, &event, valid[i].type, valid[i].count, valid[i].sq); CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); ci_process(); event = ci_take(IEC104_INTERROGATION_NEED_DATA); ci_done(&event); } { IEC104_InterrogationEvent_t event = ci_data(1); IEC60870_Asdu_t asdu; uint8_t bytes[249]; static const unsigned offsets[] = {2, 3, 4, 2, 2}; static const uint8_t values[] = {37, 0x44, 2, 0x66, 0xa6}; for (i = 0; i < 3; ++i) { ci_batch(&asdu, bytes, &event, i == 0 ? 1 : i == 1 ? 16 : 37, 1, i == 2); CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); } for (i = 0; i < sizeof(offsets) / sizeof(offsets[0]); ++i) { ci_batch(&asdu, bytes, &event, 15, 1, false); bytes[offsets[i]] = values[i]; CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); } { IEC60870_AppLayerParameters_t wrong = *IEC104_Slave_Get_Application_Layer_Parameters(app.slave); ci_batch(&asdu, bytes, &event, 15, 1, false); wrong.sizeOfIOA = 2; asdu.parameters = &wrong; CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); ci_batch(&asdu, bytes, &event, 15, 1, false); bytes[4] = 0xff; bytes[5] = 0xff; CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); } ci_done(&event); event = gi_data(); ci_batch(&asdu, bytes, &event, 15, 1, false); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); gi_done(&event, 0, 0); } ci_close(); { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; IEC60870_Asdu_t asdu; uint8_t bytes[249]; IEC104_Interrogation_Config_Default(&config); config.max_objects_per_asdu = 4; counter_custom_config = &config; ci_open(); event = ci_data(5); ci_batch(&asdu, bytes, &event, 15, 5, false); CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_ERROR); ci_done(&event); ci_close(); } } /* 计数量忙只拒绝本实例的新请求;k=1、总召每帧一点时,两种业务仍须交错推进。 */ static void test_counter_busy_independent(void) { IEC104_InterrogationEvent_t event; const uint8_t *frames[32]; size_t lengths[32]; ci_open(); event = ci_begin(5); ci_send(1, 1); ci_process(); CHECK(app.ci.rejected == 1 && app.ci.gi_pending); app_request(gi_request, sizeof(gi_request)); app_finish(); CHECK(app.gi.gi_submitted == 1); CHECK(IEC104_Interrogation_Reply_Begin(app.counter, &event, true) == IEC104_INTERROGATION_REPLY_OK); ci_process(); event = ci_take(IEC104_INTERROGATION_NEED_DATA); ci_done(&event); CHECK(collect_asdus(frames, lengths) == 11); ci_close(); { IEC104_InterrogationConfig_t config; unsigned step, acked = 0, first_counter = 32, last_general = 0, i; IEC104_Interrogation_Config_Default(&config); config.max_objects_per_asdu = 1; custom_config = &config; custom_window = 1; ci_open(); queue_request(gi_request, sizeof(gi_request)); queue_request(ci_request, sizeof(ci_request)); app_poll(); for (step = 0; step < 128; ++step) { unsigned sent; CHECK(Example_Step(&app)); app_poll(); sent = collect_asdus(frames, lengths); if (sent != acked) { push_ack(sent); acked = sent; app_poll(); } if (app.gi.pending_requests == 0 && app.ci.pending_requests == 0) { break; } } CHECK(step < 128 && acked == 30 && app.counter_completed == 1 && app.gi.gi_submitted == 1); for (i = 0; i < acked; ++i) { if (frames[i][0] == 15 && first_counter == 32) { first_counter = i; } if (frames[i][0] == 1 || frames[i][0] == 3 || frames[i][0] == 11) { last_general = i; } } CHECK(first_counter < last_general); ci_close(); custom_config = NULL; custom_window = 32; } } /* 总请求按确认、两批数据、终止逐字节核对IOA、初值和BCR状态,并检查整轮快照一致性。 */ static void test_counter_general_read(void) { static const int32_t expected[] = {120000, 345600, 7890, 0, 1, 65535, INT32_MAX, 42}; const uint8_t *frames[32]; size_t lengths[32]; unsigned i, j; ci_open(); ci_send(5, 1); app_finish(); CHECK(collect_asdus(frames, lengths) == 4); CHECK(frames[0][0] == 101 && frames[0][2] == 7 && lengths[0] == 10); CHECK(frames[3][0] == 101 && frames[3][2] == 10 && lengths[3] == 10); for (i = 1; i < 3; ++i) { CHECK(frames[i][0] == 15 && frames[i][1] == 4 && frames[i][2] == 37 && frames[i][3] == 0x5a); CHECK(lengths[i] == 38); for (j = 0; j < 4; ++j) { const uint8_t *p = frames[i] + 6 + j * 8; unsigned n = (i - 1) * 4 + j; uint32_t value = (uint32_t)p[3] | (uint32_t)p[4] << 8 | (uint32_t)p[5] << 16 | (uint32_t)p[6] << 24; CHECK(((unsigned)p[0] | (unsigned)p[1] << 8) == 3001 + n && p[2] == 0); CHECK(value == (uint32_t)expected[n] && p[7] == 0); } } CHECK(app.counter_completed == 1 && app.other_rejected == 0); ci_close(); /* 首批发送受阻后改写整个冻结区,两批仍须使用已固定的值、序号和品质。 */ ci_open(); ci_send(5, 1); ci_process(); CHECK(Example_Drain_Counter(&app)); ci_process(); congest(&last_request.connection); CHECK(Example_Drain_Counter(&app)); CHECK(app.counter_pending && app.counter_cursor == 0 && app.counter_request != 0); for (i = 0; i < EXAMPLE_COUNTER_COUNT; ++i) { app.counter_frozen[i].value = -123; app.counter_frozen[i].sequence_number = 7; app.counter_frozen[i].has_carry = 1; app.counter_frozen[i].is_adjusted = 1; app.counter_frozen[i].is_invalid = 1; } fake.send_blocked = 0; app_finish(); CHECK(collect_asdus(frames, lengths) == 4); CHECK(frames[1][9] == 0xc0 && frames[1][10] == 0xd4 && frames[1][11] == 1 && frames[1][13] == 0); CHECK(frames[2][9] == 1 && frames[2][10] == 0 && frames[2][13] == 0); CHECK(app.counter_completed == 1); ci_close(); } /* 示例只配置第1、2组,分别使用COT38、39;协议合法但未配置的第3、4组由应用拒绝。 */ static void test_counter_group_read(void) { unsigned q; for (q = 1; q <= 4; ++q) { const uint8_t *frames[32]; size_t lengths[32]; ci_open(); ci_send((uint8_t)q, 1); app_finish(); CHECK(collect_asdus(frames, lengths) == (q <= 2 ? 3U : 1U)); if (q <= 2) { CHECK(frames[1][2] == 37 + q && frames[1][1] == 4); } else { CHECK(frames[0][2] == 0x47); } ci_close(); } } /* 记录动作次数并模拟执行耗时,区分“请求尚未执行”和“已执行、确认仍在等待”。 */ typedef struct { unsigned calls; uint64_t elapsed; bool success; } CounterActionProbe; static bool counter_action(void *context, const IEC104_InterrogationRequest_t *request) { CounterActionProbe *probe = context; CHECK(request->freeze >= 1 && request->freeze <= 3 && !request->is_test); ++probe->calls; fake.now += probe->elapsed; return probe->success; } /* 三种动作只确认、不发终止;检查清零后再冻结的值、SQ回绕和分组隔离。 * TEST及WAIT不调用动作,已消费事件重试为STALE,应用拒绝只执行一次。 */ static void test_counter_freeze_modes(void) { unsigned frz; for (frz = 1; frz <= 3; ++frz) { const uint8_t *frames[32]; size_t lengths[32]; uint8_t test_request[10]; ci_open(); memcpy(test_request, ci_request, 10); test_request[2] = 0x86; test_request[9] = (uint8_t)((frz << 6) | 5); app_request(test_request, 10); app_finish(); CHECK(app.counter_current[0] == 120000 && app.counter_frozen[0].value == 120000 && app.counter_frozen[0].sequence_number == 0); CHECK(collect_asdus(frames, lengths) == 1 && frames[0][2] == 0xc7); fake.sent_bytes = 0; ci_send((uint8_t)((frz << 6) | 5), 1); app_finish(); CHECK(collect_asdus(frames, lengths) == 1 && frames[0][2] == 7); CHECK(app.counter_completed == 1); ci_send(5, 1); app_finish(); CHECK(collect_asdus(frames, lengths) == 5 && frames[2][13] == (frz == 3 ? 0 : 1)); if (frz == 2 || frz == 3) { ci_send(0x45, 1); app_finish(); ci_send(5, 1); app_finish(); CHECK(collect_asdus(frames, lengths) == 10); CHECK(frames[7][9] == 0 && frames[7][10] == 0 && frames[7][11] == 0 && frames[7][12] == 0); } ci_close(); } { unsigned i; ci_open(); for (i = 0; i < EXAMPLE_COUNTER_COUNT; ++i) { app.counter_frozen[i].sequence_number = 31; } ci_send(0x41, 1); app_finish(); for (i = 0; i < EXAMPLE_COUNTER_COUNT; ++i) { CHECK(app.counter_frozen[i].sequence_number == (i < 4 ? 0 : 31)); } ci_close(); } { IEC104_InterrogationEvent_t event; CounterActionProbe probe = {0, 0, true}; ci_open(); event = ci_begin(0x85); queue_request(gi_request, sizeof(gi_request)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_WAIT); CHECK(probe.calls == 0); app_poll(); CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_OK); CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_STALE); CHECK(probe.calls == 1); ci_process(); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); ci_empty(); ci_close(); } { uint8_t request[10]; IEC104_InterrogationEvent_t event; CounterActionProbe probe = {0, 0, true}; ci_open(); memcpy(request, ci_request, 10); request[2] = 0x86; request[9] = 0xc5; app_request(request, 10); ci_process(); event = ci_take(IEC104_INTERROGATION_BEGIN); CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_OK); CHECK(probe.calls == 0 && ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_REJECTED); ci_process(); ci_close(); } { IEC104_InterrogationEvent_t event; CounterActionProbe probe = {0, 0, false}; ci_open(); event = ci_begin(0x45); CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_OK); CHECK(probe.calls == 1 && ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_REJECTED); ci_process(); CHECK(app.ci.negative_submitted == 1); ci_close(); } } /* STOP和断线须同时终结两种召唤,旧取数事件失效;重新激活或连接后可再次读取。 */ static void test_counter_stop_disconnect(void) { unsigned mode; for (mode = 0; mode < 2; ++mode) { IEC104_InterrogationEvent_t ce, ge; ci_open(); ce = ci_data(5); ge = gi_data(); if (mode == 0) { push(stop_act, sizeof(stop_act)); app_poll(); } else { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); app_poll(); } CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == (mode == 0 ? IEC104_INTERROGATION_FINISH_STOPPED : IEC104_INTERROGATION_FINISH_DISCONNECTED)); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == (mode == 0 ? IEC104_INTERROGATION_FINISH_STOPPED : IEC104_INTERROGATION_FINISH_DISCONNECTED)); CHECK(IEC104_Interrogation_Complete_Data(app.counter, &ce) == IEC104_INTERROGATION_REPLY_STALE); CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &ge) == IEC104_INTERROGATION_REPLY_STALE); if (mode == 0) { push(start_act, sizeof(start_act)); app_poll(); } else { app_connect(); } ci_send(5, 1); app_finish(); CHECK(app.counter_completed == 1); ci_close(); } } /* 动作开始前超时不执行回调;读取超时关闭共享连接,并使另一实例收到断线终结通知。 */ static void test_counter_timeouts(void) { IEC104_InterrogationConfig_t config; IEC104_InterrogationEvent_t event; CounterActionProbe probe = {0, 0, true}; IEC104_TcpServerStatus_t service; IEC104_Interrogation_Config_Default(&config); config.begin_timeout_ms = 100; config.data_timeout_ms = 100; config.total_timeout_ms = 200; counter_custom_config = &config; ci_open(); event = ci_begin(0x85); fake.now += 100; CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_STALE); CHECK(probe.calls == 0 && ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_BEGIN_TIMEOUT); ci_process(); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && service.connected); ci_close(); counter_custom_config = &config; ci_open(); event = ci_data(5); (void)gi_begin(); fake.now += 100; ci_process(); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_DATA_TIMEOUT); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK && !service.connected); app_poll(); CHECK(gi_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_DISCONNECTED); ci_close(); /* 动作已执行,即使确认排队跨过业务期限,也不能改成否定确认或再次执行动作。 */ counter_custom_config = &config; ci_open(); event = ci_begin(0x85); congest(&last_request.connection); probe.elapsed = 150; CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_OK); fake.now += 300; ci_process(); CHECK(probe.calls == 1 && app.ci.timeouts == 0 && app.ci.negative_submitted == 0); fake.send_blocked = 0; ci_process(); ci_process(); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); ci_close(); } /* 数据WAIT不推进批次,恢复后使用重新组好的数据;终止等待也必须保留并继续提交。 */ static void test_counter_send_wait(void) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t asdu; uint8_t bytes[249]; unsigned i; ci_open(); event = ci_data(5); congest(&last_request.connection); ci_batch(&asdu, bytes, &event, 15, 1, false); CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_WAIT); CHECK(event.batch_index == 0); fake.send_blocked = 0; for (i = 0; i < 4; ++i) { app_poll(); } bytes[9] = 42; CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); ci_process(); event = ci_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.batch_index == 1); congest(&last_request.connection); CHECK(IEC104_Interrogation_Complete_Data(app.counter, &event) == IEC104_INTERROGATION_REPLY_OK); ci_process(); CHECK(app.ci.waiting); fake.send_blocked = 0; ci_process(); ci_process(); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); ci_close(); } /* 三站读取保持全部确认、逐站数据、全部终止;动作广播逐站确认,中间站拒绝不影响其他站。 * 每站使用实际CA,动作只产生一次确认和一次终结通知,不额外发送ACT_TERM。 */ static void test_counter_broadcast(void) { unsigned mode; for (mode = 0; mode < 3; ++mode) { IEC104_Station_t stations[2] = {{0}}; unsigned i; const uint8_t *frames[32]; size_t lengths[32]; CounterActionProbe probe = {0, 0, true}; ci_open(); for (i = 0; i < 2; ++i) { CHECK(IEC104_Slave_Add_Station(app.slave, &stations[i], (uint16_t)(i + 2), NULL) == IEC104_STATION_OK); } ci_send(mode == 0 ? 5 : 0x45, IEC104_CA_BROADCAST); for (i = 0; i < 3; ++i) { IEC104_InterrogationEvent_t event; ci_process(); event = ci_take(IEC104_INTERROGATION_BEGIN); CHECK(event.request.common_address == i + 1); if (mode == 0) { CHECK(IEC104_Interrogation_Reply_Begin(app.counter, &event, true) == IEC104_INTERROGATION_REPLY_OK); } else { if (mode == 2 && i == 1) { CHECK(IEC104_Interrogation_Reply_Begin(app.counter, &event, false) == IEC104_INTERROGATION_REPLY_OK); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_REJECTED); ci_process(); } else { CHECK(IEC104_CounterInterrogation_Execute_Begin(app.counter, &event, counter_action, &probe) == IEC104_INTERROGATION_REPLY_OK); ci_process(); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); } } } if (mode == 0) { for (i = 0; i < 3; ++i) { IEC104_InterrogationEvent_t event; IEC60870_Asdu_t asdu; uint8_t bytes[249]; ci_process(); event = ci_take(IEC104_INTERROGATION_NEED_DATA); CHECK(event.request.common_address == i + 1); ci_batch(&asdu, bytes, &event, 15, 1, false); CHECK(IEC104_Interrogation_Submit_Batch(app.counter, &event, &asdu) == IEC104_INTERROGATION_REPLY_OK); ci_process(); event = ci_take(IEC104_INTERROGATION_NEED_DATA); CHECK(IEC104_Interrogation_Complete_Data(app.counter, &event) == IEC104_INTERROGATION_REPLY_OK); } ci_process(); ci_process(); for (i = 0; i < 3; ++i) { CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_COMPLETED); } } app_poll(); CHECK(collect_asdus(frames, lengths) == (mode == 0 ? 9U : 3U)); for (i = 0; i < (mode == 0 ? 9U : 3U); ++i) { CHECK(frames[i][4] == i % 3 + 1 && frames[i][5] == 0); CHECK(frames[i][2] == (mode == 2 && i == 1 ? 0x47 : i < 3 ? 7 : i < 6 ? 37 : 10)); } CHECK(probe.calls == (mode == 0 ? 0U : mode == 1 ? 3U : 2U)); ci_empty(); for (i = 0; i < 2; ++i) { CHECK(IEC104_Slave_Remove_Station(app.slave, &stations[i]) == IEC104_STATION_OK); } ci_close(); } } /* Type101由计数量实例接手;未知类型仍走兜底,即使计数量正在等待应用决定也能应答。 */ static void test_dispatch_fallback(void) { uint8_t request[10]; IEC104_InterrogationEvent_t event; ci_open(); event = ci_begin(5); memcpy(request, ci_request, 10); request[0] = 200; app_request(request, 10); CHECK(app.other_rejected == 1 && app.other_submitted == 1 && app.ci.gi_pending); app_finish(); CHECK(IEC104_Interrogation_Reply_Begin(app.counter, &event, false) == IEC104_INTERROGATION_REPLY_OK); CHECK(ci_take(IEC104_INTERROGATION_FINISHED).reason == IEC104_INTERROGATION_FINISH_REJECTED); ci_process(); ci_close(); } static void check_other_reply(const uint8_t *actual, size_t actual_length, const uint8_t *request, size_t length) { size_t index; CHECK(actual_length == length); for (index = 0; index < length; ++index) { CHECK(actual[index] == (index == 2 ? (uint8_t)((request[2] & 0x80U) | 0x40U | 44U) : request[index])); } } /* 同一Poll交付两条命令时当场提交;窗口/写阻塞由HIGH承担,重复命令也逐条应答。 */ static void test_other_batch(void) { unsigned order, blocked; for (order = 0; order < 3; ++order) { for (blocked = 0; blocked < 3; ++blocked) { const uint8_t *requests[2] = { order == 1 ? read_request : fallback_request, order == 0 ? read_request : fallback_request }; size_t sizes[2] = { order == 1 ? sizeof(read_request) : sizeof(fallback_request), order == 0 ? sizeof(read_request) : sizeof(fallback_request) }; const uint8_t *frames[32]; size_t lengths[32]; IEC104_TcpServerStatus_t status; custom_window = blocked == 1 ? 1U : 32U; app_open(); fake.send_blocked = blocked == 2; queue_request(requests[0], sizes[0]); queue_request(requests[1], sizes[1]); app_poll(); CHECK(app.other_rejected == 2 && app.other_submitted == 2); CHECK(app.other_overloads == 0 && app.other_cancelled == 0); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && status.connected); if (blocked == 1) { CHECK(collect_asdus(frames, lengths) == 1 && status.high_queue.stored_asdu_count == 1); push_ack(1); } else if (blocked == 2) { CHECK(collect_asdus(frames, lengths) == 0 && status.high_queue.stored_asdu_count == 1); fake.send_blocked = 0; } app_finish(); CHECK(collect_asdus(frames, lengths) == 2); check_other_reply(frames[0], lengths[0], requests[0], sizes[0]); check_other_reply(frames[1], lengths[1], requests[1], sizes[1]); app_close(); custom_window = 32; } } } /* HIGH不足时只丢新兜底请求,恢复后没有旧请求重试,原连接继续接受新命令。 */ static void test_other_overload(void) { const uint8_t *frames[32]; size_t lengths[32]; uint8_t fresh[sizeof(read_request)]; IEC104_TcpServerStatus_t before, after; app_open(); app_request(fallback_request, sizeof(fallback_request)); CHECK(app.other_submitted == 1); congest(&last_request.connection); CHECK(IEC104_Slave_Get_Status(app.slave, &before, NULL) == IEC104_TCP_OK); queue_request(read_request, sizeof(read_request)); queue_request(read_request, sizeof(read_request)); app_poll(); CHECK(IEC104_Slave_Get_Status(app.slave, &after, NULL) == IEC104_TCP_OK && after.connected); CHECK(after.high_queue.stored_asdu_count == before.high_queue.stored_asdu_count); CHECK(app.other_rejected == 3 && app.other_submitted == 1 && app.other_overloads == 2); CHECK(app.other_cancelled == 0 && app.gi.dropped_requests + app.other_overloads == 2); fake.send_blocked = 0; app_finish(); CHECK(collect_asdus(frames, lengths) == 1); memcpy(fresh, read_request, sizeof(fresh)); fresh[3] = 0x63; app_request(fresh, sizeof(fresh)); CHECK(app.other_submitted == 2 && app.other_overloads == 2); app_finish(); CHECK(collect_asdus(frames, lengths) == 2); check_other_reply(frames[0], lengths[0], fallback_request, sizeof(fallback_request)); check_other_reply(frames[1], lengths[1], fresh, sizeof(fresh)); CHECK(IEC104_Slave_Get_Status(app.slave, &after, NULL) == IEC104_TCP_OK && after.connected); app_close(); } /* 后续STOP或STOP/START已经受理时,当前请求取消;下一激活阶段只应答新请求。 */ static void test_other_state_change(void) { unsigned restarted; for (restarted = 0; restarted < 2; ++restarted) { const uint8_t *frames[32]; size_t lengths[32]; app_open(); queue_request(fallback_request, sizeof(fallback_request)); push(stop_act, sizeof(stop_act)); if (restarted) { push(start_act, sizeof(start_act)); } app_poll(); CHECK(app.other_rejected == 1 && app.other_submitted == 0 && app.other_cancelled == 1); CHECK(app.other_overloads == 0 && collect_asdus(frames, lengths) == 0); if (!restarted) { push(start_act, sizeof(start_act)); app_poll(); } app_request(read_request, sizeof(read_request)); CHECK(app.other_submitted == 1 && app.other_cancelled == 1); app_finish(); CHECK(collect_asdus(frames, lengths) == 1); check_other_reply(frames[0], lengths[0], read_request, sizeof(read_request)); app_close(); } } /* 单总召仍等待应用BEGIN时,其他命令即可提交,不必等总召结束。 */ static void test_other_during_general(void) { const uint8_t *frames[32]; size_t lengths[32]; app_open(); app_request(gi_request, sizeof(gi_request)); CHECK(app.gi.gi_pending); app_request(read_request, sizeof(read_request)); CHECK(app.gi.gi_pending && app.other_submitted == 1); CHECK(collect_asdus(frames, lengths) == 1); check_other_reply(frames[0], lengths[0], read_request, sizeof(read_request)); app_finish(); check_gi(1, 0x5a); app_close(); } /* Clock actions are controlled; these tests never call the host Clock_Set backend. */ static const uint8_t cs_request[] = {103, 1, 6, 0x71, 1, 0, 0, 0, 0, 0x39, 0x30, 34, 12, 28, 10, 26}; typedef struct { unsigned calls; bool success, reentrant; uint64_t elapsed; IEC104_ClockSynchronizationRequest_t last; } ClockActionProbe; static ClockActionProbe cs_probe; static bool cs_action(void *context, const IEC104_ClockSynchronizationRequest_t *request) { ClockActionProbe *probe = context; IEC104_ClockSynchronizationStatus_t before, after; CHECK(request != NULL && request->generation != 0U && request->receive_serial != 0U); CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &before) && before.confirm_pending); ++probe->calls; probe->last = *request; if (probe->reentrant) { CHECK(!before.timer_active); CHECK(!IEC104_ClockSynchronization_Process(app.clock)); CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, context) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_ERROR); CHECK(IEC104_ClockSynchronization_Handle_Event(app.clock, &last_request) == IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR); CHECK(!IEC104_ClockSynchronization_Destroy(app.clock)); IEC104_ClockSynchronization_Cancel(app.clock); CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &after)); CHECK(after.pending_requests == before.pending_requests && after.confirm_pending); } fake.now += probe->elapsed; return probe->success; } static void cs_open(const IEC104_ClockSynchronizationConfig_t *config) { memset(&cs_probe, 0, sizeof(cs_probe)); cs_probe.success = true; clock_enabled = true; clock_custom_config = config; app_open(); CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs) && app.cs.active); } static void cs_close(void) { cs_test_now_step = 0U; app_close(); clock_enabled = false; clock_custom_config = NULL; } static void cs_process(void) { CHECK(IEC104_ClockSynchronization_Process(app.clock)); CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs)); } static void cs_finish(void) { unsigned i; for (i = 0U; i < 16U; ++i) { app_poll(); cs_process(); } CHECK(app.cs.pending_requests == 0U); } static void cs_send(uint8_t oa, uint16_t ca) { uint8_t request[sizeof(cs_request)]; memcpy(request, cs_request, sizeof(request)); request[3] = oa; request[4] = (uint8_t)ca; request[5] = (uint8_t)(ca >> 8); app_request(request, sizeof(request)); } static void cs_timed_event(uint8_t value) { uint8_t bytes[] = {30, 1, 3, 0, 1, 0, 0xe9, 3, 0, 0, 0x39, 0x30, 34, 12, 28, 10, 26}; IEC60870_Asdu_t asdu; bytes[9] = value; CHECK(IEC60870_Asdu_Create_From_Buffer(&asdu, IEC104_Slave_Get_Application_Layer_Parameters(app.slave), bytes, sizeof(bytes)) == IEC60870_ASDU_OK); CHECK(IEC104_Slave_Enqueue_Event(app.slave, &asdu) == IEC104_TCP_SUBMIT_OK); } static void cs_execute(void) { CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_OK); } static void cs_reply(const uint8_t *bytes, size_t length, const uint8_t *request, uint8_t cot, uint16_t ca) { size_t i; CHECK(length == sizeof(cs_request)); for (i = 0U; i < length; ++i) { uint8_t expected = i == 1U ? 1U : i == 2U ? cot : i == 4U ? (uint8_t)ca : i == 5U ? (uint8_t)(ca >> 8) : request[i]; CHECK(bytes[i] == expected); } } static void test_clock_interfaces(void) { IEC104_ClockSynchronizationConfig_t config; IEC104_ClockSynchronizationStatus_t status; IEC104_ClockSynchronization_Config_Default(&config); CHECK(config.master_utc_offset_minutes == 480 && config.execute_timeout_ms == 15000U); CHECK(IEC104_ClockSynchronization_Create(NULL, &config) == NULL); CHECK(!IEC104_ClockSynchronization_Destroy(NULL)); CHECK(!IEC104_ClockSynchronization_Process(NULL)); CHECK(!IEC104_ClockSynchronization_Get_Status(NULL, &status)); CHECK(IEC104_ClockSynchronization_Handle_Event(NULL, NULL) == IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR); CHECK(IEC104_ClockSynchronization_Execute_Next(NULL, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_ERROR); app_open(); config.master_utc_offset_minutes = 1440; CHECK(IEC104_ClockSynchronization_Create(app.slave, &config) == NULL); config.master_utc_offset_minutes = -1440; CHECK(IEC104_ClockSynchronization_Create(app.slave, &config) == NULL); app_close(); cs_open(NULL); IEC104_ClockSynchronization_Config_Default(&config); CHECK(IEC104_ClockSynchronization_Create(app.slave, &config) == NULL); CHECK(!IEC104_ClockSynchronization_Get_Status(app.clock, NULL)); CHECK(IEC104_ClockSynchronization_Handle_Event(app.clock, NULL) == IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR); CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, NULL, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_ERROR); CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY); cs_send(0x71, 1); CHECK(!IEC104_ClockSynchronization_Destroy(app.clock)); #if IEC104_TCP_SERVER_CAPACITY > 1 { IEC104_Slave_t *other_slave = IEC104_Slave_Create(); IEC104_ClockSynchronization_t *other_clock; IEC104_SlaveConfig_t slave_config; CHECK(other_slave != NULL); IEC104_Slave_Config_Default(&slave_config); CHECK(IEC104_Slave_Init(other_slave, &slave_config, NULL) == IEC104_TCP_OK); other_clock = IEC104_ClockSynchronization_Create(other_slave, &config); CHECK(other_clock != NULL); CHECK(IEC104_ClockSynchronization_Handle_Event(other_clock, &last_request) == IEC104_CLOCK_SYNCHRONIZATION_EVENT_ERROR); CHECK(IEC104_ClockSynchronization_Destroy(other_clock)); CHECK(IEC104_Slave_Close(other_slave, NULL) == IEC104_TCP_OK); CHECK(IEC104_Slave_Destroy(other_slave, NULL) == IEC104_TCP_OK); } #endif CHECK(cs_probe.calls == 0U); IEC104_ClockSynchronization_Cancel(app.clock); cs_close(); } static void test_clock_execute_and_echo(void) { const uint8_t *frames[32]; size_t lengths[32]; IEC104_SlaveEvent_t before; cs_open(NULL); app_request(cs_request, sizeof(cs_request)); before = last_request; cs_process(); CHECK(cs_probe.calls == 0U && app.cs.execute_pending); fake.now += 1655U; cs_probe.elapsed = 400U; cs_execute(); CHECK(cs_probe.calls == 1U && cs_probe.last.accepted_ms == 1000U && cs_probe.last.base_ms == 2655U); CHECK(cs_probe.last.local_time.year == 2026U && cs_probe.last.local_time.month == 10U && cs_probe.last.local_time.day == 28U && cs_probe.last.local_time.hour == 12U && cs_probe.last.local_time.minute == 34U && cs_probe.last.local_time.second == 14U && cs_probe.last.local_time.ms == 0U && cs_probe.last.station_id == IEC104_Station_Get_Id(&app.station)); CHECK(memcmp(&before, &last_request, sizeof(before)) == 0); cs_finish(); CHECK(app.cs.executed == 1U && app.cs.synchronized == 1U); CHECK(collect_asdus(frames, lengths) == 1U); cs_reply(frames[0], lengths[0], cs_request, 7, 1); CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY); app_request(cs_request, sizeof(cs_request)); fake.now += 100U; cs_execute(); cs_finish(); CHECK(cs_probe.calls == 2U && cs_probe.last.accepted_ms == 3055U && cs_probe.last.base_ms == 3155U); CHECK(cs_probe.last.local_time.second == 12U && cs_probe.last.local_time.ms == 445U); CHECK(collect_asdus(frames, lengths) == 2U); cs_reply(frames[1], lengths[1], cs_request, 7U, 1U); cs_close(); } static void test_clock_timezone_rollover(void) { IEC104_ClockSynchronizationConfig_t config; uint8_t request[sizeof(cs_request)]; IEC104_ClockSynchronization_Config_Default(&config); config.master_utc_offset_minutes = 0; cs_open(&config); memcpy(request, cs_request, sizeof(request)); request[12] |= 0x80U; app_request(request, sizeof(request)); cs_execute(); CHECK(cs_probe.last.master_time.is_summer_time && cs_probe.last.local_time.hour == 19U); cs_finish(); cs_close(); cs_open(NULL); memcpy(request, cs_request, sizeof(request)); request[9] = 0xfc; request[10] = 0xe9; request[11] = 59; request[12] = 23; request[13] = 31; request[14] = 12; request[15] = 26; app_request(request, sizeof(request)); fake.now += 200U; cs_execute(); CHECK(cs_probe.last.local_time.year == 2027U && cs_probe.last.local_time.month == 1U && cs_probe.last.local_time.day == 1U && cs_probe.last.local_time.hour == 0U && cs_probe.last.local_time.minute == 0U && cs_probe.last.local_time.second == 0U && cs_probe.last.local_time.ms == 100U); cs_finish(); cs_close(); cs_open(NULL); memcpy(request, cs_request, sizeof(request)); request[9] = 0; request[10] = 0; request[11] = 0; request[12] = 0x80; request[13] = 1; request[14] = 1; request[15] = 0; app_request(request, sizeof(request)); cs_execute(); cs_finish(); CHECK(cs_probe.last.local_time.year == 1999U && cs_probe.last.local_time.month == 12U && cs_probe.last.local_time.day == 31U && cs_probe.last.local_time.hour == 23U); { const uint8_t *frames[32]; size_t lengths[32]; CHECK(collect_asdus(frames, lengths) == 1U); cs_reply(frames[0], lengths[0], request, 7U, 1U); } cs_close(); } static void test_clock_validation(void) { static const struct { unsigned offset; uint8_t value, cot; } cases[] = { {2, 5, 45}, {4, 2, 46}, {6, 1, 47}, {2, 0x86, 0xc7}, {2, 0x46, 0x47}, {1, 0x81, 0x47}, {11, 0xa2, 0x47}, {14, 0, 0x47}, {11, 0x62, 0x47} }; unsigned i; for (i = 0U; i < sizeof(cases) / sizeof(cases[0]); ++i) { uint8_t request[sizeof(cs_request)]; const uint8_t *frames[32]; size_t lengths[32]; cs_open(NULL); memcpy(request, cs_request, sizeof(request)); request[cases[i].offset] = cases[i].value; if (i < 2U) { request[4] = 2U; request[6] = 1U; } app_request(request, sizeof(request)); cs_finish(); CHECK(cs_probe.calls == 0U && collect_asdus(frames, lengths) == 1U); cs_reply(frames[0], lengths[0], request, cases[i].cot < 64U ? (uint8_t)(cases[i].cot | 0x40U) : cases[i].cot, request[4]); cs_close(); } { uint8_t request[26]; const uint8_t *frames[32]; size_t lengths[32]; cs_open(NULL); memcpy(request, cs_request, sizeof(cs_request)); request[16] = 0x5a; app_request(request, 17U); cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][2] == 0x47U); memcpy(request + 16, cs_request + 6, 10); request[1] = 2; app_request(request, sizeof(request)); cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && cs_probe.calls == 0U); memcpy(request, cs_request, sizeof(cs_request)); request[1] = 0U; app_request(request, sizeof(cs_request)); cs_finish(); request[1] = 1U; app_request(request, sizeof(cs_request) - 1U); cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && cs_probe.calls == 0U); cs_close(); } } static void test_clock_fifo_and_full(void) { const uint8_t *frames[32]; size_t lengths[32]; unsigned i; uint8_t request[sizeof(cs_request)]; cs_open(NULL); cs_send(0x70, 1); memcpy(request, cs_request, sizeof(request)); request[3] = 0x71; request[2] = 5; app_request(request, sizeof(request)); cs_send(0x72, 1); cs_send(0x73, 1); cs_send(0x74, IEC104_CA_BROADCAST); cs_process(); CHECK(collect_asdus(frames, lengths) == 0U && app.cs.pending_requests == 4U); CHECK(app.cs.dropped_requests == 1U && app.cs.overloads == 1U && cs_probe.calls == 0U); for (i = 0U; i < 3U; ++i) { cs_execute(); } cs_finish(); CHECK(cs_probe.calls == 3U && collect_asdus(frames, lengths) == 4U); for (i = 0U; i < 4U; ++i) { CHECK(frames[i][3] == 0x70U + i && frames[i][2] == (i == 1U ? 0x6dU : 7U)); } cs_close(); cs_open(NULL); queue_request(cs_request, sizeof(cs_request)); queue_request(cs_request, sizeof(cs_request)); app_poll(); cs_execute(); cs_execute(); cs_finish(); CHECK(cs_probe.calls == 2U && app.cs.submitted == 2U && collect_asdus(frames, lengths) == 2U); cs_close(); } static void test_clock_action_contract(void) { const uint8_t *frames[32]; size_t lengths[32]; IEC104_ClockSynchronizationConfig_t config; IEC104_ClockSynchronization_Config_Default(&config); config.execute_timeout_ms = 10U; cs_open(&config); cs_probe.reentrant = true; cs_probe.elapsed = 100U; cs_send(0x71, 1); cs_execute(); cs_finish(); CHECK(cs_probe.calls == 1U && app.cs.timeouts == 0U && app.cs.synchronized == 1U); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][2] == 7U); cs_close(); cs_open(NULL); cs_probe.success = false; cs_send(0x71, 1); cs_execute(); cs_finish(); CHECK(cs_probe.calls == 1U && app.cs.executed == 1U && app.cs.synchronized == 0U); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][2] == 0x47U); cs_close(); } static void test_clock_deadlines(void) { IEC104_ClockSynchronizationConfig_t config; const uint8_t *frames[32]; size_t lengths[32]; unsigned via_execute; IEC104_ClockSynchronization_Config_Default(&config); config.execute_timeout_ms = 10U; for (via_execute = 0U; via_execute < 2U; ++via_execute) { cs_open(&config); cs_send(0x71, 1); fake.now += 9U; CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs)); CHECK(app.cs.timer_active && app.cs.timeout_remaining_ms == 1U); ++fake.now; if (via_execute) { CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY); } else { cs_process(); } cs_finish(); CHECK(cs_probe.calls == 0U && app.cs.timeouts == 1U && !app.cs.timer_active); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][2] == 0x47U); cs_close(); } cs_open(&config); cs_send(0x71, 1); fake.now = 1009U; cs_test_now_step = 2U; (void)IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe); cs_test_now_step = 0U; CHECK(cs_probe.calls == 0U || cs_probe.last.base_ms < cs_probe.last.accepted_ms + 10U); cs_finish(); cs_close(); config.execute_timeout_ms = 0U; cs_open(&config); cs_send(0x71, 1); fake.now += 100U; cs_execute(); cs_finish(); CHECK(app.cs.timeouts == 0U && cs_probe.calls == 1U); cs_close(); config.execute_timeout_ms = 10U; cs_open(&config); cs_send(0x71, 1); fake.now = 999U; CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY); fake.now = 1001U; cs_finish(); CHECK(cs_probe.calls == 0U && app.cs.time_errors + app.cs.timeouts == 1U); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][2] == 0x47U); cs_close(); } static void test_clock_time_errors(void) { IEC104_ClockSynchronizationConfig_t config; unsigned mode; IEC104_ClockSynchronization_Config_Default(&config); config.execute_timeout_ms = 0U; for (mode = 0U; mode < 3U; ++mode) { uint8_t request[sizeof(cs_request)]; const uint8_t *frames[32]; size_t lengths[32]; cs_open(&config); memcpy(request, cs_request, sizeof(request)); if (mode == 2U) { request[9] = 0xfc; request[10] = 0xe9; request[11] = 59; request[12] = 23; request[13] = 31; request[14] = 12; request[15] = 99; } app_request(request, sizeof(request)); fake.now = mode == 0U ? 999U : mode == 1U ? UINT64_MAX : 1200U; CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY); fake.now = 1200U; cs_finish(); CHECK(cs_probe.calls == 0U && app.cs.time_errors == 1U && app.cs.timeouts == 0U); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][2] == 0x47U); cs_close(); } { uint8_t request[sizeof(cs_request)]; const uint8_t *frames[32]; size_t lengths[32]; config.master_utc_offset_minutes = 0; cs_open(&config); memcpy(request, cs_request, sizeof(request)); request[9] = 0x5f; request[10] = 0xea; request[11] = 59U; request[12] = 23U; request[13] = 31U; request[14] = 12U; request[15] = 99U; app_request(request, sizeof(request)); cs_finish(); CHECK(app.cs.accepted == 0U && cs_probe.calls == 0U && app.cs.rejected == 1U); CHECK(collect_asdus(frames, lengths) == 1U); cs_reply(frames[0], lengths[0], request, 0x47U, 1U); cs_close(); } } static void test_clock_try_order_and_wait(void) { const uint8_t *frames[32]; size_t lengths[32]; IEC104_TcpServerStatus_t status; custom_window = 1U; cs_open(NULL); cs_send(0x71, 1); cs_timed_event(0); app_poll(); cs_timed_event(1); cs_execute(); cs_process(); CHECK(cs_probe.calls == 1U && app.cs.confirm_pending && app.cs.waits != 0U && !app.cs.timer_active); CHECK(!app.cs.need_process && IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK); CHECK(status.high_queue.stored_asdu_count == 0U && collect_asdus(frames, lengths) == 1U); CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_EMPTY); push_ack(1); app_poll(); cs_process(); CHECK(collect_asdus(frames, lengths) == 2U && frames[0][0] == 30U && frames[1][0] == 30U && frames[1][9] == 1U); CHECK(app.cs.confirm_pending && cs_probe.calls == 1U); push_ack(2); cs_finish(); CHECK(collect_asdus(frames, lengths) == 3U && frames[2][0] == 103U && frames[2][2] == 7U); CHECK(cs_probe.calls == 1U && app.cs.submitted == 1U); cs_close(); custom_window = 32U; { uint8_t request[sizeof(cs_request)]; IEC104_TcpServerStatus_t before; cs_open(NULL); app_request(read_request, sizeof(read_request)); congest(&last_request.connection); CHECK(IEC104_Slave_Get_Status(app.slave, &before, NULL) == IEC104_TCP_OK); memcpy(request, cs_request, sizeof(request)); request[2] = 5U; app_request(request, sizeof(request)); memset(request, 0xcc, sizeof(request)); cs_process(); CHECK(app.cs.pending_requests == 1U && app.cs.waits != 0U && cs_probe.calls == 0U && !app.cs.need_process); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK); CHECK(status.high_queue.stored_asdu_count == before.high_queue.stored_asdu_count); fake.send_blocked = 0; cs_finish(); CHECK(collect_asdus(frames, lengths) == 2U); cs_reply(frames[1], lengths[1], cs_request, 0x6dU, 1U); cs_close(); } } static void test_clock_session_cleanup(void) { unsigned mode; cs_open(NULL); cs_send(0x71, 1); push(start_act, sizeof(start_act)); app_poll(); cs_execute(); cs_finish(); CHECK(cs_probe.calls == 1U && app.cs.cancelled == 0U); cs_close(); for (mode = 0U; mode < 3U; ++mode) { const uint8_t *frames[32]; size_t lengths[32]; cs_open(NULL); cs_send(0x71, 1); if (mode != 0U) { congest(&last_request.connection); cs_execute(); cs_process(); } CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs) && app.cs.pending_requests == 1U); if (mode == 2U) { CHECK(IEC104_Slave_Close_Connection(app.slave, &last_request, NULL) == IEC104_TCP_OK); app_poll(); fake.send_blocked = 0; app_connect(); } else { push(stop_act, sizeof(stop_act)); push(start_act, sizeof(start_act)); app_poll(); fake.send_blocked = 0; } cs_process(); CHECK(app.cs.pending_requests == 0U && app.cs.cancelled == 1U); CHECK(collect_asdus(frames, lengths) == 0U && cs_probe.calls == (mode == 0U ? 0U : 1U)); cs_send(0x72, 1); cs_execute(); cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][3] == 0x72U); CHECK(cs_probe.calls == (mode == 0U ? 1U : 2U)); cs_close(); } { const uint8_t *frames[32]; size_t lengths[32], written; cs_open(NULL); cs_send(0x71, 1); written = fake.sent_bytes; fake.send_limit = 3U; cs_execute(); CHECK(fake.sent_bytes > written && fake.sent_bytes < written + sizeof(cs_request) + 6U); CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs)); CHECK(app.cs.pending_requests == 0U && app.cs.submitted == 1U); push(stop_act, sizeof(stop_act)); app_poll(); fake.send_limit = 0U; cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && cs_probe.calls == 1U && app.cs.cancelled == 0U); cs_reply(frames[0], lengths[0], cs_request, 7U, 1U); cs_close(); } } static void test_clock_stop_priority(void) { IEC104_ClockSynchronizationConfig_t config; IEC104_TcpServerStatus_t service; IEC104_ClockSynchronization_Config_Default(&config); config.execute_timeout_ms = 10U; cs_open(&config); cs_send(0x71, 1); fake.now += 10U; push(stop_act, sizeof(stop_act)); CHECK(IEC104_Slave_Poll(app.slave, 0, NULL) == IEC104_TCP_OK); CHECK(IEC104_ClockSynchronization_Execute_Next(app.clock, cs_action, &cs_probe) == IEC104_CLOCK_SYNCHRONIZATION_EXECUTE_WAIT); cs_process(); CHECK(app.cs.events_pending && app.cs.timeouts == 0U && app.cs.cancelled == 0U); app_poll(); cs_process(); CHECK(app.cs.cancelled == 1U && app.cs.timeouts == 0U && cs_probe.calls == 0U); cs_close(); cs_open(NULL); cs_send(0x71, 1); congest(&last_request.connection); cs_execute(); cs_process(); retain_stop_for_submit(); fake.send_blocked = 0; cs_process(); CHECK(IEC104_Slave_Get_Status(app.slave, &service, NULL) == IEC104_TCP_OK); CHECK(!service.connection.data_transfer_started && service.pending_event_count != 0U); CHECK(app.cs.events_pending && !app.cs.need_process && app.cs.cancelled == 0U && app.cs.pending_requests == 1U); app_poll(); cs_process(); CHECK(app.cs.cancelled == 1U && app.cs.pending_requests == 0U && cs_probe.calls == 1U); cs_close(); } static void test_clock_fatal_cleanup(void) { cs_open(NULL); cs_send(0x71, 1); congest(&last_request.connection); cs_execute(); cs_process(); sut->queue_initialized = 0; CHECK(IEC104_ClockSynchronization_Process(app.clock)); sut->queue_initialized = 1; CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs)); CHECK(app.cs.pending_requests == 0U && app.cs.cancelled == 1U && cs_probe.calls == 1U); fake.send_blocked = 0; app_poll(); cs_process(); CHECK(cs_probe.calls == 1U && app.cs.submitted == 0U); cs_close(); } static void test_clock_directed_station(void) { IEC104_Station_t replacement = {0}; uint64_t original; const uint8_t *frames[32]; size_t lengths[32]; cs_open(NULL); original = IEC104_Station_Get_Id(&app.station); cs_send(0x71, 1); CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&app.station)); CHECK(IEC104_Slave_Add_Station(app.slave, &replacement, 1U, &replacement) == IEC104_STATION_OK); cs_execute(); CHECK(cs_probe.last.station_id == original); cs_finish(); CHECK(IEC104_Station_Is_Reusable(&app.station)); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][4] == 1U); CHECK(IEC104_Slave_Remove_Station(app.slave, &replacement) == IEC104_STATION_OK); cs_close(); } static void test_clock_broadcast(void) { IEC104_Station_t second = {0}, third = {0}; const uint8_t *frames[32]; size_t lengths[32]; unsigned i; cs_open(NULL); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &third, 3U, NULL) == IEC104_STATION_OK); cs_send(0x71, IEC104_CA_BROADCAST); cs_execute(); cs_finish(); CHECK(cs_probe.calls == 1U && cs_probe.last.station_id == 0U && collect_asdus(frames, lengths) == 3U); for (i = 0U; i < 3U; ++i) { CHECK(frames[i][4] == i + 1U && frames[i][2] == 7U); } cs_probe.success = false; cs_send(0x72, IEC104_CA_BROADCAST); cs_execute(); cs_finish(); CHECK(cs_probe.calls == 2U && collect_asdus(frames, lengths) == 6U); for (i = 3U; i < 6U; ++i) { CHECK(frames[i][4] == i - 2U && frames[i][2] == 0x47U); } { uint8_t request[sizeof(cs_request)]; memcpy(request, cs_request, sizeof(request)); request[4] = 0xff; request[5] = 0xff; request[2] = 5; app_request(request, sizeof(request)); cs_finish(); CHECK(cs_probe.calls == 2U && collect_asdus(frames, lengths) == 9U); for (i = 6U; i < 9U; ++i) { CHECK(frames[i][4] == i - 5U && frames[i][2] == 0x6dU); } } CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &third) == IEC104_STATION_OK); cs_close(); for (i = 0U; i < 2U; ++i) { uint8_t request[sizeof(cs_request)]; cs_open(NULL); CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); memcpy(request, cs_request, sizeof(request)); request[4] = 0xff; request[5] = 0xff; request[2] = i == 0U ? 5U : 6U; app_request(request, sizeof(request)); cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && cs_probe.calls == 0U); cs_reply(frames[0], lengths[0], request, i == 0U ? 0x6dU : 0x6eU, IEC104_CA_BROADCAST); cs_close(); } } static void test_clock_broadcast_mutation(void) { IEC104_Station_t second = {0}, replacement = {0}, added = {0}; const uint8_t *frames[32]; size_t lengths[32]; cs_open(NULL); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); cs_send(0x71, IEC104_CA_BROADCAST); congest(&last_request.connection); cs_execute(); cs_process(); CHECK(app.cs.confirm_pending && app.cs.waits != 0U); CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &replacement, 1U, NULL) == IEC104_STATION_OK); CHECK(IEC104_Slave_Add_Station(app.slave, &added, 3U, NULL) == IEC104_STATION_OK); fake.send_blocked = 0; cs_finish(); CHECK(collect_asdus(frames, lengths) == 1U && frames[0][4] == 2U && cs_probe.calls == 1U); CHECK(IEC104_Station_Is_Reusable(&app.station)); CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &replacement) == IEC104_STATION_OK); CHECK(IEC104_Slave_Remove_Station(app.slave, &added) == IEC104_STATION_OK); cs_close(); { unsigned mode; for (mode = 0U; mode < 3U; ++mode) { memset(&second, 0, sizeof(second)); cs_open(NULL); CHECK(IEC104_Slave_Add_Station(app.slave, &second, 2U, NULL) == IEC104_STATION_OK); cs_send(0x71, IEC104_CA_BROADCAST); congest(&last_request.connection); cs_execute(); cs_process(); if (mode != 2U) { CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); } CHECK(IEC104_Slave_Remove_Station(app.slave, &second) == IEC104_STATION_OK); CHECK(!IEC104_Station_Is_Reusable(&app.station) && !IEC104_Station_Is_Reusable(&second)); if (mode == 0U) { push(stop_act, sizeof(stop_act)); app_poll(); } else if (mode == 1U) { IEC104_ClockSynchronization_Cancel(app.clock); } else { sut->queue_initialized = 0; CHECK(IEC104_ClockSynchronization_Process(app.clock)); sut->queue_initialized = 1; } CHECK(IEC104_ClockSynchronization_Get_Status(app.clock, &app.cs)); CHECK(app.cs.pending_requests == 0U && app.cs.cancelled == 1U && cs_probe.calls == 1U); if (mode == 2U) { CHECK(IEC104_Slave_Remove_Station(app.slave, &app.station) == IEC104_STATION_OK); } CHECK(IEC104_Station_Is_Reusable(&app.station) && IEC104_Station_Is_Reusable(&second)); fake.send_blocked = 0; cs_close(); } } } static void test_clock_example_integration(void) { const uint8_t *frames[32]; size_t lengths[32]; unsigned i, clocks = 0U; uint8_t counter_request[sizeof(ci_request)]; counter_enabled = true; cs_open(NULL); memcpy(counter_request, ci_request, sizeof(counter_request)); counter_request[3] = 0x72U; queue_request(gi_request, sizeof(gi_request)); queue_request(counter_request, sizeof(counter_request)); queue_request(cs_request, sizeof(cs_request)); app_poll(); CHECK(!app.clock_system && !app.clock_valid); app_finish(); CHECK(app.clock_valid && app.clock_base_ms == 1000U && app.cs.executed == 1U && app.cs.synchronized == 1U); CHECK(cs_probe.calls == 0U && app.other_rejected == 0U); for (i = 0U; i < collect_asdus(frames, lengths); ++i) { if (frames[i][0] == 103U) { ++clocks; cs_reply(frames[i], lengths[i], cs_request, 7U, 1U); } } CHECK(clocks == 1U && app.counter_completed == 1U); check_gi(5U, 0x5aU); cs_close(); counter_enabled = false; } int main(void) { test_total_call(); test_wait_each_phase(); test_validation(); test_busy(); test_stop(); test_disconnect(); test_malformed(); test_malformed_drop(); test_receiver_truncated(); test_malformed_preserves_active(); test_malformed_backpressure(); test_overload(); test_event_budget_stop_start(); test_non_gi_ignored(); test_module_contracts(); test_need_process_business(); test_need_process_send_wait(); test_need_process_reply_and_transport(); test_gi_spontaneous_order(); test_gi_accepted_order(); test_gi_send_wait_timeout(); test_gi_window_wait(); test_gi_due_ack_wait(); test_gi_submit_fatal(); test_stop_in_process_after_state_backpressure(); test_stop_during_submit(); test_fatal_during_stop_submit(); test_submit_error_without_stop(); test_submit_with_new_record(); test_event_empty_and_wait(); test_event_copy_and_send_wait(); test_event_batch_validation(); test_event_mixed_types(); test_event_capacity_batches(); test_event_reject_and_fail(); test_event_cancellation(); test_event_queue_and_destroy(); test_event_pending_stop_start(); test_event_instance_reuse(); test_slave_event_validation(); test_reply_while_waiting(); test_reply_event_backpressure(); test_reply_slot_reuse(); test_reply_non_head_events(); test_reply_round_robin(); test_reply_send_wait(); test_reply_preserves_submitted_data(); test_reply_cleanup(); test_process_reply_only(); test_immediate_reply_guard(); test_overload_drop_preserves_active(); test_qoi_sequential_retry(); test_busy_all_phases(); test_busy_cancel_match(); test_busy_stop_cleanup(); test_busy_preserves_deadline(); test_next_request_deadline(); test_begin_capacity_wait(); test_cancel_before_begin(); test_group_interrogation(); test_example_groups(); test_example_double_wait_values(); test_remote_deactivation_phases(); test_remote_deactivation_validation(); test_remote_deactivation_full_queue(); test_begin_timeout(); test_data_timeout(); test_timeout_progress(); test_total_timeout_waits(); test_timeout_disabled_and_large(); test_timeout_transport_priority(); test_preconfirm_failure_reply_order(); test_preconfirm_failure_transport_priority(); test_confirm_submission_boundary(); test_broadcast_interrogation(); test_broadcast_error_replies(); test_broadcast_busy(); test_broadcast_deactivation(); test_station_binding_lifetime(); test_station_binding_wire(); test_registry_routing(); test_registry_broadcast_many(); test_registry_delete_current(); test_registry_broadcast_mutation(); test_registry_reply_fanout(); test_registry_deactivation(); test_registry_reject_timeout_stop(); test_registry_wait_each_phase(); test_registry_malformed(); test_registry_example_order(); test_registry_capacity(); test_registry_workspace_contract(); test_registry_confirmed_deleted(); test_registry_finish_delivery(); test_registry_directed_deactivation(); test_registry_deactivation_full(); test_registry_reject_during_deactivation(); test_registry_total_deadline(); test_registry_timeout_priority(); test_counter_module_contracts(); test_counter_validation(); test_counter_batch_validation(); test_counter_busy_independent(); test_counter_general_read(); test_counter_group_read(); test_counter_freeze_modes(); test_counter_stop_disconnect(); test_counter_timeouts(); test_counter_send_wait(); test_counter_broadcast(); test_dispatch_fallback(); test_other_batch(); test_other_overload(); test_other_state_change(); test_other_during_general(); test_clock_interfaces(); test_clock_execute_and_echo(); test_clock_timezone_rollover(); test_clock_validation(); test_clock_fifo_and_full(); test_clock_action_contract(); test_clock_deadlines(); test_clock_time_errors(); test_clock_try_order_and_wait(); test_clock_session_cleanup(); test_clock_stop_priority(); test_clock_fatal_cleanup(); test_clock_directed_station(); test_clock_broadcast(); test_clock_broadcast_mutation(); test_clock_example_integration(); puts("slave example + event interrogation tests: 131 groups passed (99 general + 12 counter + 4 fallback + 16 clock)"); return 0; }