implement more complete network loopback tests;

pull/128/head
Bryan Biedenkapp 2 months ago
parent a08d2bd432
commit c731b9113a

@ -27,6 +27,67 @@ 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
// ---------------------------------------------------------------------------
@ -128,6 +189,58 @@ static bool sendLoopbackFrame(FrameQueue& senderQueue, sockaddr_storage& destina
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]")
@ -302,45 +415,11 @@ TEST_CASE("Network invokes loopback in-call callbacks for incoming control frame
uint32_t networkDestinationLen = 0U;
REQUIRE(Socket::lookup("127.0.0.1", localPort, networkDestination, networkDestinationLen) == 0);
Network network("127.0.0.1", masterPort, localPort, 6000U, "loopback-password", true, false, true, false, false, false, true, true, true, true, false, false);
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());
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(), 6000U, 6000U, 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(), 6000U, 6000U, 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(), 6000U, 6000U, 0U));
network.clock(1000U);
completeHandshake(network, masterReceiverQueue, masterSenderQueue, networkDestination, networkDestinationLen, 6000U);
bool callbackFired = false;
NET_ICC::ENUM lastCommand = NET_ICC::NOP;
@ -381,3 +460,240 @@ TEST_CASE("Network invokes loopback in-call callbacks for incoming control frame
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));
}

Loading…
Cancel
Save

Powered by TurnKey Linux.