354 lines
11 KiB
C
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;
|
|
}
|