diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 110cab39..df0da19f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -47,6 +47,7 @@ file(GLOB common_SRC "src/common/edac/*.cpp" "src/common/lookups/*.cpp" "src/common/network/*.cpp" + "src/common/network/packet/*.cpp" "src/common/network/tcp/*.cpp" "src/common/network/udp/*.cpp" "src/common/network/viface/*.cpp" @@ -101,6 +102,7 @@ file(GLOB common_INCLUDE "src/common/json/*.h" "src/common/lookups/*.h" "src/common/network/*.h" + "src/common/network/packet/*.h" "src/common/network/json/*.h" "src/common/network/tcp/*.h" "src/common/network/udp/*.h" diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index f0281270..cf503f2c 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -9,7 +9,6 @@ */ #include "Defines.h" #include "common/edac/SHA256.h" -#include "common/p25/kmm/KMMFactory.h" #include "common/json/json.h" #include "common/AESCrypto.h" #include "common/Log.h" @@ -21,19 +20,7 @@ using namespace network; #include #include #include - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -#define DEFAULT_RETRY_TIME 10U // 10 seconds -#define DUPLICATE_CONN_RETRY_TIME 3600U // 60 minutes - -#define MAX_RETRY_BEFORE_RECONNECT 4U -#define MAX_RETRY_HA_RECONNECT 2U -#define MAX_RETRY_DUP_RECONNECT 2U - -#define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds +#include // --------------------------------------------------------------------------- // Public Class Members @@ -351,1089 +338,35 @@ void Network::clock(uint32_t ms) m_pktSeq = rtpHeader.getSequence(); - // process incoming message function opcodes - switch (fneHeader.getFunction()) { - case NET_FUNC::PROTOCOL: // Protocol - { - // are protocol messages being user handled? - if (m_userHandleProtocol) { - userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, - buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader); - break; - } - - // process incoming message subfunction opcodes - switch (fneHeader.getSubFunction()) { - case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame - { - if (m_enabled && m_dmrEnabled) { - uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 1U : 0U; // this is the raw index for the stream ID array - - if (m_debug) { - LogDebug(LOG_NET, "DMR Slot %u, peer = %u, len = %u, pktSeq = %u, streamId = %u", - slotNo + 1U, peerId, length, rtpHeader.getSequence(), streamId); - } - - if (m_promiscuousPeer) { - m_rxDMRStreamId[slotNo] = streamId; - m_pktLastSeq = m_pktSeq; - - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); - } -#endif - } - else { - if (m_rxDMRStreamId[slotNo] == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxDMRStreamId[slotNo] = 0U; - } - else { - m_rxDMRStreamId[slotNo] = streamId; - } - - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxDMRStreamId[slotNo] == streamId) { - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogWarning(LOG_NET, "DMR Slot %u stream %u possible lost frames; got %u, expected %u", - slotNo, streamId, m_pktSeq, lastRxSeq); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogWarning(LOG_NET, "DMR Slot %u stream %u out-of-order; got %u, expected %u", - slotNo, streamId, m_pktSeq, lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "DMR Slot %u valid seq, seq = %u, streamId = %u", slotNo, rtpHeader.getSequence(), streamId); - } -#endif - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxDMRStreamId[slotNo] = 0U; - } - } - } - - // check if we need to skip this stream -- a non-zero stream ID means the network client is locked - // to receiving a specific stream; a zero stream ID means the network is promiscuously - // receiving streams sent to this peer - if (m_rxDMRStreamId[slotNo] != 0U && m_rxDMRStreamId[slotNo] != streamId && - rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - break; - } - } - - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer.get(), length); - if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD)) - LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - - uint8_t len = length; - m_rxDMRData.addData(&len, 1U); - m_rxDMRData.addData(buffer.get(), len); - } - } - break; - - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame - { - if (m_enabled && m_p25Enabled) { - if (m_debug) { - LogDebug(LOG_NET, "P25, peer = %u, len = %u, pktSeq = %u, streamId = %u", - peerId, length, rtpHeader.getSequence(), streamId); - } - - if (m_promiscuousPeer) { - m_rxP25StreamId = streamId; - m_pktLastSeq = m_pktSeq; - - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); - } -#endif - } - else { - if (m_rxP25StreamId == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25StreamId = 0U; - } - else { - m_rxP25StreamId = streamId; - } - - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxP25StreamId == streamId) { - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogWarning(LOG_NET, "P25 stream %u possible lost frames; got %u, expected %u", - streamId, m_pktSeq, lastRxSeq); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogWarning(LOG_NET, "P25 stream %u out-of-order; got %u, expected %u", - streamId, m_pktSeq, lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "P25 valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); - } -#endif - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25StreamId = 0U; - } - } - } - - // check if we need to skip this stream -- a non-zero stream ID means the network client is locked - // to receiving a specific stream; a zero stream ID means the network is promiscuously - // receiving streams sent to this peer - if (m_rxP25StreamId != 0U && m_rxP25StreamId != streamId && - rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - break; - } - } - - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer.get(), length); - if (length > 512) - LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - - // P25 frames can be up to 512 bytes, but we need to handle the case where the frame is larger than 255 bytes - uint8_t len = length; - if (length > 254) { - len = 254U; - m_rxP25Data.addData(&len, 1U); - len = length - 254U; - m_rxP25Data.addData(&len, 1U); - } else { - m_rxP25Data.addData(&len, 1U); - } - - m_rxP25Data.addData(buffer.get(), length); - } - } - break; - - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25_P2: // Encapsulated P25 Phase 2 data frame - { - if (m_enabled && m_p25Enabled) { - uint32_t slotNo = (buffer[19U] & 0x80U) == 0x80U ? 1U : 0U; // this is the raw index for the stream ID array - - if (m_debug) { - LogDebug(LOG_NET, "P25 Phase 2 Slot %u, peer = %u, len = %u, pktSeq = %u, streamId = %u", - slotNo + 1U, peerId, length, rtpHeader.getSequence(), streamId); - } - - if (m_promiscuousPeer) { - m_rxP25P2StreamId[slotNo] = streamId; - m_pktLastSeq = m_pktSeq; - - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); - } -#endif - } - else { - if (m_rxP25P2StreamId[slotNo] == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25P2StreamId[slotNo] = 0U; - } - else { - m_rxP25P2StreamId[slotNo] = streamId; - } - - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxP25P2StreamId[slotNo] == streamId) { - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogWarning(LOG_NET, "DMR Slot %u stream %u possible lost frames; got %u, expected %u", - slotNo, streamId, m_pktSeq, lastRxSeq); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogWarning(LOG_NET, "DMR Slot %u stream %u out-of-order; got %u, expected %u", - slotNo, streamId, m_pktSeq, lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "P25 Phase 2 Slot %u valid seq, seq = %u, streamId = %u", slotNo, rtpHeader.getSequence(), streamId); - } -#endif - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25P2StreamId[slotNo] = 0U; - } - } - } - - // check if we need to skip this stream -- a non-zero stream ID means the network client is locked - // to receiving a specific stream; a zero stream ID means the network is promiscuously - // receiving streams sent to this peer - if (m_rxP25P2StreamId[slotNo] != 0U && m_rxP25P2StreamId[slotNo] != streamId && - rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - break; - } - } - - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, P25 Phase 2", buffer.get(), length); - if (length > (int)(P25_P2_PACKET_LENGTH + PACKET_PAD)) - LogError(LOG_NET, "P25 Phase 2 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - - uint8_t len = length; - m_rxP25P2Data.addData(&len, 1U); - m_rxP25P2Data.addData(buffer.get(), len); - } - } - break; - - case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame - { - if (m_enabled && m_nxdnEnabled) { - if (m_debug) { - LogDebug(LOG_NET, "NXDN, peer = %u, len = %u, pktSeq = %u, streamId = %u", - peerId, length, rtpHeader.getSequence(), streamId); - } - - if (m_promiscuousPeer) { - m_rxNXDNStreamId = streamId; - m_pktLastSeq = m_pktSeq; - - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); - } -#endif - } - else { - if (m_rxNXDNStreamId == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxNXDNStreamId = 0U; - } - else { - m_rxNXDNStreamId = streamId; - } - - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxNXDNStreamId == streamId) { - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogWarning(LOG_NET, "NXDN stream %u possible lost frames; got %u, expected %u", - streamId, m_pktSeq, lastRxSeq); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogWarning(LOG_NET, "NXDN stream %u out-of-order; got %u, expected %u", - streamId, m_pktSeq, lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "NXDN valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); - } -#endif - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxNXDNStreamId = 0U; - } - } - } - - // check if we need to skip this stream -- a non-zero stream ID means the network client is locked - // to receiving a specific stream; a zero stream ID means the network is promiscuously - // receiving streams sent to this peer - if (m_rxNXDNStreamId != 0U && m_rxNXDNStreamId != streamId && - rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - break; - } - } - - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer.get(), length); - if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD)) - LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - - uint8_t len = length; - m_rxNXDNData.addData(&len, 1U); - m_rxNXDNData.addData(buffer.get(), len); - } - } - break; - - case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated Analog data frame - { - if (m_enabled && m_analogEnabled) { - if (m_debug) { - LogDebug(LOG_NET, "Analog, peer = %u, len = %u, pktSeq = %u, streamId = %u", - peerId, length, rtpHeader.getSequence(), streamId); - } - - if (m_promiscuousPeer) { - m_rxAnalogStreamId = streamId; - m_pktLastSeq = m_pktSeq; - - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, - streamId, rtpHeader.getSequence(), lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); - } -#endif - } - else { - if (m_rxAnalogStreamId == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxAnalogStreamId = 0U; - } - else { - m_rxAnalogStreamId = streamId; - } - - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxAnalogStreamId == streamId) { - uint16_t lastRxSeq = 0U; - - MULTIPLEX_RET_CODE ret = verifyStream(&lastRxSeq); - if (ret == MUX_LOST_FRAMES) { - LogWarning(LOG_NET, "Analog stream %u possible lost frames; got %u, expected %u", - streamId, m_pktSeq, lastRxSeq); - } - else if (ret == MUX_OUT_OF_ORDER) { - LogWarning(LOG_NET, "Analog stream %u out-of-order; got %u, expected %u", - streamId, m_pktSeq, lastRxSeq); - } -#if DEBUG_RTP_MUX - else { - LogDebugEx(LOG_NET, "Network::clock()", "Analog valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); - } -#endif - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxAnalogStreamId = 0U; - } - } - } - - // check if we need to skip this stream -- a non-zero stream ID means the network client is locked - // to receiving a specific stream; a zero stream ID means the network is promiscuously - // receiving streams sent to this peer - if (m_rxAnalogStreamId != 0U && m_rxAnalogStreamId != streamId && - rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { - break; - } - } - - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer.get(), length); - if (length < (int)ANALOG_PACKET_LENGTH) { - LogError(LOG_NET, "Analog Stream %u, frame too short? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - } else { - if (length > 512) - LogError(LOG_NET, "Analog Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - - // Analog frames are larger then 254 bytes, but we need to handle the case where the frame is larger than 255 bytes - uint8_t len = length - 254U; - m_rxAnalogData.addData(&len, 1U); - - m_rxAnalogData.addData(buffer.get(), length); - } - } - } - break; - - default: - Utils::dump("unknown protocol opcode from the master", buffer.get(), length); - break; - } - } - break; - - case NET_FUNC::MASTER: // Master - { - // process incoming message subfunction opcodes - switch (fneHeader.getSubFunction()) { - case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist - { - if (m_enabled && m_updateLookup) { - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, WL RID", buffer.get(), length); - - if (m_ridLookup != nullptr) { - // update RID lists - uint32_t len = GET_UINT32(buffer, 6U); - uint32_t offs = 11U; - for (uint32_t i = 0; i < len; i++) { - uint32_t id = GET_UINT24(buffer, offs); - m_ridLookup->toggleEntry(id, true); - offs += 4U; - } - - LogInfoEx(LOG_NET, "Network Announced %u whitelisted RIDs", len); - - // save to file if enabled and we got RIDs - if (m_saveLookup && len > 0) { - m_ridLookup->commit(); - } - } - } - } - break; - case NET_SUBFUNC::MASTER_SUBFUNC_BL_RID: // Radio ID Blacklist - { - if (m_enabled && m_updateLookup) { - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, BL RID", buffer.get(), length); - - if (m_ridLookup != nullptr) { - // update RID lists - uint32_t len = GET_UINT32(buffer, 6U); - uint32_t offs = 11U; - for (uint32_t i = 0; i < len; i++) { - uint32_t id = GET_UINT24(buffer, offs); - m_ridLookup->toggleEntry(id, false); - offs += 4U; - } - - LogInfoEx(LOG_NET, "Network Announced %u blacklisted RIDs", len); - - // save to file if enabled and we got RIDs - if (m_saveLookup && len > 0) { - m_ridLookup->commit(); - } - } - } - } - break; - - case NET_SUBFUNC::MASTER_SUBFUNC_ACTIVE_TGS: // Talkgroup Active IDs - { - if (m_enabled && m_updateLookup) { - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, ACTIVE TGS", buffer.get(), length); - - if (m_tidLookup != nullptr) { - // update TGID lists - uint32_t len = GET_UINT32(buffer, 6U); - uint32_t offs = 11U; - for (uint32_t i = 0; i < len; i++) { - uint32_t id = GET_UINT24(buffer, offs); - uint8_t slot = (buffer[offs + 3U]) & 0x03U; - bool affiliated = (buffer[offs + 3U] & 0x40U) == 0x40U; - bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U; - - // encryption strapping bits - bool strappedBit = (buffer[offs + 3U] & 0x20U) == 0x20U; - bool clearBit = (buffer[offs + 3U] & 0x10U) == 0x10U; - uint8_t strapping = lookups::TG_STRAPPING_SELECTABLE; - - if (strappedBit) { - strapping = lookups::TG_STRAPPING_STRAPPED; - } - - if (clearBit) { - strapping = lookups::TG_STRAPPING_CLEAR; - } - - lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); - - // if the TG is marked as non-preferred, and the TGID exists in the local entries - // erase the local and overwrite with the FNE data - if (nonPreferred) { - if (!tid.isInvalid()) { - m_tidLookup->eraseEntry(id, slot); - tid = m_tidLookup->find(id, slot); - } - } - - if (tid.isInvalid()) { - if (!tid.config().active()) { - m_tidLookup->eraseEntry(id, slot); - } - - LogInfoEx(LOG_NET, "Activated%s%s TG %u TS %u %s in TGID table", - (nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot, - (strappedBit) ? "strapped" : (clearBit) ? "clear" : "selectable"); - m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred, strapping); - } - - offs += 5U; - } - - LogInfoEx(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); - - // save if saving from network is enabled - if (m_saveLookup && len > 0) { - m_tidLookup->commit(); - } - } - } - } - break; - case NET_SUBFUNC::MASTER_SUBFUNC_DEACTIVE_TGS: // Talkgroup Deactivated IDs - { - if (m_enabled && m_updateLookup) { - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, DEACTIVE TGS", buffer.get(), length); - - if (m_tidLookup != nullptr) { - // update TGID lists - uint32_t len = GET_UINT32(buffer, 6U); - uint32_t offs = 11U; - for (uint32_t i = 0; i < len; i++) { - uint32_t id = GET_UINT24(buffer, offs); - uint8_t slot = (buffer[offs + 3U]); - - lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); - if (!tid.isInvalid()) { - LogInfoEx(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); - m_tidLookup->eraseEntry(id, slot); - } - - offs += 5U; - } - - LogInfoEx(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); - - // save if saving from network is enabled - if (m_saveLookup && len > 0) { - m_tidLookup->commit(); - } - } - } - } - break; - - case NET_SUBFUNC::MASTER_HA_PARAMS: // HA Parameters - { - if (m_enabled) { - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, HA PARAMS", buffer.get(), length); - - m_haIPs.clear(); - m_currentHAIP = 0U; - m_maxRetryCount = MAX_RETRY_HA_RECONNECT; - - // always add the configured address to the HA IP list - m_haIPs.push_back(PeerHAIPEntry(m_configuredAddress, m_configuredPort)); - if (m_debug) - LogDebugEx(LOG_NET, "Network::clock()", "HA PARAMS, 1, %s:%u", m_configuredAddress.c_str(), m_configuredPort); - - uint32_t len = GET_UINT32(buffer, 6U); - if (len > 0U) { - len /= HA_PARAMS_ENTRY_LEN; - } - - uint8_t offs = 10U; - for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) { - uint32_t ipAddr = GET_UINT32(buffer, offs + 4U); - uint16_t port = GET_UINT16(buffer, offs + 8U); - - std::string address = __IP_FROM_UINT(ipAddr); - - if (address == m_configuredAddress && port == m_configuredPort) { - continue; // skip if this is the same as our configured address - } - - if (m_debug) - LogDebugEx(LOG_NET, "Network::clock()", "HA PARAMS, %u, %s:%u", i + 2, address.c_str(), port); - - m_haIPs.push_back(PeerHAIPEntry(address, port)); - } - - if (m_haIPs.size() > 1U) { - m_currentHAIP = 1U; // because the first entry is our configured entry, set - // the current HA IP to the next available - LogInfoEx(LOG_NET, "Loaded %u HA IPs from master", m_haIPs.size()); - LogInfoEx(LOG_NET, "Current HA IP %s:%u", m_haIPs[m_currentHAIP].masterAddress.c_str(), m_haIPs[m_currentHAIP].masterPort); - } - } - } - break; - - default: - Utils::dump("unknown master control opcode from the master", buffer.get(), length); - break; - } - } - break; - - case NET_FUNC::INCALL_CTRL: // In-Call Control - { - uint32_t ssrc = rtpHeader.getSSRC(); - if (!m_promiscuousPeer && ssrc != peerId) { - LogWarning(LOG_NET, "PEER %u, ignoring in-call control not destined for this peer SSRC %u", m_peerId, ssrc); - break; - } - - // process incoming message subfunction opcodes - switch (fneHeader.getSubFunction()) { - case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control - { - if (m_enabled && m_dmrEnabled) { - NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; - uint32_t dstId = GET_UINT24(buffer, 11U); - uint8_t slot = buffer[14U]; - - // fire off DMR in-call callback if we have one - if (m_dmrInCallCallback != nullptr) { - m_dmrInCallCallback(command, dstId, slot, peerId, ssrc, streamId); - } - } - } - break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // P25 In-Call Control - { - if (m_enabled && m_p25Enabled) { - NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; - uint32_t dstId = GET_UINT24(buffer, 11U); - - // fire off P25 in-call callback if we have one - if (m_p25InCallCallback != nullptr) { - m_p25InCallCallback(command, dstId, peerId, ssrc, streamId); - } - } - } - break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // NXDN In-Call Control - { - if (m_enabled && m_nxdnEnabled) { - NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; - uint32_t dstId = GET_UINT24(buffer, 11U); - - // fire off NXDN in-call callback if we have one - if (m_nxdnInCallCallback != nullptr) { - m_nxdnInCallCallback(command, dstId, peerId, ssrc, streamId); - } - } - } - break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Analog In-Call Control - { - if (m_enabled && m_nxdnEnabled) { - NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; - uint32_t dstId = GET_UINT24(buffer, 11U); - - // fire off analog in-call callback if we have one - if (m_analogInCallCallback != nullptr) { - m_analogInCallCallback(command, dstId, peerId, ssrc, streamId); - } - } - } - break; - - default: - Utils::dump("unknown incall control opcode from the master", buffer.get(), length); - break; - } - } - break; - - case NET_FUNC::KEY_RSP: // Enc. Key Response - { - if (m_enabled) { - using namespace p25::kmm; + static const std::unordered_map handlers = { + { NET_FUNC::PROTOCOL, &Network::PacketHandler::protocol }, - std::unique_ptr frame = KMMFactory::create(buffer.get() + 11U); - if (frame == nullptr) { - LogWarning(LOG_NET, "PEER %u, undecodable KMM frame from master", m_peerId); - break; - } - - switch (frame->getMessageId()) { - case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: - { - KMMModifyKey* modifyKey = static_cast(frame.get()); - if (modifyKey->getAlgId() > 0U) { - KeysetItem ks = modifyKey->getKeysetItem(); - if (ks.keys().size() > 0U) { - // fetch first key (a master response should never really send back more then one key) - KeyItem ki = ks.keys()[0]; - LogInfoEx(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", m_peerId, - ks.algId(), ki.kId()); - - if (!m_passKeysWithNoPresharedKey && (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC)) { - // if the to decrypt the key with a preshared key, do that now - if (m_kmfPresharedKey != nullptr) { - uint8_t encryptedKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; - ::memset(encryptedKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - ki.getKey(encryptedKey); - - uint8_t* key = nullptr; - crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); - key = aes.decryptECB(encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, m_kmfPresharedKey); - - uint32_t keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; - switch (ks.algId()) { - case P25DEF::ALGO_DES: - keyLength = P25DEF::DES_ENC_KEY_LENGTH_BYTES; - break; - case P25DEF::ALGO_ARC4: - keyLength = P25DEF::ARC4_ENC_KEY_LENGTH_BYTES; - break; - - case P25DEF::ALGO_AES_256: - break; - default: - LogWarning(LOG_NET, "PEER %u, unknown algorithm ID $%02X, unable to determine key length", m_peerId, ks.algId()); - break; - } - - if (m_debug) - Utils::dump(1U, "Network::clock(), Key", key, keyLength); - - if (key != nullptr) { - ki.setKey(key, keyLength); - ks.keyLength(keyLength); - delete[] key; - } - } else { - if (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC) { - LogInfoEx(LOG_NET, "PEER %u, received encrypted enc. key, but no preshared key available, algId = $%02X, kID = $%04X", m_peerId, - ks.algId(), ki.kId()); - break; - } - } - } - - // fire off key response callback if we have one - if (m_keyRespCallback != nullptr) { - m_keyRespCallback(ki, ks.algId(), ks.keyLength()); - } - } - } - } - break; - - default: - break; - } - } - } - break; - - case NET_FUNC::NAK: // Master Negative Ack - { - // DVM 3.6 adds support to respond with a NAK reason, as such we just check if the NAK response is greater - // then 10 bytes and process the reason value - uint16_t reason = NET_CONN_NAK_GENERAL_FAILURE; - if (length > 10) { - reason = GET_UINT16(buffer, 10U); - switch (reason) { - case NET_CONN_NAK_MODE_NOT_ENABLED: - LogWarning(LOG_NET, "PEER %u master NAK; digital mode not enabled on FNE, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_ILLEGAL_PACKET: - LogWarning(LOG_NET, "PEER %u master NAK; illegal/unknown packet, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_FNE_UNAUTHORIZED: - LogWarning(LOG_NET, "PEER %u master NAK; unauthorized, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_BAD_CONN_STATE: - LogWarning(LOG_NET, "PEER %u master NAK; bad connection state, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_INVALID_CONFIG_DATA: - LogWarning(LOG_NET, "PEER %u master NAK; invalid configuration data, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_FNE_MAX_CONN: - LogWarning(LOG_NET, "PEER %u master NAK; FNE has reached maximum permitted connections, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_PEER_RESET: - LogWarning(LOG_NET, "PEER %u master NAK; FNE demanded connection reset, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - case NET_CONN_NAK_PEER_ACL: - LogError(LOG_NET, "PEER %u master NAK; ACL rejection, network disabled, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - if (!m_neverDisableOnACLNAK) { - m_status = NET_STAT_WAITING_LOGIN; - m_enabled = false; // ACL rejection give up stop trying to connect - } - break; - - case NET_CONN_NAK_FNE_DUPLICATE_CONN: - LogWarning(LOG_NET, "PEER %u master NAK; duplicate connection to FNE, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - m_status = NET_STAT_WAITING_CONNECT; - m_remotePeerId = 0U; - m_flaggedDuplicateConn = true; - m_maxRetryCount = MAX_RETRY_DUP_RECONNECT; - m_retryTimer.start(); - return; - - case NET_CONN_NAK_GENERAL_FAILURE: - default: - LogWarning(LOG_NET, "PEER %u master NAK; general failure, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - break; - } - } - - // if the NAK reason is unhandled by the user code, perform default handling - if (!userNakHandler(m_peerId, reason, fneHeader, rtpHeader)) { - if (m_status == NET_STAT_RUNNING && (reason == NET_CONN_NAK_FNE_MAX_CONN)) { - LogWarning(LOG_NET, "PEER %u master NAK; attemping to relogin, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - m_status = NET_STAT_WAITING_LOGIN; - m_timeoutTimer.start(); - m_retryTimer.start(); - } - else { - if (m_enabled) { - LogError(LOG_NET, "PEER %u master NAK; network reconnect, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - close(); - open(); - } - return; - } - } - } - break; - case NET_FUNC::ACK: // Repeater Ack - { - switch (m_status) { - case NET_STAT_WAITING_LOGIN: - LogInfoEx(LOG_NET, "PEER %u RPTL ACK, performing login exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - - ::memcpy(m_salt, buffer.get() + 6U, sizeof(uint32_t)); - writeAuthorisation(); - - m_status = NET_STAT_WAITING_AUTHORISATION; - m_timeoutTimer.start(); - m_retryTimer.start(); - break; - case NET_STAT_WAITING_AUTHORISATION: - LogInfoEx(LOG_NET, "PEER %u RPTK ACK, performing configuration exchange, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); + { NET_FUNC::MASTER, &Network::PacketHandler::master }, - writeConfig(); + { NET_FUNC::INCALL_CTRL, &Network::PacketHandler::inCallControl }, - m_status = NET_STAT_WAITING_CONFIG; - m_timeoutTimer.start(); - m_retryTimer.start(); - break; - case NET_STAT_WAITING_CONFIG: - LogInfoEx(LOG_NET, "PEER %u RPTC ACK, logged into the master successfully, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - m_loginStreamId = 0U; - m_remotePeerId = rtpHeader.getSSRC(); - - pktSeq(true); - - // fire off peer connected callback if we have one - if (m_peerConnectedCallback != nullptr) { - m_peerConnectedCallback(); - } - - m_status = NET_STAT_RUNNING; - m_timeoutTimer.start(); - m_retryTimer.setTimeout(DEFAULT_RETRY_TIME); - m_retryTimer.start(); + { NET_FUNC::NAK, &Network::PacketHandler::nak }, + { NET_FUNC::ACK, &Network::PacketHandler::ack }, - if (length > 6) { - bool useAlternatePortForDiagnostics = (buffer[6U] & 0x80U) == 0x80U; - if (!useAlternatePortForDiagnostics) { - // disable diagnostic and activity logging automatically if the master doesn't utilize the secondary port - m_allowDiagnosticTransfer = false; - m_allowActivityTransfer = false; - LogError(LOG_NET, "PEER %u RPTC ACK, master does not enable secondary port for metadata, diagnostic and activity logging are disabled, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - LogError(LOG_NET, "PEER %u RPTC ACK, **please update your FNE**, secondary port for metadata, is required for all services as of R05A04, remotePeerId = %u", m_peerId, rtpHeader.getSSRC()); - } - } - break; - default: - break; - } - } - break; + { NET_FUNC::KEY_RSP, &Network::PacketHandler::keyResponse }, + { NET_FUNC::KEY_LLA_RSP, &Network::PacketHandler::llaKeyResponse }, - case NET_FUNC::KEY_LLA_RSP: // LLA Enc. Key Response - { - if (m_enabled) { - using namespace p25::kmm; + { NET_FUNC::MST_DISC, &Network::PacketHandler::masterDisconnect }, - std::unique_ptr frame = KMMFactory::create(buffer.get() + 11U); - if (frame == nullptr) { - LogWarning(LOG_NET, "PEER %u, undecodable KMM frame from master", m_peerId); - break; - } + { NET_FUNC::PONG, &Network::PacketHandler::pong } + }; - switch (frame->getMessageId()) { - case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: - { - KMMModifyKey* modifyKey = static_cast(frame.get()); - if (modifyKey->getAlgId() > 0U) { - KeysetItem ks = modifyKey->getKeysetItem(); - if (ks.keys().size() > 0U) { - // fetch first key (a master response should never really send back more then one key) - KeyItem ki = ks.keys()[0]; - LogInfoEx(LOG_NET, "PEER %u, master reported LLA enc. key, algId = $%02X, kID = $%04X", m_peerId, - ks.algId(), ki.kId()); - - if (!m_passKeysWithNoPresharedKey && (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC)) { - // if the to decrypt the key with a preshared key, do that now - if (m_kmfPresharedKey != nullptr) { - uint8_t encryptedKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; - ::memset(encryptedKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); - ki.getKey(encryptedKey); - - uint8_t* key = nullptr; - crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); - key = aes.decryptECB(encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, m_kmfPresharedKey); - - uint32_t keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; - switch (ks.algId()) { - case P25DEF::ALGO_AES_128: - keyLength = P25DEF::AES_128_ENC_KEY_LENGTH_BYTES; - break; - - case P25DEF::ALGO_AES_256: - default: - LogWarning(LOG_NET, "PEER %u, unknown algorithm ID $%02X, unable to determine key length", m_peerId, ks.algId()); - break; - } - - if (m_debug) - Utils::dump(1U, "Network::clock(), Key", key, keyLength); - - if (key != nullptr) { - ki.setKey(key, keyLength); - ks.keyLength(keyLength); - delete[] key; - } - } else { - if (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC) { - LogInfoEx(LOG_NET, "PEER %u, received encrypted LLA enc. key, but no preshared key available, algId = $%02X, kID = $%04X", m_peerId, - ks.algId(), ki.kId()); - break; - } - } - } - - // fire off key response callback if we have one - if (m_llaKeyRespCallback != nullptr) { - m_llaKeyRespCallback(modifyKey->getDstLLId(), ki, ks.keyLength()); - } - } - } - } - break; - - default: - break; - } - } + // dispatch to the appropriate handler based on the function opcode + auto it = handlers.find(fneHeader.getFunction()); + if (it != handlers.end()) { + if (it->second(this, peerId, streamId, now, fneHeader, rtpHeader, buffer.get(), length)) { + return; } - break; - - case NET_FUNC::MST_DISC: // Master Disconnect - { - LogError(LOG_NET, "PEER %u master disconnect, remotePeerId = %u", m_peerId, m_remotePeerId); - m_status = NET_STAT_WAITING_CONNECT; - - // fire off peer disconnected callback if we have one - if (m_peerDisconnectedCallback != nullptr) { - m_peerDisconnectedCallback(); - } - - close(); - open(); - } - break; - case NET_FUNC::PONG: // Master Ping Response - m_timeoutTimer.start(); - if (length >= 14) { - if (m_packetDump) - Utils::dump(1U, "Network::clock(), Network Rx, PONG", buffer.get(), length); - - ulong64_t serverNow = 0U; - - // combine bytes into ulong64_t (8 byte) value - serverNow = buffer[6U]; - serverNow = (serverNow << 8) + buffer[7U]; - serverNow = (serverNow << 8) + buffer[8U]; - serverNow = (serverNow << 8) + buffer[9U]; - serverNow = (serverNow << 8) + buffer[10U]; - serverNow = (serverNow << 8) + buffer[11U]; - serverNow = (serverNow << 8) + buffer[12U]; - serverNow = (serverNow << 8) + buffer[13U]; - - // check the ping RTT and report any over the maximum defined time - uint64_t dt = (uint64_t)fabs((double)now - (double)serverNow); - if (dt > MAX_SERVER_DIFF) - LogWarning(LOG_NET, "PEER %u pong, time delay greater than %llums, now = %llu, server = %llu, dt = %llu", m_peerId, MAX_SERVER_DIFF, now, serverNow, dt); - - ++m_pingsReceived; - - // if we've been connected for at least 10 PING/PONG cycles and we're flagged duplicate connection, clear the flag - if (m_pingsReceived > 10U && m_flaggedDuplicateConn) { - m_flaggedDuplicateConn = false; - } - } - break; - default: - userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, + } + else { + // if we don't have a handler for this function opcode, pass it to the user-defined packet handler (if defined) + userPacketHandler(peerId, { fneHeader.getFunction(), fneHeader.getSubFunction() }, buffer.get(), length, fneHeader.getStreamId(), fneHeader, rtpHeader); - break; } } diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 2ea7cc15..7a2ce65c 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace network { @@ -121,6 +122,17 @@ namespace network uint16_t masterPort; //!< Master Port. }; + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + #define DEFAULT_RETRY_TIME 10U // 10 seconds + #define DUPLICATE_CONN_RETRY_TIME 3600U // 60 minutes + + #define MAX_RETRY_BEFORE_RECONNECT 4U + #define MAX_RETRY_HA_RECONNECT 2U + #define MAX_RETRY_DUP_RECONNECT 2U + // --------------------------------------------------------------------------- // Class Declaration // --------------------------------------------------------------------------- @@ -135,7 +147,7 @@ namespace network * @brief Initializes a new instance of the Network class. * @param address Network Hostname/IP address to connect to. * @param port Network port number. - * @param localPort + * @param localPort Local network port number to bind to. * @param peerId Unique ID on the network. * @param password Network authentication password. * @param duplex Flag indicating full-duplex operation. @@ -423,6 +435,148 @@ namespace network */ std::function m_llaKeyRespCallback; + using PacketHandlerFunc = bool (*)(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Implements the packet handler functions for the MetadataNetwork class. + */ + class PacketHandler { + public: + /** + * @brief Handles NET_FUNC::PROTOCOL packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool protocol(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Handles NET_FUNC::MASTER packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool master(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Handles NET_FUNC::INCALL_CTRL packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool inCallControl(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Handles NET_FUNC::NAK packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool nak(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + /** + * @brief Handles NET_FUNC::ACK packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool ack(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Handles NET_FUNC::KEY_RSP packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool keyResponse(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + /** + * @brief Handles NET_FUNC::KEY_LLA_RSP packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool llaKeyResponse(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Handles NET_FUNC::MST_DISC packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool masterDisconnect(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + + /** + * @brief Handles NET_FUNC::PONG packets. + * @param network Instance of the TrafficNetwork class. + * @param peerId Peer ID. + * @param streamId Stream ID. + * @param now Current time in milliseconds. + * @param fneHeader RTP FNE Header. + * @param rtpHeader RTP Header. + * @param buffer Buffer containing message to send to peer. + * @param length Length of buffer. + * @returns bool True, if the packet was handled, otherwise false. + */ + static bool pong(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length); + }; + /** * @brief Helper to verify the given RTP sequence for the given RTP stream. * @param[out] lastRxSeq Last Received Sequence. diff --git a/src/common/network/packet/Ack.cpp b/src/common/network/packet/Ack.cpp new file mode 100644 index 00000000..82d4069b --- /dev/null +++ b/src/common/network/packet/Ack.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::ACK packets. */ + +bool Network::PacketHandler::ack(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)now; + (void)fneHeader; + + switch (network->m_status) { + case NET_STAT_WAITING_LOGIN: + LogInfoEx(LOG_NET, "PEER %u RPTL ACK, performing login exchange, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + + ::memcpy(network->m_salt, buffer + 6U, sizeof(uint32_t)); + network->writeAuthorisation(); + + network->m_status = NET_STAT_WAITING_AUTHORISATION; + network->m_timeoutTimer.start(); + network->m_retryTimer.start(); + break; + case NET_STAT_WAITING_AUTHORISATION: + LogInfoEx(LOG_NET, "PEER %u RPTK ACK, performing configuration exchange, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + + network->writeConfig(); + + network->m_status = NET_STAT_WAITING_CONFIG; + network->m_timeoutTimer.start(); + network->m_retryTimer.start(); + break; + case NET_STAT_WAITING_CONFIG: + LogInfoEx(LOG_NET, "PEER %u RPTC ACK, logged into the master successfully, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + network->m_loginStreamId = 0U; + network->m_remotePeerId = rtpHeader.getSSRC(); + + network->pktSeq(true); + + // fire off peer connected callback if we have one + if (network->m_peerConnectedCallback != nullptr) { + network->m_peerConnectedCallback(); + } + + network->m_status = NET_STAT_RUNNING; + network->m_timeoutTimer.start(); + network->m_retryTimer.setTimeout(DEFAULT_RETRY_TIME); + network->m_retryTimer.start(); + + if (length > 6) { + bool useAlternatePortForDiagnostics = (buffer[6U] & 0x80U) == 0x80U; + if (!useAlternatePortForDiagnostics) { + // disable diagnostic and activity logging automatically if the master doesn't utilize the secondary port + network->m_allowDiagnosticTransfer = false; + network->m_allowActivityTransfer = false; + LogError(LOG_NET, "PEER %u RPTC ACK, master does not enable secondary port for metadata, diagnostic and activity logging are disabled, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + LogError(LOG_NET, "PEER %u RPTC ACK, **please update your FNE**, secondary port for metadata, is required for all services as of R05A04, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + } + } + break; + default: + break; + } + + return false; +} diff --git a/src/common/network/packet/InCallControl.cpp b/src/common/network/packet/InCallControl.cpp new file mode 100644 index 00000000..d28c7a3d --- /dev/null +++ b/src/common/network/packet/InCallControl.cpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::INCALL_CTRL packets. */ + +bool Network::PacketHandler::inCallControl(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)now; + (void)length; + + uint32_t ssrc = rtpHeader.getSSRC(); + if (!network->m_promiscuousPeer && ssrc != peerId) { + LogWarning(LOG_NET, "PEER %u, ignoring in-call control not destined for this peer SSRC %u", network->m_peerId, ssrc); + return false; + } + + // process incoming message subfunction opcodes + switch (fneHeader.getSubFunction()) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control + { + if (network->m_enabled && network->m_dmrEnabled) { + NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; + uint32_t dstId = GET_UINT24(buffer, 11U); + uint8_t slot = buffer[14U]; + + // fire off DMR in-call callback if we have one + if (network->m_dmrInCallCallback != nullptr) { + network->m_dmrInCallCallback(command, dstId, slot, peerId, ssrc, streamId); + } + } + } + break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // P25 In-Call Control + { + if (network->m_enabled && network->m_p25Enabled) { + NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; + uint32_t dstId = GET_UINT24(buffer, 11U); + + // fire off P25 in-call callback if we have one + if (network->m_p25InCallCallback != nullptr) { + network->m_p25InCallCallback(command, dstId, peerId, ssrc, streamId); + } + } + } + break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // NXDN In-Call Control + { + if (network->m_enabled && network->m_nxdnEnabled) { + NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; + uint32_t dstId = GET_UINT24(buffer, 11U); + + // fire off NXDN in-call callback if we have one + if (network->m_nxdnInCallCallback != nullptr) { + network->m_nxdnInCallCallback(command, dstId, peerId, ssrc, streamId); + } + } + } + break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Analog In-Call Control + { + if (network->m_enabled && network->m_analogEnabled) { + NET_ICC::ENUM command = (NET_ICC::ENUM)buffer[10U]; + uint32_t dstId = GET_UINT24(buffer, 11U); + + // fire off analog in-call callback if we have one + if (network->m_analogInCallCallback != nullptr) { + network->m_analogInCallCallback(command, dstId, peerId, ssrc, streamId); + } + } + } + break; + + default: + Utils::dump("unknown incall control opcode from the master", buffer, length); + break; + } + + return false; +} diff --git a/src/common/network/packet/KeyResponse.cpp b/src/common/network/packet/KeyResponse.cpp new file mode 100644 index 00000000..632c7017 --- /dev/null +++ b/src/common/network/packet/KeyResponse.cpp @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/p25/kmm/KMMFactory.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::KEY_RSP packets. */ + +bool Network::PacketHandler::keyResponse(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)now; + (void)fneHeader; + (void)rtpHeader; + (void)length; + + if (network->m_enabled) { + using namespace p25::kmm; + + std::unique_ptr frame = KMMFactory::create(buffer + 11U); + if (frame == nullptr) { + LogWarning(LOG_NET, "PEER %u, undecodable KMM frame from master", network->m_peerId); + return false; + } + + switch (frame->getMessageId()) { + case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: + { + KMMModifyKey* modifyKey = static_cast(frame.get()); + if (modifyKey->getAlgId() > 0U) { + KeysetItem ks = modifyKey->getKeysetItem(); + if (ks.keys().size() > 0U) { + // fetch first key (a master response should never really send back more then one key) + KeyItem ki = ks.keys()[0]; + LogInfoEx(LOG_NET, "PEER %u, master reported enc. key, algId = $%02X, kID = $%04X", network->m_peerId, + ks.algId(), ki.kId()); + + if (!network->m_passKeysWithNoPresharedKey && (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC)) { + // if the to decrypt the key with a preshared key, do that now + if (network->m_kmfPresharedKey != nullptr) { + uint8_t encryptedKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(encryptedKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + ki.getKey(encryptedKey); + + uint8_t* key = nullptr; + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + key = aes.decryptECB(encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); + + uint32_t keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + switch (ks.algId()) { + case P25DEF::ALGO_DES: + keyLength = P25DEF::DES_ENC_KEY_LENGTH_BYTES; + break; + case P25DEF::ALGO_ARC4: + keyLength = P25DEF::ARC4_ENC_KEY_LENGTH_BYTES; + break; + + case P25DEF::ALGO_AES_256: + break; + default: + LogWarning(LOG_NET, "PEER %u, unknown algorithm ID $%02X, unable to determine key length", network->m_peerId, ks.algId()); + break; + } + + if (network->m_debug) + Utils::dump(1U, "Network::clock(), Key", key, keyLength); + + if (key != nullptr) { + ki.setKey(key, keyLength); + ks.keyLength(keyLength); + delete[] key; + } + } + else { + if (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC) { + LogInfoEx(LOG_NET, "PEER %u, received encrypted enc. key, but no preshared key available, algId = $%02X, kID = $%04X", network->m_peerId, + ks.algId(), ki.kId()); + break; + } + } + } + + // fire off key response callback if we have one + if (network->m_keyRespCallback != nullptr) { + network->m_keyRespCallback(ki, ks.algId(), ks.keyLength()); + } + } + } + } + break; + + default: + break; + } + } + + return false; +} diff --git a/src/common/network/packet/LLAKeyResponse.cpp b/src/common/network/packet/LLAKeyResponse.cpp new file mode 100644 index 00000000..32b3022c --- /dev/null +++ b/src/common/network/packet/LLAKeyResponse.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/p25/kmm/KMMFactory.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::KEY_LLA_RSP packets. */ + +bool Network::PacketHandler::llaKeyResponse(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)now; + (void)fneHeader; + (void)rtpHeader; + (void)length; + + if (network->m_enabled) { + using namespace p25::kmm; + + std::unique_ptr frame = KMMFactory::create(buffer + 11U); + if (frame == nullptr) { + LogWarning(LOG_NET, "PEER %u, undecodable KMM frame from master", network->m_peerId); + return false; + } + + switch (frame->getMessageId()) { + case P25DEF::KMM_MessageType::MODIFY_KEY_CMD: + { + KMMModifyKey* modifyKey = static_cast(frame.get()); + if (modifyKey->getAlgId() > 0U) { + KeysetItem ks = modifyKey->getKeysetItem(); + if (ks.keys().size() > 0U) { + // fetch first key (a master response should never really send back more then one key) + KeyItem ki = ks.keys()[0]; + LogInfoEx(LOG_NET, "PEER %u, master reported LLA enc. key, algId = $%02X, kID = $%04X", network->m_peerId, + ks.algId(), ki.kId()); + + if (!network->m_passKeysWithNoPresharedKey && (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC)) { + // if the to decrypt the key with a preshared key, do that now + if (network->m_kmfPresharedKey != nullptr) { + uint8_t encryptedKey[P25DEF::MAX_ENC_KEY_LENGTH_BYTES]; + ::memset(encryptedKey, 0x00U, P25DEF::MAX_ENC_KEY_LENGTH_BYTES); + ki.getKey(encryptedKey); + + uint8_t* key = nullptr; + crypto::AES aes = crypto::AES(crypto::AESKeyLength::AES_256); + key = aes.decryptECB(encryptedKey, P25DEF::MAX_ENC_KEY_LENGTH_BYTES, network->m_kmfPresharedKey); + + uint32_t keyLength = P25DEF::MAX_ENC_KEY_LENGTH_BYTES; + switch (ks.algId()) { + case P25DEF::ALGO_AES_128: + keyLength = P25DEF::AES_128_ENC_KEY_LENGTH_BYTES; + break; + + case P25DEF::ALGO_AES_256: + default: + LogWarning(LOG_NET, "PEER %u, unknown algorithm ID $%02X, unable to determine key length", network->m_peerId, ks.algId()); + break; + } + + if (network->m_debug) + Utils::dump(1U, "Network::clock(), Key", key, keyLength); + + if (key != nullptr) { + ki.setKey(key, keyLength); + ks.keyLength(keyLength); + delete[] key; + } + } + else { + if (modifyKey->getDecryptInfoFmt() == KMM_DECRYPT_PEER_ENC) { + LogInfoEx(LOG_NET, "PEER %u, received encrypted LLA enc. key, but no preshared key available, algId = $%02X, kID = $%04X", network->m_peerId, + ks.algId(), ki.kId()); + break; + } + } + } + + // fire off key response callback if we have one + if (network->m_llaKeyRespCallback != nullptr) { + network->m_llaKeyRespCallback(modifyKey->getDstLLId(), ki, ks.keyLength()); + } + } + } + } + break; + + default: + break; + } + } + + return false; +} diff --git a/src/common/network/packet/Master.cpp b/src/common/network/packet/Master.cpp new file mode 100644 index 00000000..016736c2 --- /dev/null +++ b/src/common/network/packet/Master.cpp @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::MASTER packets. */ + +bool Network::PacketHandler::master(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)now; + (void)fneHeader; + (void)rtpHeader; + + // process incoming message subfunction opcodes + switch (fneHeader.getSubFunction()) { + case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist + { + if (network->m_enabled && network->m_updateLookup) { + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, WL RID", buffer, length); + + if (network->m_ridLookup != nullptr) { + // update RID lists + uint32_t len = GET_UINT32(buffer, 6U); + uint32_t offs = 11U; + for (uint32_t i = 0; i < len; i++) { + uint32_t id = GET_UINT24(buffer, offs); + network->m_ridLookup->toggleEntry(id, true); + offs += 4U; + } + + LogInfoEx(LOG_NET, "Network Announced %u whitelisted RIDs", len); + + // save to file if enabled and we got RIDs + if (network->m_saveLookup && len > 0) { + network->m_ridLookup->commit(); + } + } + } + } + break; + case NET_SUBFUNC::MASTER_SUBFUNC_BL_RID: // Radio ID Blacklist + { + if (network->m_enabled && network->m_updateLookup) { + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, BL RID", buffer, length); + + if (network->m_ridLookup != nullptr) { + // update RID lists + uint32_t len = GET_UINT32(buffer, 6U); + uint32_t offs = 11U; + for (uint32_t i = 0; i < len; i++) { + uint32_t id = GET_UINT24(buffer, offs); + network->m_ridLookup->toggleEntry(id, false); + offs += 4U; + } + + LogInfoEx(LOG_NET, "Network Announced %u blacklisted RIDs", len); + + // save to file if enabled and we got RIDs + if (network->m_saveLookup && len > 0) { + network->m_ridLookup->commit(); + } + } + } + } + break; + + case NET_SUBFUNC::MASTER_SUBFUNC_ACTIVE_TGS: // Talkgroup Active IDs + { + if (network->m_enabled && network->m_updateLookup) { + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, ACTIVE TGS", buffer, length); + + if (network->m_tidLookup != nullptr) { + // update TGID lists + uint32_t len = GET_UINT32(buffer, 6U); + uint32_t offs = 11U; + for (uint32_t i = 0; i < len; i++) { + uint32_t id = GET_UINT24(buffer, offs); + uint8_t slot = (buffer[offs + 3U]) & 0x03U; + bool affiliated = (buffer[offs + 3U] & 0x40U) == 0x40U; + bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U; + + // encryption strapping bits + bool strappedBit = (buffer[offs + 3U] & 0x20U) == 0x20U; + bool clearBit = (buffer[offs + 3U] & 0x10U) == 0x10U; + uint8_t strapping = lookups::TG_STRAPPING_SELECTABLE; + + if (strappedBit) { + strapping = lookups::TG_STRAPPING_STRAPPED; + } + + if (clearBit) { + strapping = lookups::TG_STRAPPING_CLEAR; + } + + lookups::TalkgroupRuleGroupVoice tid = network->m_tidLookup->find(id, slot); + + // if the TG is marked as non-preferred, and the TGID exists in the local entries + // erase the local and overwrite with the FNE data + if (nonPreferred) { + if (!tid.isInvalid()) { + network->m_tidLookup->eraseEntry(id, slot); + tid = network->m_tidLookup->find(id, slot); + } + } + + if (tid.isInvalid()) { + if (!tid.config().active()) { + network->m_tidLookup->eraseEntry(id, slot); + } + + LogInfoEx(LOG_NET, "Activated%s%s TG %u TS %u %s in TGID table", + (nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot, + (strappedBit) ? "strapped" : (clearBit) ? "clear" : "selectable"); + network->m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred, strapping); + } + + offs += 5U; + } + + LogInfoEx(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, network->m_tidLookup->groupVoice().size()); + + // save if saving from network is enabled + if (network->m_saveLookup && len > 0) { + network->m_tidLookup->commit(); + } + } + } + } + break; + case NET_SUBFUNC::MASTER_SUBFUNC_DEACTIVE_TGS: // Talkgroup Deactivated IDs + { + if (network->m_enabled && network->m_updateLookup) { + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, DEACTIVE TGS", buffer, length); + + if (network->m_tidLookup != nullptr) { + // update TGID lists + uint32_t len = GET_UINT32(buffer, 6U); + uint32_t offs = 11U; + for (uint32_t i = 0; i < len; i++) { + uint32_t id = GET_UINT24(buffer, offs); + uint8_t slot = (buffer[offs + 3U]); + + lookups::TalkgroupRuleGroupVoice tid = network->m_tidLookup->find(id, slot); + if (!tid.isInvalid()) { + LogInfoEx(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); + network->m_tidLookup->eraseEntry(id, slot); + } + + offs += 5U; + } + + LogInfoEx(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, network->m_tidLookup->groupVoice().size()); + + // save if saving from network is enabled + if (network->m_saveLookup && len > 0) { + network->m_tidLookup->commit(); + } + } + } + } + break; + + case NET_SUBFUNC::MASTER_HA_PARAMS: // HA Parameters + { + if (network->m_enabled) { + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, HA PARAMS", buffer, length); + + network->m_haIPs.clear(); + network->m_currentHAIP = 0U; + network->m_maxRetryCount = MAX_RETRY_HA_RECONNECT; + + // always add the configured address to the HA IP list + network->m_haIPs.push_back(PeerHAIPEntry(network->m_configuredAddress, network->m_configuredPort)); + if (network->m_debug) + LogDebugEx(LOG_NET, "Network::clock()", "HA PARAMS, 1, %s:%u", network->m_configuredAddress.c_str(), network->m_configuredPort); + + uint32_t len = GET_UINT32(buffer, 6U); + if (len > 0U) { + len /= HA_PARAMS_ENTRY_LEN; + } + + uint8_t offs = 10U; + for (uint8_t i = 0U; i < len; i++, offs += HA_PARAMS_ENTRY_LEN) { + uint32_t ipAddr = GET_UINT32(buffer, offs + 4U); + uint16_t port = GET_UINT16(buffer, offs + 8U); + + std::string address = __IP_FROM_UINT(ipAddr); + + if (address == network->m_configuredAddress && port == network->m_configuredPort) { + continue; // skip if this is the same as our configured address + } + + if (network->m_debug) + LogDebugEx(LOG_NET, "Network::clock()", "HA PARAMS, %u, %s:%u", i + 2, address.c_str(), port); + + network->m_haIPs.push_back(PeerHAIPEntry(address, port)); + } + + if (network->m_haIPs.size() > 1U) { + network->m_currentHAIP = 1U; // because the first entry is our configured entry, set + // the current HA IP to the next available + LogInfoEx(LOG_NET, "Loaded %u HA IPs from master", network->m_haIPs.size()); + LogInfoEx(LOG_NET, "Current HA IP %s:%u", network->m_haIPs[network->m_currentHAIP].masterAddress.c_str(), network->m_haIPs[network->m_currentHAIP].masterPort); + } + } + } + break; + + default: + Utils::dump("unknown master control opcode from the master", buffer, length); + break; + } + + return false; +} diff --git a/src/common/network/packet/MasterDisconnect.cpp b/src/common/network/packet/MasterDisconnect.cpp new file mode 100644 index 00000000..b7879a71 --- /dev/null +++ b/src/common/network/packet/MasterDisconnect.cpp @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::MST_DISC packets. */ + +bool Network::PacketHandler::masterDisconnect(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)now; + (void)fneHeader; + (void)rtpHeader; + (void)buffer; + (void)length; + + LogError(LOG_NET, "PEER %u master disconnect, remotePeerId = %u", network->m_peerId, network->m_remotePeerId); + network->m_status = NET_STAT_WAITING_CONNECT; + + // fire off peer disconnected callback if we have one + if (network->m_peerDisconnectedCallback != nullptr) { + network->m_peerDisconnectedCallback(); + } + + network->close(); + network->open(); + + return false; +} diff --git a/src/common/network/packet/Nak.cpp b/src/common/network/packet/Nak.cpp new file mode 100644 index 00000000..6a4a9e62 --- /dev/null +++ b/src/common/network/packet/Nak.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::NAK packets. */ + +bool Network::PacketHandler::nak(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)now; + + // DVM 3.6 adds support to respond with a NAK reason, as such we just check if the NAK response is greater + // then 10 bytes and process the reason value + uint16_t reason = NET_CONN_NAK_GENERAL_FAILURE; + if (length > 10) { + reason = GET_UINT16(buffer, 10U); + switch (reason) { + case NET_CONN_NAK_MODE_NOT_ENABLED: + LogWarning(LOG_NET, "PEER %u master NAK; digital mode not enabled on FNE, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_ILLEGAL_PACKET: + LogWarning(LOG_NET, "PEER %u master NAK; illegal/unknown packet, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_FNE_UNAUTHORIZED: + LogWarning(LOG_NET, "PEER %u master NAK; unauthorized, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_BAD_CONN_STATE: + LogWarning(LOG_NET, "PEER %u master NAK; bad connection state, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_INVALID_CONFIG_DATA: + LogWarning(LOG_NET, "PEER %u master NAK; invalid configuration data, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_FNE_MAX_CONN: + LogWarning(LOG_NET, "PEER %u master NAK; FNE has reached maximum permitted connections, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_PEER_RESET: + LogWarning(LOG_NET, "PEER %u master NAK; FNE demanded connection reset, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + case NET_CONN_NAK_PEER_ACL: + LogError(LOG_NET, "PEER %u master NAK; ACL rejection, network disabled, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + if (!network->m_neverDisableOnACLNAK) { + network->m_status = NET_STAT_WAITING_LOGIN; + network->m_enabled = false; // ACL rejection give up stop trying to connect + } + break; + + case NET_CONN_NAK_FNE_DUPLICATE_CONN: + LogWarning(LOG_NET, "PEER %u master NAK; duplicate connection to FNE, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + network->m_status = NET_STAT_WAITING_CONNECT; + network->m_remotePeerId = 0U; + network->m_flaggedDuplicateConn = true; + network->m_maxRetryCount = MAX_RETRY_DUP_RECONNECT; + network->m_retryTimer.start(); + return true; + + case NET_CONN_NAK_GENERAL_FAILURE: + default: + LogWarning(LOG_NET, "PEER %u master NAK; general failure, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + break; + } + } + + // if the NAK reason is unhandled by the user code, perform default handling + if (!network->userNakHandler(network->m_peerId, reason, fneHeader, rtpHeader)) { + if (network->m_status == NET_STAT_RUNNING && (reason == NET_CONN_NAK_FNE_MAX_CONN)) { + LogWarning(LOG_NET, "PEER %u master NAK; attemping to relogin, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + network->m_status = NET_STAT_WAITING_LOGIN; + network->m_timeoutTimer.start(); + network->m_retryTimer.start(); + } + else { + if (network->m_enabled) { + LogError(LOG_NET, "PEER %u master NAK; network reconnect, remotePeerId = %u", network->m_peerId, rtpHeader.getSSRC()); + network->close(); + network->open(); + } + return true; + } + } + + return false; +} diff --git a/src/common/network/packet/Pong.cpp b/src/common/network/packet/Pong.cpp new file mode 100644 index 00000000..c1e80ff5 --- /dev/null +++ b/src/common/network/packet/Pong.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define MAX_SERVER_DIFF 360ULL // maximum difference in time between a server timestamp and local timestamp in milliseconds + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::PONG packets. */ + +bool Network::PacketHandler::pong(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)peerId; + (void)streamId; + (void)fneHeader; + (void)rtpHeader; + + network->m_timeoutTimer.start(); + if (length >= 14) { + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, PONG", buffer, length); + + ulong64_t serverNow = 0U; + + // combine bytes into ulong64_t (8 byte) value + serverNow = buffer[6U]; + serverNow = (serverNow << 8) + buffer[7U]; + serverNow = (serverNow << 8) + buffer[8U]; + serverNow = (serverNow << 8) + buffer[9U]; + serverNow = (serverNow << 8) + buffer[10U]; + serverNow = (serverNow << 8) + buffer[11U]; + serverNow = (serverNow << 8) + buffer[12U]; + serverNow = (serverNow << 8) + buffer[13U]; + + // check the ping RTT and report any over the maximum defined time + uint64_t dt = (uint64_t)fabs((double)now - (double)serverNow); + if (dt > MAX_SERVER_DIFF) + LogWarning(LOG_NET, "PEER %u pong, time delay greater than %llums, now = %llu, server = %llu, dt = %llu", network->m_peerId, MAX_SERVER_DIFF, now, serverNow, dt); + + ++network->m_pingsReceived; + + // if we've been connected for at least 10 PING/PONG cycles and we're flagged duplicate connection, clear the flag + if (network->m_pingsReceived > 10U && network->m_flaggedDuplicateConn) { + network->m_flaggedDuplicateConn = false; + } + } + + return false; +} diff --git a/src/common/network/packet/Protocol.cpp b/src/common/network/packet/Protocol.cpp new file mode 100644 index 00000000..01052573 --- /dev/null +++ b/src/common/network/packet/Protocol.cpp @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Common Library + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2017-2026 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/Log.h" +#include "network/Network.h" + +using namespace network; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::PROTOCOL packets. */ + +bool Network::PacketHandler::protocol(Network* network, uint32_t peerId, uint32_t streamId, uint64_t now, + const frame::RTPFNEHeader& fneHeader, const frame::RTPHeader& rtpHeader, const uint8_t* buffer, int length) +{ + (void)now; + + // are protocol messages being user handled? + if (network->m_userHandleProtocol) { + network->userPacketHandler(fneHeader.getPeerId(), { fneHeader.getFunction(), fneHeader.getSubFunction() }, + buffer, length, fneHeader.getStreamId(), fneHeader, rtpHeader); + return false; + } + + // process incoming message subfunction opcodes + switch (fneHeader.getSubFunction()) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + { + if (network->m_enabled && network->m_dmrEnabled) { + uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 1U : 0U; // this is the raw index for the stream ID array + + if (network->m_debug) { + LogDebug(LOG_NET, "DMR Slot %u, peer = %u, len = %u, pktSeq = %u, streamId = %u", + slotNo + 1U, peerId, length, rtpHeader.getSequence(), streamId); + } + + if (network->m_promiscuousPeer) { + network->m_rxDMRStreamId[slotNo] = streamId; + network->m_pktLastSeq = network->m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif + } + else { + if (network->m_rxDMRStreamId[slotNo] == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxDMRStreamId[slotNo] = 0U; + } + else { + network->m_rxDMRStreamId[slotNo] = streamId; + } + + network->m_pktLastSeq = network->m_pktSeq; + } + else { + if (network->m_rxDMRStreamId[slotNo] == streamId) { + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "DMR Slot %u stream %u possible lost frames; got %u, expected %u", + slotNo, streamId, network->m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "DMR Slot %u stream %u out-of-order; got %u, expected %u", + slotNo, streamId, network->m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "DMR Slot %u valid seq, seq = %u, streamId = %u", slotNo, rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxDMRStreamId[slotNo] = 0U; + } + } + } + + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (network->m_rxDMRStreamId[slotNo] != 0U && network->m_rxDMRStreamId[slotNo] != streamId && + rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + break; + } + } + + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, DMR", buffer, length); + if (length > (int)(DMR_PACKET_LENGTH + PACKET_PAD)) + LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, network->m_pktSeq, length); + + uint8_t len = length; + network->m_rxDMRData.addData(&len, 1U); + network->m_rxDMRData.addData(buffer, len); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + { + if (network->m_enabled && network->m_p25Enabled) { + if (network->m_debug) { + LogDebug(LOG_NET, "P25, peer = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, length, rtpHeader.getSequence(), streamId); + } + + if (network->m_promiscuousPeer) { + network->m_rxP25StreamId = streamId; + network->m_pktLastSeq = network->m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif + } + else { + if (network->m_rxP25StreamId == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxP25StreamId = 0U; + } + else { + network->m_rxP25StreamId = streamId; + } + + network->m_pktLastSeq = network->m_pktSeq; + } + else { + if (network->m_rxP25StreamId == streamId) { + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "P25 stream %u possible lost frames; got %u, expected %u", + streamId, network->m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "P25 stream %u out-of-order; got %u, expected %u", + streamId, network->m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "P25 valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxP25StreamId = 0U; + } + } + } + + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (network->m_rxP25StreamId != 0U && network->m_rxP25StreamId != streamId && + rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + break; + } + } + + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, P25", buffer, length); + if (length > 512) + LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, network->m_pktSeq, length); + + // P25 frames can be up to 512 bytes, but we need to handle the case where the frame is larger than 255 bytes + uint8_t len = length; + if (length > 254) { + len = 254U; + network->m_rxP25Data.addData(&len, 1U); + len = length - 254U; + network->m_rxP25Data.addData(&len, 1U); + } + else { + network->m_rxP25Data.addData(&len, 1U); + } + + network->m_rxP25Data.addData(buffer, length); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25_P2: // Encapsulated P25 Phase 2 data frame + { + if (network->m_enabled && network->m_p25Enabled) { + uint32_t slotNo = (buffer[19U] & 0x80U) == 0x80U ? 1U : 0U; // this is the raw index for the stream ID array + + if (network->m_debug) { + LogDebug(LOG_NET, "P25 Phase 2 Slot %u, peer = %u, len = %u, pktSeq = %u, streamId = %u", + slotNo + 1U, peerId, length, rtpHeader.getSequence(), streamId); + } + + if (network->m_promiscuousPeer) { + network->m_rxP25P2StreamId[slotNo] = streamId; + network->m_pktLastSeq = network->m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif + } + else { + if (network->m_rxP25P2StreamId[slotNo] == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxP25P2StreamId[slotNo] = 0U; + } + else { + network->m_rxP25P2StreamId[slotNo] = streamId; + } + + network->m_pktLastSeq = network->m_pktSeq; + } + else { + if (network->m_rxP25P2StreamId[slotNo] == streamId) { + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "DMR Slot %u stream %u possible lost frames; got %u, expected %u", + slotNo, streamId, network->m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "DMR Slot %u stream %u out-of-order; got %u, expected %u", + slotNo, streamId, network->m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "P25 Phase 2 Slot %u valid seq, seq = %u, streamId = %u", slotNo, rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxP25P2StreamId[slotNo] = 0U; + } + } + } + + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (network->m_rxP25P2StreamId[slotNo] != 0U && network->m_rxP25P2StreamId[slotNo] != streamId && + rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + break; + } + } + + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, P25 Phase 2", buffer, length); + if (length > (int)(P25_P2_PACKET_LENGTH + PACKET_PAD)) + LogError(LOG_NET, "P25 Phase 2 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, network->m_pktSeq, length); + + uint8_t len = length; + network->m_rxP25P2Data.addData(&len, 1U); + network->m_rxP25P2Data.addData(buffer, len); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + { + if (network->m_enabled && network->m_nxdnEnabled) { + if (network->m_debug) { + LogDebug(LOG_NET, "NXDN, peer = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, length, rtpHeader.getSequence(), streamId); + } + + if (network->m_promiscuousPeer) { + network->m_rxNXDNStreamId = streamId; + network->m_pktLastSeq = network->m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif + } + else { + if (network->m_rxNXDNStreamId == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxNXDNStreamId = 0U; + } + else { + network->m_rxNXDNStreamId = streamId; + } + + network->m_pktLastSeq = network->m_pktSeq; + } + else { + if (network->m_rxNXDNStreamId == streamId) { + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "NXDN stream %u possible lost frames; got %u, expected %u", + streamId, network->m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "NXDN stream %u out-of-order; got %u, expected %u", + streamId, network->m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "NXDN valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxNXDNStreamId = 0U; + } + } + } + + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (network->m_rxNXDNStreamId != 0U && network->m_rxNXDNStreamId != streamId && + rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + break; + } + } + + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, NXDN", buffer, length); + if (length > (int)(NXDN_PACKET_LENGTH + PACKET_PAD)) + LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, network->m_pktSeq, length); + + uint8_t len = length; + network->m_rxNXDNData.addData(&len, 1U); + network->m_rxNXDNData.addData(buffer, len); + } + } + break; + + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated Analog data frame + { + if (network->m_enabled && network->m_analogEnabled) { + if (network->m_debug) { + LogDebug(LOG_NET, "Analog, peer = %u, len = %u, pktSeq = %u, streamId = %u", + peerId, length, rtpHeader.getSequence(), streamId); + } + + if (network->m_promiscuousPeer) { + network->m_rxAnalogStreamId = streamId; + network->m_pktLastSeq = network->m_pktSeq; + + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->m_mux->verifyStream(streamId, rtpHeader.getSequence(), fneHeader.getFunction(), &lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogError(LOG_NET, "PEER %u stream %u possible lost frames; got %u, expected %u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq, rtpHeader.getSequence()); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogError(LOG_NET, "PEER %u stream %u out-of-order; got %u, expected >%u", peerId, + streamId, rtpHeader.getSequence(), lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "PEER %u valid mux, seq = %u, streamId = %u", peerId, rtpHeader.getSequence(), streamId); + } +#endif + } + else { + if (network->m_rxAnalogStreamId == 0U) { + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxAnalogStreamId = 0U; + } + else { + network->m_rxAnalogStreamId = streamId; + } + + network->m_pktLastSeq = network->m_pktSeq; + } + else { + if (network->m_rxAnalogStreamId == streamId) { + uint16_t lastRxSeq = 0U; + + MULTIPLEX_RET_CODE ret = network->verifyStream(&lastRxSeq); + if (ret == MUX_LOST_FRAMES) { + LogWarning(LOG_NET, "Analog stream %u possible lost frames; got %u, expected %u", + streamId, network->m_pktSeq, lastRxSeq); + } + else if (ret == MUX_OUT_OF_ORDER) { + LogWarning(LOG_NET, "Analog stream %u out-of-order; got %u, expected %u", + streamId, network->m_pktSeq, lastRxSeq); + } +#if DEBUG_RTP_MUX + else { + LogDebugEx(LOG_NET, "Network::clock()", "Analog valid seq, seq = %u, streamId = %u", rtpHeader.getSequence(), streamId); + } +#endif + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + network->m_rxAnalogStreamId = 0U; + } + } + } + + // check if we need to skip this stream -- a non-zero stream ID means the network client is locked + // to receiving a specific stream; a zero stream ID means the network is promiscuously + // receiving streams sent to this peer + if (network->m_rxAnalogStreamId != 0U && network->m_rxAnalogStreamId != streamId && + rtpHeader.getSequence() != RTP_END_OF_CALL_SEQ) { + break; + } + } + + if (network->m_packetDump) + Utils::dump(1U, "Network::clock(), Network Rx, Analog", buffer, length); + if (length < (int)ANALOG_PACKET_LENGTH) { + LogError(LOG_NET, "Analog Stream %u, frame too short? this shouldn't happen, pktSeq = %u, len = %u", streamId, network->m_pktSeq, length); + } + else { + if (length > 512) + LogError(LOG_NET, "Analog Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, network->m_pktSeq, length); + + // Analog frames are larger then 254 bytes, but we need to handle the case where the frame is larger than 255 bytes + uint8_t len = length - 254U; + network->m_rxAnalogData.addData(&len, 1U); + + network->m_rxAnalogData.addData(buffer, length); + } + } + } + break; + + default: + Utils::dump("unknown protocol opcode from the master", buffer, length); + break; + } + + return false; +} diff --git a/src/fne/network/MetadataNetwork.cpp b/src/fne/network/MetadataNetwork.cpp index f41a4237..88154a45 100644 --- a/src/fne/network/MetadataNetwork.cpp +++ b/src/fne/network/MetadataNetwork.cpp @@ -203,6 +203,10 @@ void MetadataNetwork::erasePacketBufferEntry(PacketBufferMap& pktMap, uint32_t p pktMap.unlock(); } +/* +** Packet Processing +*/ + /* Process a data frames from the network. */ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) @@ -249,7 +253,7 @@ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) { NET_FUNC::NET_TREE, &MetadataNetwork::PacketHandler::networkTree } }; - // dispatch to the appropriate handler based on the function code + // dispatch to the appropriate handler based on the function opcode auto it = handlers.find(req->fneHeader.getFunction()); if (it != handlers.end()) { it->second(network, mdNetwork, req, peerId, ssrc, streamId); diff --git a/src/fne/network/MetadataNetwork.h b/src/fne/network/MetadataNetwork.h index 7a6f15e9..4af25af7 100644 --- a/src/fne/network/MetadataNetwork.h +++ b/src/fne/network/MetadataNetwork.h @@ -167,6 +167,10 @@ namespace network */ static void erasePacketBufferEntry(PacketBufferMap& pktMap, uint32_t peerId); + /* + ** Packet Processing + */ + using PacketHandlerFunc = void (*)(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); /** diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index 8c7ad202..78a23eca 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -995,6 +995,10 @@ void* TrafficNetwork::threadParrotHandler(void* arg) return nullptr; } +/* +** Packet Processing +*/ + /* Process a data frames from the network. */ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) @@ -1061,7 +1065,6 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) return; } - // process incoming message function opcodes static const std::unordered_map handlers = { { NET_FUNC::PROTOCOL, &TrafficNetwork::PacketHandler::protocol }, @@ -1080,6 +1083,7 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) { NET_FUNC::KEY_LLA_REQ, &TrafficNetwork::PacketHandler::llaKeyRequest }, }; + // dispatch to the appropriate handler based on the function opcode uint8_t func = req->fneHeader.getFunction(); auto it = handlers.find(func); if (it != handlers.end()) { @@ -1102,6 +1106,10 @@ void TrafficNetwork::taskNetworkRx(NetPacketRequest* req) } } +/* +** General Helper Functions +*/ + /* Checks if the passed peer ID is blocked from unit-to-unit traffic. */ bool TrafficNetwork::checkU2UDroppedPeer(uint32_t peerId) diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index 27068260..4976583f 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -463,10 +463,7 @@ namespace network * @param peerId The ID of the peer. * @return A reference to the mutex associated with the peer ID. */ - static std::mutex& getPeerStateLock(uint32_t peerId) - { - return s_peerStateLocks[peerId % PEER_STATE_LOCK_STRIPES]; - } + static std::mutex& getPeerStateLock(uint32_t peerId) { return s_peerStateLocks[peerId % PEER_STATE_LOCK_STRIPES]; } /** * @brief Entry point to parrot handler thread. @@ -475,6 +472,10 @@ namespace network */ static void* threadParrotHandler(void* arg); + /* + ** Packet Processing + */ + using PacketHandlerFunc = void (*)(TrafficNetwork* network, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId, uint64_t now); /** @@ -595,6 +596,10 @@ namespace network */ static void taskNetworkRx(NetPacketRequest* req); + /* + ** General Helper Functions + */ + /** * @brief Checks if the passed peer ID is blocked from unit-to-unit traffic. * @param peerId Peer ID.