/** * Digital Voice Modem - Host Software * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * @package DVM / Host Software * */ /* * Copyright (C) 2023 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "Defines.h" #include "edac/SHA256.h" #include "host/fne/HostFNE.h" #include "network/FNENetwork.h" #include "network/fne/TagDMRData.h" #include "network/fne/TagP25Data.h" #include "network/fne/TagNXDNData.h" #include "network/json/json.h" #include "Log.h" #include "StopWatch.h" #include "Utils.h" using namespace network; using namespace network::fne; #include #include #include #include // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the FNENetwork class. /// /// /// Network Hostname/IP address to listen on. /// Network port number. /// Unique ID on the network. /// Network authentication password. /// Flag indicating whether network debug is enabled. /// Flag indicating whether network verbose logging is enabled. /// Flag indicating whether DMR is enabled. /// Flag indicating whether P25 is enabled. /// Flag indicating whether NXDN is enabled. /// Delay for end of call to parrot TG playback. /// Flag indicating that the system activity logs will be sent to the network. /// Flag indicating that the system diagnostic logs will be sent to the network. /// Flag indicating if traffic should be repeated from this master. /// /// FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, uint32_t peerId, const std::string& password, bool debug, bool verbose, bool dmr, bool p25, bool nxdn, uint32_t parrotDelay, bool allowActivityTransfer, bool allowDiagnosticTransfer, uint32_t pingTime, uint32_t updateLookupTime) : BaseNetwork(peerId, true, debug, true, true, allowActivityTransfer, allowDiagnosticTransfer), m_tagDMR(nullptr), m_tagP25(nullptr), m_tagNXDN(nullptr), m_host(host), m_address(address), m_port(port), m_password(password), m_dmrEnabled(dmr), m_p25Enabled(p25), m_nxdnEnabled(nxdn), m_parrotDelay(parrotDelay), m_ridLookup(nullptr), m_tidLookup(nullptr), m_status(NET_STAT_INVALID), m_peers(), m_maintainenceTimer(1000U, pingTime), m_updateLookupTimer(1000U, (updateLookupTime * 60U)), m_verbose(verbose) { assert(host != nullptr); assert(!address.empty()); assert(port > 0U); assert(!password.empty()); m_tagDMR = new TagDMRData(this, debug); m_tagP25 = new TagP25Data(this, debug); m_tagNXDN = new TagNXDNData(this, debug); } /// /// Finalizes a instance of the FNENetwork class. /// FNENetwork::~FNENetwork() { delete m_tagDMR; delete m_tagP25; delete m_tagNXDN; } /// /// Sets the instances of the Radio ID and Talkgroup Rules lookup tables. /// /// Radio ID Lookup Table Instance /// Talkgroup Rules Lookup Table Instance void FNENetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup) { m_ridLookup = ridLookup; m_tidLookup = tidLookup; } /// /// Updates the timer by the passed number of milliseconds. /// /// void FNENetwork::clock(uint32_t ms) { if (m_status != NET_STAT_MST_RUNNING) { return; } uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); m_maintainenceTimer.clock(ms); if (m_maintainenceTimer.isRunning() && m_maintainenceTimer.hasExpired()) { // check to see if any peers have been quiet (no ping) longer than allowed std::vector peersToRemove = std::vector(); for (auto peer : m_peers) { uint32_t id = peer.first; FNEPeerConnection connection = peer.second; if (connection.connected()) { uint64_t dt = connection.lastPing() + (m_host->m_pingTime * m_host->m_maxMissedPings); if (dt < now) { LogInfoEx(LOG_NET, "PEER %u timed out, dt = %u, now = %u", id, dt, now); peersToRemove.push_back(id); } } } // remove any peers for (uint32_t peerId : peersToRemove) { m_peers.erase(peerId); } m_maintainenceTimer.start(); } m_updateLookupTimer.clock(ms); if (m_updateLookupTimer.isRunning() && m_updateLookupTimer.hasExpired()) { writeWhitelistRIDs(); writeBlacklistRIDs(); m_frameQueue->flushQueue(); writeTGIDs(); writeDeactiveTGIDs(); m_frameQueue->flushQueue(); m_updateLookupTimer.start(); } sockaddr_storage address; uint32_t addrLen; frame::RTPHeader rtpHeader; frame::RTPFNEHeader fneHeader; int length = 0U; // read message UInt8Array buffer = m_frameQueue->read(length, address, addrLen, &rtpHeader, &fneHeader); if (length > 0) { if (m_debug) Utils::dump(1U, "Network Message", buffer.get(), length); uint32_t peerId = fneHeader.getPeerId(); uint32_t streamId = fneHeader.getStreamId(); // update current peer packet sequence and stream ID if (peerId > 0 && (m_peers.find(peerId) != m_peers.end()) && streamId != 0U) { FNEPeerConnection connection = m_peers[peerId]; uint16_t pktSeq = rtpHeader.getSequence(); if ((connection.currStreamId() == streamId) && (pktSeq != connection.pktNextSeq())) { LogWarning(LOG_NET, "PEER %u Stream %u out-of-sequence; %u != %u", peerId, streamId, pktSeq, connection.pktNextSeq()); } connection.currStreamId(streamId); connection.pktLastSeq(pktSeq); connection.pktNextSeq(pktSeq + 1); if (connection.pktNextSeq() > UINT16_MAX) { connection.pktNextSeq(0U); } m_peers[peerId] = connection; } // if we don't have a stream ID and are receiving call data -- throw an error and discard if (streamId == 0 && fneHeader.getFunction() == NET_FUNC_PROTOCOL) { LogError(LOG_NET, "PEER %u Malformed packet (no stream ID for a call?)", peerId); return; } // process incoming message frame opcodes switch (fneHeader.getFunction()) { case NET_FUNC_PROTOCOL: { if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_DMR) { // Encapsulated DMR data frame if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { #if defined(ENABLE_DMR) if (m_dmrEnabled) { if (m_tagDMR != nullptr) { m_tagDMR->processFrame(buffer.get(), length, peerId, rtpHeader.getSequence(), streamId); } } #endif // defined(ENABLE_DMR) } } } else if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_P25) { // Encapsulated P25 data frame if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { #if defined(ENABLE_P25) if (m_p25Enabled) { if (m_tagP25 != nullptr) { m_tagP25->processFrame(buffer.get(), length, peerId, rtpHeader.getSequence(), streamId); } } #endif // defined(ENABLE_P25) } } } else if (fneHeader.getSubFunction() == NET_PROTOCOL_SUBFUNC_NXDN) { // Encapsulated NXDN data frame if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { #if defined(ENABLE_NXDN) if (m_nxdnEnabled) { if (m_tagNXDN != nullptr) { m_tagNXDN->processFrame(buffer.get(), length, peerId, rtpHeader.getSequence(), streamId); } } #endif // defined(ENABLE_NXDN) } } } else { Utils::dump("Unknown protocol opcode from peer", buffer.get(), length); } } break; case NET_FUNC_RPTL: // Repeater Login { if (peerId > 0 && (m_peers.find(peerId) == m_peers.end())) { FNEPeerConnection connection = FNEPeerConnection(peerId, address, addrLen); connection.lastPing(now); connection.currStreamId(streamId); std::uniform_int_distribution dist(DVM_RAND_MIN, DVM_RAND_MAX); connection.salt(dist(m_random)); LogInfoEx(LOG_NET, "Repeater logging in with PEER %u, %s:%u", peerId, connection.address().c_str(), connection.port()); connection.connectionState(NET_STAT_WAITING_AUTHORISATION); m_peers[peerId] = connection; // transmit salt to peer uint8_t salt[4U]; ::memset(salt, 0x00U, 4U); __SET_UINT32(connection.salt(), salt, 0U); writePeerACK(peerId, salt, 4U); LogInfoEx(LOG_NET, "Challenge response send to PEER %u for login", peerId); } else { writePeerNAK(peerId, TAG_REPEATER_LOGIN, address, addrLen); // check if the peer is in our peer list -- if he is, and he isn't in a running state, reset // the login sequence if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; connection.lastPing(now); if (connection.connectionState() != NET_STAT_RUNNING) { auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } } } } break; case NET_FUNC_RPTK: // Repeater Authentication { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; connection.lastPing(now); if (connection.connectionState() == NET_STAT_WAITING_AUTHORISATION) { // get the hash from the frame message uint8_t hash[length - 8U]; ::memset(hash, 0x00U, length - 8U); ::memcpy(hash, buffer.get() + 8U, length - 8U); // generate our own hash uint8_t salt[4U]; ::memset(salt, 0x00U, 4U); __SET_UINT32(connection.salt(), salt, 0U); size_t size = m_password.size(); uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; ::memcpy(in, salt, sizeof(uint32_t)); for (size_t i = 0U; i < size; i++) in[i + sizeof(uint32_t)] = m_password.at(i); uint8_t out[32U]; edac::SHA256 sha256; sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); delete[] in; // validate hash bool valid = false; if (length - 8U == 32U) { valid = true; for (uint8_t i = 0; i < 32U; i++) { if (hash[i] != out[i]) { valid = false; break; } } } if (valid) { connection.connectionState(NET_STAT_WAITING_CONFIG); writePeerACK(peerId); LogInfoEx(LOG_NET, "PEER %u has completed the login exchange", peerId); } else { LogWarning(LOG_NET, "PEER %u has failed the login exchange", peerId); writePeerNAK(peerId, TAG_REPEATER_AUTH); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } m_peers[peerId] = connection; } else { LogWarning(LOG_NET, "PEER %u tried login exchange while in an incorrect state?", peerId); writePeerNAK(peerId, TAG_REPEATER_AUTH); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } } else { writePeerNAK(peerId, TAG_REPEATER_AUTH, address, addrLen); } } break; case NET_FUNC_RPTC: // Repeater Configuration { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; connection.lastPing(now); if (connection.connectionState() == NET_STAT_WAITING_CONFIG) { uint8_t rawPayload[length - 8U]; ::memset(rawPayload, 0x00U, length - 8U); ::memcpy(rawPayload, buffer.get() + 8U, length - 8U); std::string payload(rawPayload, rawPayload + (length - 8U)); // parse JSON body json::value v; std::string err = json::parse(v, payload); if (!err.empty()) { LogWarning(LOG_NET, "PEER %u has supplied invalid configuration data", peerId); writePeerNAK(peerId, TAG_REPEATER_AUTH); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } else { // ensure parsed JSON is an object if (!v.is()) { LogWarning(LOG_NET, "PEER %u has supplied invalid configuration data", peerId); writePeerNAK(peerId, TAG_REPEATER_AUTH); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } else { connection.config(v.get()); connection.connectionState(NET_STAT_RUNNING); connection.connected(true); connection.pingsReceived(0U); connection.lastPing(now); m_peers[peerId] = connection; writePeerACK(peerId); LogInfoEx(LOG_NET, "PEER %u has completed the configuration exchange", peerId); // queue final update messages and flush writeWhitelistRIDs(peerId, true); writeBlacklistRIDs(peerId, true); m_frameQueue->flushQueue(); writeTGIDs(peerId, true); writeDeactiveTGIDs(peerId, true); m_frameQueue->flushQueue(); } } } else { LogWarning(LOG_NET, "PEER %u tried login exchange while in an incorrect state?", peerId); writePeerNAK(peerId, TAG_REPEATER_CONFIG); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } } else { writePeerNAK(peerId, TAG_REPEATER_CONFIG, address, addrLen); } } break; case NET_FUNC_RPT_CLOSING: // Repeater Closing (Disconnect) { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { LogInfoEx(LOG_NET, "PEER %u is closing down", peerId); auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { m_peers.erase(peerId); } } } } break; case NET_FUNC_PING: // Repeater Ping { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { uint32_t pingsRx = connection.pingsReceived(); connection.pingsReceived(pingsRx++); connection.lastPing(now); connection.pktLastSeq(connection.pktLastSeq() + 1); m_peers[peerId] = connection; writePeerTagged(peerId, { NET_FUNC_PONG, NET_SUBFUNC_NOP }, TAG_MASTER_PONG); if (m_debug) { LogDebug(LOG_NET, "PEER %u ping received and answered", peerId); } } else { writePeerNAK(peerId, TAG_REPEATER_PING); } } } break; case NET_FUNC_GRANT: // Repeater Grant Request { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { /* ignored */ } else { writePeerNAK(peerId, TAG_REPEATER_GRANT); } } } break; case NET_FUNC_TRANSFER: { if (fneHeader.getSubFunction() == NET_TRANSFER_SUBFUNC_ACTIVITY) { // Peer Activity Log Transfer if (m_allowActivityTransfer) { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { uint8_t rawPayload[length - 11U]; ::memset(rawPayload, 0x00U, length - 11U); ::memcpy(rawPayload, buffer.get() + 11U, length - 11U); std::string payload(rawPayload, rawPayload + (length - 11U)); std::stringstream ss; ss << peerId << " " << payload; ::ActivityLog("", false, ss.str().c_str()); } else { writePeerNAK(peerId, TAG_TRANSFER_ACT_LOG); } } } } else if (fneHeader.getSubFunction() == NET_TRANSFER_SUBFUNC_DIAG) { // Peer Diagnostic Log Transfer if (m_allowDiagnosticTransfer) { if (peerId > 0 && (m_peers.find(peerId) != m_peers.end())) { FNEPeerConnection connection = m_peers[peerId]; std::string ip = UDPSocket::address(address); // validate peer (simple validation really) if (connection.connected() && connection.address() == ip) { uint8_t rawPayload[length - 12U]; ::memset(rawPayload, 0x00U, length - 12U); ::memcpy(rawPayload, buffer.get() + 12U, length - 12U); std::string payload(rawPayload, rawPayload + (length - 12U)); bool currState = g_disableTimeDisplay; g_disableTimeDisplay = true; ::Log(9999U, nullptr, "%u %s", peerId, payload.c_str()); g_disableTimeDisplay = currState; } else { writePeerNAK(peerId, TAG_TRANSFER_DIAG_LOG); } } } } else { Utils::dump("Unknown transfer opcode from the peer", buffer.get(), length); } } break; default: Utils::dump("Unknown opcode from the peer", buffer.get(), length); break; } } else { #if defined(ENABLE_DMR) // if the DMR handler has parrot frames to playback, playback a frame if (m_tagDMR->hasParrotFrames()) { m_tagDMR->playbackParrot(); } #endif // defined(ENABLE_DMR) #if defined(ENABLE_P25) // if the P25 handler has parrot frames to playback, playback a frame if (m_tagP25->hasParrotFrames()) { m_tagP25->playbackParrot(); } #endif // defined(ENABLE_P25) #if defined(ENABLE_NXDN) // if the NXDN handler has parrot frames to playback, playback a frame if (m_tagNXDN->hasParrotFrames()) { m_tagNXDN->playbackParrot(); } #endif // defined(ENABLE_NXDN) } return; } /// /// Opens connection to the network. /// /// bool FNENetwork::open() { if (m_debug) LogMessage(LOG_NET, "Opening Network"); m_status = NET_STAT_MST_RUNNING; m_maintainenceTimer.start(); m_socket = new UDPSocket(m_address, m_port); // reinitialize the frame queue if (m_frameQueue != nullptr) { delete m_frameQueue; m_frameQueue = new FrameQueue(m_socket, m_peerId, m_debug); } bool ret = m_socket->open(); if (!ret) { m_status = NET_STAT_INVALID; } return ret; } /// /// Closes connection to the network. /// void FNENetwork::close() { if (m_debug) LogMessage(LOG_NET, "Closing Network"); if (m_status == NET_STAT_MST_RUNNING) { uint8_t buffer[9U]; ::memcpy(buffer + 0U, TAG_MASTER_CLOSING, 5U); writePeers({ NET_FUNC_MST_CLOSING, NET_SUBFUNC_NOP }, buffer, 9U); } m_socket->close(); m_maintainenceTimer.stop(); m_updateLookupTimer.stop(); m_status = NET_STAT_INVALID; } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /// /// Helper to send the list of whitelisted RIDs to the specified peer. /// /// /// void FNENetwork::writeWhitelistRIDs(uint32_t peerId, bool queueOnly) { // send radio ID white/black lists std::vector ridWhitelist; auto ridLookups = m_ridLookup->table(); for (auto entry : ridLookups) { uint32_t id = entry.first; if (entry.second.radioEnabled()) { ridWhitelist.push_back(id); } } if (ridWhitelist.size() == 0U) { return; } // build dataset uint8_t payload[4U + (ridWhitelist.size() * 4U)]; ::memset(payload, 0x00U, 4U + (ridWhitelist.size() * 4U)); __SET_UINT32(ridWhitelist.size(), payload, 0U); // write whitelisted IDs to whitelist payload uint32_t offs = 4U; for (uint32_t id : ridWhitelist) { if (m_debug) LogDebug(LOG_NET, "PEER %u Whitelisting RID %u", peerId, id); __SET_UINT32(id, payload, offs); offs += 4U; } writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_WL_RID }, TAG_MASTER_WL_RID, payload, 4U + (ridWhitelist.size() * 4U), queueOnly, true); } /// /// Helper to send the list of whitelisted RIDs to connected peers. /// void FNENetwork::writeWhitelistRIDs() { if (m_ridLookup->table().size() == 0U) { return; } for (auto peer : m_peers) { writeWhitelistRIDs(peer.first, true); } } /// /// Helper to send the list of whitelisted RIDs to the specified peer. /// /// /// void FNENetwork::writeBlacklistRIDs(uint32_t peerId, bool queueOnly) { // send radio ID blacklist std::vector ridBlacklist; auto ridLookups = m_ridLookup->table(); for (auto entry : ridLookups) { uint32_t id = entry.first; if (!entry.second.radioEnabled()) { ridBlacklist.push_back(id); } } if (ridBlacklist.size() == 0U) { return; } // build dataset uint8_t payload[4U + (ridBlacklist.size() * 4U)]; ::memset(payload, 0x00U, 4U + (ridBlacklist.size() * 4U)); __SET_UINT32(ridBlacklist.size(), payload, 0U); // write blacklisted IDs to blacklist payload uint32_t offs = 4U; for (uint32_t id : ridBlacklist) { if (m_debug) LogDebug(LOG_NET, "PEER %u Blacklisting RID %u", peerId, id); __SET_UINT32(id, payload, offs); offs += 4U; } writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_BL_RID }, TAG_MASTER_BL_RID, payload, 4U + (ridBlacklist.size() * 4U), queueOnly, true); } /// /// Helper to send the list of whitelisted RIDs to connected peers. /// void FNENetwork::writeBlacklistRIDs() { if (m_ridLookup->table().size() == 0U) { return; } for (auto peer : m_peers) { writeBlacklistRIDs(peer.first, true); } } /// /// Helper to send the list of active TGIDs to the specified peer. /// /// /// void FNENetwork::writeTGIDs(uint32_t peerId, bool queueOnly) { if (!m_tidLookup->sendTalkgroups()) { return; } std::vector> tgidList; auto groupVoice = m_tidLookup->groupVoice(); for (auto entry : groupVoice) { std::vector inclusion = entry.config().inclusion(); std::vector exclusion = entry.config().exclusion(); // peer inclusion lists take priority over exclusion lists if (inclusion.size() > 0) { auto it = std::find(inclusion.begin(), inclusion.end(), peerId); if (it == inclusion.end()) { // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- not included peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } else { if (exclusion.size() > 0) { auto it = std::find(exclusion.begin(), exclusion.end(), peerId); if (it != exclusion.end()) { // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- excluded peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } } if (entry.config().active()) { tgidList.push_back({ entry.source().tgId(), entry.source().tgSlot() }); } } // build dataset uint8_t payload[4U + (tgidList.size() * 5U)]; ::memset(payload, 0x00U, 4U + (tgidList.size() * 5U)); __SET_UINT32(tgidList.size(), payload, 0U); // write talkgroup IDs to active TGID payload uint32_t offs = 4U; for (std::pair tg : tgidList) { if (m_debug) LogDebug(LOG_NET, "PEER %u Activating TGID %u TS %u", peerId, tg.first, tg.second); __SET_UINT32(tg.first, payload, offs); payload[offs + 4U] = tg.second; offs += 5U; } writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_ACTIVE_TGS }, TAG_MASTER_ACTIVE_TGS, payload, 4U + (tgidList.size() * 5U), queueOnly, true); } /// /// Helper to send the list of active TGIDs to connected peers. /// void FNENetwork::writeTGIDs() { for (auto peer : m_peers) { writeTGIDs(peer.first, true); } } /// /// Helper to send the list of deactivated TGIDs to the specified peer. /// /// /// void FNENetwork::writeDeactiveTGIDs(uint32_t peerId, bool queueOnly) { if (!m_tidLookup->sendTalkgroups()) { return; } std::vector> tgidList; auto groupVoice = m_tidLookup->groupVoice(); for (auto entry : groupVoice) { std::vector inclusion = entry.config().inclusion(); std::vector exclusion = entry.config().exclusion(); // peer inclusion lists take priority over exclusion lists if (inclusion.size() > 0) { auto it = std::find(inclusion.begin(), inclusion.end(), peerId); if (it == inclusion.end()) { // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- not included peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } else { if (exclusion.size() > 0) { auto it = std::find(exclusion.begin(), exclusion.end(), peerId); if (it != exclusion.end()) { // LogDebug(LOG_NET, "PEER %u TGID %u TS %u -- excluded peer", peerId, entry.source().tgId(), entry.source().tgSlot()); continue; } } } if (!entry.config().active()) { tgidList.push_back({ entry.source().tgId(), entry.source().tgSlot() }); } } // build dataset uint8_t payload[4U + (tgidList.size() * 5U)]; ::memset(payload, 0x00U, 4U + (tgidList.size() * 5U)); __SET_UINT32(tgidList.size(), payload, 0U); // write talkgroup IDs to deactive TGID payload uint32_t offs = 4U; for (std::pair tg : tgidList) { if (m_debug) LogDebug(LOG_NET, "PEER %u Deactivating TGID %u TS %u", peerId, tg.first, tg.second); __SET_UINT32(tg.first, payload, offs); payload[offs + 4U] = tg.second; offs += 5U; } writePeerTagged(peerId, { NET_FUNC_MASTER, NET_MASTER_SUBFUNC_DEACTIVE_TGS }, TAG_MASTER_DEACTIVE_TGS, payload, 4U + (tgidList.size() * 5U), queueOnly, true); } /// /// Helper to send the list of deactivated TGIDs to connected peers. /// void FNENetwork::writeDeactiveTGIDs() { for (auto peer : m_peers) { writeDeactiveTGIDs(peer.first, true); } } /// /// Helper to send a raw message to the specified peer. /// /// Peer ID. /// Opcode. /// Buffer to write to the network. /// Length of buffer to write. /// /// /// bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq, uint32_t streamId, bool queueOnly) { auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { uint32_t peerStreamId = m_peers[peerId].currStreamId(); if (streamId == 0U) { streamId = peerStreamId; } sockaddr_storage addr = m_peers[peerId].socketStorage(); uint32_t addrLen = m_peers[peerId].sockStorageLen(); m_frameQueue->enqueueMessage(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); if (queueOnly) return true; return m_frameQueue->flushQueue(); } return false; } /// /// Helper to send a raw message to the specified peer. /// /// Peer ID. /// Opcode. /// Buffer to write to the network. /// Length of buffer to write. /// /// /// bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint32_t streamId, bool queueOnly, bool incPktSeq) { auto it = std::find_if(m_peers.begin(), m_peers.end(), [&](PeerMapPair x) { return x.first == peerId; }); if (it != m_peers.end()) { if (incPktSeq) { m_peers[peerId].pktLastSeq(m_peers[peerId].pktLastSeq() + 1); } uint16_t pktSeq = m_peers[peerId].pktLastSeq(); return writePeer(peerId, opcode, data, length, pktSeq, streamId, queueOnly); } return false; } /// /// Helper to send a tagged message to the specified peer. /// /// Peer ID. /// Opcode. /// Message tag. /// Buffer to write to the network. /// Length of buffer to write. /// /// bool FNENetwork::writePeerTagged(uint32_t peerId, FrameQueue::OpcodePair opcode, const char* tag, const uint8_t* data, uint32_t length, bool queueOnly, bool incPktSeq) { assert(peerId > 0); assert(tag != nullptr); if (strlen(tag) + length > DATA_PACKET_LENGTH) { return false; } uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, tag, strlen(tag)); __SET_UINT32(peerId, buffer, strlen(tag)); // Peer ID if (data != nullptr && length > 0U) { ::memcpy(buffer + strlen(tag), data, length); } uint32_t len = length + (strlen(tag) + 4U); return writePeer(peerId, opcode, buffer, len, queueOnly, incPktSeq); } /// /// Helper to send a ACK response to the specified peer. /// /// /// Buffer to write to the network. /// Length of buffer to write. bool FNENetwork::writePeerACK(uint32_t peerId, const uint8_t* data, uint32_t length) { uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_REPEATER_ACK, 6U); __SET_UINT32(peerId, buffer, 6U); // Peer ID if (data != nullptr && length > 0U) { ::memcpy(buffer + 6U, data, length); } return writePeer(peerId, { NET_FUNC_ACK, NET_SUBFUNC_NOP }, buffer, 10U + length, false, true); } /// /// Helper to send a NAK response to the specified peer. /// /// /// bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag) { assert(peerId > 0); assert(tag != nullptr); uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_MASTER_NAK, 6U); __SET_UINT32(peerId, buffer, 6U); // Peer ID LogWarning(LOG_NET, "%s from unauth PEER %u", tag, peerId); return writePeer(peerId, { NET_FUNC_NAK, NET_SUBFUNC_NOP }, buffer, 10U, false, true); } /// /// Helper to send a NAK response to the specified peer. /// /// /// /// /// bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag, sockaddr_storage& addr, uint32_t addrLen) { assert(peerId > 0); assert(tag != nullptr); uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, TAG_MASTER_NAK, 6U); __SET_UINT32(peerId, buffer, 6U); // Peer ID LogWarning(LOG_NET, "%s from unauth PEER %u", tag, peerId); m_frameQueue->enqueueMessage(buffer, 10U, createStreamId(), peerId, m_peerId, { NET_FUNC_NAK, NET_SUBFUNC_NOP }, 0U, addr, addrLen); return m_frameQueue->flushQueue(); } /// /// Helper to send a raw message to the connected peers. /// /// Buffer to write to the network. /// Length of buffer to write. void FNENetwork::writePeers(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length) { for (auto peer : m_peers) { uint32_t peerId = peer.first; uint32_t streamId = peer.second.currStreamId(); sockaddr_storage addr = peer.second.socketStorage(); uint32_t addrLen = peer.second.sockStorageLen(); uint16_t pktSeq = peer.second.pktLastSeq(); m_frameQueue->enqueueMessage(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); } m_frameQueue->flushQueue(); } /// /// Helper to send a raw message to the connected peers. /// /// Buffer to write to the network. /// Length of buffer to write. /// void FNENetwork::writePeers(FrameQueue::OpcodePair opcode, const uint8_t* data, uint32_t length, uint16_t pktSeq) { for (auto peer : m_peers) { uint32_t peerId = peer.first; uint32_t streamId = peer.second.currStreamId(); sockaddr_storage addr = peer.second.socketStorage(); uint32_t addrLen = peer.second.sockStorageLen(); m_frameQueue->enqueueMessage(data, length, streamId, peerId, m_peerId, opcode, pktSeq, addr, addrLen); } m_frameQueue->flushQueue(); } /// /// Helper to send a tagged message to the connected peers. /// /// /// Buffer to write to the network. /// Length of buffer to write. void FNENetwork::writePeersTagged(FrameQueue::OpcodePair opcode, const char* tag, const uint8_t* data, uint32_t length) { assert(tag != nullptr); if (strlen(tag) + length > DATA_PACKET_LENGTH) { return; } uint8_t buffer[DATA_PACKET_LENGTH]; ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); ::memcpy(buffer + 0U, tag, strlen(tag)); ::memcpy(buffer + strlen(tag), data, length); uint32_t len = length + strlen(tag); writePeers(opcode, buffer, len); }