diff --git a/tests/network/Network_Tests.cpp b/tests/network/Network_Tests.cpp index ecf9dbbc..84f77893 100644 --- a/tests/network/Network_Tests.cpp +++ b/tests/network/Network_Tests.cpp @@ -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 saltPayload = {}; + ::memcpy(saltPayload.data() + 6U, "ABCD", 4U); + REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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 ackPayload = {}; + REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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 configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x00U }; + REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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 saltPayload = {}; - ::memcpy(saltPayload.data() + 6U, "ABCD", 4U); - REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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 ackPayload = {}; - REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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 configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x00U }; - REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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 nakPayload(12U, 0x00U); + nakPayload[10U] = static_cast((NET_CONN_NAK_FNE_DUPLICATE_CONN >> 8U) & 0xFFU); + nakPayload[11U] = static_cast(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 configAckPayload = { 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U }; + REQUIRE(sendLoopbackFrame(masterSenderQueue, networkDestination, networkDestinationLen, std::vector(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(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(payload.data()), 0U); + const uint32_t grpDst = GET_UINT24(reinterpret_cast(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(payload.data()), 0U); + REQUIRE(unitRegId == 0x112233U); + + std::unordered_map 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 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 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)); +}