866 lines
39 KiB
C
866 lines
39 KiB
C
/* 变长通知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
|