feat(tcp): TCPStreamer DataSources, in-process TCPStreamService, stream multiplexing and TCPStreamJoin
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>
This commit is contained in:
1 parent
5562877c99
commit
50d85363a3
79 files changed
+12348
-138
No files matched your search
@@ -0,0 +1,455 @@
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
Reference in new issue
Block a user