Files
STM32F4-Base/IEC60870-Slave/test/iec104_tcp_event_ring_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

866 lines
39 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 变长通知FIFO回归:可控Net后端,无真实等待。
* 为验证跨尾部和损坏数据的事务性出队,直接包含实现;不要另行链接TcpServer.c或Net后端。
* TCP_EVENT_RING_BENCHMARK只运行公共接收路径,可用-I指定修改前实现进行同机对照。 */
#include <IEC104_TcpServer.c>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "%s:%d: %s\n", __func__, __LINE__, #x); exit(1); } } while (0)
static struct {
uint64_t now;
uint8_t input[65536];
uint8_t output[65536];
size_t length, offset, recv_limit, sent_bytes, send_limit;
unsigned accept_pending, recv_calls, send_calls, stop_confirmations;
unsigned snapshot_calls, send_blocked, recv_eof, recv_retry, pending_error;
} fake;
static IEC104_TcpServer_t *sut;
static const uint8_t start_act[] = {0x68, 4, 7, 0, 0, 0};
#ifndef TCP_EVENT_RING_BENCHMARK
static const uint8_t stop_act[] = {0x68, 4, 0x13, 0, 0, 0};
static const uint8_t asdu[] = {1, 1, 3, 0, 1, 0, 1, 0, 0, 1};
#endif
static IEC104_NetResult_t net_result(IEC104_NetResult_t result, int *error)
{
if (error != NULL) { *error = 0; }
return result;
}
IEC104_NetResult_t IEC104_Net_Init(int *error) { return net_result(IEC104_NET_OK, error); }
void IEC104_Net_Cleanup(void) {}
int IEC104_Net_Invalid_Argument_Error(void) { return 22; }
IEC104_NetResult_t IEC104_Net_Listen(const char *address, uint16_t port,
IEC104_NetSocket_t *listener, uint16_t *bound_port, int *error)
{
(void)address;
*listener = 1;
*bound_port = port;
return net_result(IEC104_NET_OK, error);
}
IEC104_NetResult_t IEC104_Net_Accept(IEC104_NetSocket_t listener,
IEC104_NetSocket_t *client, IEC104_NetPeer_t *peer, int *error)
{
CHECK(listener == 1);
*client = IEC104_NET_INVALID_SOCKET;
if (!fake.accept_pending) { return net_result(IEC104_NET_WOULD_BLOCK, error); }
fake.accept_pending = 0;
*client = 2;
memset(peer, 0, sizeof(*peer));
memcpy(peer->address, "127.0.0.1", sizeof("127.0.0.1"));
peer->port = 12000;
return net_result(IEC104_NET_OK, error);
}
IEC104_NetIoResult_t IEC104_Net_Recv(IEC104_NetSocket_t socket, void *data, size_t capacity)
{
IEC104_NetIoResult_t result = {IEC104_NET_WOULD_BLOCK, 0, 0};
size_t count = fake.length - fake.offset;
CHECK(socket == 2);
++fake.recv_calls;
if (fake.recv_retry) { --fake.recv_retry; result.result = IEC104_NET_RETRY; return result; }
if (count == 0) {
if (fake.recv_eof) { result.result = IEC104_NET_EOF; }
return result;
}
if (count > capacity) { count = capacity; }
if (fake.recv_limit && count > fake.recv_limit) { count = fake.recv_limit; }
memcpy(data, fake.input + fake.offset, count);
fake.offset += count;
result.result = IEC104_NET_OK;
result.transferred = count;
return result;
}
IEC104_NetResult_t IEC104_Net_Pending_Input(
IEC104_NetSocket_t socket, uint32_t *available, int *error)
{
CHECK(socket == 2 && available != NULL);
++fake.snapshot_calls;
if (fake.pending_error) { return net_result(IEC104_NET_ERROR, error); }
*available = (uint32_t)(fake.length - fake.offset);
return net_result(IEC104_NET_OK, error);
}
IEC104_NetIoResult_t IEC104_Net_Send(IEC104_NetSocket_t socket, const void *data, size_t length)
{
IEC104_NetIoResult_t result = {IEC104_NET_OK, length, 0};
const uint8_t *bytes = (const uint8_t *)data;
CHECK(socket == 2 && data != NULL && length > 0);
++fake.send_calls;
if (fake.send_blocked) { result.result = IEC104_NET_WOULD_BLOCK; result.transferred = 0; return result; }
if (fake.send_limit && length > fake.send_limit) { length = fake.send_limit; result.transferred = length; }
#ifndef TCP_EVENT_RING_BENCHMARK
CHECK(fake.sent_bytes + length <= sizeof(fake.output));
memcpy(fake.output + fake.sent_bytes, data, length);
#endif
fake.sent_bytes += length;
if (length == IEC104_APCI_LEN && bytes[2] == 0x23) { ++fake.stop_confirmations; }
return result;
}
IEC104_NetResult_t IEC104_Net_Wait(IEC104_NetSocket_t listener, IEC104_NetSocket_t client,
uint32_t interest, uint32_t timeout, uint32_t *ready, int *error)
{
(void)listener; (void)client; (void)timeout;
*ready = 0;
if (fake.accept_pending && (interest & IEC104_NET_EVENT_ACCEPT)) { *ready |= IEC104_NET_EVENT_ACCEPT; }
if (fake.offset < fake.length && (interest & IEC104_NET_EVENT_READ)) { *ready |= IEC104_NET_EVENT_READ; }
if (interest & IEC104_NET_EVENT_WRITE) { *ready |= IEC104_NET_EVENT_WRITE; }
return net_result(IEC104_NET_OK, error);
}
IEC104_NetResult_t IEC104_Net_Close(IEC104_NetSocket_t socket, int *error)
{
(void)socket;
return net_result(IEC104_NET_OK, error);
}
uint64_t IEC104_Net_Now_Ms(void) { return fake.now; }
static void push(const uint8_t *bytes, size_t length)
{
if (fake.offset == fake.length) { fake.offset = fake.length = 0; }
CHECK(fake.length + length <= sizeof(fake.input));
memcpy(fake.input + fake.length, bytes, length);
fake.length += length;
}
static void push_i(unsigned ns, unsigned nr, uint16_t length)
{
uint8_t frame[IEC104_MAX_APDU_LEN];
CHECK(length >= IEC104_APCI_LEN && length <= sizeof(frame));
memset(frame, 0x5a, sizeof(frame));
ns &= IEC104_SEQUENCE_MASK;
nr &= IEC104_SEQUENCE_MASK;
frame[0] = 0x68; frame[1] = (uint8_t)(length - 2U);
frame[2] = (uint8_t)(ns << 1); frame[3] = (uint8_t)(ns >> 7);
frame[4] = (uint8_t)(nr << 1); frame[5] = (uint8_t)(nr >> 7);
push(frame, length);
}
static void poll_once(void) { CHECK(IEC104_TcpServer_Poll(sut, 0, NULL) == IEC104_TCP_OK); }
static unsigned drain(void)
{
unsigned count = 0;
IEC104_TcpEvent_t event;
IEC104_TcpEventResult_t result;
while ((result = IEC104_TcpServer_NextEvent(sut, &event)) == IEC104_TCP_EVENT_READY) { ++count; }
CHECK(result == IEC104_TCP_EVENT_EMPTY);
return count;
}
static void begin(void)
{
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.k = IEC104_TCP_RECORD_CAPACITY;
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);
CHECK(sut->connection.link_state.dt_state == IEC104_DT_STARTED);
}
static void end(void)
{
CHECK(IEC104_TcpServer_Close(sut, NULL) == IEC104_TCP_OK);
drain();
CHECK(IEC104_TcpServer_Destroy(sut, NULL) == IEC104_TCP_OK);
}
#ifndef TCP_EVENT_RING_BENCHMARK
static void push_ack(unsigned nr)
{
uint8_t frame[] = {0x68, 4, 1, 0, 0, 0};
frame[4] = (uint8_t)(nr << 1); frame[5] = (uint8_t)(nr >> 7);
push(frame, sizeof(frame));
}
static void send_one(void)
{
CHECK(IEC104_TcpServer_Send_Response(sut, sut->generation, asdu, sizeof(asdu)) == IEC104_TCP_SUBMIT_OK);
}
static void fill_input(unsigned count, uint16_t length)
{
unsigned index;
for (index = 0; index < count; ++index) { push_i(index, 0, length); }
for (index = 0; index < count + 16U && !sut->rx_event_pending &&
(fake.offset < fake.length || sut->rx_begin != sut->rx_end); ++index) { poll_once(); }
CHECK(sut->client_socket != IEC104_NET_INVALID_SOCKET);
}
static void raw_begin(void)
{
sut = IEC104_TcpServer_Create();
CHECK(sut != NULL);
if (sut->generation == 0U) { sut->generation = 1; }
}
static void raw_end(void)
{
drain();
CHECK(IEC104_TcpServer_Destroy(sut, NULL) == IEC104_TCP_OK);
}
static IEC104_TcpIFrameEvent_t frame_payload(uint16_t length)
{
IEC104_TcpIFrameEvent_t frame;
uint16_t index;
memset(&frame, 0, sizeof(frame));
frame.receive_serial = 123;
frame.apdu_length = length;
frame.send_number = 17;
frame.receive_number = 3;
for (index = 0; index < length; ++index) { frame.apdu[index] = (uint8_t)(index * 7U); }
return frame;
}
static void append_frame(uint16_t length)
{
IEC104_TcpIFrameEvent_t frame = frame_payload(length);
CHECK(Append_Event(sut, IEC104_TCP_EVENT_I_FRAME, &frame, (uint16_t)(TCP_EVENT_I_PREFIX + length)));
}
static void check_event(const IEC104_TcpEvent_t *actual, const IEC104_TcpEvent_t *expected)
{
CHECK(actual->type == expected->type && actual->generation == expected->generation);
CHECK(memcmp(&actual->data, &expected->data, sizeof(actual->data)) == 0);
}
/* 每个可能起点都覆盖短/长I帧、RECORD及所有连接通知,验证头/载荷跨尾、零填充和旧代次。 */
static void test_wrap_roundtrip(void)
{
uint32_t index;
unsigned kind;
raw_begin();
for (index = 0; index < sizeof(sut->event_bytes); ++index) {
for (kind = 0; kind < 9; ++kind) {
IEC104_TcpEvent_t expected, actual;
uint16_t length;
memset(&expected, 0, sizeof(expected));
expected.generation = sut->generation;
sut->event_head = index;
if (kind < 4) {
static const uint16_t lengths[] = {IEC104_APCI_LEN, 16, 22, IEC104_MAX_APDU_LEN};
expected.type = IEC104_TCP_EVENT_I_FRAME;
expected.data.i_frame = frame_payload(lengths[kind]);
length = (uint16_t)(TCP_EVENT_I_PREFIX + lengths[kind]);
} else if (kind == 4) {
expected.type = IEC104_TCP_EVENT_RECORD;
expected.data.record.value.frame_serial = 91;
expected.data.record.value.apdu_length = IEC104_MAX_APDU_LEN;
expected.data.record.value.local_send_completed = 1;
expected.data.record.closing = 1;
length = sizeof(expected.data.record);
} else {
static const IEC104_TcpEventType_t types[] = {IEC104_TCP_EVENT_CONNECTED,
IEC104_TCP_EVENT_ACTIVATED, IEC104_TCP_EVENT_DEACTIVATED, IEC104_TCP_EVENT_DISCONNECTED};
expected.type = types[kind - 5];
expected.data.connection.peer.generation = expected.generation;
expected.data.connection.peer.port = 2404;
expected.data.connection.status_valid = 1;
length = sizeof(expected.data.connection);
}
CHECK(Append_Event(sut, expected.type, &expected.data, length));
CHECK(sut->event_used == sizeof(TcpEventHeader_t) + length);
++sut->generation;
memset(&actual, 0xa5, sizeof(actual));
CHECK(IEC104_TcpServer_NextEvent(sut, &actual) == IEC104_TCP_EVENT_READY);
check_event(&actual, &expected);
CHECK(sut->event_used == 0 && sut->event_count == 0 && sut->state_event_count == 0);
CHECK(sut->event_head == (index + sizeof(TcpEventHeader_t) + length) % sizeof(sut->event_bytes));
}
}
raw_end();
}
static void test_full_reserves(void)
{
unsigned index;
IEC104_TcpConnectionEvent_t connection = {0};
IEC104_TcpRecordEvent_t record = {0};
IEC104_TcpEvent_t event;
IEC104_TcpServer_t saved;
raw_begin();
sut->event_head = (uint32_t)sizeof(sut->event_bytes) - 1U;
for (index = 0; index < IEC104_TCP_EVENT_NORMAL_CAPACITY; ++index) {
CHECK(Ordinary_Event_Room(sut, TCP_EVENT_I_MAX_BYTES));
append_frame(IEC104_MAX_APDU_LEN);
}
CHECK(!Ordinary_Event_Room(sut, TCP_EVENT_I_MIN_BYTES));
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
CHECK(State_Event_Room(sut) && Transfer_Event_Room(sut));
CHECK(Append_Event(sut, IEC104_TCP_EVENT_ACTIVATED, &connection, sizeof(connection)));
}
CHECK(!State_Event_Room(sut) && !Transfer_Event_Room(sut));
CHECK(!Append_Event(sut, IEC104_TCP_EVENT_DEACTIVATED, &connection, sizeof(connection)));
CHECK(sut->event_used == TCP_EVENT_CAPACITY_BYTES - TCP_EVENT_CLOSE_BYTES);
append_frame(IEC104_MAX_APDU_LEN);
record.closing = 1;
for (index = 0; index < IEC104_TCP_RECORD_CAPACITY; ++index) {
record.value.frame_serial = index + 1U;
CHECK(Append_Event(sut, IEC104_TCP_EVENT_RECORD, &record, sizeof(record)));
}
CHECK(Append_Event(sut, IEC104_TCP_EVENT_DISCONNECTED, &connection, sizeof(connection)));
CHECK(sut->event_used == sizeof(sut->event_bytes) && sut->event_count == IEC104_TCP_EVENT_CAPACITY);
saved = *sut;
CHECK(!Append_Event(sut, IEC104_TCP_EVENT_RECORD, &record, sizeof(record)));
CHECK(memcmp(&saved, sut, sizeof(saved)) == 0);
CHECK(!Can_Accept_Connection(sut));
for (index = 0; index < IEC104_TCP_EVENT_NORMAL_CAPACITY; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME);
}
CHECK(!Can_Accept_Connection(sut)); /* 空间已足够,旧启停通知仍阻挡新连接。 */
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_ACTIVATED);
}
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME);
for (index = 0; index < IEC104_TCP_RECORD_CAPACITY; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_RECORD && event.data.record.closing);
CHECK(event.data.record.value.frame_serial == index + 1U);
}
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_DISCONNECTED);
CHECK(Can_Accept_Connection(sut));
raw_end();
}
static void test_room_boundaries(void)
{
unsigned index;
IEC104_TcpConnectionEvent_t connection = {0};
IEC104_TcpServer_t saved;
raw_begin();
for (index = 0; index + 1U < IEC104_TCP_EVENT_NORMAL_CAPACITY; ++index) {
append_frame(IEC104_MAX_APDU_LEN);
}
append_frame((uint16_t)(IEC104_MAX_APDU_LEN - TCP_EVENT_CONNECTION_BYTES));
CHECK(Ordinary_Event_Room(sut, TCP_EVENT_CONNECTION_BYTES));
CHECK(Can_Accept_Connection(sut));
CHECK(!Ordinary_Event_Room(sut, TCP_EVENT_I_MAX_BYTES));
append_frame(IEC104_APCI_LEN);
CHECK(!Can_Accept_Connection(sut));
raw_end();
raw_begin();
for (index = 0; index < IEC104_TCP_EVENT_NORMAL_CAPACITY; ++index) {
append_frame(IEC104_MAX_APDU_LEN);
}
/* 模拟旧会话收尾项占用普通区以外的空间;即使启停条数为0,也不能挤占新的收尾预留。 */
while (sut->event_used + TCP_EVENT_CONNECTION_BYTES <= TCP_EVENT_CAPACITY_BYTES - TCP_EVENT_CLOSE_BYTES) {
CHECK(Append_Event(sut, IEC104_TCP_EVENT_DISCONNECTED, &connection, sizeof(connection)));
}
CHECK(sut->state_event_count == 0 && !State_Event_Room(sut) && !Transfer_Event_Room(sut));
saved = *sut;
CHECK(!Append_Event(sut, IEC104_TCP_EVENT_ACTIVATED, &connection, sizeof(connection)));
CHECK(!Notify_Transfer_State(sut, IEC104_DT_STARTED));
CHECK(memcmp(sut, &saved, sizeof(saved)) == 0);
raw_end();
}
static void expect_error_unchanged(void)
{
IEC104_TcpServer_t saved = *sut;
IEC104_TcpEvent_t output, original;
memset(&output, 0xa5, sizeof(output));
original = output;
CHECK(IEC104_TcpServer_NextEvent(sut, &output) == IEC104_TCP_EVENT_ERROR);
CHECK(memcmp(&output, &original, sizeof(output)) == 0);
CHECK(memcmp(sut, &saved, sizeof(saved)) == 0);
}
static void test_invalid_entries(void)
{
IEC104_TcpServer_t saved;
TcpEventHeader_t header;
unsigned index;
raw_begin();
sut->event_head = (uint32_t)sizeof(sut->event_bytes) - 3U;
append_frame(16);
saved = *sut;
for (index = 0; index < 14; ++index) {
*sut = saved;
Event_Ring_Read(sut, sut->event_head, &header, sizeof(header));
switch (index) {
case 0: header.type = (IEC104_TcpEventType_t)99; break;
case 1: header.generation = 0; break;
case 2: header.payload_length = 0; break;
case 3: header.payload_length = UINT16_MAX; break;
case 4: header.type = IEC104_TCP_EVENT_RECORD; break;
case 5: header.type = IEC104_TCP_EVENT_CONNECTED; break;
case 6: --header.payload_length; break;
case 7: sut->event_head = sizeof(sut->event_bytes); break;
case 8: sut->event_used = sizeof(sut->event_bytes) + 1U; break;
case 9: sut->event_count = 0; break;
case 10: sut->event_count = 2; break;
case 11: sut->state_event_count = 1; break;
case 12: --sut->event_used; break;
case 13: ++sut->event_used; break;
}
if (index < 7) { Event_Ring_Write(sut, sut->event_head, &header, sizeof(header)); }
expect_error_unchanged();
}
for (index = 0; index < 4; ++index) {
static const uint16_t bad_lengths[] = {0, IEC104_APCI_LEN - 1, 17, IEC104_MAX_APDU_LEN + 1};
*sut = saved;
Event_Ring_Write(sut, Event_Ring_Advance(sut->event_head,
sizeof(header) + offsetof(IEC104_TcpIFrameEvent_t, apdu_length)), &bad_lengths[index], sizeof(uint16_t));
expect_error_unchanged();
}
*sut = saved;
raw_end();
raw_begin();
{
IEC104_TcpConnectionEvent_t connection = {0};
CHECK(Append_Event(sut, IEC104_TCP_EVENT_ACTIVATED, &connection, sizeof(connection)));
saved = *sut;
sut->state_event_count = 0;
expect_error_unchanged();
*sut = saved;
Event_Ring_Read(sut, sut->event_head, &header, sizeof(header));
header.type = IEC104_TCP_EVENT_CONNECTED;
Event_Ring_Write(sut, sut->event_head, &header, sizeof(header));
expect_error_unchanged();
*sut = saved;
}
raw_end();
}
static void test_short_capacity(void)
{
unsigned count = (unsigned)(TCP_EVENT_NORMAL_BYTES / (sizeof(TcpEventHeader_t) + TCP_EVENT_I_PREFIX + 16));
unsigned index;
IEC104_TcpEvent_t event;
IEC104_TcpWaitInfo_t wait;
IEC104_TcpServerStatus_t status;
begin();
fill_input(count + 1U, 16);
CHECK(sut->event_count == count && sut->rx_event_pending);
CHECK(IEC104_TcpServer_Prepare(sut, &wait, NULL) == IEC104_TCP_OK);
CHECK(IEC104_TcpServer_Get_Status(sut, &status, NULL) == IEC104_TCP_OK);
CHECK(wait.receive_paused && status.receive_paused && wait.pending_event_count == count);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME && event.data.i_frame.send_number == 0);
poll_once();
CHECK(!sut->rx_event_pending && sut->event_count == count);
for (index = 1; index <= count; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME && event.data.i_frame.send_number == index);
}
printf("Short APDU capacity: %u queued + one retained in Receiver\n", count);
end();
}
static void test_pending_frame_before_record(void)
{
unsigned index;
IEC104_TcpEvent_t event;
begin();
send_one();
fill_input(IEC104_TCP_EVENT_NORMAL_CAPACITY - 1U, IEC104_MAX_APDU_LEN);
push_i(IEC104_TCP_EVENT_NORMAL_CAPACITY - 1U, 0, 16);
poll_once();
CHECK(Ordinary_Event_Room(sut, TCP_EVENT_RECORD_BYTES));
CHECK(!Ordinary_Event_Room(sut, TCP_EVENT_I_MAX_BYTES));
push_i(IEC104_TCP_EVENT_NORMAL_CAPACITY, 1, IEC104_MAX_APDU_LEN);
poll_once();
CHECK(sut->rx_event_pending && sut->connection.send_manager.record_count == 1);
CHECK(sut->event_count == IEC104_TCP_EVENT_NORMAL_CAPACITY);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
poll_once();
poll_once();
CHECK(!sut->rx_event_pending && sut->connection.send_manager.record_count == 0);
for (index = 1; index <= IEC104_TCP_EVENT_NORMAL_CAPACITY; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME && event.data.i_frame.send_number == index);
}
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_RECORD && !event.data.record.closing);
CHECK(event.data.record.value.peer_acknowledged);
end();
}
static void test_delayed_receipt(int budget_limited)
{
unsigned count = budget_limited ? TCP_EVENT_BUDGET :
(unsigned)(TCP_EVENT_NORMAL_BYTES / (sizeof(TcpEventHeader_t) + TCP_EVENT_I_PREFIX + 16));
unsigned index, seen_state = 0, seen_record = 0;
IEC104_TcpEvent_t event;
begin();
send_one();
if (budget_limited) {
for (index = 0; index < count; ++index) { push_i(index, index + 1U == count ? 1U : 0U, 16); }
push(stop_act, sizeof(stop_act));
poll_once();
CHECK(sut->event_count == count && sut->connection.send_manager.record_count == 1);
} else {
fill_input(count, 16);
CHECK(!Ordinary_Event_Room(sut, TCP_EVENT_RECORD_BYTES));
push_ack(1);
push(stop_act, sizeof(stop_act));
}
poll_once();
CHECK(sut->state_event_count == 1 && fake.stop_confirmations == 1);
if (!budget_limited) {
CHECK(sut->connection.send_manager.record_count == 1);
do {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME);
} while (!Ordinary_Event_Room(sut, TCP_EVENT_RECORD_BYTES));
poll_once();
}
CHECK(sut->connection.send_manager.record_count == 0);
while (IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY) {
if (event.type == IEC104_TCP_EVENT_DEACTIVATED) { CHECK(!seen_record); ++seen_state; }
if (event.type == IEC104_TCP_EVENT_RECORD) { CHECK(seen_state == 1); ++seen_record; }
}
CHECK(seen_state == 1 && seen_record == 1);
end();
}
static void test_close_with_pending_frame_and_all_records(void)
{
unsigned index;
uint64_t generation;
IEC104_TcpEvent_t event;
begin();
generation = sut->generation;
for (index = 0; index < IEC104_TCP_RECORD_CAPACITY; ++index) { send_one(); }
CHECK(sut->connection.send_manager.record_count == IEC104_TCP_RECORD_CAPACITY);
fill_input(IEC104_TCP_EVENT_NORMAL_CAPACITY + 1U, IEC104_MAX_APDU_LEN);
CHECK(sut->rx_event_pending && sut->event_count == IEC104_TCP_EVENT_NORMAL_CAPACITY);
CHECK(IEC104_TcpServer_Close_Connection(sut, generation, NULL) == IEC104_TCP_OK);
CHECK(sut->event_count == IEC104_TCP_EVENT_NORMAL_CAPACITY + IEC104_TCP_EVENT_CLOSE_RESERVE);
for (index = 0; index <= IEC104_TCP_EVENT_NORMAL_CAPACITY; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME && event.generation == generation);
CHECK(event.data.i_frame.send_number == index && event.data.i_frame.apdu_length == IEC104_MAX_APDU_LEN);
}
for (index = 0; index < IEC104_TCP_RECORD_CAPACITY; ++index) {
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
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);
}
fake.accept_pending = 1;
poll_once();
CHECK(sut->generation > generation);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_DISCONNECTED && event.generation == generation);
CHECK(event.data.connection.peer.generation == generation);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
CHECK(event.type == IEC104_TCP_EVENT_CONNECTED && event.generation == sut->generation);
end();
}
/* 核对真正写出的完整U帧;调用次数包含WOULD_BLOCK/部分写,不能代替CON数量。
* 调用方另行断言总字节数,既检查帧内容,也防止多回或少回CON。 */
static void check_state_confirmation(size_t offset, int started)
{
const uint8_t expected[] = {0x68, 4, started ? 0x0b : 0x23, 0, 0, 0};
CHECK(offset <= fake.sent_bytes && sizeof(expected) <= fake.sent_bytes - offset);
CHECK(memcmp(fake.output + offset, expected, sizeof(expected)) == 0);
}
/* 以下场景没有业务I帧或故障;逐字段检查事件产生时的快照,不能用当前连接状态替代。
* 主动关闭的DISCONNECTED也保存Close前的ACTIVE阶段,并保留原连接的地址和代次。 */
static void check_connection_event(const IEC104_TcpEvent_t *event,
IEC104_TcpEventType_t type, uint64_t generation, int started)
{
CHECK(event->type == type && event->generation == generation);
CHECK(event->data.connection.peer.generation == generation);
CHECK(strcmp(event->data.connection.peer.address, "127.0.0.1") == 0);
CHECK(event->data.connection.peer.port == 12000);
CHECK(event->data.connection.status_valid == 1 && event->data.connection.socket_error == 0);
CHECK(event->data.connection.status.phase == IEC104_TCP_SESSION_ACTIVE);
CHECK(event->data.connection.status.fault == IEC104_TCP_FAULT_NONE);
CHECK(event->data.connection.status.pending_send_records == 0);
CHECK(event->data.connection.status.data_transfer_started == started);
}
/* 配额只暂缓下一次状态变化;已受理ACT的CON仍发送,通知不能合并或覆盖。 */
static void test_state_reserve_backpressure(void)
{
unsigned index;
const int started_before_wait = IEC104_TCP_EVENT_STATE_RESERVE % 2U == 0U;
size_t confirmed_bytes;
uint64_t generation;
IEC104_TcpEvent_t event;
begin();
generation = sut->generation;
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
push(index % 2U == 0U ? stop_act : start_act, sizeof(start_act));
poll_once();
}
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE);
/* 配额耗尽那一轮可能先让出;再推进一次,检查包括begin中START在内的全部CON。
* 时钟固定且没有I帧,本用例的输出只能由这些6字节CON组成。 */
poll_once();
confirmed_bytes = (size_t)(IEC104_TCP_EVENT_STATE_RESERVE + 1U) * IEC104_APCI_LEN;
CHECK(fake.sent_bytes == confirmed_bytes);
check_state_confirmation(0, 1);
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
check_state_confirmation((size_t)(index + 1U) * IEC104_APCI_LEN, index % 2U != 0U);
}
/* 下一个ACT要求反向切换。多推进一轮仍不得提前改变状态、占槽或回CON。 */
push(started_before_wait ? stop_act : start_act, sizeof(start_act));
poll_once();
poll_once();
CHECK(sut->rx_blocked && !sut->rx_event_pending);
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE);
CHECK(sut->event_count == IEC104_TCP_EVENT_STATE_RESERVE);
CHECK((sut->connection.link_state.dt_state == IEC104_DT_STARTED) == started_before_wait);
CHECK(fake.sent_bytes == confirmed_bytes);
/* NextEvent只释放一个槽;下一轮Poll才重试原ACT并补入一条通知。 */
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, IEC104_TCP_EVENT_DEACTIVATED, generation, 0);
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE - 1U);
CHECK(fake.sent_bytes == confirmed_bytes);
poll_once();
CHECK(!sut->rx_blocked && !sut->rx_event_pending);
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE);
CHECK((sut->connection.link_state.dt_state == IEC104_DT_STARTED) == !started_before_wait);
poll_once();
CHECK(fake.sent_bytes == confirmed_bytes + IEC104_APCI_LEN);
check_state_confirmation(confirmed_bytes, !started_before_wait);
/* 旧通知仍逐条在前,刚受理的变化追加到末尾;每条快照保持当时的启停状态。 */
for (index = 1; index <= IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
int started = index % 2U != 0U;
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, started ? IEC104_TCP_EVENT_ACTIVATED : IEC104_TCP_EVENT_DEACTIVATED,
generation, started);
}
CHECK(sut->state_event_count == 0 && sut->event_count == 0);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_EMPTY);
end();
}
/* 同状态ACT只确认、不通知;分别从STOPPED和STARTED开始,使空队列和满配额下
* 两个方向都被覆盖,不依赖STATE_RESERVE的奇偶。STOP先完成上一轮CON,确保处于IDLE。 */
static void test_repeated_state_act(void)
{
unsigned initial_started, index;
IEC104_TcpEvent_t event;
for (initial_started = 0; initial_started <= 1U; ++initial_started) {
int started = (int)initial_started;
size_t confirmed_bytes;
uint64_t generation;
begin();
generation = sut->generation;
if (!started) {
push(stop_act, sizeof(stop_act));
poll_once();
poll_once();
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, IEC104_TCP_EVENT_DEACTIVATED, generation, 0);
}
CHECK(sut->state_event_count == 0 && sut->event_count == 0);
confirmed_bytes = (started ? 1U : 2U) * IEC104_APCI_LEN;
CHECK(fake.sent_bytes == confirmed_bytes);
/* 没有未读启停通知时,重复START/STOP各追加一个CON,FIFO保持为空。 */
push(started ? start_act : stop_act, sizeof(start_act));
poll_once();
CHECK(fake.sent_bytes == confirmed_bytes + IEC104_APCI_LEN);
check_state_confirmation(confirmed_bytes, started);
CHECK(!sut->rx_blocked && !sut->rx_event_pending);
CHECK((sut->connection.link_state.dt_state == IEC104_DT_STARTED) == started);
CHECK(sut->state_event_count == 0 && sut->event_count == 0);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_EMPTY);
/* 用真实状态变化填满配额;保留全部通知,再发送当前状态对应的重复ACT。 */
confirmed_bytes = fake.sent_bytes;
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
started = !started;
push(started ? start_act : stop_act, sizeof(start_act));
poll_once();
}
poll_once();
CHECK(fake.sent_bytes == confirmed_bytes + (size_t)IEC104_TCP_EVENT_STATE_RESERVE * IEC104_APCI_LEN);
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE);
confirmed_bytes = fake.sent_bytes;
push(started ? start_act : stop_act, sizeof(start_act));
poll_once();
CHECK(fake.sent_bytes == confirmed_bytes + IEC104_APCI_LEN);
check_state_confirmation(confirmed_bytes, started);
CHECK(!sut->rx_blocked && !sut->rx_event_pending);
CHECK((sut->connection.link_state.dt_state == IEC104_DT_STARTED) == started);
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE);
CHECK(sut->event_count == IEC104_TCP_EVENT_STATE_RESERVE);
/* 重复ACT未占槽,也没有替换先前保留的任一通知。 */
started = (int)initial_started;
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
started = !started;
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, started ? IEC104_TCP_EVENT_ACTIVATED : IEC104_TCP_EVENT_DEACTIVATED,
generation, started);
}
CHECK(sut->state_event_count == 0 && sut->event_count == 0);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_EMPTY);
end();
}
}
/* 接入限制要求读完旧启停通知,不要求读完旧DISCONNECTED。
* 启停与建连/断开共用FIFO才有以下跨代次顺序;改成同步回调时需重新定义跨通道时序。 */
static void test_generation_state_order(void)
{
unsigned index;
uint64_t old_generation, new_generation;
IEC104_TcpEvent_t event;
begin();
old_generation = sut->generation;
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
push(index % 2U == 0U ? stop_act : start_act, sizeof(start_act));
poll_once();
}
poll_once();
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE);
CHECK(IEC104_TcpServer_Close_Connection(sut, old_generation, NULL) == IEC104_TCP_OK);
CHECK(sut->event_count == IEC104_TCP_EVENT_STATE_RESERVE + 1U);
/* 普通区有足够空间,接入被阻止应仅因旧启停通知尚未消费。 */
CHECK(Ordinary_Event_Room(sut, TCP_EVENT_CONNECTION_BYTES));
fake.accept_pending = 1;
for (index = 0; index < IEC104_TCP_EVENT_STATE_RESERVE; ++index) {
int started = index % 2U != 0U;
poll_once();
CHECK(fake.accept_pending == 1 && sut->client_socket == IEC104_NET_INVALID_SOCKET);
CHECK(sut->generation == old_generation);
CHECK(sut->state_event_count == IEC104_TCP_EVENT_STATE_RESERVE - index);
CHECK(sut->event_count == IEC104_TCP_EVENT_STATE_RESERVE - index + 1U);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, started ? IEC104_TCP_EVENT_ACTIVATED : IEC104_TCP_EVENT_DEACTIVATED,
old_generation, started);
}
/* 最后一条启停通知读走后,保留DISCONNECTED也允许新接入和START。 */
CHECK(sut->state_event_count == 0 && sut->event_count == 1);
poll_once();
CHECK(fake.accept_pending == 0 && sut->client_socket != IEC104_NET_INVALID_SOCKET);
new_generation = sut->generation;
CHECK(new_generation > old_generation && sut->event_count == 2);
CHECK(sut->connection.link_state.dt_state == IEC104_DT_STOPPED);
push(start_act, sizeof(start_act));
poll_once();
CHECK(sut->event_count == 3 && sut->state_event_count == 1);
CHECK(sut->connection.link_state.dt_state == IEC104_DT_STARTED);
/* 当前连接已STARTED,但CONNECTED快照仍为初始STOPPED,旧事件代次也不改变。 */
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, IEC104_TCP_EVENT_DISCONNECTED, old_generation,
IEC104_TCP_EVENT_STATE_RESERVE % 2U == 0U);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, IEC104_TCP_EVENT_CONNECTED, new_generation, 0);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY);
check_connection_event(&event, IEC104_TCP_EVENT_ACTIVATED, new_generation, 1);
CHECK(sut->state_event_count == 0 && sut->event_count == 0);
CHECK(IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_EMPTY);
end();
}
static void test_fragmented_receive_and_budget(void)
{
unsigned index, frames = 0, calls;
const unsigned batch = IEC104_TCP_EVENT_NORMAL_CAPACITY < TCP_EVENT_BUDGET ?
IEC104_TCP_EVENT_NORMAL_CAPACITY : TCP_EVENT_BUDGET;
IEC104_TcpEvent_t event;
begin();
calls = fake.recv_calls;
for (index = 0; index < batch; ++index) { push_i(index, 0, IEC104_MAX_APDU_LEN); }
poll_once();
CHECK(fake.recv_calls == calls + 1U && sut->event_count == batch);
CHECK(drain() == batch);
fake.recv_limit = 17;
push_i(batch, 0, IEC104_MAX_APDU_LEN);
for (index = 0; index < IEC104_MAX_APDU_LEN && frames == 0; ++index) {
poll_once();
while (IEC104_TcpServer_NextEvent(sut, &event) == IEC104_TCP_EVENT_READY) {
CHECK(event.type == IEC104_TCP_EVENT_I_FRAME && event.data.i_frame.apdu_length == IEC104_MAX_APDU_LEN);
CHECK(event.data.i_frame.send_number == batch);
++frames;
}
}
CHECK(frames == 1);
end();
}
int main(void)
{
printf("Sizes: header=%zu normal=%zu state=%zu close=%zu ring=%zu rx=%u combined=%zu server=%zu\n",
sizeof(TcpEventHeader_t), TCP_EVENT_NORMAL_BYTES, TCP_EVENT_STATE_BYTES, TCP_EVENT_CLOSE_BYTES,
TCP_EVENT_CAPACITY_BYTES, (unsigned)IEC104_TCP_RX_CAPACITY,
TCP_EVENT_CAPACITY_BYTES + IEC104_TCP_RX_CAPACITY, sizeof(*sut));
test_wrap_roundtrip();
test_full_reserves();
test_room_boundaries();
test_invalid_entries();
test_short_capacity();
test_pending_frame_before_record();
test_delayed_receipt(0);
test_delayed_receipt(1);
test_close_with_pending_frame_and_all_records();
test_state_reserve_backpressure();
test_repeated_state_act();
test_generation_state_order();
test_fragmented_receive_and_budget();
puts("TCP event ring: 13 cases passed");
return 0;
}
#else
int main(void)
{
static const uint16_t lengths[] = {16, IEC104_MAX_APDU_LEN};
unsigned kind;
printf("Server=%zu rx=%u\n", sizeof(*sut), (unsigned)IEC104_TCP_RX_CAPACITY);
for (kind = 0; kind < sizeof(lengths) / sizeof(lengths[0]); ++kind) {
unsigned batch, index, calls, frames = 0;
const unsigned batches = 200000;
clock_t started;
begin();
calls = fake.recv_calls;
started = clock();
for (batch = 0; batch < batches; ++batch) {
unsigned delivered = 0, attempts = 0;
for (index = 0; index < TCP_EVENT_BUDGET; ++index) {
push_i(batch * TCP_EVENT_BUDGET + index, 0, lengths[kind]);
}
do {
poll_once();
if (sut->client_socket == IEC104_NET_INVALID_SOCKET) {
IEC104_TcpServerStatus_t status;
CHECK(IEC104_TcpServer_Get_Status(sut, &status, NULL) == IEC104_TCP_OK);
fprintf(stderr, "benchmark closed: batch=%u APDU=%u fault=%d\n", batch, lengths[kind],
status.connection.fault);
}
CHECK(sut->client_socket != IEC104_NET_INVALID_SOCKET);
delivered += drain();
CHECK(++attempts <= TCP_IO_BUDGET);
} while (delivered < TCP_EVENT_BUDGET);
CHECK(delivered == TCP_EVENT_BUDGET);
frames += delivered;
/* 模拟主站等待S确认后再发下一批;给原有调度一次无新输入的发送机会。 */
poll_once();
CHECK(drain() == 0);
}
CHECK(frames == batches * TCP_EVENT_BUDGET);
CHECK(fake.recv_calls - calls == 2U * batches);
printf("APDU=%u frames=%u recv=%u us_per_frame=%.3f\n", lengths[kind], frames,
fake.recv_calls - calls, 1000000.0 * (double)(clock() - started) / CLOCKS_PER_SEC / frames);
end();
}
{
unsigned index, calls, frames = 0;
begin();
for (index = 0; index < 2U * TCP_EVENT_BUDGET; ++index) { push_i(index, 0, IEC104_MAX_APDU_LEN); }
calls = fake.recv_calls;
for (index = 0; index < TCP_IO_BUDGET && frames < 2U * TCP_EVENT_BUDGET; ++index) {
poll_once();
frames += drain();
}
CHECK(frames == 2U * TCP_EVENT_BUDGET);
printf("Burst: APDU=%u frames=%u recv=%u\n", IEC104_MAX_APDU_LEN, frames, fake.recv_calls - calls);
end();
}
return 0;
}
#endif