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,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);
|
||||
}
|
||||
Reference in new issue
Block a user