diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 9891ad4c..83401a88 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -82,22 +82,6 @@ master: # Recommended: 40000 us (40ms) for terrestrial, 80000 us (80ms) for satellite. defaultMaxWait: 40000 - # Per-peer jitter buffer overrides for specific peers with problematic network conditions. - peerOverrides: [] - # Example: Satellite link peer with high latency and jitter - # - peerId: 31003 - # enabled: true - # maxSize: 6 - # maxWait: 80000 - # Example: Cellular peer with variable jitter - # - peerId: 31004 - # enabled: true - # maxSize: 5 - # maxWait: 60000 - # Example: Fiber peer with low latency - disable jitter buffer - # - peerId: 31005 - # enabled: false - # Flag indicating whether or not denied traffic will be logged. # (This is useful for debugging talkgroup rules and other ACL issues, but can be very noisy on a busy system.) logDenials: false diff --git a/configs/peer_list.example.dat b/configs/peer_list.example.dat index 922ec695..32e64280 100644 --- a/configs/peer_list.example.dat +++ b/configs/peer_list.example.dat @@ -22,11 +22,14 @@ # call, normal call collision rules are applied to the traffic being transmitted. # If this flag is enabled (1), and the connected peer tries to transmit over an on going # call, call collision rules are ignored, and the peer is given priority. +# * JITTER ENABLED [OPTIONAL] - Flag indicating whether the adaptive jitter buffer is enabled. +# * JITTER MAX FRAMES [OPTIONAL] - Maximum buffer size in frames (range: 2-8 frames). +# * JITTER MAX WAIT [OPTIONAL] - Maximum wait time in microseconds (range: 10000-200000 us). # -# Entry Format: "Peer ID,Peer Password,Peer Replication (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled),Has Call Priority (1 = Enabled / 0 = Disabled)" +# Entry Format: "Peer ID,Peer Password,Peer Replication (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),Can Issue Inhibit (1 = Enabled / 0 = Disabled),Has Call Priority (1 = Enabled / 0 = Disabled),Jitter Enabled (1 = Enabled / 0 = Disabled),Jitter Max Size, Jitter Max Wait" # Examples: -#1234,,0,,1,0,0, -#5678,MYSECUREPASSWORD,0,,0,0,0, -#9876,MYSECUREPASSWORD,1,,0,0,0, -#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0,0, -#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0,0, +#1234,,0,,1,0,0,0,4,40000, +#5678,MYSECUREPASSWORD,0,,0,0,0,0,4,40000, +#9876,MYSECUREPASSWORD,1,,0,0,0,0,4,40000, +#5432,MYSECUREPASSWORD,,Peer Alias 1,0,0,0,0,4,40000, +#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,0,0,0,4,40000, diff --git a/src/common/lookups/PeerListLookup.cpp b/src/common/lookups/PeerListLookup.cpp index 674c72b3..14cbd9b1 100644 --- a/src/common/lookups/PeerListLookup.cpp +++ b/src/common/lookups/PeerListLookup.cpp @@ -242,6 +242,21 @@ bool PeerListLookup::load() if (parsed.size() >= 7) hasCallPriority = ::atoi(parsed[6].c_str()) == 1; + // parse jitter buffer enabled flag + bool jitterBufferEnabled = false; + if (parsed.size() >= 8) + jitterBufferEnabled = ::atoi(parsed[7].c_str()) == 1; + + // parse jitter buffer max size + uint16_t jitterBufferMaxSize = DEFAULT_JITTER_MAX_SIZE; + if (parsed.size() >= 9) + jitterBufferMaxSize = (uint16_t)::atoi(parsed[8].c_str()); + + // parse jitter buffer max wait time + uint32_t jitterBufferMaxWait = DEFAULT_JITTER_MAX_WAIT; + if (parsed.size() >= 10) + jitterBufferMaxWait = (uint32_t)::atoi(parsed[9].c_str()); + // parse optional password std::string password = ""; if (parsed.size() >= 2) @@ -253,17 +268,21 @@ bool PeerListLookup::load() entry.canRequestKeys(canRequestKeys); entry.canIssueInhibit(canIssueInhibit); entry.hasCallPriority(hasCallPriority); + entry.jitterBufferEnabled(jitterBufferEnabled); + entry.jitterBufferMaxSize(jitterBufferMaxSize); + entry.jitterBufferMaxWait(jitterBufferMaxWait); m_table[id] = entry; // log depending on what was loaded - LogInfoEx(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s%s", id, + LogInfoEx(LOG_HOST, "Loaded peer ID %u%s into peer ID lookup table, %s%s%s%s%s%s", id, (!alias.empty() ? (" (" + alias + ")").c_str() : ""), (!password.empty() ? "using unique peer password" : "using master password"), (peerReplica) ? ", Replication Enabled" : "", (canRequestKeys) ? ", Can Request Keys" : "", (canIssueInhibit) ? ", Can Issue Inhibit" : "", - (hasCallPriority) ? ", Has Call Priority" : ""); + (hasCallPriority) ? ", Has Call Priority" : "", + (jitterBufferEnabled) ? ", Jitter Buffer Enabled" : ""); } } @@ -358,6 +377,22 @@ bool PeerListLookup::save(bool quiet) line += "0,"; } + // add jitter buffer enabled flag + bool jitterBufferEnabled = entry.second.jitterBufferEnabled(); + if (jitterBufferEnabled) { + line += "1,"; + } else { + line += "0,"; + } + + // add jitter buffer max size + uint16_t jitterBufferMaxSize = entry.second.jitterBufferMaxSize(); + line += std::to_string(jitterBufferMaxSize) + ","; + + // add jitter buffer max wait time + uint32_t jitterBufferMaxWait = entry.second.jitterBufferMaxWait(); + line += std::to_string(jitterBufferMaxWait); + line += "\n"; file << line; lines++; diff --git a/src/common/lookups/PeerListLookup.h b/src/common/lookups/PeerListLookup.h index 9d9563c4..c9938382 100644 --- a/src/common/lookups/PeerListLookup.h +++ b/src/common/lookups/PeerListLookup.h @@ -25,6 +25,7 @@ #include "common/Defines.h" #include "common/lookups/LookupTable.h" +#include "common/network/AdaptiveJitterBuffer.h" #include #include @@ -53,6 +54,9 @@ namespace lookups m_canRequestKeys(false), m_canIssueInhibit(false), m_hasCallPriority(false), + m_jitterBufferEnabled(false), + m_jitterBufferMaxSize(4U), + m_jitterBufferMaxWait(40000U), m_peerDefault(false) { /* stub */ @@ -73,6 +77,9 @@ namespace lookups m_canRequestKeys(false), m_canIssueInhibit(false), m_hasCallPriority(false), + m_jitterBufferEnabled(false), + m_jitterBufferMaxSize(4U), + m_jitterBufferMaxWait(40000U), m_peerDefault(peerDefault) { /* stub */ @@ -92,6 +99,9 @@ namespace lookups m_canRequestKeys = data.m_canRequestKeys; m_canIssueInhibit = data.m_canIssueInhibit; m_hasCallPriority = data.m_hasCallPriority; + m_jitterBufferEnabled = data.m_jitterBufferEnabled; + m_jitterBufferMaxSize = data.m_jitterBufferMaxSize; + m_jitterBufferMaxWait = data.m_jitterBufferMaxWait; m_peerDefault = data.m_peerDefault; } @@ -114,6 +124,29 @@ namespace lookups m_peerDefault = peerDefault; } + /** + * @brief Sets jitter buffer parameters. + * @param maxWait Maximum wait time in microseconds. + * @param maxSize Maximum buffer size in frames. + * @param enabled Jitter buffer enabled flag. + */ + void setJitterBuffer(uint32_t maxWait, uint16_t maxSize, bool enabled) + { + m_jitterBufferMaxWait = maxWait; + m_jitterBufferMaxSize = maxSize; + m_jitterBufferEnabled = enabled; + + // clamp jitter buffer parameters + if (m_jitterBufferMaxSize < MIN_JITTER_MAX_SIZE) + m_jitterBufferMaxSize = MIN_JITTER_MAX_SIZE; + if (m_jitterBufferMaxSize > MAX_JITTER_MAX_SIZE) + m_jitterBufferMaxSize = MAX_JITTER_MAX_SIZE; + if (m_jitterBufferMaxWait < MIN_JITTER_MAX_WAIT) + m_jitterBufferMaxWait = MIN_JITTER_MAX_WAIT; + if (m_jitterBufferMaxWait > MAX_JITTER_MAX_WAIT) + m_jitterBufferMaxWait = MAX_JITTER_MAX_WAIT; + } + public: /** * @brief Peer ID. @@ -143,6 +176,20 @@ namespace lookups * @brief Flag indicating if the peer has call transmit priority. */ DECLARE_PROPERTY_PLAIN(bool, hasCallPriority); + + /** + * @brief Jitter buffer enabled flag. + */ + DECLARE_PROPERTY_PLAIN(bool, jitterBufferEnabled); + /** + * @brief Maximum buffer size in frames. + */ + DECLARE_PROPERTY_PLAIN(uint16_t, jitterBufferMaxSize); + /** + * @brief Maximum wait time in microseconds. + */ + DECLARE_PROPERTY_PLAIN(uint32_t, jitterBufferMaxWait); + /** * @brief Flag indicating if the peer is default. */ diff --git a/src/common/network/AdaptiveJitterBuffer.h b/src/common/network/AdaptiveJitterBuffer.h index dc724dc1..093c8f46 100644 --- a/src/common/network/AdaptiveJitterBuffer.h +++ b/src/common/network/AdaptiveJitterBuffer.h @@ -27,6 +27,19 @@ namespace network { + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + #define DEFAULT_JITTER_MAX_SIZE 4U + #define DEFAULT_JITTER_MAX_WAIT 40000U + + #define MIN_JITTER_MAX_SIZE 2U + #define MAX_JITTER_MAX_SIZE 8U + + #define MIN_JITTER_MAX_WAIT 10000U + #define MAX_JITTER_MAX_WAIT 200000U + // --------------------------------------------------------------------------- // Structure Declaration // --------------------------------------------------------------------------- diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index c2687f02..664d5b2a 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -137,7 +137,6 @@ FNENetwork::FNENetwork(HostFNE* host, const std::string& address, uint16_t port, m_jitterBufferEnabled(false), m_jitterMaxSize(4U), m_jitterMaxWait(40000U), - m_peerJitterOverrides(), m_threadPool(workerCnt, "fne"), m_disablePacketData(false), m_dumpPacketData(false), @@ -243,46 +242,18 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) // jitter buffer configuration yaml::Node jitterConf = conf["jitterBuffer"]; m_jitterBufferEnabled = jitterConf["enabled"].as(false); - m_jitterMaxSize = (uint16_t)jitterConf["defaultMaxSize"].as(4U); - m_jitterMaxWait = jitterConf["defaultMaxWait"].as(40000U); + m_jitterMaxSize = (uint16_t)jitterConf["defaultMaxSize"].as(DEFAULT_JITTER_MAX_SIZE); + m_jitterMaxWait = jitterConf["defaultMaxWait"].as(DEFAULT_JITTER_MAX_WAIT); // clamp jitter buffer parameters - if (m_jitterMaxSize < 2U) - m_jitterMaxSize = 2U; - if (m_jitterMaxSize > 8U) - m_jitterMaxSize = 8U; - if (m_jitterMaxWait < 10000U) - m_jitterMaxWait = 10000U; - if (m_jitterMaxWait > 200000U) - m_jitterMaxWait = 200000U; - - // parse per-peer jitter buffer overrides - m_peerJitterOverrides.clear(); - yaml::Node peerOverrides = jitterConf["peerOverrides"]; - if (peerOverrides.size() > 0U) { - for (size_t i = 0; i < peerOverrides.size(); i++) { - yaml::Node peerConf = peerOverrides[i]; - uint32_t peerId = peerConf["peerId"].as(0U); - if (peerId != 0U) { - JitterBufferConfig jbConfig; - jbConfig.enabled = peerConf["enabled"].as(m_jitterBufferEnabled); - jbConfig.maxSize = (uint16_t)peerConf["maxSize"].as(m_jitterMaxSize); - jbConfig.maxWait = peerConf["maxWait"].as(m_jitterMaxWait); - - // clamp per-peer parameters - if (jbConfig.maxSize < 2U) - jbConfig.maxSize = 2U; - if (jbConfig.maxSize > 8U) - jbConfig.maxSize = 8U; - if (jbConfig.maxWait < 10000U) - jbConfig.maxWait = 10000U; - if (jbConfig.maxWait > 200000U) - jbConfig.maxWait = 200000U; - - m_peerJitterOverrides[peerId] = jbConfig; - } - } - } + if (m_jitterMaxSize < MIN_JITTER_MAX_SIZE) + m_jitterMaxSize = MIN_JITTER_MAX_SIZE; + if (m_jitterMaxSize > MAX_JITTER_MAX_SIZE) + m_jitterMaxSize = MAX_JITTER_MAX_SIZE; + if (m_jitterMaxWait < MIN_JITTER_MAX_WAIT) + m_jitterMaxWait = MIN_JITTER_MAX_WAIT; + if (m_jitterMaxWait > MAX_JITTER_MAX_WAIT) + m_jitterMaxWait = MAX_JITTER_MAX_WAIT; #if defined(ENABLE_SSL) m_kmfServicesEnabled = conf["kmfServicesEnabled"].as(false); @@ -399,13 +370,10 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" InfluxDB Bucket: %s", m_influxBucket.c_str()); LogInfo(" InfluxDB Log Raw TSBK/CSBK/RCCH: %s", m_influxLogRawData ? "yes" : "no"); } - LogInfo(" Jitter Buffer Enabled: %s", m_jitterBufferEnabled ? "yes" : "no"); + LogInfo(" Global Jitter Buffer Enabled: %s", m_jitterBufferEnabled ? "yes" : "no"); if (m_jitterBufferEnabled) { - LogInfo(" Jitter Buffer Default Max Size: %u frames", m_jitterMaxSize); - LogInfo(" Jitter Buffer Default Max Wait: %u microseconds", m_jitterMaxWait); - if (!m_peerJitterOverrides.empty()) { - LogInfo(" Jitter Buffer Peer Overrides: %zu peer(s) configured", m_peerJitterOverrides.size()); - } + LogInfo(" Global Jitter Buffer Default Max Size: %u frames", m_jitterMaxSize); + LogInfo(" Global Jitter Buffer Default Max Wait: %u microseconds", m_jitterMaxWait); } LogInfo(" Parrot Repeat to Only Originating Peer: %s", m_parrotOnlyOriginating ? "yes" : "no"); LogInfo(" P25 OTAR KMF Services Enabled: %s", m_kmfServicesEnabled ? "yes" : "no"); @@ -431,33 +399,6 @@ void FNENetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::Talkgrou m_adjSiteMapLookup = adjSiteMapLookup; } -/* Applies jitter buffer configuration to a peer connection. */ - -void FNENetwork::applyJitterBufferConfig(uint32_t peerId, FNEPeerConnection* connection) -{ - if (connection == nullptr) { - return; - } - - // check if there's a per-peer override - auto it = m_peerJitterOverrides.find(peerId); - if (it != m_peerJitterOverrides.end()) { - const JitterBufferConfig& config = it->second; - connection->setJitterBufferParams(config.enabled, config.maxSize, config.maxWait); - if (m_verbose && config.enabled) { - LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (per-peer), maxSize = %u, maxWait = %u", - peerId, config.maxSize, config.maxWait); - } - } else { - // use default settings - connection->setJitterBufferParams(m_jitterBufferEnabled, m_jitterMaxSize, m_jitterMaxWait); - if (m_verbose && m_jitterBufferEnabled) { - LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (global), maxSize = %u, maxWait = %u", - peerId, m_jitterMaxSize, m_jitterMaxWait); - } - } -} - /* Sets endpoint preshared encryption key. */ void FNENetwork::setPresharedKey(const uint8_t* presharedKey) @@ -2138,6 +2079,34 @@ void FNENetwork::logSpanningTree(FNEPeerConnection* connection) } } +/* Applies jitter buffer configuration to a peer connection. */ + +void FNENetwork::applyJitterBufferConfig(uint32_t peerId, FNEPeerConnection* connection) +{ + if (connection == nullptr) { + return; + } + + if (m_jitterBufferEnabled) { + // use global settings + connection->setJitterBufferParams(m_jitterBufferEnabled, m_jitterMaxSize, m_jitterMaxWait); + if (m_verbose && m_jitterBufferEnabled) { + LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (global), maxSize = %u, maxWait = %u", + peerId, m_jitterMaxSize, m_jitterMaxWait); + } + } else { + lookups::PeerId peerEntry = m_peerListLookup->find(peerId); + if (!peerEntry.peerDefault()) { + connection->setJitterBufferParams(peerEntry.jitterBufferEnabled(), + peerEntry.jitterBufferMaxSize(), peerEntry.jitterBufferMaxWait()); + if (m_verbose && peerEntry.jitterBufferEnabled()) { + LogInfoEx(LOG_MASTER, "PEER %u jitter buffer configured (per-peer), maxSize = %u, maxWait = %u", + peerId, peerEntry.jitterBufferMaxSize(), peerEntry.jitterBufferMaxWait()); + } + } + } +} + /* Erases a stream ID from the given peer ID connection. */ void FNENetwork::eraseStreamPktSeq(uint32_t peerId, uint32_t streamId) diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 87099d0a..2377dd27 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -393,19 +393,9 @@ namespace network bool m_influxLogRawData; influxdb::ServerInfo m_influxServer; - /** - * @brief Structure containing jitter buffer configuration for a peer. - */ - struct JitterBufferConfig { - bool enabled; //!< Jitter buffer enabled flag - uint16_t maxSize; //!< Maximum buffer size in frames - uint32_t maxWait; //!< Maximum wait time in microseconds - }; - bool m_jitterBufferEnabled; uint16_t m_jitterMaxSize; uint32_t m_jitterMaxWait; - std::unordered_map m_peerJitterOverrides; ThreadPool m_threadPool;