Files
STM32F4-Base/IEC60870-Slave/test/iec104_tcp_scheduler_test.c
T
STF 4c55544f05
CI / L0 静态门禁 (pre-commit) (push) Failing after 45s
CI / L0.5 主机侧单测 (MSVC) (push) Failing after 42s
通信协议代码
2026-10-26 08:31:48 -04:00

354 lines
11 KiB
C

/* 调度回归复用可控Net后端;不另行链接TcpServer.c、Net或字节环测试的翻译单元。 */
#define main event_ring_suite_main
#include "iec104_tcp_event_ring_test.c"
#undef main
static IEC104_TcpServerStatus_t scheduler_status(void)
{
IEC104_TcpServerStatus_t status;
CHECK(IEC104_TcpServer_Get_Status(sut, &status, NULL) == IEC104_TCP_OK);
CHECK(status.connection_status_valid);
return status;
}
static void scheduler_begin(uint32_t t1, uint32_t local_timeout)
{
IEC104_TcpServerConfig_t config;
memset(&fake, 0, sizeof(fake));
fake.now = 1000;
fake.accept_pending = 1;
sut = IEC104_TcpServer_Create();
CHECK(sut != NULL);
IEC104_TcpServer_Config_Default(&config);
config.protocol.link.t1_ms = t1;
config.protocol.link.t2_ms = 50;
config.protocol.local_send_timeout_ms = local_timeout;
config.protocol.stop_timeout_ms = 100;
CHECK(IEC104_TcpServer_Init(sut, &config, NULL) == IEC104_TCP_OK);
poll_once();
CHECK(drain() == 1);
push(start_act, sizeof(start_act));
poll_once();
CHECK(drain() == 1 && fake.sent_bytes == sizeof(start_act));
}
static void scheduler_send_one(void)
{
CHECK(IEC104_TcpServer_Enqueue_Event(sut, asdu, sizeof(asdu)) == IEC104_TCP_SUBMIT_OK);
poll_once();
CHECK(fake.sent_bytes == sizeof(start_act) + IEC104_APCI_LEN + sizeof(asdu));
}
static void check_connected(void)
{
IEC104_TcpServerStatus_t status = scheduler_status();
CHECK(status.connected && status.connection.fault == IEC104_TCP_FAULT_NONE);
}
static void check_fault(IEC104_TcpFault_t fault)
{
IEC104_TcpServerStatus_t status = scheduler_status();
CHECK(!status.connected && status.connection.fault == fault);
CHECK(!sut->rx_snapshot_active && sut->rx_snapshot_remaining == 0);
CHECK(!sut->connection.receive_batch_active && !sut->connection.receive_batch_pending);
}
static void check_immediate_continuation(void)
{
IEC104_TcpWaitInfo_t info;
CHECK(IEC104_TcpServer_Prepare(sut, &info, NULL) == IEC104_TCP_OK);
CHECK(info.need_process && info.timer_active && info.timeout_ms == 0);
CHECK(!sut->connection.receive_batch_active && sut->connection.receive_batch_pending);
}
/* 每轮仍最多交付8个I事件;ACK可能在同一recv尾部或任意分段后的内核队列中。 */
static void test_ack_boundary(unsigned preceding_i, size_t recv_limit, uint16_t apdu_length)
{
unsigned index, frames = 0, records = 0;
IEC104_TcpEvent_t event;
scheduler_begin(100, 5000);
scheduler_send_one();
for (index = 0; index < preceding_i; ++index) { push_i(index, 0, apdu_length); }
push_ack(1);
fake.recv_limit = recv_limit;
fake.now = 1100;
for (index = 0; index < 2000 && !records; ++index) {
unsigned round_frames = 0;
poll_once();
check_connected();
if (sut->rx_snapshot_active) { check_immediate_continuation(); }
while (IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY) {
if (event.type == IEC104_TCP_EVENT_I_FRAME) {
CHECK(event.data.i_frame.send_number == frames);
CHECK(event.data.i_frame.apdu_length == apdu_length);
++frames;
++round_frames;
} else {
CHECK(event.type == IEC104_TCP_EVENT_RECORD && !event.data.record.closing);
CHECK(event.data.record.value.local_send_completed && event.data.record.value.peer_acknowledged);
++records;
}
}
CHECK(round_frames <= TCP_EVENT_BUDGET);
}
CHECK(frames == preceding_i && records == 1);
CHECK(!sut->rx_snapshot_active && !sut->connection.receive_batch_pending);
CHECK(fake.offset == fake.length && sut->rx_end == 0);
end();
}
/* 快照之外的新流量不能延长旧ACK期限;覆盖用户态尾部与跨多次recv的内核快照。 */
static void test_fixed_snapshot(size_t recv_limit, int control_only)
{
unsigned index, rounds = 0, old_count = control_only ? 3U * TCP_IO_BUDGET : 33U;
unsigned next_sequence = old_count, snapshots;
scheduler_begin(100, 5000);
scheduler_send_one();
snapshots = fake.snapshot_calls;
for (index = 0; index < old_count; ++index) {
if (control_only) { push_ack(0); } else { push_i(index, 0, 16); }
}
fake.recv_limit = recv_limit;
fake.now = 1100;
poll_once();
check_connected();
check_immediate_continuation();
CHECK(fake.snapshot_calls == snapshots + 1);
snapshots = fake.snapshot_calls;
drain();
while (sut->client_socket != IEC104_NET_INVALID_SOCKET && rounds++ < old_count + 10U) {
for (index = 0; index < 10; ++index) {
if (control_only) { push_ack(0); } else { push_i(next_sequence++, 0, 16); }
}
poll_once();
drain();
}
check_fault(IEC104_TCP_FAULT_I_ACK_TIMEOUT);
CHECK(fake.snapshot_calls == snapshots && fake.offset < fake.length);
end();
}
static void test_missing_ack(void)
{
scheduler_begin(100, 5000);
scheduler_send_one();
fake.now = 1100;
poll_once();
check_fault(IEC104_TCP_FAULT_I_ACK_TIMEOUT);
end();
}
static void test_writable_stream(size_t send_limit)
{
unsigned round, index;
size_t written;
scheduler_begin(1000, 100);
fake.send_limit = send_limit;
written = fake.sent_bytes;
for (round = 0; round < 8; ++round) {
fake.now = 1000 + round * 50U;
for (index = 0; index < TCP_EVENT_BUDGET; ++index) {
push_i(round * TCP_EVENT_BUDGET + index, 0, 16);
}
poll_once();
check_connected();
CHECK(drain() == TCP_EVENT_BUDGET);
CHECK(fake.sent_bytes > written);
written = fake.sent_bytes;
}
end();
}
static void test_business_output_after_yield(void)
{
unsigned index;
size_t written;
scheduler_begin(1000, 100);
for (index = 0; index < TCP_EVENT_BUDGET; ++index) { push_i(index, 0, 16); }
CHECK(IEC104_TcpServer_Enqueue_Event(sut, asdu, sizeof(asdu)) == IEC104_TCP_SUBMIT_OK);
written = fake.sent_bytes;
poll_once();
check_connected();
CHECK(fake.sent_bytes >= written + IEC104_APCI_LEN + sizeof(asdu));
CHECK(sut->connection.send_manager.record_count == 1);
end();
}
static void test_blocked_output(int recover)
{
unsigned index, calls;
size_t written;
scheduler_begin(1000, 100);
written = fake.sent_bytes;
fake.send_blocked = 1;
for (index = 0; index < TCP_EVENT_BUDGET; ++index) { push_i(index, 0, 16); }
calls = fake.send_calls;
poll_once();
check_connected();
CHECK(fake.send_calls == calls + 1 && fake.sent_bytes == written);
drain();
if (recover) {
fake.now = 1050;
fake.send_blocked = 0;
poll_once();
check_connected();
CHECK(fake.sent_bytes > written);
} else {
fake.now = 1100;
for (index = 0; index < 9; ++index) { push_i(TCP_EVENT_BUDGET + index, 0, 16); }
poll_once();
check_fault(IEC104_TCP_FAULT_LOCAL_SEND_TIMEOUT);
}
end();
}
static void test_testfr_boundary(int provide_confirmation)
{
static const uint8_t test_con[] = {0x68, 4, 0x83, 0, 0, 0};
unsigned index;
scheduler_begin(100, 5000);
/* 等到t3,真实推进TESTFR_ACT发送并启动探测t1。 */
fake.now += sut->config.protocol.link.t3_ms;
poll_once();
CHECK(sut->connection.testfr_timer_active);
for (index = 0; index < 9; ++index) { push_i(index, 0, 16); }
if (provide_confirmation) { push(test_con, sizeof(test_con)); }
fake.now += 100;
poll_once();
check_connected();
check_immediate_continuation();
drain();
poll_once();
if (provide_confirmation) {
check_connected();
CHECK(!sut->connection.testfr_timer_active);
} else {
check_fault(IEC104_TCP_FAULT_TESTFR_TIMEOUT);
}
end();
}
/* 原有容量策略:真正空间不足仍暂停接收,不用预算续收标志掩盖严格期限。 */
static void test_real_backpressure(int state_full)
{
unsigned index;
unsigned count = (unsigned)(TCP_EVENT_NORMAL_BYTES /
(sizeof(TcpEventHeader_t) + TCP_EVENT_I_PREFIX + 16));
scheduler_begin(100, 5000);
scheduler_send_one();
if (state_full) {
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
push(index % 2 == 0 ? stop_act : start_act, sizeof(start_act));
poll_once();
}
push(IEC104_TCP_EVENT_STATE_RESERVE % 2 == 0 ? stop_act : start_act, sizeof(start_act));
} else {
for (index = 0; index < count; ++index) { push_i(index, 0, 16); poll_once(); }
push_i(count, 0, 16);
}
push_ack(1);
poll_once();
check_connected();
CHECK(state_full ? sut->rx_blocked : sut->rx_event_pending);
CHECK(!sut->connection.receive_batch_pending);
fake.now = 1100;
poll_once();
check_fault(IEC104_TCP_FAULT_I_ACK_TIMEOUT);
end();
}
static void test_response_during_continuation(void)
{
scheduler_begin(100, 5000);
scheduler_send_one();
push_i(0, 0, 16);
push_ack(1);
fake.recv_limit = 16;
fake.now = 1100;
poll_once();
check_immediate_continuation();
CHECK(drain() == 1);
CHECK(IEC104_TcpServer_Send_Response(sut, sut->generation, asdu, sizeof(asdu)) == IEC104_TCP_SUBMIT_OK);
poll_once();
check_connected();
CHECK(drain() == 1);
push_ack(2);
fake.now = 1200;
poll_once();
check_connected();
CHECK(drain() == 1);
end();
}
static void test_invalid_ack(void)
{
unsigned index;
scheduler_begin(100, 5000);
scheduler_send_one();
for (index = 0; index < TCP_EVENT_BUDGET; ++index) { push_i(index, 0, 16); }
push_ack(2);
fake.now = 1100;
poll_once();
check_immediate_continuation();
drain();
poll_once();
check_fault(IEC104_TCP_FAULT_PROTOCOL);
end();
}
static void test_snapshot_failure(void)
{
scheduler_begin(100, 5000);
push_i(0, 0, 16);
fake.pending_error = 1;
poll_once();
check_fault(IEC104_TCP_FAULT_TRANSPORT);
end();
}
static void test_eof_response(void)
{
scheduler_begin(100, 5000);
push_i(0, 0, 16);
fake.recv_eof = 1;
poll_once();
check_connected();
CHECK(!sut->rx_eof && drain() == 1);
CHECK(IEC104_TcpServer_Send_Response(sut, sut->generation, asdu, sizeof(asdu)) == IEC104_TCP_SUBMIT_OK);
CHECK(fake.sent_bytes == sizeof(start_act) + IEC104_APCI_LEN + sizeof(asdu));
poll_once();
CHECK(!scheduler_status().connected);
end();
}
int main(void)
{
test_ack_boundary(7, 0, 16);
test_ack_boundary(8, 0, 16);
test_ack_boundary(9, 0, 16);
test_ack_boundary(33, 0, 16);
test_ack_boundary(1, 16, 16);
test_ack_boundary(1, 1, 16);
test_ack_boundary(16, 0, IEC104_MAX_APDU_LEN);
test_ack_boundary(33, 17, IEC104_MAX_APDU_LEN);
test_fixed_snapshot(0, 0);
test_fixed_snapshot(16, 0);
test_fixed_snapshot(0, 1);
test_missing_ack();
test_writable_stream(0);
test_writable_stream(2);
test_business_output_after_yield();
test_blocked_output(1);
test_blocked_output(0);
test_testfr_boundary(1);
test_testfr_boundary(0);
test_real_backpressure(0);
test_real_backpressure(1);
test_response_during_continuation();
test_invalid_ack();
test_snapshot_failure();
test_eof_response();
puts("TCP scheduler: 25 cases passed (including strict backpressure policy checks)");
return 0;
}