Add TCPStreamer/TCPStreamerClient (TCP transport behind the UDPSServerI/UDPSClientI seam), an optional single in-process TCPStreamService serving many streams over worker threads, multiplexed SUBSCRIBE (comma list, stream index in fragmentIdx), and TCPStreamJoin, which merges several streams into one sample-and-hold stream paced by a master. StreamHub takes a comma list of streams over one connection. Includes GTests, E2E scenarios s58-s73 and docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
456 lines
14 KiB
C++
456 lines
14 KiB
C++
/**
|
|
* @file TCPSServerGTest.cpp
|
|
* @brief GTest coverage for TCPSServer (UDPS framing over TCP, producer side).
|
|
*
|
|
* The server is driven the way UDPStreamer drives it (single thread:
|
|
* ServiceClients / SendConfig / SendData) and observed through raw loopback
|
|
* sockets, so these tests pin down the wire behaviour.
|
|
*
|
|
* @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
|
|
* the Development of Fusion Energy ('Fusion for Energy').
|
|
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
|
|
* by the European Commission - subsequent versions of the EUPL (the "Licence")
|
|
* You may not use this work except in compliance with the Licence.
|
|
* You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
|
|
*/
|
|
|
|
#define DLL_API
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
|
|
#include "ConfigurationDatabase.h"
|
|
#include "Sleep.h"
|
|
#include "TCPSServer.h"
|
|
#include "TCPTestUtil.h"
|
|
|
|
using namespace MARTe;
|
|
using namespace tcptest;
|
|
|
|
namespace {
|
|
|
|
/** Initialise @p server on an ephemeral port with the given limits. */
|
|
bool InitServer(TCPSServer &server, uint32 maxPayload = 60000u, uint32 maxClients = 16u,
|
|
uint32 depth = 8u, uint32 stallS = 5u, uint32 sndBuf = 0u) {
|
|
ConfigurationDatabase cdb;
|
|
(void)cdb.Write("Port", static_cast<uint32>(0u));
|
|
(void)cdb.Write("MaxPayloadSize", maxPayload);
|
|
(void)cdb.Write("MaxClients", maxClients);
|
|
(void)cdb.Write("ClientQueueDepth", depth);
|
|
(void)cdb.Write("ClientStallTimeout", stallS);
|
|
(void)cdb.Write("SendBufferSize", sndBuf);
|
|
return server.Initialise(cdb);
|
|
}
|
|
|
|
/** Pump ServiceClients() until @p n clients are attached. */
|
|
bool WaitClients(TCPSServer &server, uint32 n, uint32 timeoutMs) {
|
|
const uint64 deadline = NowMs() + timeoutMs;
|
|
while (NowMs() < deadline) {
|
|
server.ServiceClients();
|
|
if (server.GetClientCount() == n) {
|
|
return true;
|
|
}
|
|
Sleep::MSec(5u);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Pump ServiceClients() for @p ms. */
|
|
void Pump(TCPSServer &server, uint32 ms) {
|
|
const uint64 deadline = NowMs() + ms;
|
|
while (NowMs() < deadline) {
|
|
server.ServiceClients();
|
|
Sleep::MSec(5u);
|
|
}
|
|
}
|
|
|
|
struct FastReader {
|
|
int fd;
|
|
std::vector<uint32> counters;
|
|
std::vector<uint8> configPayload;
|
|
bool sawConfigFirst;
|
|
bool badFrame;
|
|
uint32 stopAt;
|
|
};
|
|
|
|
void *FastReaderMain(void *arg) {
|
|
FastReader *r = static_cast<FastReader *>(arg);
|
|
r->sawConfigFirst = false;
|
|
r->badFrame = false;
|
|
bool first = true;
|
|
Frame f;
|
|
for (;;) {
|
|
if (!ReadFrame(r->fd, f, 3000)) {
|
|
break;
|
|
}
|
|
if (first) {
|
|
r->sawConfigFirst = (f.type == UDPS_TYPE_CONFIG);
|
|
first = false;
|
|
}
|
|
if (f.type == UDPS_TYPE_CONFIG) {
|
|
r->configPayload = f.payload;
|
|
} else if (f.type == UDPS_TYPE_DATA) {
|
|
r->counters.push_back(f.counter);
|
|
if (f.payload.size() != 60000u) {
|
|
r->badFrame = true;
|
|
}
|
|
if (f.counter >= r->stopAt) {
|
|
break;
|
|
}
|
|
} else {
|
|
r->badFrame = true;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TCPSServerGTest, TestInitialise_Defaults) {
|
|
TCPSServer server;
|
|
ConfigurationDatabase cdb;
|
|
ASSERT_TRUE(server.Initialise(cdb));
|
|
EXPECT_EQ(TCPSServer::TCPS_DEFAULT_MAX_PAYLOAD, server.GetMaxPayloadSize());
|
|
EXPECT_EQ(static_cast<uint16>(TCPSServer::TCPS_DEFAULT_PORT), server.GetPort());
|
|
EXPECT_FALSE(server.IsMulticast());
|
|
EXPECT_FALSE(server.HasClients());
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestInitialise_RejectsBadValues) {
|
|
{
|
|
TCPSServer s;
|
|
ConfigurationDatabase cdb;
|
|
(void)cdb.Write("MaxPayloadSize", static_cast<uint32>(0u));
|
|
EXPECT_FALSE(s.Initialise(cdb));
|
|
}
|
|
{
|
|
TCPSServer s;
|
|
ConfigurationDatabase cdb;
|
|
(void)cdb.Write("MaxPayloadSize", static_cast<uint32>(2000000u));
|
|
EXPECT_FALSE(s.Initialise(cdb));
|
|
}
|
|
{
|
|
TCPSServer s;
|
|
ConfigurationDatabase cdb;
|
|
(void)cdb.Write("ClientQueueDepth", static_cast<uint32>(0u));
|
|
EXPECT_FALSE(s.Initialise(cdb));
|
|
}
|
|
{
|
|
TCPSServer s;
|
|
ConfigurationDatabase cdb;
|
|
(void)cdb.Write("MaxClients", static_cast<uint32>(0u));
|
|
EXPECT_FALSE(s.Initialise(cdb));
|
|
}
|
|
{
|
|
TCPSServer s;
|
|
ConfigurationDatabase cdb;
|
|
(void)cdb.Write("Port", static_cast<uint32>(70000u));
|
|
EXPECT_FALSE(s.Initialise(cdb));
|
|
}
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestStartStop_Idempotent) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server));
|
|
ASSERT_TRUE(server.Start());
|
|
EXPECT_TRUE(server.Start());
|
|
EXPECT_NE(0u, server.GetPort());
|
|
EXPECT_TRUE(server.Stop());
|
|
EXPECT_TRUE(server.Stop());
|
|
ASSERT_TRUE(server.Start());
|
|
EXPECT_NE(0u, server.GetPort());
|
|
EXPECT_TRUE(server.Stop());
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestConfigBeforeData_LateJoin) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server));
|
|
ASSERT_TRUE(server.Start());
|
|
|
|
std::vector<uint8> cfg = Pattern(40u, 3u);
|
|
ASSERT_TRUE(server.SendConfig(&cfg[0], 40u));
|
|
std::vector<uint8> d = Pattern(100u, 9u);
|
|
EXPECT_TRUE(server.SendData(1u, &d[0], 100u)); // nobody listening: harmless
|
|
|
|
int fd = Connect(server.GetPort());
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_TRUE(WaitClients(server, 1u, 2000u));
|
|
EXPECT_TRUE(server.HasClients());
|
|
|
|
ASSERT_TRUE(server.SendData(2u, &d[0], 100u));
|
|
|
|
Frame f;
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(UDPS_TYPE_CONFIG, f.type);
|
|
EXPECT_EQ(1u, f.totalFragments);
|
|
EXPECT_EQ(cfg, f.payload);
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(UDPS_TYPE_DATA, f.type);
|
|
EXPECT_EQ(2u, f.counter);
|
|
EXPECT_EQ(d, f.payload);
|
|
|
|
close(fd);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestMultipleClients_InOrder) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server, 60000u, 16u, 64u));
|
|
ASSERT_TRUE(server.Start());
|
|
std::vector<uint8> cfg = Pattern(24u, 1u);
|
|
ASSERT_TRUE(server.SendConfig(&cfg[0], 24u));
|
|
|
|
int fds[3];
|
|
for (int i = 0; i < 3; i++) {
|
|
fds[i] = Connect(server.GetPort());
|
|
ASSERT_GE(fds[i], 0);
|
|
}
|
|
ASSERT_TRUE(WaitClients(server, 3u, 2000u));
|
|
|
|
for (uint32 c = 1u; c <= 50u; c++) {
|
|
std::vector<uint8> d = Pattern(200u, static_cast<uint8>(c));
|
|
ASSERT_TRUE(server.SendData(c, &d[0], 200u));
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
Frame f;
|
|
ASSERT_TRUE(ReadFrame(fds[i], f, 2000));
|
|
EXPECT_EQ(UDPS_TYPE_CONFIG, f.type);
|
|
for (uint32 c = 1u; c <= 50u; c++) {
|
|
ASSERT_TRUE(ReadFrame(fds[i], f, 2000)) << "client " << i << " frame " << c;
|
|
EXPECT_EQ(UDPS_TYPE_DATA, f.type);
|
|
ASSERT_EQ(c, f.counter);
|
|
EXPECT_EQ(Pattern(200u, static_cast<uint8>(c)), f.payload);
|
|
}
|
|
close(fds[i]);
|
|
}
|
|
EXPECT_EQ(0u, server.GetClientDrops());
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestReconfigDeliveredToConnectedClient) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server));
|
|
ASSERT_TRUE(server.Start());
|
|
|
|
int fd = Connect(server.GetPort());
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_TRUE(WaitClients(server, 1u, 2000u));
|
|
|
|
std::vector<uint8> cfg1 = Pattern(30u, 1u);
|
|
std::vector<uint8> cfg2 = Pattern(50u, 2u);
|
|
std::vector<uint8> d = Pattern(64u, 5u);
|
|
ASSERT_TRUE(server.SendConfig(&cfg1[0], 30u));
|
|
ASSERT_TRUE(server.SendData(1u, &d[0], 64u));
|
|
Frame f;
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(cfg1, f.payload);
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(1u, f.counter);
|
|
|
|
ASSERT_TRUE(server.SendConfig(&cfg2[0], 50u));
|
|
ASSERT_TRUE(server.SendData(2u, &d[0], 64u));
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(UDPS_TYPE_CONFIG, f.type);
|
|
EXPECT_EQ(cfg2, f.payload);
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(UDPS_TYPE_DATA, f.type);
|
|
EXPECT_EQ(2u, f.counter);
|
|
|
|
// A client joining now gets the NEW config.
|
|
int fd2 = Connect(server.GetPort());
|
|
ASSERT_GE(fd2, 0);
|
|
ASSERT_TRUE(WaitClients(server, 2u, 2000u));
|
|
ASSERT_TRUE(ReadFrame(fd2, f, 2000));
|
|
EXPECT_EQ(UDPS_TYPE_CONFIG, f.type);
|
|
EXPECT_EQ(cfg2, f.payload);
|
|
|
|
close(fd);
|
|
close(fd2);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestMaxClientsRefused) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server, 60000u, 2u));
|
|
ASSERT_TRUE(server.Start());
|
|
|
|
int fds[3];
|
|
for (int i = 0; i < 3; i++) {
|
|
fds[i] = Connect(server.GetPort());
|
|
ASSERT_GE(fds[i], 0);
|
|
}
|
|
Pump(server, 300u);
|
|
EXPECT_EQ(2u, server.GetClientCount());
|
|
|
|
int closedCount = 0;
|
|
for (int i = 0; i < 3; i++) {
|
|
if (PeerClosed(fds[i], 200)) {
|
|
closedCount++;
|
|
}
|
|
close(fds[i]);
|
|
}
|
|
EXPECT_EQ(1, closedCount);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestPeerCloseEvicts) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server));
|
|
ASSERT_TRUE(server.Start());
|
|
|
|
int fd = Connect(server.GetPort());
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_TRUE(WaitClients(server, 1u, 2000u));
|
|
close(fd);
|
|
EXPECT_TRUE(WaitClients(server, 0u, 2000u));
|
|
EXPECT_FALSE(server.HasClients());
|
|
|
|
// The slot is reusable.
|
|
fd = Connect(server.GetPort());
|
|
ASSERT_GE(fd, 0);
|
|
EXPECT_TRUE(WaitClients(server, 1u, 2000u));
|
|
close(fd);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestOversizeFrameRefused) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server));
|
|
ASSERT_TRUE(server.Start());
|
|
int fd = Connect(server.GetPort());
|
|
ASSERT_GE(fd, 0);
|
|
ASSERT_TRUE(WaitClients(server, 1u, 2000u));
|
|
|
|
std::vector<uint8> big(TCPSServer::TCPS_MAX_FRAME_PAYLOAD + 1u, 0u);
|
|
EXPECT_FALSE(server.SendData(1u, &big[0], static_cast<uint32>(big.size())));
|
|
EXPECT_FALSE(server.SendConfig(&big[0], static_cast<uint32>(big.size())));
|
|
EXPECT_TRUE(server.HasClients());
|
|
|
|
// A frame between MaxPayloadSize and the hard limit is still carried.
|
|
std::vector<uint8> mid(100000u, 7u);
|
|
EXPECT_TRUE(server.SendData(2u, &mid[0], static_cast<uint32>(mid.size())));
|
|
Frame f;
|
|
ASSERT_TRUE(ReadFrame(fd, f, 2000));
|
|
EXPECT_EQ(2u, f.counter);
|
|
EXPECT_EQ(mid.size(), f.payload.size());
|
|
close(fd);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestSlowClientDrops_FastClientComplete) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server, 60000u, 16u, 32u, 0u, 65536u));
|
|
ASSERT_TRUE(server.Start());
|
|
std::vector<uint8> cfg = Pattern(24u, 1u);
|
|
ASSERT_TRUE(server.SendConfig(&cfg[0], 24u));
|
|
|
|
int slow = Connect(server.GetPort(), 4096);
|
|
int fast = Connect(server.GetPort());
|
|
ASSERT_GE(slow, 0);
|
|
ASSERT_GE(fast, 0);
|
|
ASSERT_TRUE(WaitClients(server, 2u, 2000u));
|
|
|
|
FastReader reader;
|
|
reader.fd = fast;
|
|
reader.stopAt = 200u;
|
|
pthread_t th;
|
|
ASSERT_EQ(0, pthread_create(&th, NULL, FastReaderMain, &reader));
|
|
|
|
for (uint32 c = 1u; c <= 200u; c++) {
|
|
std::vector<uint8> d = Pattern(60000u, static_cast<uint8>(c));
|
|
ASSERT_TRUE(server.SendData(c, &d[0], 60000u));
|
|
server.ServiceClients();
|
|
Sleep::MSec(2u);
|
|
}
|
|
|
|
// Drain the slow client: the stream must still be well-formed, begin with
|
|
// CONFIG and have strictly increasing counters (gaps = dropped frames).
|
|
uint32 slowFrames = 0u;
|
|
uint32 lastCounter = 0u;
|
|
bool slowFirstConfig = false;
|
|
bool slowInOrder = true;
|
|
uint32 idle = 0u;
|
|
while (idle < 20u) {
|
|
server.ServiceClients();
|
|
Frame f;
|
|
if (ReadFrame(slow, f, 10)) {
|
|
idle = 0u;
|
|
if (slowFrames == 0u) {
|
|
slowFirstConfig = (f.type == UDPS_TYPE_CONFIG);
|
|
}
|
|
if (f.type == UDPS_TYPE_DATA) {
|
|
if (f.counter <= lastCounter) {
|
|
slowInOrder = false;
|
|
}
|
|
lastCounter = f.counter;
|
|
if (f.payload.size() != 60000u) {
|
|
slowInOrder = false;
|
|
}
|
|
}
|
|
slowFrames++;
|
|
} else {
|
|
idle++;
|
|
}
|
|
}
|
|
|
|
pthread_join(th, NULL);
|
|
|
|
ASSERT_EQ(200u, reader.counters.size());
|
|
for (uint32 i = 0u; i < 200u; i++) {
|
|
ASSERT_EQ(i + 1u, reader.counters[i]);
|
|
}
|
|
EXPECT_TRUE(reader.sawConfigFirst);
|
|
EXPECT_FALSE(reader.badFrame);
|
|
EXPECT_GT(server.GetClientDrops(), 100u);
|
|
EXPECT_TRUE(slowFirstConfig);
|
|
EXPECT_TRUE(slowInOrder);
|
|
EXPECT_GT(slowFrames, 1u);
|
|
EXPECT_LT(slowFrames, 201u); // some frames were dropped for it
|
|
EXPECT_EQ(2u, server.GetClientCount());
|
|
|
|
close(slow);
|
|
close(fast);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestStallDisconnect) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server, 60000u, 16u, 4u, 1u, 8192u));
|
|
ASSERT_TRUE(server.Start());
|
|
int slow = Connect(server.GetPort(), 4096);
|
|
ASSERT_GE(slow, 0);
|
|
ASSERT_TRUE(WaitClients(server, 1u, 2000u));
|
|
|
|
std::vector<uint8> d = Pattern(60000u, 1u);
|
|
for (uint32 c = 1u; c <= 6u; c++) {
|
|
ASSERT_TRUE(server.SendData(c, &d[0], 60000u));
|
|
}
|
|
EXPECT_TRUE(server.HasClients()); // not yet: the stall clock is still running
|
|
const uint64 t0 = NowMs();
|
|
EXPECT_TRUE(WaitClients(server, 0u, 4000u));
|
|
EXPECT_GE(NowMs() - t0, 800u); // evicted by the timeout, not at once
|
|
EXPECT_EQ(1u, server.GetStallEvictions());
|
|
close(slow);
|
|
server.Stop();
|
|
}
|
|
|
|
TEST(TCPSServerGTest, TestStallDisabled_KeepsClient) {
|
|
TCPSServer server;
|
|
ASSERT_TRUE(InitServer(server, 60000u, 16u, 4u, 0u, 8192u));
|
|
ASSERT_TRUE(server.Start());
|
|
int slow = Connect(server.GetPort(), 4096);
|
|
ASSERT_GE(slow, 0);
|
|
ASSERT_TRUE(WaitClients(server, 1u, 2000u));
|
|
std::vector<uint8> d = Pattern(60000u, 1u);
|
|
for (uint32 c = 1u; c <= 6u; c++) {
|
|
ASSERT_TRUE(server.SendData(c, &d[0], 60000u));
|
|
}
|
|
Pump(server, 1500u);
|
|
EXPECT_EQ(1u, server.GetClientCount());
|
|
EXPECT_EQ(0u, server.GetStallEvictions());
|
|
close(slow);
|
|
server.Stop();
|
|
}
|