297 lines
9.9 KiB
C++
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();
|
|
}
|