/** * @file TCPStreamServiceGTest.cpp * @brief GTests for TCPStreamService / TCPSServiceStream and the Service mode * of the TCPStreamer DataSource. * * Most tests drive a TCPSServiceStream directly (no RealTimeApplication) with * raw sockets, which makes the producer side deterministic. The last tests use * the TCPStreamer / TCPStreamerClient DataSources on top of a service. */ #define DLL_API #include #include #include #include "gtest/gtest.h" #include "AdvancedErrorManagement.h" #include "ClassRegistryDatabase.h" #include "ConfigurationDatabase.h" #include "GAM.h" #include "MemoryOperationsHelper.h" #include "ObjectRegistryDatabase.h" #include "RealTimeApplication.h" #include "Sleep.h" #include "StandardParser.h" #include "TCPSServiceStream.h" #include "TCPStreamJoin.h" #include "TCPStreamService.h" #include "TCPStreamer.h" #include "TCPStreamerClient.h" #include "TCPTestUtil.h" #include "UDPSProtocol.h" using namespace MARTe; using namespace tcptest; namespace { class SvcTestWriterGAM : public GAM { public: CLASS_REGISTER_DECLARATION() SvcTestWriterGAM() : GAM() {} virtual ~SvcTestWriterGAM() {} virtual bool Setup() { return true; } virtual bool Execute() { return true; } }; CLASS_REGISTER(SvcTestWriterGAM, "1.0") class SvcTestReaderGAM : public GAM { public: CLASS_REGISTER_DECLARATION() SvcTestReaderGAM() : GAM() {} virtual ~SvcTestReaderGAM() {} virtual bool Setup() { return true; } virtual bool Execute() { void *in = GetInputSignalMemory(0u); void *out = GetOutputSignalMemory(0u); if ((in != NULL_PTR(void *)) && (out != NULL_PTR(void *))) { (void)MemoryOperationsHelper::Copy(out, in, sizeof(uint32)); } return true; } }; CLASS_REGISTER(SvcTestReaderGAM, "1.0") std::string PortStr(uint16 port) { char buf[16]; snprintf(buf, sizeof(buf), "%u", static_cast(port)); return buf; } std::vector NameBytes(const char *s) { return std::vector(reinterpret_cast(s), reinterpret_cast(s) + strlen(s)); } /** Connect and send a SUBSCRIBE for @p name. */ int Subscribe(uint16 port, const char *name, int rcvBuf = 0) { int fd = Connect(port, rcvBuf); if (fd >= 0) { std::vector f = MakeFrame(UDPS_TYPE_SUBSCRIBE, 0u, NameBytes(name)); if (!SendAll(fd, &f[0], f.size())) { close(fd); fd = -1; } } return fd; } /** A service created programmatically and published under the name "Svc". */ struct ServiceFixture { explicit ServiceFixture(const char *extra = "") : ok(false) { ObjectRegistryDatabase::Instance()->Purge(); std::string cfg = std::string("Class = TCPStreamService\nPort = 0\n") + extra + "\n"; ConfigurationDatabase cdb; StreamString s = cfg.c_str(); (void)s.Seek(0LLU); StandardParser parser(s, cdb); if (!parser.Parse() || !cdb.MoveToRoot()) { return; } svc = ReferenceT(new TCPStreamService()); svc->SetName("Svc"); // Class = ... is consumed by the registry, not by the object itself. (void)cdb.Delete("Class"); ok = svc->Initialise(cdb); if (ok) { ok = ObjectRegistryDatabase::Instance()->Insert(svc); } } ~ServiceFixture() { svc = ReferenceT(); ObjectRegistryDatabase::Instance()->Purge(); } uint16 Port() const { return svc->GetBoundPort(); } bool ok; ReferenceT svc; }; bool WaitClients(const TCPSServiceStream &st, uint32 n, int timeoutMs = 3000) { for (int i = 0; i < timeoutMs / 5; i++) { if (st.GetClientCount() >= n) { return true; } Sleep::MSec(5u); } return st.GetClientCount() >= n; } std::vector Payload(uint32 n, uint8 seed) { return Pattern(n, seed); } bool StartStream(TCPSServiceStream &st) { ConfigurationDatabase cdb; if (!cdb.Write("MaxPayloadSize", static_cast(60000u)) || !cdb.MoveToRoot()) { return false; } return st.Initialise(cdb) && st.Start(); } } // namespace TEST(TCPStreamServiceGTest, ClassIsRegistered) { EXPECT_TRUE(ClassRegistryDatabase::Instance()->Find("TCPStreamService") != NULL_PTR(const ClassRegistryItem *)); } TEST(TCPStreamServiceGTest, DefaultsAndEphemeralPort) { ServiceFixture fx; ASSERT_TRUE(fx.ok); EXPECT_NE(0u, fx.Port()); EXPECT_EQ(2u, fx.svc->GetWorkerCount()); EXPECT_EQ(0u, fx.svc->GetStreamCount()); EXPECT_EQ(0u, fx.svc->GetClientCount()); } TEST(TCPStreamServiceGTest, RejectsInvalidParameters) { const char *const bad[] = { "Workers = 0", "Workers = 64", "MaxClients = 0", "MaxStreams = 0", "ClientQueueDepth = 0", "HandshakeTimeout = 0", "Port = 70000" }; for (size_t i = 0u; i < sizeof(bad) / sizeof(bad[0]); i++) { ServiceFixture fx(bad[i]); EXPECT_FALSE(fx.ok) << bad[i]; } } TEST(TCPStreamServiceGTest, SubscriberGetsConfigThenData) { ServiceFixture fx("ClientQueueDepth = 32"); ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "Alpha"); ASSERT_TRUE(StartStream(st)); EXPECT_EQ(1u, fx.svc->GetStreamCount()); EXPECT_EQ(fx.Port(), st.GetPort()); ASSERT_TRUE(st.SendConfig(&Payload(150u, 1u)[0], 150u)); int fd = Subscribe(fx.Port(), "Alpha"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(st, 1u)); EXPECT_TRUE(st.HasClients()); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); EXPECT_EQ(Payload(150u, 1u), f.payload); for (uint32 c = 1u; c <= 20u; c++) { ASSERT_TRUE(st.SendData(c, &Payload(64u, static_cast(c))[0], 64u)); } for (uint32 c = 1u; c <= 20u; c++) { ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_DATA, f.type); EXPECT_EQ(c, f.counter); EXPECT_EQ(Payload(64u, static_cast(c)), f.payload); } close(fd); } TEST(TCPStreamServiceGTest, ConfigPublishedAfterSubscribeIsDelivered) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "Alpha"); ASSERT_TRUE(StartStream(st)); int fd = Subscribe(fx.Port(), "Alpha"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(st, 1u)); ASSERT_TRUE(st.SendConfig(&Payload(40u, 9u)[0], 40u)); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); EXPECT_EQ(40u, f.payload.size()); close(fd); } TEST(TCPStreamServiceGTest, UnknownStreamIsRejected) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "Alpha"); ASSERT_TRUE(StartStream(st)); int fd = Subscribe(fx.Port(), "Nope"); ASSERT_GE(fd, 0); EXPECT_TRUE(PeerClosed(fd, 3000)); EXPECT_EQ(1u, fx.svc->GetRejectedSubscriptions()); EXPECT_EQ(0u, st.GetClientCount()); close(fd); } TEST(TCPStreamServiceGTest, GarbageHandshakeIsRejected) { ServiceFixture fx; ASSERT_TRUE(fx.ok); int fd = Connect(fx.Port()); ASSERT_GE(fd, 0); std::vector junk(UDPS_HEADER_SIZE, 0xAAu); ASSERT_TRUE(SendAll(fd, &junk[0], junk.size())); EXPECT_TRUE(PeerClosed(fd, 3000)); EXPECT_EQ(1u, fx.svc->GetRejectedSubscriptions()); close(fd); } TEST(TCPStreamServiceGTest, WrongFrameTypeFirstIsRejected) { ServiceFixture fx; ASSERT_TRUE(fx.ok); int fd = Connect(fx.Port()); ASSERT_GE(fd, 0); std::vector f = MakeFrame(UDPS_TYPE_DATA, 0u, NameBytes("Alpha")); ASSERT_TRUE(SendAll(fd, &f[0], f.size())); EXPECT_TRUE(PeerClosed(fd, 3000)); close(fd); } TEST(TCPStreamServiceGTest, SilentConnectionTimesOut) { ServiceFixture fx("HandshakeTimeout = 1"); ASSERT_TRUE(fx.ok); int fd = Connect(fx.Port()); ASSERT_GE(fd, 0); EXPECT_TRUE(PeerClosed(fd, 4000)); close(fd); } TEST(TCPStreamServiceGTest, StreamsAreIsolated) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "A"); TCPSServiceStream b("Svc", "B"); ASSERT_TRUE(StartStream(a)); ASSERT_TRUE(StartStream(b)); EXPECT_EQ(2u, fx.svc->GetStreamCount()); ASSERT_TRUE(a.SendConfig(&Payload(20u, 1u)[0], 20u)); ASSERT_TRUE(b.SendConfig(&Payload(30u, 2u)[0], 30u)); int fa = Subscribe(fx.Port(), "A"); int fb = Subscribe(fx.Port(), "B"); ASSERT_GE(fa, 0); ASSERT_GE(fb, 0); ASSERT_TRUE(WaitClients(a, 1u)); ASSERT_TRUE(WaitClients(b, 1u)); EXPECT_EQ(2u, fx.svc->GetClientCount()); Frame f; ASSERT_TRUE(ReadFrame(fa, f, 2000)); EXPECT_EQ(20u, f.payload.size()); ASSERT_TRUE(ReadFrame(fb, f, 2000)); EXPECT_EQ(30u, f.payload.size()); ASSERT_TRUE(a.SendData(1u, &Payload(8u, 0xA0u)[0], 8u)); ASSERT_TRUE(ReadFrame(fa, f, 2000)); EXPECT_EQ(Payload(8u, 0xA0u), f.payload); EXPECT_FALSE(ReadFrame(fb, f, 200)) << "stream B must not see stream A data"; ASSERT_TRUE(b.SendData(1u, &Payload(8u, 0xB0u)[0], 8u)); ASSERT_TRUE(ReadFrame(fb, f, 2000)); EXPECT_EQ(Payload(8u, 0xB0u), f.payload); close(fa); close(fb); } TEST(TCPStreamServiceGTest, SeveralSubscribersOfOneStream) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "A"); ASSERT_TRUE(StartStream(st)); ASSERT_TRUE(st.SendConfig(&Payload(10u, 1u)[0], 10u)); int fds[4]; for (int i = 0; i < 4; i++) { fds[i] = Subscribe(fx.Port(), "A"); ASSERT_GE(fds[i], 0); } ASSERT_TRUE(WaitClients(st, 4u)); ASSERT_TRUE(st.SendData(7u, &Payload(16u, 3u)[0], 16u)); for (int i = 0; i < 4; i++) { Frame f; ASSERT_TRUE(ReadFrame(fds[i], f, 2000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); ASSERT_TRUE(ReadFrame(fds[i], f, 2000)); EXPECT_EQ(UDPS_TYPE_DATA, f.type); EXPECT_EQ(7u, f.counter); close(fds[i]); } } TEST(TCPStreamServiceGTest, StopDisconnectsSubscribersAndStreamCanRestart) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "A"); ASSERT_TRUE(StartStream(st)); int fd = Subscribe(fx.Port(), "A"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(st, 1u)); EXPECT_TRUE(st.Stop()); EXPECT_EQ(0u, fx.svc->GetStreamCount()); EXPECT_TRUE(PeerClosed(fd, 3000)); close(fd); ASSERT_TRUE(st.Start()); ASSERT_TRUE(st.SendConfig(&Payload(12u, 4u)[0], 12u)); fd = Subscribe(fx.Port(), "A"); ASSERT_GE(fd, 0); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); close(fd); } TEST(TCPStreamServiceGTest, DuplicateStreamNameIsRefused) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "Same"); TCPSServiceStream b("Svc", "Same"); ASSERT_TRUE(StartStream(a)); EXPECT_FALSE(StartStream(b)); EXPECT_EQ(1u, fx.svc->GetStreamCount()); } TEST(TCPStreamServiceGTest, MissingServiceFailsStart) { ObjectRegistryDatabase::Instance()->Purge(); TCPSServiceStream st("NoSuchService", "A"); EXPECT_FALSE(StartStream(st)); } TEST(TCPStreamServiceGTest, MaxClientsIsEnforced) { ServiceFixture fx("MaxClients = 1"); ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "A"); ASSERT_TRUE(StartStream(st)); int first = Subscribe(fx.Port(), "A"); ASSERT_GE(first, 0); ASSERT_TRUE(WaitClients(st, 1u)); int second = Connect(fx.Port()); ASSERT_GE(second, 0); EXPECT_TRUE(PeerClosed(second, 3000)); close(second); EXPECT_EQ(1u, st.GetClientCount()); close(first); } TEST(TCPStreamServiceGTest, ClientCountDropsWhenSubscriberLeaves) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "A"); ASSERT_TRUE(StartStream(st)); int fd = Subscribe(fx.Port(), "A"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(st, 1u)); close(fd); for (int i = 0; (i < 400) && (st.GetClientCount() != 0u); i++) { Sleep::MSec(5u); } EXPECT_EQ(0u, st.GetClientCount()); EXPECT_FALSE(st.HasClients()); } TEST(TCPStreamServiceGTest, SlowSubscriberOnlyLosesItsOwnFrames) { ServiceFixture fx("ClientQueueDepth = 16 ClientStallTimeout = 1 SendBufferSize = 4096"); ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "A"); ASSERT_TRUE(StartStream(st)); int slow = Subscribe(fx.Port(), "A", 2048); /* never read */ int fast = Subscribe(fx.Port(), "A"); ASSERT_GE(slow, 0); ASSERT_GE(fast, 0); ASSERT_TRUE(WaitClients(st, 2u)); const uint32 frames = 3000u; const std::vector body = Payload(2000u, 5u); for (uint32 c = 1u; c <= frames; c++) { ASSERT_TRUE(st.SendData(c, &body[0], static_cast(body.size()))); Frame f; ASSERT_TRUE(ReadFrame(fast, f, 3000)) << "fast subscriber starved at frame " << c; ASSERT_EQ(c, f.counter); } EXPECT_GT(st.GetClientDrops(), 0u); EXPECT_EQ(2u, st.GetClientCount()); /* The stalled subscriber is evicted; the fast one is untouched. */ for (int i = 0; (i < 600) && (fx.svc->GetStallEvictions() == 0u); i++) { Sleep::MSec(10u); } EXPECT_EQ(1u, fx.svc->GetStallEvictions()); ASSERT_TRUE(WaitClients(st, 1u)); for (int i = 0; (i < 200) && (st.GetClientCount() != 1u); i++) { Sleep::MSec(10u); } EXPECT_EQ(1u, st.GetClientCount()); ASSERT_TRUE(st.SendData(frames + 1u, &body[0], static_cast(body.size()))); Frame f; ASSERT_TRUE(ReadFrame(fast, f, 3000)); EXPECT_EQ(frames + 1u, f.counter); close(slow); close(fast); } TEST(TCPStreamServiceGTest, MultiplexedConnectionTagsFramesWithStreamIndex) { ServiceFixture fx("ClientQueueDepth = 32"); ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "A"); TCPSServiceStream b("Svc", "B"); ASSERT_TRUE(StartStream(a)); ASSERT_TRUE(StartStream(b)); ASSERT_TRUE(a.SendConfig(&Payload(20u, 1u)[0], 20u)); ASSERT_TRUE(b.SendConfig(&Payload(30u, 2u)[0], 30u)); /* List order, not registration order, defines the index. */ int fd = Subscribe(fx.Port(), "B,A"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(a, 1u)); ASSERT_TRUE(WaitClients(b, 1u)); EXPECT_EQ(1u, a.GetClientCount()); EXPECT_EQ(1u, b.GetClientCount()); uint32 configs = 0u; for (int i = 0; i < 2; i++) { Frame f; ASSERT_TRUE(ReadFrame(fd, f, 2000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); EXPECT_EQ(1u, f.totalFragments); if (f.fragmentIdx == 0u) { EXPECT_EQ(30u, f.payload.size()); /* B */ } else { EXPECT_EQ(1u, f.fragmentIdx); EXPECT_EQ(20u, f.payload.size()); /* A */ } configs++; } EXPECT_EQ(2u, configs); ASSERT_TRUE(a.SendData(1u, &Payload(8u, 0xA0u)[0], 8u)); ASSERT_TRUE(b.SendData(1u, &Payload(8u, 0xB0u)[0], 8u)); for (int i = 0; i < 2; i++) { Frame f; ASSERT_TRUE(ReadFrame(fd, f, 2000)); EXPECT_EQ(UDPS_TYPE_DATA, f.type); EXPECT_EQ((f.fragmentIdx == 0u) ? Payload(8u, 0xB0u) : Payload(8u, 0xA0u), f.payload); EXPECT_LT(f.fragmentIdx, 2u); } close(fd); } TEST(TCPStreamServiceGTest, SingleNameConnectionStillUsesIndexZero) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "A"); ASSERT_TRUE(StartStream(a)); int fd = Subscribe(fx.Port(), "A"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(a, 1u)); ASSERT_TRUE(a.SendData(1u, &Payload(8u, 1u)[0], 8u)); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 2000)); EXPECT_EQ(0u, f.fragmentIdx); close(fd); } TEST(TCPStreamServiceGTest, MultiplexedBadListsAreRejected) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "A"); TCPSServiceStream b("Svc", "B"); ASSERT_TRUE(StartStream(a)); ASSERT_TRUE(StartStream(b)); const char *const bad[] = { "A,Nope", "A,A", "A,,B", "A,B,", ",A", "A, B" }; for (size_t i = 0u; i < sizeof(bad) / sizeof(bad[0]); i++) { int fd = Subscribe(fx.Port(), bad[i]); ASSERT_GE(fd, 0); EXPECT_TRUE(PeerClosed(fd, 3000)) << bad[i]; close(fd); } /* A failed attach must not leave half a subscription behind. */ Sleep::MSec(50u); EXPECT_EQ(0u, a.GetClientCount()); EXPECT_EQ(0u, b.GetClientCount()); EXPECT_EQ(sizeof(bad) / sizeof(bad[0]), static_cast(fx.svc->GetRejectedSubscriptions())); } TEST(TCPStreamServiceGTest, StreamNameWithCommaIsRefused) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream st("Svc", "A,B"); EXPECT_FALSE(StartStream(st)); } TEST(TCPStreamServiceGTest, StoppingOneStreamClosesMultiplexedConnections) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "A"); TCPSServiceStream b("Svc", "B"); ASSERT_TRUE(StartStream(a)); ASSERT_TRUE(StartStream(b)); int fd = Subscribe(fx.Port(), "A,B"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(a, 1u)); ASSERT_TRUE(WaitClients(b, 1u)); EXPECT_TRUE(a.Stop()); EXPECT_TRUE(PeerClosed(fd, 3000)); for (int i = 0; (i < 400) && (b.GetClientCount() != 0u); i++) { Sleep::MSec(5u); } EXPECT_EQ(0u, b.GetClientCount()); close(fd); } TEST(TCPStreamServiceGTest, FastStreamCannotEvictSlowStreamFrames) { ServiceFixture fx("ClientQueueDepth = 4 ClientStallTimeout = 60 SendBufferSize = 4096"); ASSERT_TRUE(fx.ok); TCPSServiceStream fast("Svc", "Fast"); TCPSServiceStream slow("Svc", "Slow"); ASSERT_TRUE(StartStream(fast)); ASSERT_TRUE(StartStream(slow)); int fd = Subscribe(fx.Port(), "Fast,Slow", 2048); /* not read while filling */ ASSERT_GE(fd, 0); ASSERT_TRUE(WaitClients(fast, 1u)); ASSERT_TRUE(WaitClients(slow, 1u)); const std::vector big = Payload(2000u, 7u); for (uint32 c = 1u; c <= 300u; c++) { ASSERT_TRUE(fast.SendData(c, &big[0], static_cast(big.size()))); } for (uint32 c = 1u; c <= 3u; c++) { ASSERT_TRUE(slow.SendData(c, &Payload(16u, static_cast(c))[0], 16u)); } uint32 nFast = 0u; uint32 nSlow = 0u; Frame f; while (ReadFrame(fd, f, 500)) { if (f.fragmentIdx == 0u) { nFast++; } else { EXPECT_EQ(1u, f.fragmentIdx); nSlow++; } } EXPECT_EQ(3u, nSlow) << "slow stream frames were evicted by the fast stream"; EXPECT_LT(nFast, 300u); EXPECT_GT(fast.GetClientDrops(), 0u); EXPECT_EQ(0u, slow.GetClientDrops()); close(fd); } TEST(TCPStreamServiceGTest, ManyStreamsManyClientsAcrossWorkers) { ServiceFixture fx("Workers = 3 ClientQueueDepth = 64"); ASSERT_TRUE(fx.ok); const int numStreams = 4; TCPSServiceStream *streams[numStreams]; const char *names[numStreams] = { "S0", "S1", "S2", "S3" }; int fds[numStreams][3]; for (int s = 0; s < numStreams; s++) { streams[s] = new TCPSServiceStream("Svc", names[s]); ASSERT_TRUE(StartStream(*streams[s])); ASSERT_TRUE(streams[s]->SendConfig(&Payload(8u, static_cast(s))[0], 8u)); for (int c = 0; c < 3; c++) { fds[s][c] = Subscribe(fx.Port(), names[s]); ASSERT_GE(fds[s][c], 0); } ASSERT_TRUE(WaitClients(*streams[s], 3u)); } EXPECT_EQ(12u, fx.svc->GetClientCount()); for (uint32 n = 1u; n <= 50u; n++) { for (int s = 0; s < numStreams; s++) { ASSERT_TRUE(streams[s]->SendData(n, &Payload(32u, static_cast(s))[0], 32u)); } } for (int s = 0; s < numStreams; s++) { for (int c = 0; c < 3; c++) { Frame f; ASSERT_TRUE(ReadFrame(fds[s][c], f, 2000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); for (uint32 n = 1u; n <= 50u; n++) { ASSERT_TRUE(ReadFrame(fds[s][c], f, 2000)); EXPECT_EQ(n, f.counter); EXPECT_EQ(Payload(32u, static_cast(s)), f.payload); } close(fds[s][c]); } } for (int s = 0; s < numStreams; s++) { delete streams[s]; } } /*---------------------------------------------------------------------------*/ /* DataSources on top of the service */ /*---------------------------------------------------------------------------*/ namespace { ReferenceT LoadApplication(const std::string &config) { ConfigurationDatabase cdb; StreamString cfgStr = config.c_str(); (void)cfgStr.Seek(0LLU); StandardParser parser(cfgStr, cdb); if (!parser.Parse()) { return ReferenceT(); } ObjectRegistryDatabase *god = ObjectRegistryDatabase::Instance(); god->Purge(); if (!god->Initialise(cdb)) { return ReferenceT(); } ReferenceT app = god->Find("Test"); if (!app.IsValid() || !app->ConfigureApplication()) { return ReferenceT(); } return app; } struct AppGuard { ~AppGuard() { ObjectRegistryDatabase::Instance()->Purge(); } }; /** * Application with a service at the root and two service-mode streamers * (Fast, Slow) and, optionally, one client DataSource. */ std::string ServiceAppConfig(uint16 port, const std::string &fastExtra, const std::string &clientExtra) { std::string cfg = "+Service = { Class = TCPStreamService Port = " + PortStr(port) + " Workers = 2 }\n" "+Test = {\n Class = RealTimeApplication\n +Functions = {\n Class = ReferenceContainer\n" " +WFast = { Class = SvcTestWriterGAM\n" " OutputSignals = { Counter = { DataSource = Fast Type = uint32 } } }\n" " +WSlow = { Class = SvcTestWriterGAM\n" " OutputSignals = { Counter = { DataSource = Slow Type = uint32 } } }\n"; std::string functions = " WFast WSlow"; if (!clientExtra.empty()) { cfg += " +Reader = { Class = SvcTestReaderGAM\n" " InputSignals = { Counter = { DataSource = Client Type = uint32 } }\n" " OutputSignals = { Counter = { DataSource = DDB Type = uint32 } } }\n"; functions += " Reader"; } cfg += " }\n +Data = {\n Class = ReferenceContainer\n"; if (!clientExtra.empty()) { cfg += " DefaultDataSource = DDB\n +DDB = { Class = GAMDataSource }\n"; } cfg += " +Timings = { Class = TimingDataSource }\n" " +Fast = { Class = TCPStreamer Service = Service StreamName = FastStream " + fastExtra + "\n Signals = { Counter = { Type = uint32 } } }\n" " +Slow = { Class = TCPStreamer Service = Service\n" " Signals = { Counter = { Type = uint32 } } }\n"; if (!clientExtra.empty()) { cfg += " +Client = { Class = TCPStreamerClient Port = " + PortStr(port) + " " + clientExtra + "\n Signals = { Counter = { Type = uint32 } } }\n"; } cfg += " }\n +States = {\n Class = ReferenceContainer\n +State1 = { Class = RealTimeState\n" " +Threads = { Class = ReferenceContainer\n" " +Thread1 = { Class = RealTimeThread Functions = {" + functions + " } } } } }\n" " +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n}\n"; return cfg; } void SetCounter(ReferenceT &ds, uint32 v) { void *mem = NULL_PTR(void *); ASSERT_TRUE(ds->GetSignalMemoryBuffer(0u, 0u, mem)); memcpy(mem, &v, sizeof(v)); } } // namespace TEST(TCPStreamServiceGTest, StreamerServiceModeRejectsServiceOwnedKeys) { const char *const keys[] = { "Port = 44900", "MaxClients = 4", "ClientQueueDepth = 4", "ClientStallTimeout = 1", "SendBufferSize = 4096" }; for (size_t i = 0u; i < sizeof(keys) / sizeof(keys[0]); i++) { AppGuard guard; ReferenceT app = LoadApplication(ServiceAppConfig(44901u, keys[i], "")); EXPECT_FALSE(app.IsValid()) << keys[i]; } } TEST(TCPStreamServiceGTest, StreamerServiceModeStartsWithoutService) { AppGuard guard; std::string cfg = ServiceAppConfig(44902u, "", ""); /* Point the streamers at an object that does not exist. */ size_t pos; while ((pos = cfg.find("Service = Service")) != std::string::npos) { cfg.replace(pos, strlen("Service = Service"), "Service = Missing"); } ReferenceT app = LoadApplication(cfg); ASSERT_TRUE(app.IsValid()); EXPECT_FALSE(app->PrepareNextState("State1") == ErrorManagement::NoError); } TEST(TCPStreamServiceGTest, StreamerServiceModeAccessors) { AppGuard guard; ReferenceT app = LoadApplication(ServiceAppConfig(44903u, "", "")); ASSERT_TRUE(app.IsValid()); ReferenceT fast = ObjectRegistryDatabase::Instance()->Find("Test.Data.Fast"); ReferenceT slow = ObjectRegistryDatabase::Instance()->Find("Test.Data.Slow"); ASSERT_TRUE(fast.IsValid()); ASSERT_TRUE(slow.IsValid()); EXPECT_TRUE(fast->UsesService()); ASSERT_TRUE(app->PrepareNextState("State1") == ErrorManagement::NoError); ReferenceT svc = ObjectRegistryDatabase::Instance()->Find("Service"); ASSERT_TRUE(svc.IsValid()); EXPECT_EQ(2u, svc->GetStreamCount()); EXPECT_EQ(44903u, fast->GetBoundPort()); EXPECT_EQ(0u, fast->GetClientCount()); /* StreamName defaults to the DataSource name. */ int fd = Subscribe(44903u, "Slow"); ASSERT_GE(fd, 0); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); close(fd); fd = Subscribe(44903u, "FastStream"); ASSERT_GE(fd, 0); ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); close(fd); } TEST(TCPStreamServiceGTest, ServiceStreamerToClientRoundTrip) { AppGuard guard; ReferenceT app = LoadApplication(ServiceAppConfig( 44904u, "", "StreamName = FastStream ReconnectDelay = 0.1")); ASSERT_TRUE(app.IsValid()); ReferenceT fast = ObjectRegistryDatabase::Instance()->Find("Test.Data.Fast"); ReferenceT slow = ObjectRegistryDatabase::Instance()->Find("Test.Data.Slow"); ReferenceT client = ObjectRegistryDatabase::Instance()->Find("Test.Data.Client"); ASSERT_TRUE(fast.IsValid()); ASSERT_TRUE(slow.IsValid()); ASSERT_TRUE(client.IsValid()); ASSERT_TRUE(app->PrepareNextState("State1") == ErrorManagement::NoError); void *mem = NULL_PTR(void *); ASSERT_TRUE(client->GetSignalMemoryBuffer(0u, 0u, mem)); uint32 got = 0u; for (uint32 n = 0u; (n < 600u) && (got != 111u); n++) { SetCounter(fast, 111u); SetCounter(slow, 222u); /* must never reach the client */ ASSERT_TRUE(fast->Synchronise()); ASSERT_TRUE(slow->Synchronise()); (void)client->Synchronise(); memcpy(&got, mem, sizeof(got)); EXPECT_NE(222u, got); } EXPECT_EQ(111u, got); EXPECT_TRUE(client->IsConnected()); EXPECT_EQ(1u, fast->GetClientCount()); EXPECT_EQ(0u, slow->GetClientCount()); EXPECT_EQ(0u, client->GetProtocolErrors()); } /*---------------------------------------------------------------------------*/ /* TCPStreamJoin */ /*---------------------------------------------------------------------------*/ namespace { struct SigSpec { const char *name; uint32 timeIdx; }; /** CONFIG payload with the given signals, publish mode and HRT frequency. */ std::vector MakeConfig(const std::vector &sigs, uint8 mode, uint64 hrt, bool withHrt = true) { std::vector p(4u + sigs.size() * UDPS_SIGNAL_DESC_SIZE + 1u + (withHrt ? 8u : 0u), 0u); uint32 n = static_cast(sigs.size()); memcpy(&p[0], &n, 4u); for (size_t i = 0u; i < sigs.size(); i++) { UDPSSignalDescriptor d; memset(&d, 0, sizeof(d)); strncpy(d.name, sigs[i].name, UDPS_MAX_SIGNAL_NAME - 1u); d.numRows = 1u; d.numCols = 1u; d.timeSignalIdx = sigs[i].timeIdx; memcpy(&p[4u + i * UDPS_SIGNAL_DESC_SIZE], &d, UDPS_SIGNAL_DESC_SIZE); } p[4u + sigs.size() * UDPS_SIGNAL_DESC_SIZE] = mode; if (withHrt) { memcpy(&p[4u + sigs.size() * UDPS_SIGNAL_DESC_SIZE + 1u], &hrt, 8u); } return p; } /** DATA payload: 8-byte timestamp then @p body. */ std::vector MakeData(uint64 ts, const std::vector &body) { std::vector p(8u + body.size()); memcpy(&p[0], &ts, 8u); if (!body.empty()) { memcpy(&p[8], &body[0], body.size()); } return p; } /** Creates a TCPStreamJoin the way the configuration loader would. */ ReferenceT MakeJoin(const std::string ¶ms, bool &ok) { ConfigurationDatabase cdb; StreamString s = (params + "\n").c_str(); (void)s.Seek(0LLU); StandardParser parser(s, cdb); ReferenceT j(new TCPStreamJoin()); ok = parser.Parse() && cdb.MoveToRoot(); if (ok) { j->SetName("Join"); ok = j->Initialise(cdb); } return j; } bool WaitSvcClients(const TCPStreamService *svc, uint32 n, int timeoutMs = 3000) { for (int i = 0; i < timeoutMs / 5; i++) { if (svc->GetClientCount() >= n) { return true; } Sleep::MSec(5u); } return svc->GetClientCount() >= n; } const uint64 kHrt = 1000000000ull; } TEST(TCPStreamServiceGTest, JoinClassIsRegistered) { EXPECT_TRUE(ClassRegistryDatabase::Instance()->Find("TCPStreamJoin") != NULL_PTR(const ClassRegistryItem *)); } TEST(TCPStreamServiceGTest, JoinMergesConfigAndHoldsSlowValues) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream fast("Svc", "fast"); TCPSServiceStream slow("Svc", "slow"); ASSERT_TRUE(StartStream(fast)); ASSERT_TRUE(StartStream(slow)); std::vector fs, ss; SigSpec a = {"t", UDPS_NO_TIME_SIGNAL}, b = {"x", 0u}; fs.push_back(a); fs.push_back(b); SigSpec c = {"time", UDPS_NO_TIME_SIGNAL}, d = {"y", 0u}; ss.push_back(c); ss.push_back(d); std::vector fcfg = MakeConfig(fs, UDPS_PUBLISH_STRICT, kHrt); std::vector scfg = MakeConfig(ss, UDPS_PUBLISH_DECIMATE, kHrt); ASSERT_TRUE(fast.SendConfig(&fcfg[0], static_cast(fcfg.size()))); ASSERT_TRUE(slow.SendConfig(&scfg[0], static_cast(scfg.size()))); bool ok = false; ReferenceT join = MakeJoin( "Class = TCPStreamJoin Service = Svc StreamName = all Sources = \"fast, slow\" Master = fast", ok); ASSERT_TRUE(ok); EXPECT_TRUE(join->IsActive()); EXPECT_TRUE(join->HasConfig()); EXPECT_EQ(2u, join->GetNumSources()); // A tapped source must keep publishing even without a socket subscriber. EXPECT_TRUE(fast.HasClients()); EXPECT_TRUE(slow.HasClients()); EXPECT_EQ(3u, fx.svc->GetStreamCount()); int fd = Subscribe(fx.Port(), "all"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitSvcClients(fx.svc.operator->(), 1u)); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 3000)); ASSERT_EQ(UDPS_TYPE_CONFIG, f.type); ASSERT_EQ(4u + 4u * UDPS_SIGNAL_DESC_SIZE + 1u + 8u, f.payload.size()); uint32 n = 0u; memcpy(&n, &f.payload[0], 4u); EXPECT_EQ(4u, n); const char *const names[] = {"fast_t", "fast_x", "slow_time", "slow_y"}; const uint32 timeIdx[] = {UDPS_NO_TIME_SIGNAL, 0u, UDPS_NO_TIME_SIGNAL, 2u}; for (uint32 i = 0u; i < 4u; i++) { UDPSSignalDescriptor dd; memcpy(&dd, &f.payload[4u + i * UDPS_SIGNAL_DESC_SIZE], UDPS_SIGNAL_DESC_SIZE); EXPECT_STREQ(names[i], dd.name); EXPECT_EQ(timeIdx[i], dd.timeSignalIdx) << names[i]; } const size_t tail = 4u + 4u * UDPS_SIGNAL_DESC_SIZE; EXPECT_EQ(UDPS_PUBLISH_STRICT, f.payload[tail]); uint64 hrt = 0u; memcpy(&hrt, &f.payload[tail + 1u], 8u); EXPECT_EQ(kHrt, hrt); // The master alone cannot emit until the slow source has delivered one frame. ASSERT_TRUE(fast.SendData(1u, &MakeData(100u, Payload(8u, 1u))[0], 16u)); EXPECT_EQ(0u, join->GetFramesEmitted()); ASSERT_TRUE(slow.SendData(1u, &MakeData(555u, Payload(4u, 0x40u))[0], 12u)); EXPECT_EQ(0u, join->GetFramesEmitted()); // the slow source never paces the output ASSERT_TRUE(fast.SendData(2u, &MakeData(200u, Payload(8u, 2u))[0], 16u)); ASSERT_TRUE(fast.SendData(3u, &MakeData(300u, Payload(8u, 3u))[0], 16u)); ASSERT_TRUE(slow.SendData(2u, &MakeData(777u, Payload(4u, 0x50u))[0], 12u)); ASSERT_TRUE(fast.SendData(4u, &MakeData(400u, Payload(8u, 4u))[0], 16u)); EXPECT_EQ(3u, join->GetFramesEmitted()); const uint64 ts[] = {200u, 300u, 400u}; const uint8 fseed[] = {2u, 3u, 4u}; const uint8 sseed[] = {0x40u, 0x40u, 0x50u}; for (uint32 i = 0u; i < 3u; i++) { ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_DATA, f.type); EXPECT_EQ(i + 1u, f.counter); ASSERT_EQ(8u + 8u + 4u, f.payload.size()); uint64 got = 0u; memcpy(&got, &f.payload[0], 8u); EXPECT_EQ(ts[i], got); EXPECT_EQ(Payload(8u, fseed[i]), std::vector(f.payload.begin() + 8, f.payload.begin() + 16)); EXPECT_EQ(Payload(4u, sseed[i]), std::vector(f.payload.begin() + 16, f.payload.end())); } close(fd); } TEST(TCPStreamServiceGTest, JoinWaitsForSourcesAndFollowsTheirLifecycle) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream fast("Svc", "fast"); ASSERT_TRUE(StartStream(fast)); std::vector fs; SigSpec a = {"v", UDPS_NO_TIME_SIGNAL}; fs.push_back(a); std::vector cfg = MakeConfig(fs, UDPS_PUBLISH_STRICT, kHrt); ASSERT_TRUE(fast.SendConfig(&cfg[0], static_cast(cfg.size()))); bool ok = false; ReferenceT join = MakeJoin( "Class = TCPStreamJoin Service = Svc StreamName = all Sources = \"fast,slow\"", ok); ASSERT_TRUE(ok); EXPECT_FALSE(join->IsActive()); EXPECT_EQ(1u, fx.svc->GetStreamCount()); // merged stream not registered yet TCPSServiceStream *slow = new TCPSServiceStream("Svc", "slow"); ASSERT_TRUE(StartStream(*slow)); EXPECT_TRUE(join->IsActive()); EXPECT_FALSE(join->HasConfig()); // slow has not published a CONFIG yet EXPECT_EQ(3u, fx.svc->GetStreamCount()); ASSERT_TRUE(slow->SendConfig(&cfg[0], static_cast(cfg.size()))); EXPECT_TRUE(join->HasConfig()); int fd = Subscribe(fx.Port(), "all"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitSvcClients(fx.svc.operator->(), 1u)); Frame f; ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); // Losing a source removes the merged stream and drops its subscribers. delete slow; EXPECT_FALSE(join->IsActive()); EXPECT_FALSE(join->HasConfig()); EXPECT_EQ(1u, fx.svc->GetStreamCount()); EXPECT_TRUE(PeerClosed(fd, 3000)); close(fd); // The source returns: the merged stream comes back once its CONFIG is known. slow = new TCPSServiceStream("Svc", "slow"); ASSERT_TRUE(StartStream(*slow)); EXPECT_TRUE(join->IsActive()); ASSERT_TRUE(slow->SendConfig(&cfg[0], static_cast(cfg.size()))); EXPECT_TRUE(join->HasConfig()); EXPECT_EQ(3u, fx.svc->GetStreamCount()); // A CONFIG change of a source republishes the merged CONFIG. fd = Subscribe(fx.Port(), "all"); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitSvcClients(fx.svc.operator->(), 1u)); ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); EXPECT_EQ(4u + 2u * UDPS_SIGNAL_DESC_SIZE + 1u + 8u, f.payload.size()); fs.push_back(a); fs[1].name = "w"; std::vector cfg2 = MakeConfig(fs, UDPS_PUBLISH_STRICT, kHrt); ASSERT_TRUE(slow->SendConfig(&cfg2[0], static_cast(cfg2.size()))); ASSERT_TRUE(ReadFrame(fd, f, 3000)); EXPECT_EQ(UDPS_TYPE_CONFIG, f.type); EXPECT_EQ(4u + 3u * UDPS_SIGNAL_DESC_SIZE + 1u + 8u, f.payload.size()); close(fd); delete slow; } TEST(TCPStreamServiceGTest, JoinRefusesUnmergeableSources) { ServiceFixture fx; ASSERT_TRUE(fx.ok); TCPSServiceStream a("Svc", "a"); TCPSServiceStream b("Svc", "b"); ASSERT_TRUE(StartStream(a)); ASSERT_TRUE(StartStream(b)); std::vector s; SigSpec v = {"v", UDPS_NO_TIME_SIGNAL}; s.push_back(v); bool ok = false; // Accumulate source: the merged CONFIG is never published. { std::vector ca = MakeConfig(s, UDPS_PUBLISH_ACCUMULATE, kHrt); std::vector cb = MakeConfig(s, UDPS_PUBLISH_STRICT, kHrt); ASSERT_TRUE(a.SendConfig(&ca[0], static_cast(ca.size()))); ASSERT_TRUE(b.SendConfig(&cb[0], static_cast(cb.size()))); ReferenceT j = MakeJoin( "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,b\"", ok); ASSERT_TRUE(ok); EXPECT_TRUE(j->IsActive()); EXPECT_FALSE(j->HasConfig()); } // Different HRT frequencies. { std::vector ca = MakeConfig(s, UDPS_PUBLISH_STRICT, kHrt); std::vector cb = MakeConfig(s, UDPS_PUBLISH_STRICT, kHrt / 2u); ASSERT_TRUE(a.SendConfig(&ca[0], static_cast(ca.size()))); ASSERT_TRUE(b.SendConfig(&cb[0], static_cast(cb.size()))); ReferenceT j = MakeJoin( "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,b\"", ok); ASSERT_TRUE(ok); EXPECT_FALSE(j->HasConfig()); } // An unknown HRT frequency matches any. { std::vector ca = MakeConfig(s, UDPS_PUBLISH_STRICT, 0u, false); std::vector cb = MakeConfig(s, UDPS_PUBLISH_STRICT, kHrt); ASSERT_TRUE(a.SendConfig(&ca[0], static_cast(ca.size()))); ASSERT_TRUE(b.SendConfig(&cb[0], static_cast(cb.size()))); ReferenceT j = MakeJoin( "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,b\"", ok); ASSERT_TRUE(ok); EXPECT_TRUE(j->HasConfig()); } // A merged signal name that does not fit 63 characters. { std::string longName(62u, 'q'); std::vector ls; SigSpec l = {longName.c_str(), UDPS_NO_TIME_SIGNAL}; ls.push_back(l); std::vector ca = MakeConfig(ls, UDPS_PUBLISH_STRICT, kHrt); std::vector cb = MakeConfig(s, UDPS_PUBLISH_STRICT, kHrt); ASSERT_TRUE(a.SendConfig(&ca[0], static_cast(ca.size()))); ASSERT_TRUE(b.SendConfig(&cb[0], static_cast(cb.size()))); ReferenceT j = MakeJoin( "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,b\"", ok); ASSERT_TRUE(ok); EXPECT_FALSE(j->HasConfig()); } } TEST(TCPStreamServiceGTest, JoinRejectsInvalidParameters) { ServiceFixture fx; ASSERT_TRUE(fx.ok); const char *const bad[] = { "Class = TCPStreamJoin StreamName = m Sources = \"a,b\"", // no Service "Class = TCPStreamJoin Service = Svc StreamName = m", // no Sources "Class = TCPStreamJoin Service = Nope StreamName = m Sources = \"a,b\"", // unknown service "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,a\"", // duplicate "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,m\"", // own output "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,,b\"", // empty name "Class = TCPStreamJoin Service = Svc StreamName = m Sources = \"a,b\" Master = c", // bad master }; for (size_t i = 0u; i < sizeof(bad) / sizeof(bad[0]); i++) { bool ok = true; ReferenceT j = MakeJoin(bad[i], ok); EXPECT_FALSE(ok) << bad[i]; } }