From 193055330ae252a5634501580a58608d5908f2f5 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Mon, 17 Aug 2026 17:33:36 -0400 Subject: [PATCH] handle replicating radio aliases from FNE to FNE; implement RADIO_ALIAS_SYNC for fnecore consoles to be able to request the radio alias list from the core; --- configs/fne-config.example.yml | 11 +++ src/common/network/RTPFNEHeader.h | 4 ++ src/fne/HostFNE.cpp | 18 ++++- src/fne/HostFNE.h | 4 +- src/fne/network/MetadataNetwork.cpp | 2 + src/fne/network/MetadataNetwork.h | 11 +++ src/fne/network/PeerNetwork.cpp | 68 ++++++++++++++++++ src/fne/network/PeerNetwork.h | 8 +++ src/fne/network/TrafficNetwork.cpp | 71 ++++++++++++++++++- src/fne/network/TrafficNetwork.h | 16 ++++- src/fne/network/packet/RadioAliasSync.cpp | 84 +++++++++++++++++++++++ 11 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 src/fne/network/packet/RadioAliasSync.cpp diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index fb929ca9..fe317df5 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -411,6 +411,17 @@ system: # Flag indicating whether or not the Radio ID ACL updates are logged. verbose: false + # + # Radio Alias Configuration + # + radio_alias: + # Full path to the RID Alias file. + file: rid_alias.dat + # Amount of time between updates of RID Alias file. (minutes) + time: 2 + # Flag indicating whether or not the RID ACL updates are logged. + verbose: false + # # Peer ACL configuration # diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 3896e75b..2b6ccc89 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -70,6 +70,9 @@ namespace network KEY_LLA_REQ = 0x80U, //!< Encryption Key LLA Request KEY_LLA_RSP = 0x81U, //!< Encryption Key LLA Response + + RADIO_ALIAS_SYNC = 0x85U, //!< Radio Alias List Sync + KEYS_INVENTORY = 0x8EU, //!< Encryption Key Container Inventory KEYS_UPDATE = 0x8FU, //!< Encryption Key Container Update @@ -119,6 +122,7 @@ namespace network REPL_TALKGROUP_LIST = 0x00U, //!< FNE Replication Talkgroup Transfer REPL_RID_LIST = 0x01U, //!< FNE Replication Radio ID Transfer REPL_PEER_LIST = 0x02U, //!< FNE Replication Peer List Transfer + REPL_RID_ALIAS_LIST = 0x03U, //!< FNE Replication Radio Alias Transfer REPL_ACT_PEER_LIST = 0xA2U, //!< FNE Replication Active Peer List Transfer REPL_HA_PARAMS = 0xA3U, //!< FNE Replication HA Parameters diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index c84ce180..64d703a2 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -74,6 +74,7 @@ HostFNE::HostFNE(const std::string& confFile) : m_nxdnEnabled(false), m_analogEnabled(false), m_ridLookup(nullptr), + m_ridAliasLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), m_cryptoLookup(nullptr), @@ -190,6 +191,20 @@ int HostFNE::run() m_ridLookup = new RadioIdLookup(ridLookupFile, ridReloadTime, true, verboseRIDRules); m_ridLookup->read(); + // try to load radio aliases table + std::string ridAliasLookupFile = systemConf["radio_alias"]["file"].as(); + uint32_t ridAliasReloadTime = systemConf["radio_alias"]["time"].as(0U); + bool verboseRIDAliasRules = systemConf["radio_alias"]["verbose"].as(false); + + LogInfo("Radio Alias Lookups"); + LogInfo(" File: %s", ridAliasLookupFile.length() > 0U ? ridAliasLookupFile.c_str() : "None"); + if (ridReloadTime > 0U) + LogInfo(" Reload: %u mins", ridAliasReloadTime); + LogInfo(" Verbose: %s", verboseRIDAliasRules ? "true" : "false"); + + m_ridAliasLookup = new RadioAliasLookup(ridAliasLookupFile, ridAliasReloadTime, verboseRIDAliasRules); + m_ridAliasLookup->read(); + // initialize master networking ret = createMasterNetwork(); if (!ret) @@ -669,7 +684,7 @@ bool HostFNE::createMasterNetwork() m_network->setOptions(masterConf, true); m_network->setPacketDump(packetDump); - m_network->setLookups(m_ridLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup, m_adjSiteMapLookup); + m_network->setLookups(m_ridLookup, m_ridAliasLookup, m_tidLookup, m_peerListLookup, m_cryptoLookup, m_adjSiteMapLookup); bool ret = m_network->open(); if (!ret) { @@ -891,6 +906,7 @@ bool HostFNE::createPeerNetworks() network->setMetadata(identity, 0U, 0U, 0.0F, 0.0F, 0, 0, 0, latitude, longitude, 0, location); network->setLookups(m_ridLookup, m_tidLookup); network->setMasterPeerId(masterPeerId); + network->setRadioAliasLookups(m_ridAliasLookup); network->setPeerLookups(m_peerListLookup); network->setPeerReplicationSaveACL(m_peerReplicaSavesACL); network->setNakFallOver(nakFallOver, nakFallOverCount); diff --git a/src/fne/HostFNE.h b/src/fne/HostFNE.h index 77000a77..e7e99ba9 100644 --- a/src/fne/HostFNE.h +++ b/src/fne/HostFNE.h @@ -4,7 +4,7 @@ * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2023-2025 Bryan Biedenkapp, N2PLL + * Copyright (C) 2023-2026 Bryan Biedenkapp, N2PLL * */ /** @@ -18,6 +18,7 @@ #include "fne/Defines.h" #include "common/lookups/RadioIdLookup.h" +#include "common/lookups/RadioAliasLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" #include "common/lookups/AdjSiteMapLookup.h" @@ -116,6 +117,7 @@ private: bool m_analogEnabled; lookups::RadioIdLookup* m_ridLookup; + lookups::RadioAliasLookup* m_ridAliasLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; lookups::AdjSiteMapLookup* m_adjSiteMapLookup; diff --git a/src/fne/network/MetadataNetwork.cpp b/src/fne/network/MetadataNetwork.cpp index 07a5c830..2ebac5c7 100644 --- a/src/fne/network/MetadataNetwork.cpp +++ b/src/fne/network/MetadataNetwork.cpp @@ -253,6 +253,8 @@ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) { NET_FUNC::KEYS_INVENTORY, &MetadataNetwork::PacketHandler::keysInventory }, { NET_FUNC::KEYS_UPDATE, &MetadataNetwork::PacketHandler::keysUpdate }, + { NET_FUNC::RADIO_ALIAS_SYNC, &MetadataNetwork::PacketHandler::radioAliasSync }, + { NET_FUNC::REPL, &MetadataNetwork::PacketHandler::replication }, { NET_FUNC::NET_TREE, &MetadataNetwork::PacketHandler::networkTree } }; diff --git a/src/fne/network/MetadataNetwork.h b/src/fne/network/MetadataNetwork.h index 04918ad0..5eed5ba1 100644 --- a/src/fne/network/MetadataNetwork.h +++ b/src/fne/network/MetadataNetwork.h @@ -207,6 +207,17 @@ namespace network */ static void announce(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + /** + * @brief Handles NET_FUNC::RADIO_ALIAS_SYNC packets. + * @param network Instance of the TrafficNetwork class. + * @param mdNetwork Instance of the MetadataNetwork class. + * @param req Instance of the NetPacketRequest structure. + * @param peerId Peer ID of the packet. + * @param ssrc SSRC of the packet. + * @param streamId Stream ID of the packet. + */ + static void radioAliasSync(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId); + /** * @brief Handles NET_FUNC::KEYS_INVENTORY packets. * @param network Instance of the TrafficNetwork class. diff --git a/src/fne/network/PeerNetwork.cpp b/src/fne/network/PeerNetwork.cpp index f8887e5c..6a1159e7 100644 --- a/src/fne/network/PeerNetwork.cpp +++ b/src/fne/network/PeerNetwork.cpp @@ -50,12 +50,14 @@ PeerNetwork::PeerNetwork(const std::string& address, uint16_t port, uint16_t loc m_netTreeDiscCallback(nullptr), m_peerReplicaCallback(nullptr), m_masterPeerId(0U), + m_ridAliasLookup(nullptr), m_pidLookup(nullptr), m_peerReplica(false), m_peerReplicaSavesACL(false), m_tgidPkt(true, "Peer Replication, TGID List"), m_ridPkt(true, "Peer Replication, RID List"), m_pidPkt(true, "Peer Replication, PID List"), + m_ridAliasPkt(true, "Peer Replication, RID Alias List"), m_threadPool(WORKER_CNT, "peer"), m_prevSpanningTreeChildren(0U), m_nakFallOver(false), @@ -91,6 +93,13 @@ PeerNetwork::~PeerNetwork() m_threadPool.wait(); } +/* Sets the instnace of the Radio Alias lookup table. */ + +void PeerNetwork::setRadioAliasLookups(lookups::RadioAliasLookup* ridAliasLookup) +{ + m_ridAliasLookup = ridAliasLookup; +} + /* Sets the instances of the Peer List lookup tables. */ void PeerNetwork::setPeerLookups(lookups::PeerListLookup* pidLookup) @@ -457,6 +466,65 @@ void PeerNetwork::userPacketHandler(uint32_t peerId, FrameQueue::OpcodePair opco } break; + case NET_SUBFUNC::REPL_RID_ALIAS_LIST: // Peer List + { + uint32_t decompressedLen = 0U; + uint8_t* decompressed = nullptr; + + if (m_ridAliasPkt.decode(data, &decompressed, &decompressedLen)) { + if (m_ridAliasLookup == nullptr) { + LogError(LOG_PEER, "Radio Alias lookup not available yet."); + m_ridAliasPkt.clear(); + delete[] decompressed; + break; + } + + // store to file + DECLARE_CHAR_ARRAY(str, decompressedLen + 1U); + ::memcpy(str, decompressed, decompressedLen); + str[decompressedLen] = 0; // null termination + + // randomize filename + std::ostringstream s; + if (!m_peerReplicaSavesACL) { + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); + s << "/tmp/rid_alias.dat." << dist(mt); + } else { + s << m_ridAliasLookup->filename(); + } + + std::string filename = s.str(); + std::ofstream file(filename, std::ofstream::out); + if (file.fail()) { + LogError(LOG_PEER, "Cannot open the radio alias lookup file - %s", filename.c_str()); + m_ridAliasPkt.clear(); + delete[] decompressed; + break; + } + + file << str; + file.close(); + + m_ridAliasLookup->stop(true); + m_ridAliasLookup->setReloadTime(0U); + m_ridAliasLookup->filename(filename); + m_ridAliasLookup->reload(); + + // flag this peer as replica enabled + m_peerReplica = true; + if (m_peerReplicaCallback != nullptr) + m_peerReplicaCallback(this); + + // cleanup temporary file + ::remove(filename.c_str()); + m_ridAliasPkt.clear(); + delete[] decompressed; + } + } + break; + default: break; } diff --git a/src/fne/network/PeerNetwork.h b/src/fne/network/PeerNetwork.h index 56325133..be60a66a 100644 --- a/src/fne/network/PeerNetwork.h +++ b/src/fne/network/PeerNetwork.h @@ -17,6 +17,7 @@ #define __PEER_NETWORK_H__ #include "fne/Defines.h" +#include "common/lookups/RadioAliasLookup.h" #include "common/lookups/PeerListLookup.h" #include "common/network/Network.h" #include "common/network/PacketBuffer.h" @@ -93,6 +94,11 @@ namespace network * @param masterPeerId Master Peer ID. */ void setMasterPeerId(uint32_t masterPeerId) { m_masterPeerId = masterPeerId; } + /** + * @brief Sets the instnace of the Radio Alias lookup table. + * @param ridAliasLookup Radio Alias Lookup Table Instance + */ + void setRadioAliasLookups(lookups::RadioAliasLookup* ridAliasLookup); /** * @brief Sets the instances of the Peer List lookup tables. * @param pidLookup Peer List Lookup Table Instance @@ -339,6 +345,7 @@ namespace network private: uint32_t m_masterPeerId; + lookups::RadioAliasLookup* m_ridAliasLookup; lookups::PeerListLookup* m_pidLookup; bool m_peerReplica; bool m_peerReplicaSavesACL; @@ -346,6 +353,7 @@ namespace network PacketBuffer m_tgidPkt; PacketBuffer m_ridPkt; PacketBuffer m_pidPkt; + PacketBuffer m_ridAliasPkt; ThreadPool m_threadPool; diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index 2fc8263e..335072e4 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -93,6 +93,7 @@ TrafficNetwork::TrafficNetwork(HostFNE* host, const std::string& address, uint16 m_kmfEncKeyRequest(false), m_kmfPresharedKey(nullptr), m_ridLookup(nullptr), + m_ridAliasLookup(nullptr), m_tidLookup(nullptr), m_peerListLookup(nullptr), m_adjSiteMapLookup(nullptr), @@ -525,12 +526,13 @@ void TrafficNetwork::setOptions(yaml::Node& conf, bool printOptions) } } -/* Sets the instances of the Radio ID, Talkgroup ID Peer List, and Crypto lookup tables. */ +/* Sets the instances of the Radio ID, Radio Alias, Talkgroup ID, Peer List, and Crypto lookup tables. */ -void TrafficNetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::PeerListLookup* peerListLookup, - CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup) +void TrafficNetwork::setLookups(lookups::RadioIdLookup* ridLookup, lookups::RadioAliasLookup* ridAliasLookup, lookups::TalkgroupRulesLookup* tidLookup, + lookups::PeerListLookup* peerListLookup, CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup) { m_ridLookup = ridLookup; + m_ridAliasLookup = ridAliasLookup; m_tidLookup = tidLookup; m_peerListLookup = peerListLookup; m_cryptoLookup = cryptoLookup; @@ -1836,6 +1838,7 @@ void TrafficNetwork::taskMetadataUpdate(MetadataUpdateRequest* req) network->writeWhitelistRIDs(req->peerId, streamId, true); network->writeTGIDs(req->peerId, streamId, true); network->writePeerList(req->peerId, streamId); + network->writeRadioAliasList(req->peerId, streamId); network->writeHAParameters(req->peerId, streamId, true); } @@ -2270,6 +2273,68 @@ void TrafficNetwork::writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId) payload, 4U + (tgidList.size() * 5U), streamId, true); } +/* Helper to send the list of radio aliases to the specified peer. */ + +void TrafficNetwork::writeRadioAliasList(uint32_t peerId, uint32_t streamId) +{ + // sending REPL style RID alias list to replica neighbor FNE peers + FNEPeerConnection* connection = m_peers[peerId]; + if (connection != nullptr) { + std::string tempFile; + if (m_isReplica) { + std::ostringstream s; + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0x00U, 0xFFFFFFFFU); + s << "/tmp/rid_alias.dat." << dist(mt); + + tempFile = s.str(); + std::string origFile = m_ridAliasLookup->filename(); + m_ridAliasLookup->filename(tempFile); + m_ridAliasLookup->commit(true); + m_ridAliasLookup->filename(origFile); + } else { + tempFile = m_ridAliasLookup->filename(); + } + + // read entire file into string buffer + std::stringstream b; + std::ifstream stream(tempFile); + if (stream.is_open()) { + while (stream.peek() != EOF) { + b << (char)stream.get(); + } + + stream.close(); + } + + if (m_isReplica) + ::remove(tempFile.c_str()); + + // convert to a byte array + uint32_t len = b.str().size(); + DECLARE_UINT8_ARRAY(buffer, len); + ::memcpy(buffer, b.str().data(), len); + + PacketBuffer pkt(true, "Peer Replication, RID Alias List"); + pkt.encode((uint8_t*)buffer, len); + + LogInfoEx(LOG_REPL, "PEER %u (%s) Peer Replication, RID Alias List, blocks %u, streamId = %u", peerId, connection->identWithQualifier().c_str(), + pkt.fragments.size(), streamId); + if (pkt.fragments.size() > 0U) { + for (auto frag : pkt.fragments) { + writePeer(peerId, m_peerId, { NET_FUNC::REPL, NET_SUBFUNC::REPL_RID_ALIAS_LIST }, + frag.second->data, FRAG_SIZE, 0U, streamId); + Thread::sleep(60U); // pace block transmission + } + } + + pkt.clear(); + } + + return; +} + /* Helper to send the list of peers to the specified peer. */ void TrafficNetwork::writePeerList(uint32_t peerId, uint32_t streamId) diff --git a/src/fne/network/TrafficNetwork.h b/src/fne/network/TrafficNetwork.h index 395de81a..36554c8b 100644 --- a/src/fne/network/TrafficNetwork.h +++ b/src/fne/network/TrafficNetwork.h @@ -29,6 +29,7 @@ #include "common/concurrent/shared_unordered_map.h" #include "common/json/json.h" #include "common/lookups/RadioIdLookup.h" +#include "common/lookups/RadioAliasLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/PeerListLookup.h" #include "common/lookups/AdjSiteMapLookup.h" @@ -236,15 +237,16 @@ namespace network callhandler::TagAnalogData* analogTrafficHandler() const { return m_tagAnalog; } /** - * @brief Sets the instances of the Radio ID, Talkgroup ID Peer List, and Crypto lookup tables. + * @brief Sets the instances of the Radio ID, Radio Alias, Talkgroup ID, Peer List, and Crypto lookup tables. * @param ridLookup Radio ID Lookup Table Instance + * @param ridAliasLookup Radio Alias Lookup Table Instance * @param tidLookup Talkgroup Rules Lookup Table Instance * @param peerListLookup Peer List Lookup Table Instance * @param cryptoLookup Crypto Container Lookup Table Instance * @param adjSiteMapLookup Adjacent Site Map Lookup Table Instance */ - void setLookups(lookups::RadioIdLookup* ridLookup, lookups::TalkgroupRulesLookup* tidLookup, lookups::PeerListLookup* peerListLookup, - CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup); + void setLookups(lookups::RadioIdLookup* ridLookup, lookups::RadioAliasLookup* ridAliasLookup, lookups::TalkgroupRulesLookup* tidLookup, + lookups::PeerListLookup* peerListLookup, CryptoContainer* cryptoLookup, lookups::AdjSiteMapLookup* adjSiteMapLookup); /** * @brief Sets endpoint preshared encryption key. * @param presharedKey Encryption preshared key for networking. @@ -364,6 +366,7 @@ namespace network uint8_t* m_kmfPresharedKey; lookups::RadioIdLookup* m_ridLookup; + lookups::RadioAliasLookup* m_ridAliasLookup; lookups::TalkgroupRulesLookup* m_tidLookup; lookups::PeerListLookup* m_peerListLookup; lookups::AdjSiteMapLookup* m_adjSiteMapLookup; @@ -853,6 +856,13 @@ namespace network * @param streamId Stream ID for this message. */ void writeDeactiveTGIDs(uint32_t peerId, uint32_t streamId); + /** + * @brief Helper to send the list of radio aliases to the specified peer. + * @note This doesn't have a data layout document because it is *only* sent as a packet buffered message. + * @param peerId Peer ID. + * @param streamId Stream ID for this message. + */ + void writeRadioAliasList(uint32_t peerId, uint32_t streamId); /** * @brief Helper to send the list of peers to the specified peer. * @note This doesn't have a data layout document because it is *only* sent as a packet buffered message. diff --git a/src/fne/network/packet/RadioAliasSync.cpp b/src/fne/network/packet/RadioAliasSync.cpp new file mode 100644 index 00000000..6a4bafc0 --- /dev/null +++ b/src/fne/network/packet/RadioAliasSync.cpp @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Converged FNE Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2026 Bryan Biedenkapp, N2PLL + * + */ +#include "fne/Defines.h" +#include "common/Log.h" +#include "network/MetadataNetwork.h" +#include "fne/ActivityLog.h" +#include "HostFNE.h" + +using namespace network; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Handles NET_FUNC::RADIO_ALIAS_SYNC packets. */ + +void MetadataNetwork::PacketHandler::radioAliasSync(TrafficNetwork* network, MetadataNetwork* mdNetwork, NetPacketRequest* req, uint32_t peerId, uint32_t ssrc, uint32_t streamId) +{ + (void)mdNetwork; + + if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { + FNEPeerConnection* connection = network->m_peers[peerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + // read entire file into buffer + std::stringstream b; + std::ifstream stream(network->m_ridAliasLookup->filename(), std::ios::in | std::ios::binary); + + uint32_t len = 0U; + UInt8Array bufferUInt8Array = nullptr; + uint8_t* buffer = nullptr; + + if (stream.is_open()) { + stream.seekg(0, std::ios::end); + len = (uint32_t)stream.tellg(); + stream.seekg(0, std::ios::beg); + + bufferUInt8Array = std::make_unique(len); + buffer = bufferUInt8Array.get(); + ::memset(buffer, 0x00U, len); + + uint32_t i = 0U; + while (stream.peek() != EOF) { + buffer[i] = (uint8_t)stream.get(); + i++; + } + + stream.close(); + } + + PacketBuffer pkt(true, "Radio Alias Sync"); + pkt.encode((uint8_t*)buffer, len); + + LogInfoEx(LOG_REPL, "PEER %u Radio Alias Sync, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); + if (pkt.fragments.size() > 0U) { + for (auto frag : pkt.fragments) { + // violate most handling rules for responding to packets -- we need to directly respond to the calling peer as + // they may not be logged in as a standard peer + network->writePeer(peerId, network->m_peerId, { NET_FUNC::RADIO_ALIAS_SYNC, NET_SUBFUNC::NOP }, + frag.second->data, FRAG_SIZE, 0U, streamId); + Thread::sleep(60U); // pace block transmission + } + } + + pkt.clear(); + } + else { + network->writePeerNAK(peerId, streamId, TAG_PEER_REPLICA, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } +}