/* 纯事件总召模块及示例接入回归:真实Slave/TCP提交路径 + 可控Net,无系统socket。 * 仅链接scripts/build.ps1中的公共依赖,不再链接TcpServer.c或Net后端。 * HIGH缩到251字节,通过应用应答和真实发送背压覆盖各业务阶段及自动推进的停止条件。 */ #define main existing_event_ring_main #include "iec104_tcp_event_ring_test.c" #undef main #define IEC104_SLAVE_EXAMPLE_NO_MAIN #include "../examples/iec104_slave_example.c" static ExampleApp app; static unsigned master_ns; static IEC104_SlaveEvent_t last_request; static const IEC104_InterrogationConfig_t *custom_config; static const uint8_t gi_request[] = {100, 1, 0x86, 0x5a, 1, 0, 0, 0, 0, 20}; 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 = 32; CHECK(IEC104_Slave_Init(slave, &config, NULL) == IEC104_TCP_OK); CHECK(Example_Init(&app, slave, 1)); if (custom_config != NULL) { CHECK(IEC104_Interrogation_Destroy(app.interrogation)); app.interrogation = IEC104_Interrogation_Create(slave, custom_config); CHECK(app.interrogation != NULL); } app_connect(); } static void app_close(void) { IEC104_Slave_t *slave = app.slave; Example_Cancel(&app); CHECK(Example_Drain_Business(&app)); CHECK(IEC104_Slave_Close(slave, NULL) == IEC104_TCP_OK); CHECK(Example_Drain(&app)); CHECK(IEC104_Interrogation_Destroy(app.interrogation)); 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 && !app.other_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[6], gi_count = 0U; unsigned total = collect_asdus(frames, lengths); CHECK(total == 6 + extra_reply); for (f = 0U; f < total; ++f) { if (frames[f][3] == oa && (frames[f][2] & 0x40U) == 0U) { CHECK(gi_count < 6U); indexes[gi_count++] = f; } } CHECK(gi_count == 6U); /* 其他请求的否定应答可穿插,但本次总召内容及自身顺序不变。 */ for (f = 0; f < 6; ++f) { const uint8_t *a = frames[indexes[f]]; CHECK(a[3] == oa && a[4] == 1 && a[5] == 0); if (f == 0 || f == 5) { 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 < 3; size_t width = is_signal ? 4U : 6U; unsigned first = (is_signal ? f - 1U : f - 3U) * 4U; CHECK(a[0] == (is_signal ? 1 : 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_signal ? 1001U : 2001U) + first + p); if (is_signal) { CHECK(object[3] == values[first + p]); } 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_TERMINATE, 4} }; unsigned i; for (i = 0; i < sizeof(phases) / sizeof(phases[0]); ++i) { size_t point_index; app_open(); app_request(gi_request, sizeof(gi_request)); while ((app.gi.phase != phases[i].phase || app.gi.batch_index != phases[i].batch)) { CHECK(Example_Step(&app)); } congest(&last_request.connection); point_index = app.gi.batch_index; /* DATA阶段可能先生成NEED_DATA;应用应答后的下一轮才尝试发送。 */ 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.other_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_pending) || (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) == 7); 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) { unsigned i; for (i = 0; i < 3; ++i) { IEC104_TcpServerStatus_t status; uint8_t request[11]; app_open(); memcpy(request, gi_request, 10); request[10] = 0; if (i == 0) { request[1] = 0x81; } if (i == 1) { request[1] = 0; } app_request(request, i == 2 ? 11 : 10); CHECK(IEC104_Slave_Get_Status(app.slave, &status, NULL) == IEC104_TCP_OK && !status.connected); CHECK(app.gi.pending_requests == 0 && (app.gi.submitted + app.other_submitted) == 0 && (app.gi.rejected + app.other_rejected) == 1); 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)); } 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(); } /* 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[2][0] == 1 && frames[6][0] == 1 && frames[7][2] == 0x8a); CHECK(frames[1][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); 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); 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_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); for (i = 0; i < 3; ++i) { gi_process(); CHECK(app.gi.waiting && !app.gi.application_waiting && app.gi.batch_index == 0); gi_empty(); } fake.send_blocked = 0; for (i = 0; i < 32 && app.gi.batch_index == 0; ++i) { 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, IEC104_INTERROGATION_FINISH_INVALID_REQUEST }; unsigned mode; for (mode = 0; mode < 4; ++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(); } if (mode == 3) { uint8_t invalid[11]; memcpy(invalid, gi_request, 10); invalid[10] = 0; app_request(invalid, sizeof(invalid)); } 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; 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); 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] <= 22 ? 4U : 1U)); if (count == 1) { CHECK(frames[0][2] == 0xc7 && app.gi.gi_submitted == 0); } else { CHECK(frames[0][2] == 0x87 && frames[3][2] == 0x8a); for (f = 1; f <= 2; ++f) { const uint8_t *a = frames[f]; unsigned first = groups[i] == 21 ? 1001 : 2001; CHECK(a[0] == (groups[i] == 21 ? 1 : 11) && a[1] == 4 && a[2] == (0x80 | groups[i])); CHECK((unsigned)(a[6] | (unsigned)a[7] << 8 | (unsigned)a[8] << 16) == first + (f - 1) * 4); } } CHECK(frames[0][9] == groups[i] && app.session_request == 0); 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); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == 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) { 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 + 7); 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 == 60000); 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); 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); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); } else { CHECK(IEC104_Interrogation_Complete_Data(app.interrogation, &event) == IEC104_INTERROGATION_REPLY_OK); } } if (mode != 1) { 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; } } /* 直接检查传给业务层的输入事件:立即应答、排队、撤销和丢弃都不能改写原请求CA。 */ 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); } /* 广播全站及全部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); CHECK(IEC104_Interrogation_Submit_Batch(app.interrogation, &event, &batch) == IEC104_INTERROGATION_REPLY_OK); if (wait) { congest(&last_request.connection); gi_process_only(); CHECK(app.gi.phase == IEC104_INTERROGATION_DATA && app.gi.waiting && app.gi.batch_index == 0); fake.send_blocked = 0; } 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)); 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(); } } } int main(void) { test_total_call(); test_wait_each_phase(); test_validation(); test_busy(); test_stop(); test_disconnect(); test_malformed(); test_overload(); test_event_budget_stop_start(); test_non_gi_ignored(); test_module_contracts(); 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_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(); puts("slave example + event interrogation tests: 59 groups passed"); return 0; }