Files
MARTe-Integrated-Components/Test/GTest/UDPSClientGTest.cpp
T

297 lines
9.9 KiB
C++

/**
* @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();
}