You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
700 lines
28 KiB
700 lines
28 KiB
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Digital Voice Modem - Test Suite
|
|
* GPLv2 Open Source. Use is subject to license terms.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES FROM THIS FILE HEADER.
|
|
*
|
|
* Copyright (C) 2026 Bryan Biedenkapp, N2PLL
|
|
*
|
|
*/
|
|
#include "Defines.h"
|
|
#include "common/network/FrameQueue.h"
|
|
#include "common/network/Network.h"
|
|
#include "common/network/RTPFNEHeader.h"
|
|
#include "common/network/RTPHeader.h"
|
|
#include "common/network/udp/Socket.h"
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
using namespace network;
|
|
using namespace network::frame;
|
|
using namespace network::udp;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Class Declaration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @brief TestableNetwork is a subclass of Network that exposes internal state for testing purposes. It allows
|
|
* direct manipulation of the network status, activity transfer flags, and diagnostic transfer flags. This class
|
|
*/
|
|
class TestableNetwork : public Network {
|
|
public:
|
|
using Network::Network;
|
|
|
|
/**
|
|
* @brief Sets the network connection status for testing purposes.
|
|
* @param status The NET_CONN_STATUS value to set.
|
|
*/
|
|
void setStatusForTest(NET_CONN_STATUS status) { m_status = status; }
|
|
/**
|
|
* @brief Gets the current network connection status for testing purposes.
|
|
* @returns NET_CONN_STATUS The current network connection status.
|
|
*/
|
|
NET_CONN_STATUS getStatusForTest() const { return m_status; }
|
|
|
|
/**
|
|
* @brief Sets the allow activity transfer flag for testing purposes.
|
|
* @param enabled True to enable activity transfer, false to disable.
|
|
*/
|
|
void setAllowActivityTransferForTest(bool enabled) { m_allowActivityTransfer = enabled; }
|
|
/**
|
|
* @brief Sets the allow diagnostic transfer flag for testing purposes.
|
|
* @param enabled True to enable diagnostic transfer, false to disable.
|
|
*/
|
|
void setAllowDiagnosticTransferForTest(bool enabled) { m_allowDiagnosticTransfer = enabled; }
|
|
/**
|
|
* @brief Gets the allow activity transfer flag for testing purposes.
|
|
* @returns bool True if activity transfer is allowed, false otherwise.
|
|
*/
|
|
bool getAllowActivityTransferForTest() const { return m_allowActivityTransfer; }
|
|
/**
|
|
* @brief Gets the allow diagnostic transfer flag for testing purposes.
|
|
* @returns bool True if diagnostic transfer is allowed, false otherwise.
|
|
*/
|
|
bool getAllowDiagnosticTransferForTest() const { return m_allowDiagnosticTransfer; }
|
|
|
|
/**
|
|
* @brief Gets the duplicate connection flag for testing purposes.
|
|
* @returns bool True if a duplicate connection has been flagged, false otherwise.
|
|
*/
|
|
bool hasDuplicateConnFlagForTest() const { return m_flaggedDuplicateConn; }
|
|
/**
|
|
* @brief Gets the maximum retry count for testing purposes.
|
|
* @returns uint8_t The maximum retry count.
|
|
*/
|
|
uint8_t getMaxRetryCountForTest() const { return m_maxRetryCount; }
|
|
/**
|
|
* @brief Gets the current retry count for testing purposes.
|
|
* @returns uint8_t The current retry count.
|
|
*/
|
|
uint32_t getRemotePeerIdForTest() const { return m_remotePeerId; }
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Global Functions
|
|
// ---------------------------------------------------------------------------
|
|
namespace {
|
|
|
|
/**
|
|
* @brief Reserve a UDP port on the loopback interface for testing.
|
|
* @returns uint16_t Reserved UDP port number, or 0 if reservation failed.
|
|
*/
|
|
static uint16_t reserveLoopbackPort()
|
|
{
|
|
#if defined(_WIN32)
|
|
SOCKET fd = ::socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (fd == INVALID_SOCKET)
|
|
return 0U;
|
|
#else
|
|
int fd = ::socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (fd < 0)
|
|
return 0U;
|
|
#endif // defined(_WIN32)
|
|
|
|
sockaddr_in address = {};
|
|
address.sin_family = AF_INET;
|
|
address.sin_port = htons(0U);
|
|
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
|
|
if (::bind(fd, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0) {
|
|
#if defined(_WIN32)
|
|
::closesocket(fd);
|
|
#else
|
|
::close(fd);
|
|
#endif // defined(_WIN32)
|
|
return 0U;
|
|
}
|
|
|
|
socklen_t addrLen = sizeof(address);
|
|
if (::getsockname(fd, reinterpret_cast<sockaddr*>(&address), &addrLen) < 0) {
|
|
#if defined(_WIN32)
|
|
::closesocket(fd);
|
|
#else
|
|
::close(fd);
|
|
#endif // defined(_WIN32)
|
|
return 0U;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
::closesocket(fd);
|
|
#else
|
|
::close(fd);
|
|
#endif // defined(_WIN32)
|
|
|
|
return ntohs(address.sin_port);
|
|
}
|
|
|
|
/**
|
|
* @brief Wait for a frame message to be received on the specified FrameQueue.
|
|
* @param queue Reference to the FrameQueue to read from.
|
|
* @param[out] out String to store the received message.
|
|
* @param[out] rtpHeader RTPHeader to store the received RTP header.
|
|
* @param[out] fneHeader RTPFNEHeader to store the received FNE header.
|
|
* @returns bool True if a message was received, otherwise false.
|
|
*/
|
|
static bool waitForFrameMessage(FrameQueue& queue, std::string& out, RTPHeader& rtpHeader, RTPFNEHeader& fneHeader)
|
|
{
|
|
for (uint32_t attempt = 0U; attempt < 100U; attempt++) {
|
|
int messageLength = -1;
|
|
sockaddr_storage source = {};
|
|
uint32_t sourceLen = 0U;
|
|
UInt8Array message = queue.read(messageLength, source, sourceLen, &rtpHeader, &fneHeader);
|
|
if (message != nullptr && messageLength > 0) {
|
|
out.assign(reinterpret_cast<char*>(message.get()), (size_t)messageLength);
|
|
return true;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Send a frame message to the specified destination using the FrameQueue.
|
|
* @param senderQueue Reference to the FrameQueue to send from.
|
|
* @param destination sockaddr_storage containing the destination address.
|
|
* @param destinationLen Length of the destination address.
|
|
* @param payload Vector containing the message payload to send.
|
|
* @param opcode OpcodePair containing the function and sub-function.
|
|
* @param streamId Stream ID for the message.
|
|
* @param peerId Peer ID for the message.
|
|
* @param ssrc SSRC for the RTP header.
|
|
* @param sequence Sequence number for the RTP header.
|
|
* @returns bool True if the message was sent successfully, otherwise false.
|
|
*/
|
|
static bool sendLoopbackFrame(FrameQueue& senderQueue, sockaddr_storage& destination, uint32_t destinationLen,
|
|
const std::vector<uint8_t>& payload, FrameQueue::OpcodePair opcode, uint32_t streamId, uint32_t peerId,
|
|
uint32_t ssrc, uint16_t sequence)
|
|
{
|
|
return senderQueue.write(payload.data(), (uint32_t)payload.size(), streamId, peerId, ssrc, opcode, sequence,
|
|
destination, destinationLen);
|
|
}
|
|
|
|
/**
|
|
* @brief Complete the handshake process for the TestableNetwork instance.
|
|
* @param network Reference to the TestableNetwork instance.
|
|
* @param masterReceiverQueue Reference to the FrameQueue for receiving messages from the master.
|
|
* @param masterSenderQueue Reference to the FrameQueue for sending messages to the master.
|
|
* @param networkDestination sockaddr_storage containing the destination address for the network.
|
|
* @param networkDestinationLen Length of the destination address.
|
|
* @param peerId Peer ID for the network.
|
|
*/
|
|
static void completeHandshake(TestableNetwork& network, FrameQueue& masterReceiverQueue, FrameQueue& masterSenderQueue,
|
|
sockaddr_storage& networkDestination, uint32_t networkDestinationLen, uint32_t peerId)
|
|
{
|
|
network.clock(11000U);
|
|
|
|
std::string loginPayload;
|
|
RTPHeader loginRtp;
|
|
RTPFNEHeader loginFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, loginPayload, loginRtp, loginFne));
|
|
REQUIRE(loginFne.getFunction() == NET_FUNC::RPTL);
|
|
|
|
std::array<uint8_t, 10U> saltPayload = {};
|
|
::memcpy(saltPayload.data() + 6U, "ABCD", 4U);
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(saltPayload.begin(), saltPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, loginFne.getStreamId(), peerId, peerId, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::string authPayload;
|
|
RTPHeader authRtp;
|
|
RTPFNEHeader authFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, authPayload, authRtp, authFne));
|
|
REQUIRE(authFne.getFunction() == NET_FUNC::RPTK);
|
|
|
|
std::array<uint8_t, 8U> ackPayload = {};
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(ackPayload.begin(), ackPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, authFne.getStreamId(), peerId, peerId, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::string configPayload;
|
|
RTPHeader configRtp;
|
|
RTPFNEHeader configFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, configPayload, configRtp, configFne));
|
|
REQUIRE(configFne.getFunction() == NET_FUNC::RPTC);
|
|
|
|
std::array<uint8_t, 8U> configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x00U };
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(configAckPayload.begin(), configAckPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, configFne.getStreamId(), peerId, peerId, 0U));
|
|
|
|
network.clock(1000U);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Network performs a full loopback login handshake", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0x1234U, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0x5678U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
Network network("127.0.0.1", masterPort, localPort, 4242U, "loopback-password", true, false, true, true, true, true, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
bool connected = false;
|
|
network.setPeerConnectedCallback([&connected]() {
|
|
connected = true;
|
|
});
|
|
|
|
network.clock(11000U);
|
|
|
|
std::string loginPayload;
|
|
RTPHeader loginRtp;
|
|
RTPFNEHeader loginFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, loginPayload, loginRtp, loginFne));
|
|
REQUIRE(loginFne.getFunction() == NET_FUNC::RPTL);
|
|
REQUIRE(loginPayload.size() == 8U);
|
|
REQUIRE(std::string(loginPayload.begin(), loginPayload.begin() + 4U) == TAG_REPEATER_LOGIN);
|
|
|
|
std::array<uint8_t, 10U> saltPayload = {};
|
|
::memcpy(saltPayload.data() + 6U, "ABCD", 4U);
|
|
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(saltPayload.begin(), saltPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, loginFne.getStreamId(), 4242U, 4242U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::string authPayload;
|
|
RTPHeader authRtp;
|
|
RTPFNEHeader authFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, authPayload, authRtp, authFne));
|
|
REQUIRE(authFne.getFunction() == NET_FUNC::RPTK);
|
|
REQUIRE(authPayload.size() == 40U);
|
|
REQUIRE(std::string(authPayload.begin(), authPayload.begin() + 4U) == TAG_REPEATER_AUTH);
|
|
|
|
std::array<uint8_t, 8U> ackPayload = {};
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(ackPayload.begin(), ackPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, authFne.getStreamId(), 4242U, 4242U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::string configPayload;
|
|
RTPHeader configRtp;
|
|
RTPFNEHeader configFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, configPayload, configRtp, configFne));
|
|
REQUIRE(configFne.getFunction() == NET_FUNC::RPTC);
|
|
REQUIRE(configPayload.size() >= 8U);
|
|
REQUIRE(std::string(configPayload.begin(), configPayload.begin() + 4U) == TAG_REPEATER_CONFIG);
|
|
|
|
std::array<uint8_t, 8U> configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x00U };
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(configAckPayload.begin(), configAckPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, configFne.getStreamId(), 4242U, 4242U, 0U));
|
|
|
|
network.clock(1000U);
|
|
REQUIRE(connected);
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("Network stores loopback DMR protocol frames into the receive ring buffer", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0x9ABCU, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0xDEF0U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
Network network("127.0.0.1", masterPort, localPort, 7777U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
network.resetDMR(1U);
|
|
|
|
network.clock(11000U);
|
|
|
|
std::string loginPayload;
|
|
RTPHeader loginRtp;
|
|
RTPFNEHeader loginFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, loginPayload, loginRtp, loginFne));
|
|
|
|
std::array<uint8_t, 10U> saltPayload = {};
|
|
::memcpy(saltPayload.data() + 6U, "ABCD", 4U);
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(saltPayload.begin(), saltPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, loginFne.getStreamId(), 7777U, 7777U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::string authPayload;
|
|
RTPHeader authRtp;
|
|
RTPFNEHeader authFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, authPayload, authRtp, authFne));
|
|
|
|
std::array<uint8_t, 8U> ackPayload = {};
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(ackPayload.begin(), ackPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, authFne.getStreamId(), 7777U, 7777U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::string configPayload;
|
|
RTPHeader configRtp;
|
|
RTPFNEHeader configFne;
|
|
REQUIRE(waitForFrameMessage(masterReceiverQueue, configPayload, configRtp, configFne));
|
|
|
|
std::array<uint8_t, 8U> configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x00U };
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(configAckPayload.begin(), configAckPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, configFne.getStreamId(), 7777U, 7777U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
std::vector<uint8_t> payload = { 0x10U, 0x11U, 0x12U, 0x13U, 0x14U, 0x15U };
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, payload,
|
|
{ NET_FUNC::PROTOCOL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, 0x12345678U, 7777U, 7777U, 0x1234U));
|
|
|
|
network.clock(0U);
|
|
|
|
bool ret = false;
|
|
uint32_t frameLength = 0U;
|
|
UInt8Array decoded = network.readDMR(ret, frameLength);
|
|
REQUIRE(ret);
|
|
REQUIRE(frameLength == payload.size());
|
|
REQUIRE(decoded != nullptr);
|
|
REQUIRE(std::memcmp(decoded.get(), payload.data(), payload.size()) == 0);
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("Network invokes loopback in-call callbacks for incoming control frames", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0xA1B2U, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0xC3D4U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
TestableNetwork network("127.0.0.1", masterPort, localPort, 6000U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
completeHandshake(network, masterReceiverQueue, masterSenderQueue, networkDestination, networkDestinationLen, 6000U);
|
|
|
|
bool callbackFired = false;
|
|
NET_ICC::ENUM lastCommand = NET_ICC::NOP;
|
|
uint32_t lastDstId = 0U;
|
|
uint8_t lastSlot = 0U;
|
|
uint32_t lastPeerId = 0U;
|
|
uint32_t lastSsrc = 0U;
|
|
uint32_t lastStreamId = 0U;
|
|
|
|
network.setDMRICCCallback([&](NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId) {
|
|
callbackFired = true;
|
|
lastCommand = command;
|
|
lastDstId = dstId;
|
|
lastSlot = slotNo;
|
|
lastPeerId = peerId;
|
|
lastSsrc = ssrc;
|
|
lastStreamId = streamId;
|
|
});
|
|
|
|
std::vector<uint8_t> payload(15U, 0x00U);
|
|
payload[10U] = static_cast<uint8_t>(NET_ICC::REJECT_TRAFFIC);
|
|
payload[11U] = 0x0AU;
|
|
payload[12U] = 0x0BU;
|
|
payload[13U] = 0x0CU;
|
|
payload[14U] = 0x02U;
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, payload,
|
|
{ NET_FUNC::INCALL_CTRL, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR }, 0x55AA55AAU, 6000U, 6000U, 0x0102U));
|
|
|
|
network.clock(1000U);
|
|
|
|
REQUIRE(callbackFired);
|
|
REQUIRE(lastCommand == NET_ICC::REJECT_TRAFFIC);
|
|
REQUIRE(lastDstId == 0x0A0B0CU);
|
|
REQUIRE(lastSlot == 0x02U);
|
|
REQUIRE(lastPeerId == 6000U);
|
|
REQUIRE(lastSsrc == 6000U);
|
|
REQUIRE(lastStreamId == 0x55AA55AAU);
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("Network handles duplicate-connection NAKs by returning to waiting-connect", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0xB1B2U, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0xD1D2U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
TestableNetwork network("127.0.0.1", masterPort, localPort, 9001U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
completeHandshake(network, masterReceiverQueue, masterSenderQueue, networkDestination, networkDestinationLen, 9001U);
|
|
|
|
std::vector<uint8_t> nakPayload(12U, 0x00U);
|
|
nakPayload[10U] = static_cast<uint8_t>((NET_CONN_NAK_FNE_DUPLICATE_CONN >> 8U) & 0xFFU);
|
|
nakPayload[11U] = static_cast<uint8_t>(NET_CONN_NAK_FNE_DUPLICATE_CONN & 0xFFU);
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, nakPayload,
|
|
{ NET_FUNC::NAK, NET_SUBFUNC::NOP }, 0x12345678U, 9001U, 9001U, 0x0102U));
|
|
|
|
network.clock(1000U);
|
|
|
|
REQUIRE(network.getStatusForTest() == NET_STAT_WAITING_CONNECT);
|
|
REQUIRE(network.hasDuplicateConnFlagForTest());
|
|
REQUIRE(network.getMaxRetryCountForTest() == 2U);
|
|
REQUIRE(network.getRemotePeerIdForTest() == 0U);
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("Network enables or disables metadata-transfer flags from the config ack", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0xE1E2U, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0xF1F2U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
TestableNetwork network("127.0.0.1", masterPort, localPort, 9002U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
network.setAllowActivityTransferForTest(false);
|
|
network.setAllowDiagnosticTransferForTest(false);
|
|
network.setStatusForTest(NET_STAT_WAITING_CONFIG);
|
|
|
|
std::array<uint8_t, 8U> configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U };
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(configAckPayload.begin(), configAckPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, 0x12345678U, 9002U, 9002U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
REQUIRE(!network.getAllowActivityTransferForTest());
|
|
REQUIRE(!network.getAllowDiagnosticTransferForTest());
|
|
|
|
network.setAllowActivityTransferForTest(true);
|
|
network.setAllowDiagnosticTransferForTest(true);
|
|
|
|
network.setStatusForTest(NET_STAT_WAITING_CONFIG);
|
|
configAckPayload[6U] = 0x80U;
|
|
REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector<uint8_t>(configAckPayload.begin(), configAckPayload.end()),
|
|
{ NET_FUNC::ACK, NET_SUBFUNC::NOP }, 0x12345678U, 9002U, 9002U, 0U));
|
|
|
|
network.clock(1000U);
|
|
|
|
REQUIRE(network.getAllowActivityTransferForTest());
|
|
REQUIRE(network.getAllowDiagnosticTransferForTest());
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("BaseNetwork emits announcement frames with the expected payloads while running", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
Socket metadataReceiver("127.0.0.1", masterPort + 1U);
|
|
REQUIRE(metadataReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0x1111U, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0x2222U, false);
|
|
FrameQueue metadataReceiverQueue(&metadataReceiver, 0x3333U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
TestableNetwork network("127.0.0.1", masterPort, localPort, 9003U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
completeHandshake(network, masterReceiverQueue, masterSenderQueue, networkDestination, networkDestinationLen, 9003U);
|
|
|
|
REQUIRE(network.announceGroupAffiliation(0x123456U, 0x654321U));
|
|
std::string payload;
|
|
RTPHeader rtpHeader;
|
|
RTPFNEHeader fneHeader;
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::ANNOUNCE);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL);
|
|
REQUIRE(payload.size() == MSG_ANNC_GRP_AFFIL);
|
|
const uint32_t grpSrc = GET_UINT24(reinterpret_cast<const uint8_t*>(payload.data()), 0U);
|
|
const uint32_t grpDst = GET_UINT24(reinterpret_cast<const uint8_t*>(payload.data()), 3U);
|
|
REQUIRE(grpSrc == 0x123456U);
|
|
REQUIRE(grpDst == 0x654321U);
|
|
|
|
REQUIRE(network.announceUnitRegistration(0x112233U));
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::ANNOUNCE);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG);
|
|
REQUIRE(payload.size() == MSG_ANNC_UNIT_REG);
|
|
const uint32_t unitRegId = GET_UINT24(reinterpret_cast<const uint8_t*>(payload.data()), 0U);
|
|
REQUIRE(unitRegId == 0x112233U);
|
|
|
|
std::unordered_map<uint32_t, uint32_t> affs = { {0x111111U, 0x222222U}, {0x333333U, 0x444444U} };
|
|
REQUIRE(network.announceAffiliationUpdate(affs));
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::ANNOUNCE);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_AFFILS);
|
|
REQUIRE(payload.size() == 4U + (affs.size() * 8U));
|
|
|
|
std::vector<uint32_t> regs = { 0xAAAAAAU, 0xBBBBBBU };
|
|
REQUIRE(network.announceUnitRegUpdate(regs));
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::ANNOUNCE);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REGS);
|
|
REQUIRE(payload.size() == 4U + (regs.size() * 3U));
|
|
|
|
std::vector<uint32_t> peers = { 0x010203U, 0x040506U };
|
|
REQUIRE(network.announceSiteVCs(peers));
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::ANNOUNCE);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC);
|
|
REQUIRE(payload.size() == 4U + (peers.size() * 4U));
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("BaseNetwork emits transfer and status frames over the metadata path when enabled", "[network][loopback]")
|
|
{
|
|
const uint16_t masterPort = reserveLoopbackPort();
|
|
REQUIRE(masterPort != 0U);
|
|
|
|
const uint16_t localPort = reserveLoopbackPort();
|
|
REQUIRE(localPort != 0U);
|
|
|
|
Socket masterReceiver("127.0.0.1", masterPort);
|
|
REQUIRE(masterReceiver.open(AF_INET));
|
|
|
|
Socket metadataReceiver("127.0.0.1", masterPort + 1U);
|
|
REQUIRE(metadataReceiver.open(AF_INET));
|
|
|
|
FrameQueue masterReceiverQueue(&masterReceiver, 0x3333U, false);
|
|
FrameQueue masterSenderQueue(&masterReceiver, 0x4444U, false);
|
|
FrameQueue metadataReceiverQueue(&metadataReceiver, 0x5555U, false);
|
|
|
|
sockaddr_storage networkDestination = {};
|
|
uint32_t networkDestinationLen = 0U;
|
|
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
|
|
|
|
TestableNetwork network("127.0.0.1", masterPort, localPort, 9004U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
|
|
network.enable(true);
|
|
REQUIRE(network.open());
|
|
|
|
completeHandshake(network, masterReceiverQueue, masterSenderQueue, networkDestination, networkDestinationLen, 9004U);
|
|
|
|
REQUIRE(network.writeActLog("activity-test"));
|
|
std::string payload;
|
|
RTPHeader rtpHeader;
|
|
RTPFNEHeader fneHeader;
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::TRANSFER);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY);
|
|
REQUIRE(payload.find("activity-test") != std::string::npos);
|
|
|
|
REQUIRE(network.writeDiagLog("diagnostic-test"));
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::TRANSFER);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG);
|
|
REQUIRE(payload.find("diagnostic-test") != std::string::npos);
|
|
|
|
json::object statusObj;
|
|
REQUIRE(network.writePeerStatus(statusObj));
|
|
REQUIRE(waitForFrameMessage(metadataReceiverQueue, payload, rtpHeader, fneHeader));
|
|
REQUIRE(fneHeader.getFunction() == NET_FUNC::TRANSFER);
|
|
REQUIRE(fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS);
|
|
REQUIRE(payload.size() > 0U);
|
|
|
|
masterReceiver.close();
|
|
}
|
|
|
|
TEST_CASE("RTPStreamMultiplex detects lost and out-of-order sequences and resets on end-of-call", "[network][loopback]")
|
|
{
|
|
RTPStreamMultiplex mux;
|
|
|
|
mux.setPktSeq(0x1234U, 1U);
|
|
|
|
uint16_t lastSeq = 0U;
|
|
REQUIRE(mux.verifyStream(0x1234U, 1U, NET_FUNC::PROTOCOL, &lastSeq) == MUX_VALID_SUCCESS);
|
|
REQUIRE(lastSeq == 1U);
|
|
|
|
REQUIRE(mux.verifyStream(0x1234U, 3U, NET_FUNC::PROTOCOL, &lastSeq) == MUX_LOST_FRAMES);
|
|
REQUIRE(lastSeq == 1U);
|
|
|
|
REQUIRE(mux.verifyStream(0x1234U, 2U, NET_FUNC::PROTOCOL, &lastSeq) == MUX_OUT_OF_ORDER);
|
|
REQUIRE(lastSeq == 3U);
|
|
|
|
REQUIRE(mux.verifyStream(0x1234U, RTP_END_OF_CALL_SEQ, NET_FUNC::PROTOCOL, &lastSeq) == MUX_VALID_SUCCESS);
|
|
REQUIRE_FALSE(mux.hasPktSeq(0x1234U));
|
|
}
|