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

+699
View File
@@ -0,0 +1,699 @@
/**
* @file TCPSClientGTest.cpp
* @brief GTest coverage for TCPSClient (UDPS framing over TCP, consumer side).
*
* Each test runs the real TCPSClient either against the real TCPSServer or
* against a scripted raw-socket "server" that misbehaves on purpose (bad
* magic, oversize length, byte-at-a-time delivery, silence).
*
* @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 "TCPSClient.h"
#include "TCPSServer.h"
#include "TCPTestUtil.h"
using namespace MARTe;
using namespace tcptest;
namespace {
/** Thread-safe recorder of everything the client delivers. */
class Recorder : public UDPSClientListener {
public:
Recorder() : connects(0u), disconnects(0u) {
pthread_mutex_init(&mu, NULL);
}
~Recorder() {
pthread_mutex_destroy(&mu);
}
virtual void OnUDPSConfig(const uint8 *p, uint32 n) {
pthread_mutex_lock(&mu);
configs.push_back(std::vector<uint8>(p, p + n));
order.push_back('C');
pthread_mutex_unlock(&mu);
}
virtual void OnUDPSData(const uint8 *p, uint32 n) {
pthread_mutex_lock(&mu);
datas.push_back(std::vector<uint8>(p, p + n));
order.push_back('D');
pthread_mutex_unlock(&mu);
}
virtual void OnUDPSFragment(uint32 counter, uint32 nBytes, bool complete) {
pthread_mutex_lock(&mu);
counters.push_back(counter);
pthread_mutex_unlock(&mu);
}
virtual void OnUDPSConnected() {
pthread_mutex_lock(&mu);
connects++;
pthread_mutex_unlock(&mu);
}
virtual void OnUDPSDisconnected() {
pthread_mutex_lock(&mu);
disconnects++;
pthread_mutex_unlock(&mu);
}
uint32 NumConfigs() { return Locked(configs.size()); }
uint32 NumDatas() { return Locked(datas.size()); }
uint32 Connects() { return Locked(connects); }
uint32 Disconnects() { return Locked(disconnects); }
std::vector<uint8> Config(size_t i) {
pthread_mutex_lock(&mu);
std::vector<uint8> v = configs[i];
pthread_mutex_unlock(&mu);
return v;
}
std::vector<uint8> Data(size_t i) {
pthread_mutex_lock(&mu);
std::vector<uint8> v = datas[i];
pthread_mutex_unlock(&mu);
return v;
}
uint32 Counter(size_t i) {
pthread_mutex_lock(&mu);
uint32 v = counters[i];
pthread_mutex_unlock(&mu);
return v;
}
std::string Order() {
pthread_mutex_lock(&mu);
std::string v = order;
pthread_mutex_unlock(&mu);
return v;
}
private:
uint32 Locked(size_t v) {
pthread_mutex_lock(&mu);
uint32 r = static_cast<uint32>(v);
pthread_mutex_unlock(&mu);
return r;
}
pthread_mutex_t mu;
std::vector<std::vector<uint8> > configs;
std::vector<std::vector<uint8> > datas;
std::vector<uint32> counters;
std::string order;
uint32 connects;
uint32 disconnects;
};
bool InitClient(TCPSClient &c, uint16 port, float32 reconnect = 0.1f, float32 silence = 0.0f) {
ConfigurationDatabase cdb;
(void)cdb.Write("ServerAddr", "127.0.0.1");
(void)cdb.Write("Port", static_cast<uint32>(port));
(void)cdb.Write("ReconnectDelay", reconnect);
(void)cdb.Write("SilenceTimeout", silence);
return c.Initialise(cdb);
}
bool InitServer(TCPSServer &s, uint16 port) {
ConfigurationDatabase cdb;
(void)cdb.Write("Port", static_cast<uint32>(port));
return s.Initialise(cdb);
}
/** Pump the server until @p cond is true or timeout. */
#define PUMP_UNTIL(server, cond, timeoutMs, result) \
{ \
const uint64 pumpDeadline = NowMs() + (timeoutMs); \
(result) = false; \
while (NowMs() < pumpDeadline) { \
(server).ServiceClients(); \
if (cond) { \
(result) = true; \
break; \
} \
Sleep::MSec(5u); \
} \
}
#define WAIT_UNTIL(cond, timeoutMs, result) \
{ \
const uint64 waitDeadline = NowMs() + (timeoutMs); \
(result) = false; \
while (NowMs() < waitDeadline) { \
if (cond) { \
(result) = true; \
break; \
} \
Sleep::MSec(5u); \
} \
}
} // namespace
TEST(TCPSClientGTest, TestInitialise_RequiresPort) {
TCPSClient c;
ConfigurationDatabase cdb;
EXPECT_FALSE(c.Initialise(cdb));
}
TEST(TCPSClientGTest, TestInitialise_RejectsNegativeTimes) {
{
TCPSClient c;
ConfigurationDatabase cdb;
(void)cdb.Write("Port", static_cast<uint32>(44500u));
(void)cdb.Write("SilenceTimeout", -1.0f);
EXPECT_FALSE(c.Initialise(cdb));
}
{
TCPSClient c;
ConfigurationDatabase cdb;
(void)cdb.Write("Port", static_cast<uint32>(44500u));
(void)cdb.Write("ReconnectDelay", -1.0f);
EXPECT_FALSE(c.Initialise(cdb));
}
}
TEST(TCPSClientGTest, TestReceivesConfigThenData) {
TCPSServer server;
ASSERT_TRUE(InitServer(server, 0u));
ASSERT_TRUE(server.Start());
std::vector<uint8> cfg = Pattern(136u * 2u + 16u, 4u);
ASSERT_TRUE(server.SendConfig(&cfg[0], static_cast<uint32>(cfg.size())));
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, server.GetPort()));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
bool ok;
PUMP_UNTIL(server, rec.NumConfigs() == 1u, 3000u, ok);
ASSERT_TRUE(ok);
EXPECT_TRUE(client.IsConnected());
EXPECT_EQ(1u, rec.Connects());
EXPECT_EQ(cfg, rec.Config(0u));
for (uint32 c = 1u; c <= 20u; c++) {
std::vector<uint8> d = Pattern(300u, static_cast<uint8>(c));
ASSERT_TRUE(server.SendData(c, &d[0], 300u));
}
PUMP_UNTIL(server, rec.NumDatas() == 20u, 3000u, ok);
ASSERT_TRUE(ok);
for (uint32 c = 1u; c <= 20u; c++) {
EXPECT_EQ(Pattern(300u, static_cast<uint8>(c)), rec.Data(c - 1u));
EXPECT_EQ(c, rec.Counter(c - 1u));
}
EXPECT_EQ("C" + std::string(20, 'D'), rec.Order());
EXPECT_EQ(0u, client.GetProtocolErrors());
EXPECT_TRUE(client.Stop());
EXPECT_EQ(1u, rec.Disconnects());
server.Stop();
}
TEST(TCPSClientGTest, TestLargeFrame) {
TCPSServer server;
ASSERT_TRUE(InitServer(server, 0u));
ASSERT_TRUE(server.Start());
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, server.GetPort()));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
bool ok;
PUMP_UNTIL(server, server.GetClientCount() == 1u, 3000u, ok);
ASSERT_TRUE(ok);
std::vector<uint8> big = Pattern(900000u, 11u);
ASSERT_TRUE(server.SendData(1u, &big[0], static_cast<uint32>(big.size())));
PUMP_UNTIL(server, rec.NumDatas() == 1u, 5000u, ok);
ASSERT_TRUE(ok);
EXPECT_TRUE(big == rec.Data(0u));
client.Stop();
server.Stop();
}
TEST(TCPSClientGTest, TestReconnectAfterServerRestart) {
uint16 port = 0u;
int probe = Listen(port);
ASSERT_GE(probe, 0);
close(probe);
TCPSServer server;
ASSERT_TRUE(InitServer(server, port));
ASSERT_TRUE(server.Start());
std::vector<uint8> cfg = Pattern(40u, 2u);
ASSERT_TRUE(server.SendConfig(&cfg[0], 40u));
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 0.2f));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
bool ok;
PUMP_UNTIL(server, rec.NumConfigs() == 1u, 3000u, ok);
ASSERT_TRUE(ok);
server.Stop();
WAIT_UNTIL(!client.IsConnected(), 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_EQ(1u, rec.Disconnects());
std::vector<uint8> cfg2 = Pattern(60u, 9u);
ASSERT_TRUE(server.Start());
ASSERT_TRUE(server.SendConfig(&cfg2[0], 60u));
PUMP_UNTIL(server, rec.NumConfigs() == 2u, 4000u, ok);
ASSERT_TRUE(ok);
EXPECT_EQ(cfg2, rec.Config(1u));
EXPECT_EQ(2u, client.GetConnectionCount());
EXPECT_EQ(2u, rec.Connects());
client.Stop();
server.Stop();
}
TEST(TCPSClientGTest, TestRejectsBadMagic_ThenRecovers) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 0.1f));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
int s1 = AcceptOne(lfd, 3000);
ASSERT_GE(s1, 0);
std::vector<uint8> junk(UDPS_HEADER_SIZE, 0xAAu);
ASSERT_TRUE(SendAll(s1, &junk[0], junk.size()));
bool ok;
WAIT_UNTIL(client.GetProtocolErrors() == 1u, 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_TRUE(PeerClosed(s1, 1000));
close(s1);
// The client re-opens the connection and a well-formed stream works.
int s2 = AcceptOne(lfd, 3000);
ASSERT_GE(s2, 0);
std::vector<uint8> cfg = Pattern(20u, 1u);
std::vector<uint8> f = MakeFrame(UDPS_TYPE_CONFIG, 1u, cfg);
ASSERT_TRUE(SendAll(s2, &f[0], f.size()));
WAIT_UNTIL(rec.NumConfigs() == 1u, 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_EQ(0u, rec.NumDatas());
client.Stop();
close(s2);
close(lfd);
}
TEST(TCPSClientGTest, TestRejectsOversizeLength) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 5.0f)); // no quick reconnect
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
uint8 hdr[UDPS_HEADER_SIZE];
UDPSBuildHeader(hdr, UDPS_TYPE_DATA, 1u, 0u, 1u, UDPSClient::UDPS_CLIENT_MAX_PACKET_BYTES + 1u);
ASSERT_TRUE(SendAll(s, hdr, sizeof(hdr)));
bool ok;
WAIT_UNTIL(client.GetProtocolErrors() == 1u, 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_FALSE(client.IsConnected());
EXPECT_EQ(0u, rec.NumDatas());
client.Stop();
close(s);
close(lfd);
}
TEST(TCPSClientGTest, TestRejectsFragmentedFrame) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 5.0f));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
std::vector<uint8> f = MakeFrame(UDPS_TYPE_DATA, 1u, Pattern(10u, 1u), 2u);
ASSERT_TRUE(SendAll(s, &f[0], f.size()));
bool ok;
WAIT_UNTIL(client.GetProtocolErrors() == 1u, 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_EQ(0u, rec.NumDatas());
client.Stop();
close(s);
close(lfd);
}
TEST(TCPSClientGTest, TestPartialFrameReads) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 0.1f));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
std::vector<uint8> p1 = Pattern(64u, 1u);
std::vector<uint8> p2 = Pattern(3000u, 2u);
std::vector<uint8> f1 = MakeFrame(UDPS_TYPE_CONFIG, 1u, p1);
std::vector<uint8> f2 = MakeFrame(UDPS_TYPE_DATA, 5u, p2);
// Frame 1: one byte at a time, so header and payload both split.
for (size_t i = 0u; i < f1.size(); i++) {
ASSERT_TRUE(SendAll(s, &f1[i], 1u));
if ((i % 8u) == 0u) {
Sleep::MSec(2u);
}
}
// Frame 2: split in three uneven chunks with pauses.
size_t cuts[3] = {5u, 20u, f2.size() - 25u};
size_t at = 0u;
for (int k = 0; k < 3; k++) {
ASSERT_TRUE(SendAll(s, &f2[at], cuts[k] - at));
at = cuts[k];
Sleep::MSec(80u);
}
ASSERT_TRUE(SendAll(s, &f2[at], f2.size() - at));
// Frames 3+4 coalesced in one write.
std::vector<uint8> f3 = MakeFrame(UDPS_TYPE_DATA, 6u, Pattern(10u, 3u));
std::vector<uint8> f4 = MakeFrame(UDPS_TYPE_DATA, 7u, Pattern(0u, 0u));
std::vector<uint8> both(f3);
both.insert(both.end(), f4.begin(), f4.end());
ASSERT_TRUE(SendAll(s, &both[0], both.size()));
bool ok;
WAIT_UNTIL(rec.NumDatas() == 3u, 3000u, ok);
ASSERT_TRUE(ok);
EXPECT_EQ(p1, rec.Config(0u));
EXPECT_EQ(p2, rec.Data(0u));
EXPECT_EQ(Pattern(10u, 3u), rec.Data(1u));
EXPECT_EQ(0u, rec.Data(2u).size());
EXPECT_EQ(5u, rec.Counter(0u));
EXPECT_EQ(7u, rec.Counter(2u));
EXPECT_EQ(0u, client.GetProtocolErrors());
client.Stop();
close(s);
close(lfd);
}
TEST(TCPSClientGTest, TestUnknownFrameTypeIgnored) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
std::vector<uint8> a = MakeFrame(UDPS_TYPE_ACK, 1u, Pattern(4u, 1u));
std::vector<uint8> b = MakeFrame(UDPS_TYPE_DATA, 2u, Pattern(4u, 2u));
ASSERT_TRUE(SendAll(s, &a[0], a.size()));
ASSERT_TRUE(SendAll(s, &b[0], b.size()));
bool ok;
WAIT_UNTIL(rec.NumDatas() == 1u, 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_EQ(0u, client.GetProtocolErrors());
client.Stop();
close(s);
close(lfd);
}
TEST(TCPSClientGTest, TestSilenceTimeoutReconnects) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 0.1f, 0.3f));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
// The peer never sends anything: the client must give up and retry.
bool ok;
WAIT_UNTIL(client.GetConnectionCount() >= 2u, 4000u, ok);
EXPECT_TRUE(ok);
EXPECT_GE(rec.Disconnects(), 1u);
client.Stop();
close(lfd);
}
TEST(TCPSClientGTest, TestNoServer_RetriesAndStopsPromptly) {
uint16 port = 0u;
int probe = Listen(port);
ASSERT_GE(probe, 0);
close(probe); // nothing listens on `port` now
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, port, 0.05f));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
Sleep::MSec(300u);
EXPECT_FALSE(client.IsConnected());
EXPECT_EQ(0u, client.GetConnectionCount());
const uint64 t0 = NowMs();
EXPECT_TRUE(client.Stop());
EXPECT_LT(NowMs() - t0, 1000u);
// A server appearing later is picked up on restart.
TCPSServer server;
ASSERT_TRUE(InitServer(server, port));
ASSERT_TRUE(server.Start());
ASSERT_TRUE(client.Start());
bool ok;
PUMP_UNTIL(server, client.IsConnected(), 3000u, ok);
EXPECT_TRUE(ok);
client.Stop();
server.Stop();
}
TEST(TCPSClientGTest, TestStopWhileConnected_IsPrompt) {
TCPSServer server;
ASSERT_TRUE(InitServer(server, 0u));
ASSERT_TRUE(server.Start());
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitClient(client, server.GetPort()));
client.SetListener(&rec);
ASSERT_TRUE(client.Start());
bool ok;
PUMP_UNTIL(server, client.IsConnected(), 3000u, ok);
ASSERT_TRUE(ok);
const uint64 t0 = NowMs();
EXPECT_TRUE(client.Stop());
EXPECT_LT(NowMs() - t0, 1000u);
EXPECT_FALSE(client.IsConnected());
PUMP_UNTIL(server, server.GetClientCount() == 0u, 2000u, ok);
EXPECT_TRUE(ok);
server.Stop();
}
// ---------------------------------------------------------------------------
// Multiplexed connections (StreamName = "a,b,c")
// ---------------------------------------------------------------------------
namespace {
bool InitMuxClient(TCPSClient &c, uint16 port, const char *names) {
ConfigurationDatabase cdb;
(void)cdb.Write("ServerAddr", "127.0.0.1");
(void)cdb.Write("Port", static_cast<uint32>(port));
(void)cdb.Write("ReconnectDelay", 5.0f);
(void)cdb.Write("StreamName", names);
return c.Initialise(cdb);
}
/** Records the per-stream gap reported while each DATA frame is delivered. */
class GapRecorder : public Recorder {
public:
GapRecorder() : source(NULL_PTR(UDPSClientI *)) {}
void Watch(UDPSClientI *s) { source = s; }
virtual void OnUDPSData(const uint8 *p, uint32 n) {
gaps.push_back(source != NULL_PTR(UDPSClientI *) ? source->GetLastDataGap() : 0xFFFFu);
Recorder::OnUDPSData(p, n);
}
std::vector<uint32> gaps; // read only after NumDatas() reached its target
private:
UDPSClientI *source;
};
} // namespace
TEST(TCPSClientGTest, TestMux_StreamNameValidation) {
const char *const bad[] = { "a,,b", ",a", "a,", "a,b,a" };
for (size_t i = 0u; i < sizeof(bad) / sizeof(bad[0]); i++) {
TCPSClient c;
EXPECT_FALSE(InitMuxClient(c, 44500u, bad[i])) << bad[i];
}
{
// 65 distinct names exceed the 64 stream limit.
std::string many;
for (int i = 0; i < 65; i++) {
char b[16];
snprintf(b, sizeof(b), "%ss%d", i ? "," : "", i);
many += b;
}
TCPSClient c;
EXPECT_FALSE(InitMuxClient(c, 44500u, many.c_str()));
}
{
TCPSClient c;
ASSERT_TRUE(InitMuxClient(c, 44500u, "fast,slow,third"));
EXPECT_EQ(3u, c.GetNumStreams());
EXPECT_TRUE(c.GetStream(0u) != NULL_PTR(UDPSClientI *));
EXPECT_TRUE(c.GetStream(2u) != NULL_PTR(UDPSClientI *));
EXPECT_TRUE(c.GetStream(3u) == NULL_PTR(UDPSClientI *));
}
{
TCPSClient c;
ASSERT_TRUE(InitMuxClient(c, 44500u, "only"));
EXPECT_EQ(1u, c.GetNumStreams());
}
}
TEST(TCPSClientGTest, TestMux_SendsListAndDemuxesByIndex) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
GapRecorder r0, r1;
TCPSClient client;
ASSERT_TRUE(InitMuxClient(client, port, "fast,slow"));
r0.Watch(client.GetStream(0u));
r1.Watch(client.GetStream(1u));
client.GetStream(0u)->SetListener(&r0);
client.GetStream(1u)->SetListener(&r1);
ASSERT_TRUE(client.GetStream(0u)->Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
Frame sub;
ASSERT_TRUE(ReadFrame(s, sub, 3000));
EXPECT_EQ(UDPS_TYPE_SUBSCRIBE, sub.type);
EXPECT_EQ(std::string("fast,slow"), std::string(sub.payload.begin(), sub.payload.end()));
std::vector<uint8> cfg0 = Pattern(20u, 1u);
std::vector<uint8> cfg1 = Pattern(30u, 2u);
std::vector<uint8> f;
f = MakeFrame(UDPS_TYPE_CONFIG, 1u, cfg0, 1u, 0u);
ASSERT_TRUE(SendAll(s, &f[0], f.size()));
f = MakeFrame(UDPS_TYPE_CONFIG, 1u, cfg1, 1u, 1u);
ASSERT_TRUE(SendAll(s, &f[0], f.size()));
// Stream 0: counters 1,2,3 ; stream 1: counters 10, 13 (gap of 2).
const uint32 c0[] = { 1u, 2u, 3u };
for (size_t i = 0u; i < 3u; i++) {
f = MakeFrame(UDPS_TYPE_DATA, c0[i], Pattern(8u, static_cast<uint8>(c0[i])), 1u, 0u);
ASSERT_TRUE(SendAll(s, &f[0], f.size()));
}
const uint32 c1[] = { 10u, 13u };
for (size_t i = 0u; i < 2u; i++) {
f = MakeFrame(UDPS_TYPE_DATA, c1[i], Pattern(12u, static_cast<uint8>(c1[i])), 1u, 1u);
ASSERT_TRUE(SendAll(s, &f[0], f.size()));
}
bool ok;
WAIT_UNTIL(r0.NumDatas() == 3u && r1.NumDatas() == 2u, 3000u, ok);
ASSERT_TRUE(ok);
EXPECT_EQ(cfg0, r0.Config(0u));
EXPECT_EQ(cfg1, r1.Config(0u));
EXPECT_EQ(1u, r0.NumConfigs());
EXPECT_EQ(1u, r1.NumConfigs());
EXPECT_EQ(Pattern(8u, 3u), r0.Data(2u));
EXPECT_EQ(Pattern(12u, 13u), r1.Data(1u));
ASSERT_EQ(3u, r0.gaps.size());
ASSERT_EQ(2u, r1.gaps.size());
EXPECT_EQ(0u, r0.gaps[0]);
EXPECT_EQ(0u, r0.gaps[2]);
EXPECT_EQ(0u, r1.gaps[0]);
EXPECT_EQ(2u, r1.gaps[1]) << "gaps are tracked per stream";
EXPECT_EQ(0u, client.GetProtocolErrors());
client.GetStream(0u)->Stop();
close(s);
close(lfd);
}
TEST(TCPSClientGTest, TestMux_StreamIndexOutOfRangeIsProtocolError) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
Recorder rec;
TCPSClient client;
ASSERT_TRUE(InitMuxClient(client, port, "a,b"));
client.GetStream(0u)->SetListener(&rec);
ASSERT_TRUE(client.GetStream(0u)->Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
std::vector<uint8> f = MakeFrame(UDPS_TYPE_DATA, 1u, Pattern(8u, 1u), 1u, 2u);
ASSERT_TRUE(SendAll(s, &f[0], f.size()));
bool ok;
WAIT_UNTIL(client.GetProtocolErrors() == 1u, 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_EQ(0u, rec.NumDatas());
client.GetStream(0u)->Stop();
close(s);
close(lfd);
}
TEST(TCPSClientGTest, TestMux_FacadeStartStopIsReferenceCounted) {
uint16 port = 0u;
int lfd = Listen(port);
ASSERT_GE(lfd, 0);
TCPSClient client;
ASSERT_TRUE(InitMuxClient(client, port, "a,b"));
UDPSClientI *a = client.GetStream(0u);
UDPSClientI *b = client.GetStream(1u);
ASSERT_TRUE(a->Start());
ASSERT_TRUE(a->Start()); // idempotent per facade
ASSERT_TRUE(b->Start());
int s = AcceptOne(lfd, 3000);
ASSERT_GE(s, 0);
bool ok;
WAIT_UNTIL(client.IsConnected(), 3000u, ok);
EXPECT_TRUE(ok);
EXPECT_TRUE(a->Stop());
EXPECT_TRUE(a->Stop());
Sleep::MSec(150u);
EXPECT_TRUE(client.IsConnected()) << "b still wants the connection";
EXPECT_TRUE(b->Stop());
WAIT_UNTIL(!client.IsConnected(), 3000u, ok);
EXPECT_TRUE(ok);
Frame sub; // drain the SUBSCRIBE the client sent on connect
EXPECT_TRUE(ReadFrame(s, sub, 1000));
EXPECT_TRUE(PeerClosed(s, 1000));
close(s);
close(lfd);
}