Implemented hearthbit client side + tests
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* @file UDPSClientGTest.cpp
|
||||
* @brief GTest coverage for UDPSClient unicast keepalive.
|
||||
*
|
||||
* UDPSServer evicts silent unicast clients after its ClientTimeout (default
|
||||
* 30 s). UDPSClient must therefore re-send a keepalive ACK from the same
|
||||
* socket on KeepAliveInterval so the server refreshes its last-seen without
|
||||
* re-sending CONFIG. These tests drive a real UDPSClient against a local
|
||||
* UDP socket acting as the server and assert the wire behaviour; a final
|
||||
* pair runs the REAL UDPSServer + UDPSClient past the eviction deadline to
|
||||
* lock the fix in against regression.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @warning Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the Licence is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the Licence permissions and limitations under the Licence.
|
||||
*/
|
||||
|
||||
#define DLL_API
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Standard header includes */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Project header includes */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
#include "BasicUDPSocket.h"
|
||||
#include "ConfigurationDatabase.h"
|
||||
#include "InternetHost.h"
|
||||
#include "Sleep.h"
|
||||
#include "UDPSClient.h"
|
||||
#include "UDPSProtocol.h"
|
||||
#include "UDPSServer.h"
|
||||
|
||||
using namespace MARTe;
|
||||
|
||||
namespace {
|
||||
|
||||
/** @return the bound local port of @p sock, or 0 on failure. */
|
||||
uint16 GetBoundPort(BasicUDPSocket &sock) {
|
||||
struct sockaddr_in addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
if (getsockname(sock.GetReadHandle(),
|
||||
reinterpret_cast<struct sockaddr *>(&addr), &len) != 0) {
|
||||
return 0u;
|
||||
}
|
||||
return ntohs(addr.sin_port);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one datagram from @p sock within @p timeoutMs.
|
||||
* @return true and fills @p type/@p srcPort on a valid UDPS datagram; false
|
||||
* on timeout or malformed packet.
|
||||
*/
|
||||
bool WaitDatagram(BasicUDPSocket &sock, int timeoutMs, uint8 &type,
|
||||
uint16 &srcPort) {
|
||||
int fd = sock.GetReadHandle();
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
fd_set rset;
|
||||
FD_ZERO(&rset);
|
||||
FD_SET(fd, &rset);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeoutMs / 1000;
|
||||
tv.tv_usec = (timeoutMs % 1000) * 1000;
|
||||
int nready = select(fd + 1, &rset, NULL, NULL, &tv);
|
||||
if (nready <= 0) {
|
||||
return false;
|
||||
}
|
||||
uint8 buf[UDPS_HEADER_SIZE];
|
||||
uint32 size = UDPS_HEADER_SIZE;
|
||||
if (!sock.Read(reinterpret_cast<char8 *>(buf), size)) {
|
||||
return false;
|
||||
}
|
||||
if (size < UDPS_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
const UDPSPacketHeader *hdr = reinterpret_cast<const UDPSPacketHeader *>(buf);
|
||||
if (hdr->magic != UDPS_MAGIC) {
|
||||
return false;
|
||||
}
|
||||
type = hdr->type;
|
||||
InternetHost src = sock.GetSource();
|
||||
srcPort = src.GetPort();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pump the UDPSServer service loop for @p durationMs, like UDPStreamer
|
||||
* does from its background thread.
|
||||
*/
|
||||
void PumpServer(UDPSServer &server, uint32 durationMs) {
|
||||
uint32 elapsed = 0u;
|
||||
while (elapsed < durationMs) {
|
||||
server.ServiceClients();
|
||||
Sleep::MSec(20u);
|
||||
elapsed += 20u;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pump the server loop until a client registers (or timeout).
|
||||
* @return true if at least one client connected.
|
||||
*/
|
||||
bool WaitForClient(UDPSServer &server, uint32 timeoutMs) {
|
||||
uint32 elapsed = 0u;
|
||||
while (elapsed < timeoutMs) {
|
||||
server.ServiceClients();
|
||||
if (server.GetClientCount() > 0u) {
|
||||
return true;
|
||||
}
|
||||
Sleep::MSec(20u);
|
||||
elapsed += 20u;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Method definitions */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
TEST(UDPSClientGTest, TestUnicastKeepAliveSendsPeriodicAck) {
|
||||
/* Fake server socket (ephemeral port) */
|
||||
BasicUDPSocket server;
|
||||
ASSERT_TRUE(server.Open());
|
||||
ASSERT_TRUE(server.Listen(0u));
|
||||
uint16 serverPort = GetBoundPort(server);
|
||||
ASSERT_NE(serverPort, 0u);
|
||||
|
||||
ConfigurationDatabase cfg;
|
||||
ASSERT_TRUE(cfg.Write("ServerAddr", "127.0.0.1"));
|
||||
ASSERT_TRUE(cfg.Write("Port", static_cast<uint32>(serverPort)));
|
||||
ASSERT_TRUE(cfg.Write("KeepAliveInterval", 1u));
|
||||
/* SilenceTimeout=0 keeps the session stable for the whole test */
|
||||
ASSERT_TRUE(cfg.Write("SilenceTimeout", 0u));
|
||||
|
||||
UDPSClient client;
|
||||
ASSERT_TRUE(client.Initialise(cfg));
|
||||
ASSERT_TRUE(client.Start());
|
||||
|
||||
/* 1) CONNECT from the client's ephemeral socket */
|
||||
uint8 type = 0xFFu;
|
||||
uint16 clientPort = 0u;
|
||||
ASSERT_TRUE(WaitDatagram(server, 3000, type, clientPort));
|
||||
EXPECT_EQ(type, UDPS_TYPE_CONNECT);
|
||||
ASSERT_NE(clientPort, 0u);
|
||||
|
||||
/* 2) Keepalive ACKs arrive periodically from the SAME socket */
|
||||
uint32 acks = 0u;
|
||||
uint32 elapsedMs = 0u;
|
||||
while ((acks < 2u) && (elapsedMs < 3500u)) {
|
||||
uint8 t = 0xFFu;
|
||||
uint16 port = 0u;
|
||||
bool got = WaitDatagram(server, 1000, t, port);
|
||||
elapsedMs += 1000u;
|
||||
if (!got) {
|
||||
continue;
|
||||
}
|
||||
if ((t == UDPS_TYPE_ACK) && (port == clientPort)) {
|
||||
acks++;
|
||||
}
|
||||
}
|
||||
EXPECT_GE(acks, 2u);
|
||||
|
||||
client.Stop();
|
||||
server.Close();
|
||||
}
|
||||
|
||||
TEST(UDPSClientGTest, TestKeepAliveDisabledWhenIntervalZero) {
|
||||
BasicUDPSocket server;
|
||||
ASSERT_TRUE(server.Open());
|
||||
ASSERT_TRUE(server.Listen(0u));
|
||||
uint16 serverPort = GetBoundPort(server);
|
||||
ASSERT_NE(serverPort, 0u);
|
||||
|
||||
ConfigurationDatabase cfg;
|
||||
ASSERT_TRUE(cfg.Write("ServerAddr", "127.0.0.1"));
|
||||
ASSERT_TRUE(cfg.Write("Port", static_cast<uint32>(serverPort)));
|
||||
ASSERT_TRUE(cfg.Write("KeepAliveInterval", 0u));
|
||||
ASSERT_TRUE(cfg.Write("SilenceTimeout", 0u));
|
||||
|
||||
UDPSClient client;
|
||||
ASSERT_TRUE(client.Initialise(cfg));
|
||||
ASSERT_TRUE(client.Start());
|
||||
|
||||
uint8 type = 0xFFu;
|
||||
uint16 clientPort = 0u;
|
||||
ASSERT_TRUE(WaitDatagram(server, 3000, type, clientPort));
|
||||
EXPECT_EQ(type, UDPS_TYPE_CONNECT);
|
||||
|
||||
/* No keepalive configured: nothing else must arrive */
|
||||
EXPECT_FALSE(WaitDatagram(server, 2000, type, clientPort));
|
||||
|
||||
client.Stop();
|
||||
server.Close();
|
||||
}
|
||||
|
||||
TEST(UDPSClientGTest, TestKeepAlivePreventsServerEviction) {
|
||||
/* Regression test for the 30 s unicast disconnect: UDPSServer evicts a
|
||||
* silent client after ClientTimeout; the client's periodic ACKs must
|
||||
* keep it registered. Drive the REAL server + client pair, like
|
||||
* UDPStreamer + StreamHub do, past the eviction deadline. */
|
||||
|
||||
/* Free-port probe (UDP has no TIME_WAIT) */
|
||||
BasicUDPSocket probe;
|
||||
ASSERT_TRUE(probe.Open());
|
||||
ASSERT_TRUE(probe.Listen(0u));
|
||||
uint16 serverPort = GetBoundPort(probe);
|
||||
probe.Close();
|
||||
ASSERT_NE(serverPort, 0u);
|
||||
|
||||
/* Server with a short eviction timeout so the test is fast */
|
||||
ConfigurationDatabase serverCfg;
|
||||
ASSERT_TRUE(serverCfg.Write("Port", static_cast<uint32>(serverPort)));
|
||||
ASSERT_TRUE(serverCfg.Write("ClientTimeout", 3u));
|
||||
UDPSServer server;
|
||||
ASSERT_TRUE(server.Initialise(serverCfg));
|
||||
ASSERT_TRUE(server.Start());
|
||||
|
||||
/* Client: keepalive every 1 s (< server timeout), silence disabled */
|
||||
ConfigurationDatabase clientCfg;
|
||||
ASSERT_TRUE(clientCfg.Write("ServerAddr", "127.0.0.1"));
|
||||
ASSERT_TRUE(clientCfg.Write("Port", static_cast<uint32>(serverPort)));
|
||||
ASSERT_TRUE(clientCfg.Write("KeepAliveInterval", 1u));
|
||||
ASSERT_TRUE(clientCfg.Write("SilenceTimeout", 0u));
|
||||
UDPSClient client;
|
||||
ASSERT_TRUE(client.Initialise(clientCfg));
|
||||
ASSERT_TRUE(client.Start());
|
||||
|
||||
ASSERT_TRUE(WaitForClient(server, 3000)); /* CONNECT registered */
|
||||
EXPECT_EQ(server.GetClientCount(), 1u);
|
||||
|
||||
/* Pump well past ClientTimeout: keepalive ACKs must prevent eviction */
|
||||
PumpServer(server, 5000);
|
||||
EXPECT_EQ(server.GetClientCount(), 1u);
|
||||
|
||||
client.Stop();
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
TEST(UDPSClientGTest, TestServerEvictsWithoutKeepAlive) {
|
||||
/* Negative control: without keepalive the same harness MUST evict, which
|
||||
* proves TestKeepAlivePreventsServerEviction passes because of the ACKs
|
||||
* and not because eviction is broken. */
|
||||
|
||||
BasicUDPSocket probe;
|
||||
ASSERT_TRUE(probe.Open());
|
||||
ASSERT_TRUE(probe.Listen(0u));
|
||||
uint16 serverPort = GetBoundPort(probe);
|
||||
probe.Close();
|
||||
ASSERT_NE(serverPort, 0u);
|
||||
|
||||
ConfigurationDatabase serverCfg;
|
||||
ASSERT_TRUE(serverCfg.Write("Port", static_cast<uint32>(serverPort)));
|
||||
ASSERT_TRUE(serverCfg.Write("ClientTimeout", 3u));
|
||||
UDPSServer server;
|
||||
ASSERT_TRUE(server.Initialise(serverCfg));
|
||||
ASSERT_TRUE(server.Start());
|
||||
|
||||
ConfigurationDatabase clientCfg;
|
||||
ASSERT_TRUE(clientCfg.Write("ServerAddr", "127.0.0.1"));
|
||||
ASSERT_TRUE(clientCfg.Write("Port", static_cast<uint32>(serverPort)));
|
||||
ASSERT_TRUE(clientCfg.Write("KeepAliveInterval", 0u));
|
||||
ASSERT_TRUE(clientCfg.Write("SilenceTimeout", 0u));
|
||||
UDPSClient client;
|
||||
ASSERT_TRUE(client.Initialise(clientCfg));
|
||||
ASSERT_TRUE(client.Start());
|
||||
|
||||
ASSERT_TRUE(WaitForClient(server, 3000)); /* CONNECT registered */
|
||||
EXPECT_EQ(server.GetClientCount(), 1u);
|
||||
|
||||
/* No ACKs: the server must evict after ClientTimeout */
|
||||
PumpServer(server, 5000);
|
||||
EXPECT_EQ(server.GetClientCount(), 0u);
|
||||
|
||||
client.Stop();
|
||||
server.Stop();
|
||||
}
|
||||
Reference in New Issue
Block a user