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:
Martino FerrariandClaude Opus 4.6 committed 2026-11-06 13:41:27 +01:00
1 parent 5562877c99
commit 50d85363a3
79 files changed
+12348 -138

No files matched your search

@@ -84,6 +84,7 @@ UDPStreamer::UDPStreamer()
: MemoryDataSourceI(), EmbeddedServiceMethodBinderI(), executor(*this) {
port = UDPS_DEFAULT_PORT;
maxPayloadSize = UDPS_DEFAULT_MAX_PAYLOAD;
server = new UDPSServer();
cpuMask = 0xFFFFFFFFu;
stackSize = THREADS_DEFAULT_STACKSIZE;
publishMode = UDPStreamerPublishStrict;
@@ -135,7 +136,11 @@ UDPStreamer::~UDPStreamer() {
}
}
(void)server.Stop();
if (server != NULL_PTR(UDPSServerI *)) {
(void)server->Stop();
delete server;
server = NULL_PTR(UDPSServerI *);
}
HeapI *heapAccum = GlobalObjectsDatabase::Instance()->GetStandardHeap();
if (accumBuffer != NULL_PTR(uint8 *)) {
@@ -192,7 +197,7 @@ bool UDPStreamer::Initialise(StructuredDataI &data) {
if (ok) {
if (!data.Read("MaxPayloadSize", maxPayloadSize)) {
maxPayloadSize = UDPS_DEFAULT_MAX_PAYLOAD;
maxPayloadSize = DefaultMaxPayloadSize();
REPORT_ERROR(ErrorManagement::Information,
"MaxPayloadSize not specified; using default %u.",
maxPayloadSize);
@@ -286,38 +291,58 @@ bool UDPStreamer::Initialise(StructuredDataI &data) {
}
if (ok) {
// Build server config with already-resolved Port and MaxPayloadSize so
// UDPSServer::Initialise() always sees them, even when defaults were used.
ConfigurationDatabase serverCfg;
(void)serverCfg.Write("Port", static_cast<uint32>(port));
(void)serverCfg.Write("MaxPayloadSize", maxPayloadSize);
// Forward optional multicast / timeout params if present in caller's data.
StreamString mcGroup;
if (data.Read("MulticastGroup", mcGroup) && (mcGroup.Size() > 0u)) {
(void)serverCfg.Write("MulticastGroup", mcGroup.Buffer());
uint32 dp = 0u;
if (data.Read("DataPort", dp)) {
(void)serverCfg.Write("DataPort", dp);
}
StreamString iface;
if (data.Read("Interface", iface)) {
(void)serverCfg.Write("Interface", iface);
} else {
ok = false;
REPORT_ERROR(ErrorManagement::InitialisationError,
"Missing mandatory interface for multicasting");
}
}
uint32 clientTimeout = 0u;
if (data.Read("ClientTimeout", clientTimeout)) {
(void)serverCfg.Write("ClientTimeout", clientTimeout);
}
ok = server.Initialise(serverCfg);
ok = InitialiseTransport(data);
}
return ok;
}
uint32 UDPStreamer::DefaultMaxPayloadSize() const {
return UDPS_DEFAULT_MAX_PAYLOAD;
}
bool UDPStreamer::InitialiseTransport(StructuredDataI &data) {
bool ok = true;
// Build server config with already-resolved Port and MaxPayloadSize so
// UDPSServer::Initialise() always sees them, even when defaults were used.
ConfigurationDatabase serverCfg;
(void)serverCfg.Write("Port", static_cast<uint32>(port));
(void)serverCfg.Write("MaxPayloadSize", maxPayloadSize);
// Forward optional multicast / timeout params if present in caller's data.
StreamString mcGroup;
if (data.Read("MulticastGroup", mcGroup) && (mcGroup.Size() > 0u)) {
(void)serverCfg.Write("MulticastGroup", mcGroup.Buffer());
uint32 dp = 0u;
if (data.Read("DataPort", dp)) {
(void)serverCfg.Write("DataPort", dp);
}
StreamString iface;
if (data.Read("Interface", iface)) {
(void)serverCfg.Write("Interface", iface);
} else {
ok = false;
REPORT_ERROR(ErrorManagement::InitialisationError,
"Missing mandatory interface for multicasting");
}
}
uint32 clientTimeout = 0u;
if (data.Read("ClientTimeout", clientTimeout)) {
(void)serverCfg.Write("ClientTimeout", clientTimeout);
}
ok = server->Initialise(serverCfg);
return ok;
}
void UDPStreamer::SetServer(UDPSServerI *newServer) {
if (server != NULL_PTR(UDPSServerI *)) {
delete server;
}
server = newServer;
}
UDPSServerI *UDPStreamer::Transport() const { return server; }
bool UDPStreamer::SetConfiguredDatabase(StructuredDataI &data) {
bool ok = MemoryDataSourceI::SetConfiguredDatabase(data);
if (!ok) {
@@ -804,7 +829,7 @@ bool UDPStreamer::PrepareNextState(const char8 *const currentStateName,
const char8 *const nextStateName) {
bool ok = true;
ok = server.Start();
ok = server->Start();
/* Build the CONFIG payload and cache it in the server so any CONNECT client
* receives it immediately. The config is static for the lifetime of this
@@ -818,7 +843,7 @@ bool UDPStreamer::PrepareNextState(const char8 *const currentStateName,
if (cfgBuf != NULL_PTR(uint8 *)) {
uint32 cfgPayloadSize = 0u;
if (BuildConfigPayload(cfgBuf, configBufSize, cfgPayloadSize)) {
(void)server.SendConfig(cfgBuf, cfgPayloadSize);
(void)server->SendConfig(cfgBuf, cfgPayloadSize);
} else {
REPORT_ERROR(ErrorManagement::Warning,
"Could not build initial CONFIG payload.");
@@ -992,7 +1017,7 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
/* --- Poll for incoming control commands (CONNECT / DISCONNECT / ACK) ---
*/
server.ServiceClients();
server->ServiceClients();
/* Synchronise() already gates publication to the correct rate (size/time
* for Accumulate, every-Nth for Decimate, every call for Strict). The
@@ -1015,12 +1040,12 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
}
bufMutex.FastUnLock();
if ((fill > 0u) && server.HasClients()) {
if ((fill > 0u) && server->HasClients()) {
SerializeAccumulated(scratchBuffer, scratchTimestamps, fill);
uint32 sendBytes =
UDPS_TIMESTAMP_BYTES + 4u + fill * singleCycleWireBytes;
packetCounter++;
if (!server.SendData(packetCounter, wireBuffer, sendBytes)) {
if (!server->SendData(packetCounter, wireBuffer, sendBytes)) {
REPORT_ERROR(ErrorManagement::Warning,
"Failed to send Accumulate DATA packet (counter=%u).",
packetCounter);
@@ -1040,11 +1065,11 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
}
bufMutex.FastUnLock();
if (pending && server.HasClients()) {
if (pending && server->HasClients()) {
QuantizeAndSerialize(scratchBuffer, ts);
packetCounter++;
if (!server.SendData(packetCounter, wireBuffer, totalWireBytes)) {
if (!server->SendData(packetCounter, wireBuffer, totalWireBytes)) {
REPORT_ERROR(ErrorManagement::Warning,
"Failed to send DATA packet (counter=%u).",
packetCounter);
@@ -1056,7 +1081,7 @@ ErrorManagement::ErrorType UDPStreamer::Execute(ExecutionInfo &info) {
}
if (info.GetStage() == ExecutionInfo::TerminationStage) {
(void)server.Stop();
(void)server->Stop();
REPORT_ERROR(ErrorManagement::Information,
"UDPStreamer background thread terminated.");
}
@@ -1367,9 +1392,9 @@ uint16 UDPStreamer::GetPort() const { return port; }
uint32 UDPStreamer::GetMaxPayloadSize() const { return maxPayloadSize; }
bool UDPStreamer::IsClientConnected() const { return server.HasClients(); }
bool UDPStreamer::IsClientConnected() const { return server->HasClients(); }
bool UDPStreamer::IsMulticast() const { return server.IsMulticast(); }
bool UDPStreamer::IsMulticast() const { return server->IsMulticast(); }
uint32 UDPStreamer::GetDroppedPublications() const { return droppedPublications; }
@@ -39,6 +39,7 @@
#include "SingleThreadService.h"
#include "StreamString.h"
#include "UDPSServer.h"
#include "UDPSTransportI.h"
/*---------------------------------------------------------------------------*/
/* Class declaration */
@@ -333,6 +334,33 @@ public:
*/
uint32 GetDroppedPublications() const;
protected:
/**
* @brief MaxPayloadSize used when the configuration does not specify one.
* @details Transport specific (a datagram budget for UDP, a frame budget for
* TCP), hence overridable. Called from Initialise(), not the constructor.
*/
virtual uint32 DefaultMaxPayloadSize() const;
/**
* @brief Configures the transport from the already-resolved Port and
* MaxPayloadSize plus whatever transport keys @p data carries.
* @details The default implementation configures the UDPSServer (unicast /
* multicast). A subclass that installs another transport with SetServer()
* overrides this to configure it instead.
*/
virtual bool InitialiseTransport(StructuredDataI &data);
/**
* @brief Replaces the transport. Takes ownership; only valid before Start().
*/
void SetServer(UDPSServerI *newServer);
/**
* @brief The transport currently in use (owned by this object).
*/
UDPSServerI *Transport() const;
private:
/**
* @brief Serializes the CONFIG payload into buf and sets payloadSize.
@@ -417,8 +445,8 @@ private:
uint8 *wireBuffer; /**< Pre-allocated buffer for the serialized DATA payload */
uint32 totalWireBytes; /**< Total bytes of all signals after quantization + 8-byte timestamp prefix */
/* Networking — handled by UDPSServer */
UDPSServer server; /**< Multi-client session manager (unicast and multicast) */
/* Networking — handled by a UDPSServerI (UDPSServer by default) */
UDPSServerI *server; /**< Owned transport; see SetServer() */
/* Thread management */
SingleThreadService executor; /**< Background thread service */
@@ -135,4 +135,6 @@
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicUDPSocket.h \
/home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \
../../../..//Common/UDP/UDPSProtocol.h
../../../..//Common/UDP/UDPSProtocol.h \
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h \
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h
@@ -135,4 +135,6 @@ UDPStreamer.o: UDPStreamer.cpp \
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/BasicUDPSocket.h \
/home/martino/workspace/MARTe2/Source/Core/BareMetal/L1Portability/Sleep.h \
/home/martino/workspace/MARTe2/Source/Core/FileSystem/L1Portability/InternetHost.h \
../../../..//Common/UDP/UDPSProtocol.h
../../../..//Common/UDP/UDPSProtocol.h \
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h \
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h