From 3da4eb2d409e395dc5554de3b42516aee64675fd Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 26 Mar 2025 17:03:14 -0400 Subject: [PATCH] REST -> RPC Migration (#84) * migrate away from REST API for inter-dvmhost operations towards a custom UDP RPC framework; * replace config parameters for REST API in some places with properly named RPC; swap peer Network, FNE DiagNetwork and FNENetwork over from if-else-if ladders to switch statements (switch statements perform logically better after compilation because the compiler tends to optimize these into jump-tables which execute faster); * continued work on inter-dvmhost REST to RPC transition; * update build bumper for R04G20 to R04G21; * cleanup config file; * clean up doc/commenting; --- configs/config.example.yml | 46 +- src/common/Defines.h | 3 +- src/common/lookups/ChannelLookup.h | 14 +- src/common/network/Network.cpp | 525 +++++++++-------- src/common/network/Network.h | 2 +- src/common/network/RPC.cpp | 303 ++++++++++ src/common/network/RPC.h | 162 ++++++ src/common/network/RPCHeader.cpp | 60 ++ src/common/network/RPCHeader.h | 88 +++ src/common/network/udp/Socket.cpp | 2 - src/common/yaml/Yaml.cpp | 2 + src/fne/network/DiagNetwork.cpp | 254 +++++---- src/fne/network/FNENetwork.cpp | 650 ++++++++++++---------- src/host/CMakeLists.txt | 2 - src/host/Host.Config.cpp | 65 ++- src/host/Host.cpp | 174 +++++- src/host/Host.h | 21 +- src/host/dmr/Control.cpp | 237 ++++++-- src/host/dmr/Control.h | 41 +- src/host/dmr/Slot.cpp | 64 ++- src/host/dmr/packet/ControlSignaling.cpp | 84 +-- src/host/network/RESTAPI.cpp | 346 ------------ src/host/network/RESTAPI.h | 29 - src/host/network/RESTDefines.h | 5 - src/host/network/RPCDefines.h | 48 ++ src/host/nxdn/Control.cpp | 217 +++++--- src/host/nxdn/Control.h | 32 +- src/host/nxdn/packet/ControlSignaling.cpp | 38 +- src/host/p25/Control.cpp | 225 +++++--- src/host/p25/Control.h | 32 +- src/host/p25/packet/ControlSignaling.cpp | 103 ++-- src/remote/RESTClientMain.cpp | 17 - src/sysview/NodeStatusWnd.h | 4 +- 33 files changed, 2465 insertions(+), 1430 deletions(-) create mode 100644 src/common/network/RPC.cpp create mode 100644 src/common/network/RPC.h create mode 100644 src/common/network/RPCHeader.cpp create mode 100644 src/common/network/RPCHeader.h create mode 100644 src/host/network/RPCDefines.h diff --git a/configs/config.example.yml b/configs/config.example.yml index 6a72c049..4f733792 100644 --- a/configs/config.example.yml +++ b/configs/config.example.yml @@ -36,6 +36,9 @@ log: # Network Configuration # network: + # + # FNE Configuration + # # Flag indicating whether or not host networking is enabled. enable: true # Network Peer ID @@ -77,6 +80,21 @@ network: # Flag indicating whether or not verbose debug logging is enabled. debug: false + # + # RPC Configuration + # + # IP address of the network interface to listen for RPC calls on (or 0.0.0.0 for all). + rpcAddress: 127.0.0.1 + # Port number for RPC calls to listen on. + rpcPort: 9890 + # RPC access password. + rpcPassword: "ULTRA-VERY-SECURE-DEFAULT" + # Flag indicating whether or not verbose RPC debug logging is enabled. + rpcDebug: false + + # + # REST API Configuration + # # Flag indicating whether or not REST API is enabled. restEnable: false # IP address of the network interface to listen for REST API on (or 0.0.0.0 for all). @@ -392,14 +410,12 @@ system: # These parameters should be left default for dedicated control channels. # controlCh: - # REST API IP Address for control channel. (If blank, notifications are disabled.) - restAddress: - # REST API Port number for control channel. (If 0, notifications are disabled.) - restPort: 0 - # REST API access password for control channel. - restPassword: "PASSWORD" - # Flag indicating whether or not REST API is operating in SSL mode. - restSsl: false + # RPC IP Address for control channel. (If blank, notifications are disabled.) + rpcAddress: 127.0.0.1 + # RPC Port number for control channel. (If 0, notifications are disabled.) + rpcPort: 0 + # RPC access password for control channel. + rpcPassword: "ULTRA-VERY-SECURE-DEFAULT" # Flag indicating voice channels will notify the control channel of traffic status. notifyEnable: true # Amount of time between network presence announcements. (seconds) @@ -414,14 +430,12 @@ system: - channelId: 2 # Channel Number (used to calculate actual host frequency based on the identity table). channelNo: 1 - # REST API IP Address for voice channel. - restAddress: 127.0.0.1 - # REST API Port number for voice channel. - restPort: 9990 - # REST API access password for voice channel. - restPassword: "PASSWORD" - # Flag indicating whether or not REST API is operating in SSL mode. - restSsl: false + # RPC IP Address for voice channel. + rpcAddress: 127.0.0.1 + # RPC Port number for voice channel. + rpcPort: 9890 + # RPC access password for voice channel. + rpcPassword: "ULTRA-VERY-SECURE-DEFAULT" secure: # AES-128 16-byte Key (used for LLA) diff --git a/src/common/Defines.h b/src/common/Defines.h index 1c70f498..dc7bfdba 100644 --- a/src/common/Defines.h +++ b/src/common/Defines.h @@ -108,7 +108,7 @@ typedef unsigned long long ulong64_t; #define __EXE_NAME__ "" #define VERSION_MAJOR "04" -#define VERSION_MINOR "20" +#define VERSION_MINOR "21" #define VERSION_REV "G" #define __NETVER__ "DVM_R" VERSION_MAJOR VERSION_REV VERSION_MINOR @@ -153,6 +153,7 @@ typedef unsigned long long ulong64_t; const uint32_t REMOTE_MODEM_PORT = 3334; const uint32_t TRAFFIC_DEFAULT_PORT = 62031; const uint32_t REST_API_DEFAULT_PORT = 9990; +const uint32_t RPC_DEFAULT_PORT = 9890; /** * @brief Operational Host States diff --git a/src/common/lookups/ChannelLookup.h b/src/common/lookups/ChannelLookup.h index 7a979352..06fe8a78 100644 --- a/src/common/lookups/ChannelLookup.h +++ b/src/common/lookups/ChannelLookup.h @@ -61,12 +61,12 @@ namespace lookups * @brief Initializes a new instance of the VoiceChData class. * @param chId Voice Channel Identity. * @param chNo Voice Channel Number. - * @param address REST API Address. - * @param port REST API Port. - * @param password REST API Password. + * @param address RPC/REST API Address. + * @param port RPC/REST API Port. + * @param password RPC/REST API Password. * @param ssl Flag indicating REST is using SSL. */ - VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) : + VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl = false) : m_chId(chId), m_chNo(chNo), m_address(address), @@ -116,15 +116,15 @@ namespace lookups */ __READONLY_PROPERTY_PLAIN(uint32_t, chNo); /** - * @brief REST API Address. + * @brief RPC/REST API Address. */ __PROPERTY_PLAIN(std::string, address); /** - * @brief REST API Port. + * @brief RPC/REST API Port. */ __PROPERTY_PLAIN(uint16_t, port); /** - * @brief REST API Password. + * @brief RPC/REST API Password. */ __READONLY_PROPERTY_PLAIN(std::string, password); /** diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index e288e8eb..d2405f0c 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -251,9 +251,9 @@ void Network::clock(uint32_t ms) m_pktLastSeq = 0U; } - // process incoming message frame opcodes + // process incoming message function opcodes switch (fneHeader.getFunction()) { - case NET_FUNC::PROTOCOL: + case NET_FUNC::PROTOCOL: // Protocol { // are protocol messages being user handled? if (m_userHandleProtocol) { @@ -262,336 +262,371 @@ void Network::clock(uint32_t ms) break; } - if (fneHeader.getSubFunction() == 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 + // process incomfing 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; - } - else { - if (m_rxDMRStreamId[slotNo] == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxDMRStreamId[slotNo] = 0U; - } - else { - m_rxDMRStreamId[slotNo] = streamId; - } + 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; } else { - if (m_rxDMRStreamId[slotNo] == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "DMR Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } + 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) { + if (m_pktSeq != 0U && m_pktLastSeq != 0U) { + if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { + LogWarning(LOG_NET, "DMR Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxDMRStreamId[slotNo] = 0U; + m_pktLastSeq = m_pktSeq; + } + + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxDMRStreamId[slotNo] = 0U; + } } } - } - if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length); - if (length > 255) - LogError(LOG_NET, "DMR Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + if (m_debug) + Utils::dump(1U, "[Network::clock()] Network Received, DMR", buffer.get(), length); + if (length > 255) + 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); - } - } - else if (fneHeader.getSubFunction() == 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); + uint8_t len = length; + m_rxDMRData.addData(&len, 1U); + m_rxDMRData.addData(buffer.get(), len); } + } + break; - if (m_promiscuousPeer) { - m_rxP25StreamId = streamId; - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxP25StreamId == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25StreamId = 0U; - } - else { - m_rxP25StreamId = streamId; - } + 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; } else { - if (m_rxP25StreamId == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "P25 Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } + 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) { + if (m_pktSeq != 0U && m_pktLastSeq != 0U) { + if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { + LogWarning(LOG_NET, "P25 Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } + + m_pktLastSeq = m_pktSeq; + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxP25StreamId = 0U; + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxP25StreamId = 0U; + } } } - } - if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length); - if (length > 255) - LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + if (m_debug) + Utils::dump(1U, "[Network::clock()] Network Received, P25", buffer.get(), length); + if (length > 255) + LogError(LOG_NET, "P25 Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); - uint8_t len = length; - m_rxP25Data.addData(&len, 1U); - m_rxP25Data.addData(buffer.get(), len); - } - } - else if (fneHeader.getSubFunction() == 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); + uint8_t len = length; + m_rxP25Data.addData(&len, 1U); + m_rxP25Data.addData(buffer.get(), len); } + } + break; - if (m_promiscuousPeer) { - m_rxNXDNStreamId = streamId; - m_pktLastSeq = m_pktSeq; - } - else { - if (m_rxNXDNStreamId == 0U) { - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxNXDNStreamId = 0U; - } - else { - m_rxNXDNStreamId = streamId; - } + 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; } else { - if (m_rxNXDNStreamId == streamId) { - if (m_pktSeq != 0U && m_pktLastSeq != 0U) { - if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { - LogWarning(LOG_NET, "NXDN Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); - } + 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) { + if (m_pktSeq != 0U && m_pktLastSeq != 0U) { + if (m_pktSeq >= 1U && ((m_pktSeq != m_pktLastSeq + 1) && (m_pktSeq - 1 != m_pktLastSeq + 1))) { + LogWarning(LOG_NET, "NXDN Stream %u out-of-sequence; %u != %u", streamId, m_pktSeq, m_pktLastSeq + 1); + } + } - if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { - m_rxNXDNStreamId = 0U; + m_pktLastSeq = m_pktSeq; + } + + if (rtpHeader.getSequence() == RTP_END_OF_CALL_SEQ) { + m_rxNXDNStreamId = 0U; + } } } - } - if (m_debug) - Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length); - if (length > 255) - LogError(LOG_NET, "NXDN Stream %u, frame oversized? this shouldn't happen, pktSeq = %u, len = %u", streamId, m_pktSeq, length); + if (m_debug) + Utils::dump(1U, "[Network::clock()] Network Received, NXDN", buffer.get(), length); + if (length > 255) + 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); + uint8_t len = length; + m_rxNXDNData.addData(&len, 1U); + m_rxNXDNData.addData(buffer.get(), len); + } } - } - else { + break; + + default: Utils::dump("unknown protocol opcode from the master", buffer.get(), length); + break; } } break; - case NET_FUNC::MASTER: + case NET_FUNC::MASTER: // Master { - if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_WL_RID) { // Radio ID Whitelist - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); - m_ridLookup->toggleEntry(id, true); - offs += 4U; - } + // process incomfing message subfunction opcodes + switch (fneHeader.getSubFunction()) { + case NET_SUBFUNC::MASTER_SUBFUNC_WL_RID: // Radio ID Whitelist + { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); + m_ridLookup->toggleEntry(id, true); + offs += 4U; + } - LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len); + LogMessage(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(); + // save to file if enabled and we got RIDs + if (m_saveLookup && len > 0) { + m_ridLookup->commit(); + } } } } - } - else if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_BL_RID) { // Radio ID Blacklist - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); - m_ridLookup->toggleEntry(id, false); - offs += 4U; - } + break; + case NET_SUBFUNC::MASTER_SUBFUNC_BL_RID: // Radio ID Blacklist + { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); + m_ridLookup->toggleEntry(id, false); + offs += 4U; + } - LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len); + LogMessage(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(); + // save to file if enabled and we got RIDs + if (m_saveLookup && len > 0) { + m_ridLookup->commit(); + } } } } - } - else if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_ACTIVE_TGS) { // Talkgroup Active IDs - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); - uint8_t slot = (buffer[offs + 3U]) & 0x03U; - bool affiliated = (buffer[offs + 3U] & 0x40U) == 0x40U; - bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U; - - 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); + break; + + case NET_SUBFUNC::MASTER_SUBFUNC_ACTIVE_TGS: // Talkgroup Active IDs + { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); + uint8_t slot = (buffer[offs + 3U]) & 0x03U; + bool affiliated = (buffer[offs + 3U] & 0x40U) == 0x40U; + bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U; + + 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); + if (tid.isInvalid()) { + if (!tid.config().active()) { + m_tidLookup->eraseEntry(id, slot); + } + + LogMessage(LOG_NET, "Activated%s%s TG %u TS %u in TGID table", + (nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot); + m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred); } - - LogMessage(LOG_NET, "Activated%s%s TG %u TS %u in TGID table", - (nonPreferred) ? " non-preferred" : "", (affiliated) ? " affiliated" : "", id, slot); - m_tidLookup->addEntry(id, slot, true, affiliated, nonPreferred); - } - offs += 5U; - } + offs += 5U; + } - LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); + LogMessage(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(); + // save if saving from network is enabled + if (m_saveLookup && len > 0) { + m_tidLookup->commit(); + } } } } - } - else if (fneHeader.getSubFunction() == NET_SUBFUNC::MASTER_SUBFUNC_DEACTIVE_TGS) { // Talkgroup Deactivated IDs - if (m_enabled && m_updateLookup) { - if (m_debug) - Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); - uint8_t slot = (buffer[offs + 3U]); - - lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); - if (!tid.isInvalid()) { - LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); - m_tidLookup->eraseEntry(id, slot); - } + break; + case NET_SUBFUNC::MASTER_SUBFUNC_DEACTIVE_TGS: // Talkgroup Deactivated IDs + { + if (m_enabled && m_updateLookup) { + if (m_debug) + Utils::dump(1U, "Network Received, 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_UINT16(buffer, offs); + uint8_t slot = (buffer[offs + 3U]); + + lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot); + if (!tid.isInvalid()) { + LogMessage(LOG_NET, "Deactivated TG %u TS %u in TGID table", id, slot); + m_tidLookup->eraseEntry(id, slot); + } - offs += 5U; - } + offs += 5U; + } - LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into talkgroup rules table", len, m_tidLookup->groupVoice().size()); + LogMessage(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(); + // save if saving from network is enabled + if (m_saveLookup && len > 0) { + m_tidLookup->commit(); + } } } } - } - else { + break; + + default: Utils::dump("unknown master control opcode from the master", buffer.get(), length); + break; } } break; - case NET_FUNC::INCALL_CTRL: + case NET_FUNC::INCALL_CTRL: // In-Call Control { - if (fneHeader.getSubFunction() == 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_UINT16(buffer, 11U); - uint8_t slot = buffer[14U]; - - if (m_dmrInCallCallback != nullptr) { - m_dmrInCallCallback(command, dstId, slot); + // process incomfing 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_UINT16(buffer, 11U); + uint8_t slot = buffer[14U]; + + if (m_dmrInCallCallback != nullptr) { + m_dmrInCallCallback(command, dstId, slot); + } } } - } - else if (fneHeader.getSubFunction() == 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_UINT16(buffer, 11U); - - if (m_p25InCallCallback != nullptr) { - m_p25InCallCallback(command, dstId); - } + 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_UINT16(buffer, 11U); + + if (m_p25InCallCallback != nullptr) { + m_p25InCallCallback(command, dstId); + } + } } - } - else if (fneHeader.getSubFunction() == 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_UINT16(buffer, 11U); - - if (m_nxdnInCallCallback != nullptr) { - m_nxdnInCallCallback(command, dstId); + 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_UINT16(buffer, 11U); + + if (m_nxdnInCallCallback != nullptr) { + m_nxdnInCallCallback(command, dstId); + } } } - } - else { - Utils::dump("unknown protocol opcode from the master", buffer.get(), length); + break; + + default: + Utils::dump("unknown incall control opcode from the master", buffer.get(), length); + break; } } break; - case NET_FUNC::KEY_RSP: // Enc. Key Response + case NET_FUNC::KEY_RSP: // Enc. Key Response { if (m_enabled) { using namespace p25::kmm; @@ -629,7 +664,7 @@ void Network::clock(uint32_t ms) } break; - case NET_FUNC::NAK: // Master Negative Ack + 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 @@ -687,7 +722,7 @@ void Network::clock(uint32_t ms) } } break; - case NET_FUNC::ACK: // Repeater Ack + case NET_FUNC::ACK: // Repeater Ack { switch (m_status) { case NET_STAT_WAITING_LOGIN: @@ -732,7 +767,7 @@ void Network::clock(uint32_t ms) } } break; - case NET_FUNC::MST_CLOSING: // Master Shutdown + case NET_FUNC::MST_CLOSING: // Master Shutdown { LogError(LOG_NET, "PEER %u master is closing down, remotePeerId = %u", m_peerId, m_remotePeerId); m_status = NET_STAT_WAITING_CONNECT; @@ -740,7 +775,7 @@ void Network::clock(uint32_t ms) open(); } break; - case NET_FUNC::PONG: // Master Ping Response + case NET_FUNC::PONG: // Master Ping Response m_timeoutTimer.start(); if (length >= 14) { if (m_debug) diff --git a/src/common/network/Network.h b/src/common/network/Network.h index b7d75809..46641d29 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -74,7 +74,7 @@ namespace network /** * @brief Implements the core peer networking logic. - * @ingroup network + * @ingroup network_core */ class HOST_SW_API Network : public BaseNetwork { public: diff --git a/src/common/network/RPC.cpp b/src/common/network/RPC.cpp new file mode 100644 index 00000000..cb60fff7 --- /dev/null +++ b/src/common/network/RPC.cpp @@ -0,0 +1,303 @@ +// 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) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "common/edac/CRC.h" +#include "common/edac/SHA256.h" +#include "common/network/RPCHeader.h" +#include "common/network/json/json.h" +#include "common/Log.h" +#include "common/Utils.h" +#include "network/RPC.h" + +using namespace network; +using namespace network::frame; + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the RPC class. */ + +RPC::RPC(const std::string& address, uint16_t port, uint16_t localPort, const std::string& password, bool debug) : + m_address(address), + m_port(port), + m_debug(debug), + m_socket(nullptr), + m_frameQueue(nullptr), + m_password(password), + m_handlers() +{ + assert(!address.empty()); + assert(port > 0U); + assert(!password.empty()); + + m_socket = new udp::Socket(address, port); + m_frameQueue = new RawFrameQueue(m_socket, debug); +} + +/* Finalizes a instance of the RPC class. */ + +RPC::~RPC() +{ + if (m_frameQueue != nullptr) { + delete m_frameQueue; + } + + if (m_socket != nullptr) { + delete m_socket; + } +} + +/* Updates the timer by the passed number of milliseconds. */ + +void RPC::clock(uint32_t ms) +{ + sockaddr_storage address; + uint32_t addrLen; + + frame::RPCHeader rpcHeader; + int length = 0U; + + // read message + UInt8Array buffer = m_frameQueue->read(length, address, addrLen); + uint8_t* raw = buffer.get(); + if (length > 0) { + if (length < RPC_HEADER_LENGTH_BYTES) { + LogError(LOG_NET, "RPC::clock(), message received from network is malformed! %u bytes != %u bytes", + RPC_HEADER_LENGTH_BYTES, length); + return; + } + + // decode RTP header + if (!rpcHeader.decode(buffer.get())) { + LogError(LOG_NET, "RPC::clock(), invalid RPC packet received from network"); + return; + } + + if (m_debug) { + LogDebugEx(LOG_NET, "RPC::clock()", "RPC, func = $%04X, messageLength = %u", rpcHeader.getFunction(), rpcHeader.getMessageLength()); + } + + // copy message + uint32_t messageLength = rpcHeader.getMessageLength(); + UInt8Array message = std::unique_ptr(new uint8_t[messageLength]); + ::memcpy(message.get(), raw + RPC_HEADER_LENGTH_BYTES, messageLength); + + uint16_t calc = edac::CRC::createCRC16(message.get(), messageLength * 8U); + if (m_debug) { + LogDebugEx(LOG_NET, "RPC::clock()", "RPC, calc = $%04X, crc = $%04X", calc, rpcHeader.getCRC()); + } + + if (calc != rpcHeader.getCRC()) { + LogError(LOG_NET, "RPC::clock(), failed CRC CCITT-162 check"); + return; + } + + // parse JSON body + std::string content = std::string((char*)message.get()); + + json::value v; + std::string err = json::parse(v, content); + if (!err.empty()) { + LogError(LOG_NET, "RPC::clock(), invalid RPC JSON payload, %s", err.c_str()); + return; + } + + // ensure parsed JSON is an object + if (!v.is()) { + LogError(LOG_NET, "RPC::clock(), invalid RPC JSON payload, request was not a JSON object"); + return; + } + + json::object request = v.get(); + json::object response; + + // find RPC function callback + if (m_handlers.find(rpcHeader.getFunction()) != m_handlers.end()) { + m_handlers[rpcHeader.getFunction()](request, response); + + // remove the reply handler (these should be temporary) + if ((rpcHeader.getFunction() & RPC_REPLY_FUNC) == RPC_REPLY_FUNC) { + m_handlers.erase(rpcHeader.getFunction()); + } else { + reply(rpcHeader.getFunction(), response, address, addrLen); + } + } else { + bool isReply = (rpcHeader.getFunction() & RPC_REPLY_FUNC) == RPC_REPLY_FUNC; + if (isReply) { + if (!request["status"].is()) { + ::LogError(LOG_NET, "RPC %s:%u, invalid RPC response", udp::Socket::address(address).c_str(), udp::Socket::port(address)); + return; + } + + int status = request["status"].get(); + if (status != network::RPC::OK) { + if (request["message"].is()) { + std::string retMsg = request["message"].get(); + ::LogError(LOG_NET, "RPC %s:%u failed, %s", udp::Socket::address(address).c_str(), udp::Socket::port(address), retMsg.c_str()); + } + } + } + else + LogWarning(LOG_NET, "RPC::clock(), ignoring unhandled function, func = $%04X, reply = %u", rpcHeader.getFunction() & 0x3FFFU, isReply); + } + } +} + +/* Writes an RPC request to the network. */ + +bool RPC::req(uint16_t func, const json::object& request, RPCType reply, std::string address, uint16_t port) +{ + sockaddr_storage addr; + uint32_t addrLen = 0U; + if (udp::Socket::lookup(address, port, addr, addrLen) == 0) { + return req(func, request, reply, addr, addrLen); + } + + return false; +} + +/* Writes an RPC request to the network. */ + +bool RPC::req(uint16_t func, const json::object& request, RPCType reply, sockaddr_storage& address, uint32_t addrLen) +{ + json::value v = json::value(request); + std::string json = v.serialize(); + + // generate RPC header + RPCHeader header = RPCHeader(); + header.setFunction(func & 0x3FFFU); + header.setMessageLength(json.length() + 1U); + + // generate message + CharArray __message = std::make_unique(json.length() + 1U); + char* message = __message.get(); + + ::snprintf(message, json.length() + 1U, "%s", json.c_str()); + + uint16_t crc = edac::CRC::createCRC16((uint8_t*)message, (json.length() + 1U) * 8U); + if (m_debug) { + LogDebugEx(LOG_NET, "RPC::req()", "RPC, crc = $%04X", crc); + } + + header.setCRC(crc); + + // generate RPC message + UInt8Array __buffer = std::make_unique(json.length() + 9U); + uint8_t* buffer = __buffer.get(); + + header.encode(buffer); + ::memcpy(buffer + 8U, message, json.length() + 1U); + + // install reply handler + if (reply != nullptr) + m_handlers[func | RPC_REPLY_FUNC] = reply; + + return m_frameQueue->write(buffer, json.length() + 9U, address, addrLen); +} + +/* Helper to generate a default response error payload. */ + +void RPC::defaultResponse(json::object& reply, std::string message, StatusType status) +{ + reply = json::object(); + + int s = (int)status; + reply["status"].set(s); + reply["message"].set(message); +} + +/* Opens connection to the network. */ + +bool RPC::open() +{ + if (m_debug) + LogMessage(LOG_NET, "Opening RPC network"); + + // generate AES256 key + size_t size = m_password.size(); + + uint8_t* in = new uint8_t[size]; + for (size_t i = 0U; i < size; i++) + in[i] = m_password.at(i); + + uint8_t passwordHash[32U]; + ::memset(passwordHash, 0x00U, 32U); + + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size), passwordHash); + + delete[] in; + + m_socket->setPresharedKey(passwordHash); + + return m_socket->open(); +} + +/* Closes connection to the network. */ + +void RPC::close() +{ + if (m_debug) + LogMessage(LOG_NET, "Closing RPC network"); + + m_socket->close(); +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + + +/* Writes an RPC reply to the network. */ + +bool RPC::reply(uint16_t func, json::object& reply, sockaddr_storage& address, uint32_t addrLen) +{ + json::value v = json::value(reply); + std::string json = v.serialize(); + + // generate RPC header + RPCHeader header = RPCHeader(); + header.setFunction(func | RPC_REPLY_FUNC); + header.setMessageLength(json.length() + 1U); + + // generate message + CharArray __message = std::make_unique(json.length() + 1U); + char* message = __message.get(); + + ::snprintf(message, json.length() + 1U, "%s", json.c_str()); + + uint16_t crc = edac::CRC::createCRC16((uint8_t*)message, (json.length() + 1U) * 8U); + header.setCRC(crc); + + // generate RPC message + UInt8Array __buffer = std::make_unique(json.length() + 9U); + uint8_t* buffer = __buffer.get(); + + header.encode(buffer); + ::memcpy(buffer + 8U, message, json.length() + 1U); + + return m_frameQueue->write(buffer, json.length() + 9U, address, addrLen); +} + +/* Default status response handler. */ + +void RPC::defaultHandler(json::object& request, json::object& reply) +{ + reply = json::object(); + + int s = (int)StatusType::UNHANDLED_REQUEST; + reply["status"].set(s); + reply["message"].set("unhandled request"); +} diff --git a/src/common/network/RPC.h b/src/common/network/RPC.h new file mode 100644 index 00000000..31530d49 --- /dev/null +++ b/src/common/network/RPC.h @@ -0,0 +1,162 @@ +// 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) 2025 Bryan Biedenkapp, N2PLL +* +*/ +/** + * @file RPC.h + * @ingroup network_core + * @file RPC.cpp + * @ingroup network_core + */ +#if !defined(__RPC_H__) +#define __RPC_H__ + +#include "common/Defines.h" +#include "common/network/RawFrameQueue.h" +#include "common/network/json/json.h" +#include "common/network/udp/Socket.h" + +#include +#include +#include +#include + +namespace network +{ + // --------------------------------------------------------------------------- + // Macros + // --------------------------------------------------------------------------- + + #define RPC_FUNC_BIND(funcAddr, classInstance) std::bind(&funcAddr, classInstance, std::placeholders::_1, std::placeholders::_2) + + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Implements the Remote Procedure Call networking logic. + * @ingroup network_core + */ + class HOST_SW_API RPC { + public: + typedef std::function RPCType; + + /** + * @brief Status/Response Codes + */ + enum StatusType { + OK = 200, //! OK 200 + + BAD_REQUEST = 400, //! Bad Request 400 + INVALID_ARGS = 401, //! Invalid Arguments 401 + UNHANDLED_REQUEST = 402, //! Unhandled Request 402 + } status; + + /** + * @brief Initializes a new instance of the RPC class. + * @param address Network Hostname/IP address to connect to. + * @param port Network port number. + * @param localPort + * @param peerId Unique ID on the network. + * @param password Network authentication password. + * @param debug Flag indicating whether network debug is enabled. + */ + RPC(const std::string& address, uint16_t port, uint16_t localPort, const std::string& password, bool debug); + /** + * @brief Finalizes a instance of the RPC class. + */ + ~RPC(); + + /** + * @brief Updates the timer by the passed number of milliseconds. + * @param ms Number of milliseconds. + */ + void clock(uint32_t ms); + + /** + * @brief Writes an RPC request to the network. + * @param request JSON content body for request. + * @param reply Reply handler. + * @param address IP address to write data to. + * @param port Port number to write data to. + * @returns bool True, if message was written, otherwise false. + */ + bool req(uint16_t func, const json::object& request, RPCType reply, std::string address, uint16_t port); + /** + * @brief Writes an RPC request to the network. + * @param request JSON content body for request. + * @param reply Reply handler. + * @param address IP address to write data to. + * @param addrLen + * @returns bool True, if message was written, otherwise false. + */ + bool req(uint16_t func, const json::object& request, RPCType reply, sockaddr_storage& address, uint32_t addrLen); + + /** + * @brief Helper to generate a default response error payload. + * @param reply JSON reply. + * @param message Textual error message to send. + * @param status Status to send. + */ + void defaultResponse(json::object &reply, std::string message, StatusType status = StatusType::BAD_REQUEST); + + /** + * @brief Opens connection to the network. + * @returns bool True, if networking has started, otherwise false. + */ + bool open(); + + /** + * @brief Closes connection to the network. + */ + void close(); + + /** + * @brief Helper to register an RPC handler. + * @param func Function opcode. + * @param handler Function handler. + */ + void registerHandler(uint16_t func, RPCType handler) { m_handlers[func] = handler; } + /** + * @brief Helper to unregister an RPC handler. + * @param func Function opcode. + */ + void unregisterHandler(uint16_t func) { m_handlers.erase(func); } + + private: + std::string m_address; + uint16_t m_port; + + bool m_debug; + + udp::Socket* m_socket; + RawFrameQueue* m_frameQueue; + + std::string m_password; + + std::map m_handlers; + + /** + * @brief Writes an RPC reply to the network. + * @param request JSON content body for reply. + * @param address IP address to write data to. + * @param addrLen + * @returns bool True, if message was written, otherwise false. + */ + bool reply(uint16_t func, json::object& request, sockaddr_storage& address, uint32_t addrLen); + + /** + * @brief Default status response handler. + * @param request JSON request. + * @param reply JSON response. + */ + void defaultHandler(json::object& request, json::object& reply); + }; +} // namespace network + +#endif // __RPC_H__ diff --git a/src/common/network/RPCHeader.cpp b/src/common/network/RPCHeader.cpp new file mode 100644 index 00000000..80cb4e72 --- /dev/null +++ b/src/common/network/RPCHeader.cpp @@ -0,0 +1,60 @@ +// 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) 2025 Bryan Biedenkapp, N2PLL + * + */ +#include "Defines.h" +#include "network/RPCHeader.h" +#include "Utils.h" + +using namespace network::frame; + +#include + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/* Initializes a new instance of the RPCHeader class. */ + +RPCHeader::RPCHeader() : + m_crc16(0U), + m_func(0U), + m_messageLength(0U) +{ + /* stub */ +} + +/* Finalizes a instance of the RPCHeader class. */ + +RPCHeader::~RPCHeader() = default; + +/* Decode a RTP header. */ + +bool RPCHeader::decode(const uint8_t* data) +{ + assert(data != nullptr); + + m_crc16 = (data[0U] << 8) | (data[1U] << 0); // CRC-16 + m_func = __GET_UINT16B(data, 2U); // Function + m_messageLength = __GET_UINT32(data, 4U); // Message Length + + return true; +} + +/* Encode a RTP header. */ + +void RPCHeader::encode(uint8_t* data) +{ + assert(data != nullptr); + + data[0U] = (m_crc16 >> 8) & 0xFFU; // CRC-16 MSB + data[1U] = (m_crc16 >> 0) & 0xFFU; // CRC-16 LSB + __SET_UINT16B(m_func, data, 2U); // Function + + __SET_UINT32(m_messageLength, data, 4U); // Message Length +} diff --git a/src/common/network/RPCHeader.h b/src/common/network/RPCHeader.h new file mode 100644 index 00000000..f6d8ec81 --- /dev/null +++ b/src/common/network/RPCHeader.h @@ -0,0 +1,88 @@ +// 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) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file RPCHeader.h + * @ingroup network_core + * @file RPCHeader.cpp + * @ingroup network_core + */ +#if !defined(__RPC_HEADER_H__) +#define __RPC_HEADER_H__ + +#include "common/Defines.h" + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define RPC_HEADER_LENGTH_BYTES 8 +#define RPC_REPLY_FUNC 0x8000U + +namespace network +{ + namespace frame + { + // --------------------------------------------------------------------------- + // Class Declaration + // --------------------------------------------------------------------------- + + /** + * @brief Represents the Remote Procedure Call network frame header. + * \code{.unparsed} + * Byte 0 1 2 3 + * Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Payload CRC-16 | Function | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Message Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 8 bytes + * \endcode + */ + class HOST_SW_API RPCHeader { + public: + /** + * @brief Initializes a new instance of the RPCHeader class. + */ + RPCHeader(); + /** + * @brief Finalizes a instance of the RPCHeader class. + */ + ~RPCHeader(); + + /** + * @brief Decode a RPC header. + * @param[in] data Buffer containing RPC header to decode. + */ + bool decode(const uint8_t* data); + /** + * @brief Encode a RPC header. + * @param[out] data Buffer to encode an RPC header. + */ + void encode(uint8_t* data); + + public: + /** + * @brief Payload packet CRC-16. + */ + __PROPERTY(uint16_t, crc16, CRC); + /** + * @brief Function. + */ + __PROPERTY(uint16_t, func, Function); + /** + * @brief Message Length. + */ + __PROPERTY(uint32_t, messageLength, MessageLength); + }; + } // namespace frame +} // namespace network + +#endif // __RPC_HEADER_H__ diff --git a/src/common/network/udp/Socket.cpp b/src/common/network/udp/Socket.cpp index 58b2a603..ec418998 100644 --- a/src/common/network/udp/Socket.cpp +++ b/src/common/network/udp/Socket.cpp @@ -152,8 +152,6 @@ bool Socket::open(const uint32_t af, const std::string& address, const uint16_t if (!bind(address, port)) { return false; } - - LogInfoEx(LOG_NET, "Opening UDP port on %u", port); } return true; diff --git a/src/common/yaml/Yaml.cpp b/src/common/yaml/Yaml.cpp index b16fb06e..033f3948 100644 --- a/src/common/yaml/Yaml.cpp +++ b/src/common/yaml/Yaml.cpp @@ -10,6 +10,7 @@ */ #include "yaml/Yaml.h" +#include #include #include #include @@ -1989,6 +1990,7 @@ namespace yaml } catch (Exception const& e) { + ::fprintf(stderr, "YAML Error - %s\n", e.message()); delete pImp; return false; } diff --git a/src/fne/network/DiagNetwork.cpp b/src/fne/network/DiagNetwork.cpp index 567a6507..729ca8d1 100644 --- a/src/fne/network/DiagNetwork.cpp +++ b/src/fne/network/DiagNetwork.cpp @@ -178,9 +178,9 @@ void* DiagNetwork::threadedNetworkRx(void* arg) ::pthread_setname_np(req->thread, peerName.str().c_str()); #endif // _GNU_SOURCE - // process incoming message frame opcodes + // process incoming message function opcodes switch (req->fneHeader.getFunction()) { - case NET_FUNC::TRANSFER: + case NET_FUNC::TRANSFER: // Transfer { // resolve peer ID (used for Activity Log and Status Transfer) bool validPeerId = false; @@ -203,8 +203,118 @@ void* DiagNetwork::threadedNetworkRx(void* arg) } } - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY) { // Peer Activity Log Transfer - if (network->m_allowActivityTransfer) { + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: // Peer Activity Log Transfer + { + if (network->m_allowActivityTransfer) { + if (pktPeerId > 0 && validPeerId) { + FNEPeerConnection* connection = network->m_peers[pktPeerId]; + if (connection != nullptr) { + std::string ip = udp::Socket::address(req->address); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + UInt8Array __rawPayload = std::make_unique(req->length - 11U); + uint8_t* rawPayload = __rawPayload.get(); + ::memset(rawPayload, 0x00U, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + ::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identity().c_str(), payload.c_str()); + + // report activity log to InfluxDB + if (network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("activity") + .tag("peerId", std::to_string(pktPeerId)) + .field("identity", connection->identity()) + .field("msg", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(network->m_influxServer); + } + + // repeat traffic to the connected SysView peers + if (network->m_peers.size() > 0U) { + for (auto peer : network->m_peers) { + if (peer.second != nullptr) { + if (peer.second->isSysView()) { + sockaddr_storage addr = peer.second->socketStorage(); + uint32_t addrLen = peer.second->sockStorageLen(); + + network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, + { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen); + } + } else { + continue; + } + } + } + + // attempt to repeat traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId); + } + } + } + } + } + else { + network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + } + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG: // Peer Diagnostic Log Transfer + { + if (network->m_allowDiagnosticTransfer) { + 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) { + UInt8Array __rawPayload = std::make_unique(req->length - 11U); + uint8_t* rawPayload = __rawPayload.get(); + ::memset(rawPayload, 0x00U, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + bool currState = g_disableTimeDisplay; + g_disableTimeDisplay = true; + ::Log(9999U, nullptr, nullptr, 0U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); + g_disableTimeDisplay = currState; + + // report diagnostic log to InfluxDB + if (network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("diag") + .tag("peerId", std::to_string(peerId)) + .field("identity", connection->identity()) + .field("msg", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(network->m_influxServer); + } + } + else { + network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + } + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: // Peer Status Transfer + { if (pktPeerId > 0 && validPeerId) { FNEPeerConnection* connection = network->m_peers[pktPeerId]; if (connection != nullptr) { @@ -212,149 +322,51 @@ void* DiagNetwork::threadedNetworkRx(void* arg) // validate peer (simple validation really) if (connection->connected() && connection->address() == ip) { - UInt8Array __rawPayload = std::make_unique(req->length - 11U); - uint8_t* rawPayload = __rawPayload.get(); - ::memset(rawPayload, 0x00U, req->length - 11U); - ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); - std::string payload(rawPayload, rawPayload + (req->length - 11U)); - - ::ActivityLog("%.9u (%8s) %s", pktPeerId, connection->identity().c_str(), payload.c_str()); - - // report activity log to InfluxDB - if (network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("activity") - .tag("peerId", std::to_string(pktPeerId)) - .field("identity", connection->identity()) - .field("msg", payload) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .request(network->m_influxServer); - } - - // repeat traffic to the connected SysView peers if (network->m_peers.size() > 0U) { + // attempt to repeat status traffic to SysView clients for (auto peer : network->m_peers) { if (peer.second != nullptr) { if (peer.second->isSysView()) { sockaddr_storage addr = peer.second->socketStorage(); uint32_t addrLen = peer.second->sockStorageLen(); + if (network->m_debug) { + LogDebug(LOG_NET, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u", + pktPeerId, peer.first, req->length); + } network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, - { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, RTP_END_OF_CALL_SEQ, addr, addrLen); + { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen); } } else { continue; } } - } - // attempt to repeat traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId); + // attempt to repeat status traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, + req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId); + } } } } } } else { - network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); + network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED); } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG) { // Peer Diagnostic Log Transfer - if (network->m_allowDiagnosticTransfer) { - 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) { - UInt8Array __rawPayload = std::make_unique(req->length - 11U); - uint8_t* rawPayload = __rawPayload.get(); - ::memset(rawPayload, 0x00U, req->length - 11U); - ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); - std::string payload(rawPayload, rawPayload + (req->length - 11U)); - - bool currState = g_disableTimeDisplay; - g_disableTimeDisplay = true; - ::Log(9999U, nullptr, nullptr, 0U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); - g_disableTimeDisplay = currState; - - // report diagnostic log to InfluxDB - if (network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("diag") - .tag("peerId", std::to_string(peerId)) - .field("identity", connection->identity()) - .field("msg", payload) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .request(network->m_influxServer); - } - } - else { - network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS) { // Peer Status Transfer - if (pktPeerId > 0 && validPeerId) { - FNEPeerConnection* connection = network->m_peers[pktPeerId]; - if (connection != nullptr) { - std::string ip = udp::Socket::address(req->address); - - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_peers.size() > 0U) { - // attempt to repeat status traffic to SysView clients - for (auto peer : network->m_peers) { - if (peer.second != nullptr) { - if (peer.second->isSysView()) { - sockaddr_storage addr = peer.second->socketStorage(); - uint32_t addrLen = peer.second->sockStorageLen(); - - if (network->m_debug) { - LogDebug(LOG_NET, "SysView, srcPeer = %u, dstPeer = %u, peer status message, len = %u", - pktPeerId, peer.first, req->length); - } - network->m_frameQueue->write(req->buffer, req->length, network->createStreamId(), pktPeerId, network->m_peerId, - { NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, RTP_END_OF_CALL_SEQ, addr, addrLen); - } - } else { - continue; - } - } + break; - // attempt to repeat status traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::TRANSFER, NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS }, - req->buffer, req->length, RTP_END_OF_CALL_SEQ, 0U, false, true, pktPeerId); - } - } - } - } - } - } - else { - network->writePeerNAK(pktPeerId, network->createStreamId(), TAG_TRANSFER_STATUS, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - else { + default: network->writePeerNAK(peerId, network->createStreamId(), TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET); Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length); + break; } } break; @@ -412,4 +424,4 @@ void* DiagNetwork::threadedNetworkRx(void* arg) } return nullptr; -} \ No newline at end of file +} diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 2c51fc86..0e304f75 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -548,86 +548,98 @@ void* FNENetwork::threadedNetworkRx(void* arg) return nullptr; } - // process incoming message frame opcodes + // process incoming message function opcodes switch (req->fneHeader.getFunction()) { - case NET_FUNC::PROTOCOL: + case NET_FUNC::PROTOCOL: // Protocol { - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR) { // Encapsulated DMR data frame - 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); - connection->lastPing(now); + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + { + 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); + connection->lastPing(now); - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_dmrEnabled) { - if (network->m_tagDMR != nullptr) { - network->m_tagDMR->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_dmrEnabled) { + if (network->m_tagDMR != nullptr) { + network->m_tagDMR->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + } + } else { + network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } - } else { - network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } } } + else { + network->writePeerNAK(peerId, TAG_DMR_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } } - else { - network->writePeerNAK(peerId, TAG_DMR_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_P25) { // Encapsulated P25 data frame - 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); - connection->lastPing(now); + break; - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_p25Enabled) { - if (network->m_tagP25 != nullptr) { - network->m_tagP25->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + { + 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); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_p25Enabled) { + if (network->m_tagP25 != nullptr) { + network->m_tagP25->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + } + } else { + network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } - } else { - network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } } } + else { + network->writePeerNAK(peerId, TAG_P25_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } } - else { - network->writePeerNAK(peerId, TAG_P25_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN) { // Encapsulated NXDN data frame - 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); - connection->lastPing(now); + break; - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { - if (network->m_nxdnEnabled) { - if (network->m_tagNXDN != nullptr) { - network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + { + 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); + connection->lastPing(now); + + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip) { + if (network->m_nxdnEnabled) { + if (network->m_tagNXDN != nullptr) { + network->m_tagNXDN->processFrame(req->buffer, req->length, peerId, req->rtpHeader.getSequence(), streamId); + } + } else { + network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } - } else { - network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } } } + else { + network->writePeerNAK(peerId, TAG_NXDN_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); + } } - else { - network->writePeerNAK(peerId, TAG_NXDN_DATA, NET_CONN_NAK_FNE_UNAUTHORIZED, req->address, req->addrLen); - } - } - else { - Utils::dump("Unknown protocol opcode from peer", req->buffer, req->length); + break; + + default: + Utils::dump("unknown protocol opcode from peer", req->buffer, req->length); + break; } } break; - case NET_FUNC::RPTL: // Repeater Login + case NET_FUNC::RPTL: // Repeater Login { if (peerId > 0 && (network->m_peers.find(peerId) == network->m_peers.end())) { if (network->m_peers.size() >= MAX_HARD_CONN_CAP) { @@ -722,7 +734,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) } } break; - case NET_FUNC::RPTK: // Repeater Authentication + case NET_FUNC::RPTK: // Repeater Authentication { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -830,7 +842,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) } } break; - case NET_FUNC::RPTC: // Repeater Configuration + case NET_FUNC::RPTC: // Repeater Configuration { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -957,7 +969,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) } break; - case NET_FUNC::RPT_CLOSING: // Repeater Closing (Disconnect) + case NET_FUNC::RPT_CLOSING: // Repeater Closing (Disconnect) { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -976,7 +988,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) } } break; - case NET_FUNC::PING: // Repeater Ping + case NET_FUNC::PING: // Repeater Ping { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -1037,7 +1049,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) } break; - case NET_FUNC::GRANT_REQ: // Repeater Grant Request + case NET_FUNC::GRANT_REQ: // Repeater Grant Request { if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { FNEPeerConnection* connection = network->m_peers[peerId]; @@ -1096,14 +1108,14 @@ void* FNENetwork::threadedNetworkRx(void* arg) } break; - case NET_FUNC::INCALL_CTRL: // In-Call Control + case NET_FUNC::INCALL_CTRL: // In-Call Control { // FNEs are god-like entities, and we don't recognize the authority of foreign FNEs telling us what // to do... } break; - case NET_FUNC::KEY_REQ: // Enc. Key Request + case NET_FUNC::KEY_REQ: // Enc. Key Request { using namespace p25::defines; using namespace p25::kmm; @@ -1220,7 +1232,7 @@ void* FNENetwork::threadedNetworkRx(void* arg) } break; - case NET_FUNC::TRANSFER: + case NET_FUNC::TRANSFER: // Transfer { // are activity/diagnostic transfers occurring from the alternate port? if (network->m_host->m_useAlternatePortForDiagnostics) { @@ -1228,323 +1240,351 @@ void* FNENetwork::threadedNetworkRx(void* arg) // since they should be coming from the alternate port anyway } - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY) { // Peer Activity Log Transfer - if (network->m_allowActivityTransfer) { - 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) { - UInt8Array __rawPayload = std::make_unique(req->length - 11U); - uint8_t* rawPayload = __rawPayload.get(); - ::memset(rawPayload, 0x00U, req->length - 11U); - ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); - std::string payload(rawPayload, rawPayload + (req->length - 11U)); - - ::ActivityLog("%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); - - // report activity log to InfluxDB - if (network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("activity") - .tag("peerId", std::to_string(peerId)) - .field("identity", connection->identity()) - .field("msg", payload) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .request(network->m_influxServer); + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::TRANSFER_SUBFUNC_ACTIVITY: // Peer Activity Log Transfer + { + if (network->m_allowActivityTransfer) { + 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) { + UInt8Array __rawPayload = std::make_unique(req->length - 11U); + uint8_t* rawPayload = __rawPayload.get(); + ::memset(rawPayload, 0x00U, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + ::ActivityLog("%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); + + // report activity log to InfluxDB + if (network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("activity") + .tag("peerId", std::to_string(peerId)) + .field("identity", connection->identity()) + .field("msg", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(network->m_influxServer); + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); } - } - else { - network->writePeerNAK(peerId, streamId, TAG_TRANSFER_ACT_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG) { // Peer Diagnostic Log Transfer - if (network->m_allowDiagnosticTransfer) { - 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) { - UInt8Array __rawPayload = std::make_unique(req->length - 11U); - uint8_t* rawPayload = __rawPayload.get(); - ::memset(rawPayload, 0x00U, req->length - 11U); - ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); - std::string payload(rawPayload, rawPayload + (req->length - 11U)); - - bool currState = g_disableTimeDisplay; - g_disableTimeDisplay = true; - ::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); - g_disableTimeDisplay = currState; - - // report diagnostic log to InfluxDB - if (network->m_enableInfluxDB) { - influxdb::QueryBuilder() - .meas("diag") - .tag("peerId", std::to_string(peerId)) - .field("identity", connection->identity()) - .field("msg", payload) - .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) - .request(network->m_influxServer); + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_DIAG: // Peer Diagnostic Log Transfer + { + if (network->m_allowDiagnosticTransfer) { + 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) { + UInt8Array __rawPayload = std::make_unique(req->length - 11U); + uint8_t* rawPayload = __rawPayload.get(); + ::memset(rawPayload, 0x00U, req->length - 11U); + ::memcpy(rawPayload, req->buffer + 11U, req->length - 11U); + std::string payload(rawPayload, rawPayload + (req->length - 11U)); + + bool currState = g_disableTimeDisplay; + g_disableTimeDisplay = true; + ::Log(9999U, nullptr, "%.9u (%8s) %s", peerId, connection->identity().c_str(), payload.c_str()); + g_disableTimeDisplay = currState; + + // report diagnostic log to InfluxDB + if (network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("diag") + .tag("peerId", std::to_string(peerId)) + .field("identity", connection->identity()) + .field("msg", payload) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(network->m_influxServer); + } + } + else { + network->writePeerNAK(peerId, streamId, TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); } - } - else { - network->writePeerNAK(peerId, streamId, TAG_TRANSFER_DIAG_LOG, NET_CONN_NAK_FNE_UNAUTHORIZED); } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS) { // Peer Status Transfer + break; + + case NET_SUBFUNC::TRANSFER_SUBFUNC_STATUS: // Peer Status Transfer // main traffic port status transfers aren't supported for performance reasons - } - else { + break; + + default: network->writePeerNAK(peerId, streamId, TAG_TRANSFER, NET_CONN_NAK_ILLEGAL_PACKET); Utils::dump("unknown transfer opcode from the peer", req->buffer, req->length); + break; } } break; - case NET_FUNC::ANNOUNCE: + case NET_FUNC::ANNOUNCE: // Announce { - if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL) { // Announce Group Affiliation - 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); - lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; - if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } + // process incoming message subfunction opcodes + switch (req->fneHeader.getSubFunction()) { + case NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL: // Announce Group Affiliation + { + 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); + lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; + if (aff == nullptr) { + LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address - uint32_t dstId = __GET_UINT16(req->buffer, 3U); // Destination Address - aff->groupUnaff(srcId); - aff->groupAff(srcId, dstId); - - // attempt to repeat traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address + uint32_t dstId = __GET_UINT16(req->buffer, 3U); // Destination Address + aff->groupUnaff(srcId); + aff->groupAff(srcId, dstId); + + // attempt to repeat traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_AFFIL }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + } } } } } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG) { // Announce Unit Registration - 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); - lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; - if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } + break; - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address - aff->unitReg(srcId); - - // attempt to repeat traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); - } - } - } + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG: // Announce Unit Registration + { + 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); + lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; + if (aff == nullptr) { + LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); - } - } - } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG) { // Announce Unit Deregistration - 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); - lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; - if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address - aff->unitDereg(srcId); - - // attempt to repeat traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address + aff->unitReg(srcId); + + // attempt to repeat traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_REG }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + } } } } } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL) { // Announce Group Affiliation Removal - 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); - lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; - if (aff == nullptr) { - LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); - } + break; + case NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG: // Announce Unit Deregistration + { + 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); + lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; + if (aff == nullptr) { + LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip && aff != nullptr) { - uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address - aff->groupUnaff(srcId); - - // attempt to repeat traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address + aff->unitDereg(srcId); + + // attempt to repeat traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_UNIT_DEREG }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + } } } } } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_AFFILS) { // Announce Update All Affiliations - 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); + break; - // validate peer (simple validation really) - if (connection->connected() && connection->address() == ip) { + case NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL: // Announce Group Affiliation Removal + { + 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); lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; if (aff == nullptr) { LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); } - if (aff != nullptr) { - aff->clearGroupAff(0U, true); - - // update TGID lists - uint32_t len = __GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t srcId = __GET_UINT16(req->buffer, offs); - uint32_t dstId = __GET_UINT16(req->buffer, offs + 4U); - - aff->groupAff(srcId, dstId); - offs += 8U; - } - LogMessage(LOG_NET, "PEER %u (%s) announced %u affiliations", peerId, connection->identity().c_str(), len); + // validate peer (simple validation really) + if (connection->connected() && connection->address() == ip && aff != nullptr) { + uint32_t srcId = __GET_UINT16(req->buffer, 0U); // Source Address + aff->groupUnaff(srcId); // attempt to repeat traffic to Peer-Link masters if (network->m_host->m_peerNetworks.size() > 0) { for (auto peer : network->m_host->m_peerNetworks) { if (peer.second != nullptr) { if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_GRP_UNAFFIL }, req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); } } } } } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } } } } - } - else if (req->fneHeader.getSubFunction() == NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC) { // Announce Site VCs - 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) { - std::vector vcPeers; - - // update peer association - uint32_t len = __GET_UINT32(req->buffer, 0U); - uint32_t offs = 4U; - for (uint32_t i = 0; i < len; i++) { - uint32_t vcPeerId = __GET_UINT32(req->buffer, offs); - if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) { - FNEPeerConnection* vcConnection = network->m_peers[vcPeerId]; - if (vcConnection != nullptr) { - vcConnection->ccPeerId(peerId); - vcPeers.push_back(vcPeerId); + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_AFFILS: // Announce Update All Affiliations + { + 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) { + lookups::AffiliationLookup* aff = network->m_peerAffiliations[peerId]; + if (aff == nullptr) { + LogError(LOG_NET, "PEER %u (%s) has uninitialized affiliations lookup?", peerId, connection->identity().c_str()); + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_INVALID); + } + + if (aff != nullptr) { + aff->clearGroupAff(0U, true); + + // update TGID lists + uint32_t len = __GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t srcId = __GET_UINT16(req->buffer, offs); + uint32_t dstId = __GET_UINT16(req->buffer, offs + 4U); + + aff->groupAff(srcId, dstId); + offs += 8U; + } + LogMessage(LOG_NET, "PEER %u (%s) announced %u affiliations", peerId, connection->identity().c_str(), len); + + // attempt to repeat traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_AFFILS }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + } + } + } } } - offs += 4U; } - LogMessage(LOG_NET, "PEER %u (%s) announced %u VCs", peerId, connection->identity().c_str(), len); - network->m_ccPeerMap[peerId] = vcPeers; - - // attempt to repeat traffic to Peer-Link masters - if (network->m_host->m_peerNetworks.size() > 0) { - for (auto peer : network->m_host->m_peerNetworks) { - if (peer.second != nullptr) { - if (peer.second->isEnabled() && peer.second->isPeerLink()) { - peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, - req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } + } + } + } + break; + + case NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC: // Announce Site VCs + { + 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) { + std::vector vcPeers; + + // update peer association + uint32_t len = __GET_UINT32(req->buffer, 0U); + uint32_t offs = 4U; + for (uint32_t i = 0; i < len; i++) { + uint32_t vcPeerId = __GET_UINT32(req->buffer, offs); + if (vcPeerId > 0 && (network->m_peers.find(vcPeerId) != network->m_peers.end())) { + FNEPeerConnection* vcConnection = network->m_peers[vcPeerId]; + if (vcConnection != nullptr) { + vcConnection->ccPeerId(peerId); + vcPeers.push_back(vcPeerId); + } + } + offs += 4U; + } + LogMessage(LOG_NET, "PEER %u (%s) announced %u VCs", peerId, connection->identity().c_str(), len); + network->m_ccPeerMap[peerId] = vcPeers; + + // attempt to repeat traffic to Peer-Link masters + if (network->m_host->m_peerNetworks.size() > 0) { + for (auto peer : network->m_host->m_peerNetworks) { + if (peer.second != nullptr) { + if (peer.second->isEnabled() && peer.second->isPeerLink()) { + peer.second->writeMaster({ NET_FUNC::ANNOUNCE, NET_SUBFUNC::ANNC_SUBFUNC_SITE_VC }, + req->buffer, req->length, req->rtpHeader.getSequence(), streamId, false, false); + } } } } } - } - else { - network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + else { + network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_FNE_UNAUTHORIZED); + } } } } - } - else { + break; + default: network->writePeerNAK(peerId, streamId, TAG_ANNOUNCE, NET_CONN_NAK_ILLEGAL_PACKET); Utils::dump("unknown announcement opcode from the peer", req->buffer, req->length); } diff --git a/src/host/CMakeLists.txt b/src/host/CMakeLists.txt index b98c582e..a06febba 100644 --- a/src/host/CMakeLists.txt +++ b/src/host/CMakeLists.txt @@ -50,6 +50,4 @@ file(GLOB dvmhost_SRC "src/host/modem/port/specialized/*.cpp" "src/host/network/*.h" "src/host/network/*.cpp" - "src/remote/RESTClient.cpp" - "src/remote/RESTClient.h" ) diff --git a/src/host/Host.Config.cpp b/src/host/Host.Config.cpp index a69964d8..299386a8 100644 --- a/src/host/Host.Config.cpp +++ b/src/host/Host.Config.cpp @@ -187,19 +187,18 @@ bool Host::readParams() { yaml::Node controlCh = rfssConfig["controlCh"]; - std::string restApiAddress = controlCh["restAddress"].as(""); - uint16_t restApiPort = (uint16_t)controlCh["restPort"].as(REST_API_DEFAULT_PORT); - std::string restApiPassword = controlCh["restPassword"].as(); - bool restSsl = controlCh["restSsl"].as(false); + std::string rpcApiAddress = controlCh["rpcAddress"].as(""); + uint16_t rpcApiPort = (uint16_t)controlCh["rpcPort"].as(RPC_DEFAULT_PORT); + std::string rpcApiPassword = controlCh["rpcPassword"].as(); m_presenceTime = controlCh["presence"].as(120U); - VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword, restSsl); + VoiceChData data = VoiceChData(m_channelId, m_channelNo, rpcApiAddress, rpcApiPort, rpcApiPassword); m_controlChData = data; if (!m_controlChData.address().empty() && m_controlChData.port() > 0) { - ::LogInfoEx(LOG_HOST, "Control Channel REST API Address %s:%u SSL %u", m_controlChData.address().c_str(), m_controlChData.port(), restSsl); + ::LogInfoEx(LOG_HOST, "Control Channel RPC Address %s:%u", m_controlChData.address().c_str(), m_controlChData.port()); } else { - ::LogInfoEx(LOG_HOST, "No Control Channel REST API Configured, CC notify disabled"); + ::LogInfoEx(LOG_HOST, "No Control Channel RPC Configured, CC notify disabled"); } } @@ -236,14 +235,13 @@ bool Host::readParams() chNo = 4095U; } - std::string restApiAddress = channel["restAddress"].as("0.0.0.0"); - uint16_t restApiPort = (uint16_t)channel["restPort"].as(REST_API_DEFAULT_PORT); - std::string restApiPassword = channel["restPassword"].as(); - bool restSsl = channel["restSsl"].as(false); + std::string rpcApiAddress = channel["rpcAddress"].as("0.0.0.0"); + uint16_t rpcApiPort = (uint16_t)channel["rpcPort"].as(RPC_DEFAULT_PORT); + std::string rpcApiPassword = channel["rpcPassword"].as(); - ::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", chId, chNo, restApiAddress.c_str(), restApiPort, restSsl); + ::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X RPC Address %s:%u", chId, chNo, rpcApiAddress.c_str(), rpcApiPort); - VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword, restSsl); + VoiceChData data = VoiceChData(chId, chNo, rpcApiAddress, rpcApiPort, rpcApiPassword); m_channelLookup->setRFChData(chNo, data); m_channelLookup->addRFCh(chNo); } @@ -330,7 +328,8 @@ bool Host::readParams() if (!m_authoritative) { m_supervisor = false; - LogWarning(LOG_HOST, "Host is non-authoritative! This requires REST API to handle permit TG for VCs and grant TG for CCs!"); + LogWarning(LOG_HOST, "Host is non-authoritative! This requires a second instance configured in site controller mode."); + LogWarning(LOG_HOST, "RPC is required to handle permit TG for VCs!"); } } else { @@ -728,9 +727,37 @@ bool Host::createModem() bool Host::createNetwork() { yaml::Node networkConf = m_conf["network"]; + std::string rpcAddress = networkConf["rpcAddress"].as("127.0.0.1"); + uint16_t rpcPort = (uint16_t)networkConf["rpcPort"].as(RPC_DEFAULT_PORT); + std::string rpcPassword = networkConf["rpcPassword"].as("ULTRA-VERY-SECURE-DEFAULT"); + bool rpcDebug = networkConf["rpcDebug"].as(false); + + // initialize RPC + m_rpcAddress = rpcAddress; + m_rpcPort = rpcPort; + g_RPC = new RPC(rpcAddress, rpcPort, 0U, rpcPassword, rpcDebug); + bool ret = g_RPC->open(); + if (!ret) { + delete g_RPC; + g_RPC = nullptr; + LogError(LOG_HOST, "failed to initialize RPC networking!"); + return false; + } + bool netEnable = networkConf["enable"].as(false); bool restApiEnable = networkConf["restEnable"].as(false); + LogInfo("Network Parameters"); + LogInfo(" Enabled: %s", netEnable ? "yes" : "no"); + LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no"); + + LogInfo(" RPC Address: %s", rpcAddress.c_str()); + LogInfo(" RPC Port: %u", rpcPort); + + if (rpcDebug) { + LogInfo(" RPC Debug: yes"); + } + // dump out if both networking and REST API are disabled if (!netEnable && !restApiEnable) { return true; @@ -826,12 +853,10 @@ bool Host::createNetwork() IdenTable entry = m_idenTable->find(m_channelId); - LogInfo("Network Parameters"); - LogInfo(" Enabled: %s", netEnable ? "yes" : "no"); if (netEnable) { LogInfo(" Peer ID: %u", id); - LogInfo(" Address: %s", address.c_str()); - LogInfo(" Port: %u", port); + LogInfo(" Master Address: %s", address.c_str()); + LogInfo(" Master Port: %u", port); if (local > 0U) LogInfo(" Local: %u", local); else @@ -851,7 +876,7 @@ bool Host::createNetwork() LogInfo(" Debug: yes"); } } - LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no"); + if (restApiEnable) { LogInfo(" REST API Address: %s", restApiAddress.c_str()); LogInfo(" REST API Port: %u", restApiPort); @@ -900,7 +925,7 @@ bool Host::createNetwork() ::LogSetNetwork(m_network); } - // initialize network remote command + // initialize REST API if (restApiEnable) { m_restAddress = restApiAddress; m_restPort = restApiPort; diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 666a3585..118ce8ab 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -17,7 +17,6 @@ #include "common/Thread.h" #include "common/Utils.h" #include "modem/port/specialized/V24UDPPort.h" -#include "remote/RESTClient.h" #include "host/Host.h" #include "ActivityLog.h" #include "HostMain.h" @@ -35,6 +34,12 @@ using namespace lookups; #include #endif // !defined(_WIN32) +// --------------------------------------------------------------------------- +// Global Variables +// --------------------------------------------------------------------------- + +network::RPC* g_RPC = nullptr; + // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- @@ -160,6 +165,8 @@ Host::Host(const std::string& confFile) : m_restAddress("0.0.0.0"), m_restPort(REST_API_DEFAULT_PORT), m_RESTAPI(nullptr), + m_rpcAddress("0.0.0.0"), + m_rpcPort(RPC_DEFAULT_PORT), m_dmr(nullptr), m_p25(nullptr), m_nxdn(nullptr) @@ -799,6 +806,10 @@ int Host::run() ** Initialize Threads */ + /** RPC */ + if (!Thread::runAsThread(this, threadRPC)) + return EXIT_FAILURE; + /** Watchdog */ if (!Thread::runAsThread(this, threadWatchdog)) return EXIT_FAILURE; @@ -1676,6 +1687,11 @@ void Host::setState(uint8_t state) delete m_modem; } + if (g_RPC != nullptr) { + g_RPC->close(); + delete g_RPC; + } + ::LogSetNetwork(nullptr); if (m_network != nullptr) { m_network->close(); @@ -1707,6 +1723,64 @@ void Host::setState(uint8_t state) } } +/* Entry point to RPC clock thread. */ + +void* Host::threadRPC(void* arg) +{ + thread_t* th = (thread_t*)arg; + if (th != nullptr) { +#if defined(_WIN32) + ::CloseHandle(th->thread); +#else + ::pthread_detach(th->thread); +#endif // defined(_WIN32) + + std::string threadName("host:rpc"); + Host* host = static_cast(th->obj); + if (host == nullptr) { + g_killed = true; + LogError(LOG_HOST, "[FAIL] %s", threadName.c_str()); + } + + if (g_killed) { + delete th; + return nullptr; + } + + LogMessage(LOG_HOST, "[ OK ] %s", threadName.c_str()); +#ifdef _GNU_SOURCE + ::pthread_setname_np(th->thread, threadName.c_str()); +#endif // _GNU_SOURCE + + StopWatch stopWatch; + stopWatch.start(); + + while (!g_killed) { + // scope is intentional + { + // ------------------------------------------------------ + // -- RPC Clocking -- + // ------------------------------------------------------ + + uint32_t ms = stopWatch.elapsed(); + stopWatch.start(); + + g_RPC->clock(ms); + } + + if (host->m_state != STATE_IDLE) + Thread::sleep(m_activeTickDelay); + if (host->m_state == STATE_IDLE) + Thread::sleep(m_idleTickDelay); + } + + LogMessage(LOG_HOST, "[STOP] %s", threadName.c_str()); + delete th; + } + + return nullptr; +} + /* Entry point to modem clock thread. */ void* Host::threadModem(void* arg) @@ -2080,6 +2154,72 @@ void* Host::threadPresence(void* arg) ::pthread_setname_np(th->thread, threadName.c_str()); #endif // _GNU_SOURCE + // register VC -> CC notification RPC handler + g_RPC->registerHandler(RPC_REGISTER_CC_VC, [=](json::object &req, json::object &reply) { + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + if (!host->m_dmrTSCCData && !host->m_p25CCData && !host->m_nxdnCCData) { + g_RPC->defaultResponse(reply, "Host is not a control channel, cannot register voice channel"); + return; + } + + // validate channelNo is a string within the JSON blob + if (!req["channelNo"].is()) { + g_RPC->defaultResponse(reply, "channelNo was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t channelNo = req["channelNo"].get(); + + // validate peerId is a string within the JSON blob + if (!req["peerId"].is()) { + g_RPC->defaultResponse(reply, "peerId was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t peerId = req["peerId"].get(); + + // LogDebug(LOG_REST, "RPC_REGISTER_CC_VC callback, channelNo = %u, peerId = %u", channelNo, peerId); + + // validate restAddress is a string within the JSON blob + if (!req["rpcAddress"].is()) { + g_RPC->defaultResponse(reply, "rpcAddress was not a valid string", network::RPC::INVALID_ARGS); + return; + } + + if (!req["rpcPort"].is()) { + g_RPC->defaultResponse(reply, "rpcPort was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + std::string rpcAddress = req["rpcAddress"].get(); + uint16_t rpcPort = (uint16_t)req["rpcPort"].get(); + + auto voiceChData = host->rfCh()->rfChDataTable(); + if (voiceChData.find(channelNo) != voiceChData.end()) { + ::lookups::VoiceChData voiceCh = host->rfCh()->getRFChData(channelNo); + + if (voiceCh.address() == "0.0.0.0") { + voiceCh.address(rpcAddress); + } + + if (voiceCh.port() == 0U || voiceCh.port() == RPC_DEFAULT_PORT) { + voiceCh.port(rpcPort); + } + + host->rfCh()->setRFChData(channelNo, voiceCh); + + host->m_voiceChPeerId[channelNo] = peerId; + LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo); + LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port()); + + g_fireCCVCNotification = true; // announce this registration immediately to the FNE + } else { + LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo); + g_RPC->defaultResponse(reply, "registration rejected", network::RPC::BAD_REQUEST); + } + }); + Timer presenceNotifyTimer(1000U, host->m_presenceTime); presenceNotifyTimer.start(); bool hasInitialRegistered = false; @@ -2096,15 +2236,15 @@ void* Host::threadPresence(void* arg) presenceNotifyTimer.clock(ms); // VC -> CC presence registration - if (!host->m_controlChData.address().empty() && host->m_controlChData.port() != 0 && host->m_network != nullptr && host->m_RESTAPI != nullptr && + if (!host->m_controlChData.address().empty() && host->m_controlChData.port() != 0 && host->m_network != nullptr && !host->m_dmrCtrlChannel && !host->m_p25CtrlChannel && !host->m_nxdnCtrlChannel) { if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || !hasInitialRegistered) { LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); hasInitialRegistered = true; std::string localAddress = network::udp::Socket::getLocalAddress(); - if (host->m_restAddress == "0.0.0.0") { - host->m_restAddress = localAddress; + if (host->m_rpcAddress == "0.0.0.0") { + host->m_rpcAddress = localAddress; } // callback REST API to release the granted TG on the specified control channel @@ -2112,14 +2252,26 @@ void* Host::threadPresence(void* arg) req["channelNo"].set(host->m_channelNo); uint32_t peerId = host->m_network->getPeerId(); req["peerId"].set(peerId); - req["restAddress"].set(host->m_restAddress); - req["restPort"].set(host->m_restPort); + req["rpcAddress"].set(host->m_rpcAddress); + req["rpcPort"].set(host->m_rpcPort); - int ret = RESTClient::send(host->m_controlChData.address(), host->m_controlChData.port(), host->m_controlChData.password(), - HTTP_PUT, PUT_REGISTER_CC_VC, req, host->m_controlChData.ssl(), REST_QUICK_WAIT, false); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", host->m_controlChData.address().c_str(), host->m_controlChData.port()); - } + g_RPC->req(RPC_REGISTER_CC_VC, req, [=](json::object &req, json::object &reply) { + if (!req["status"].is()) { + ::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration, invalid RPC response", host->m_controlChData.address().c_str(), host->m_controlChData.port()); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", host->m_controlChData.address().c_str(), host->m_controlChData.port()); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_HOST, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_HOST, "CC %s:%u, VC registered, peerId = %u", host->m_controlChData.address().c_str(), host->m_controlChData.port(), host->m_network->getPeerId()); + }, host->m_controlChData.address(), host->m_controlChData.port()); presenceNotifyTimer.start(); } diff --git a/src/host/Host.h b/src/host/Host.h index aaccb0fe..436ea661 100644 --- a/src/host/Host.h +++ b/src/host/Host.h @@ -34,11 +34,13 @@ #include "common/lookups/TalkgroupRulesLookup.h" #include "common/network/json/json.h" #include "common/network/Network.h" +#include "common/network/RPC.h" #include "common/yaml/Yaml.h" #include "dmr/Control.h" #include "p25/Control.h" #include "nxdn/Control.h" #include "network/RESTAPI.h" +#include "network/RPCDefines.h" #include "modem/Modem.h" #include "modem/ModemV24.h" @@ -54,6 +56,13 @@ #include #endif // defined(_WIN32) +// --------------------------------------------------------------------------- +// Externs +// --------------------------------------------------------------------------- + +/** @brief */ +extern network::RPC* g_RPC; + // --------------------------------------------------------------------------- // Class Prototypes // --------------------------------------------------------------------------- @@ -226,7 +235,10 @@ private: friend class RESTAPI; std::string m_restAddress; uint16_t m_restPort; - RESTAPI *m_RESTAPI; + RESTAPI* m_RESTAPI; + + std::string m_rpcAddress; + uint16_t m_rpcPort; std::unique_ptr m_dmr; std::unique_ptr m_p25; @@ -268,6 +280,13 @@ private: */ void setState(uint8_t state); + /** + * @brief Entry point to RPC clocking thread. + * @param arg Instance of the thread_t structure. + * @returns void* (Ignore) + */ + static void* threadRPC(void* arg); + /** * @brief Entry point to modem clocking thread. * @param arg Instance of the thread_t structure. diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index acc9349d..e6c21b89 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -14,6 +14,7 @@ #include "common/dmr/lc/csbk/CSBKFactory.h" #include "common/Log.h" #include "dmr/Control.h" +#include "Host.h" using namespace dmr; using namespace dmr::defines; @@ -67,6 +68,12 @@ Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint m_slot2 = new Slot(2U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, dumpCSBKData, debug, verbose); m_tsccCntInterval.start(); + + // register RPC handlers + g_RPC->registerHandler(RPC_PERMIT_DMR_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); + g_RPC->registerHandler(RPC_RELEASE_DMR_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this)); + g_RPC->registerHandler(RPC_TOUCH_DMR_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this)); + g_RPC->registerHandler(RPC_DMR_TSCC_PAYLOAD_ACT, RPC_FUNC_BIND(Control::RPC_tsccPayloadActivate, this)); } /* Finalizes a instance of the Control class. */ @@ -201,6 +208,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa if (disableGrantSourceIdCheck) { LogInfo(" TSCC Disable Grant Source ID Check: yes"); } + if (m_supervisor) + LogMessage(LOG_DMR, "Host is configured to operate as a DMR TSCC, site controller mode."); } if (disableNetworkGrant) { LogInfo(" Disable Network Grants: yes"); @@ -436,40 +445,6 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, uint8_t slot, bool grp) } } -/* Releases a granted TG. */ - -void Control::releaseGrantTG(uint32_t dstId, uint8_t slot) -{ - switch (slot) { - case 1U: - m_slot1->releaseGrantTG(dstId); - break; - case 2U: - m_slot2->releaseGrantTG(dstId); - break; - default: - LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); - break; - } -} - -/* Touchs a granted TG to keep a channel grant alive. */ - -void Control::touchGrantTG(uint32_t dstId, uint8_t slot) -{ - switch (slot) { - case 1U: - m_slot1->touchGrantTG(dstId); - break; - case 2U: - m_slot2->touchGrantTG(dstId); - break; - default: - LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); - break; - } -} - /* Gets instance of the AffiliationLookup class. */ dmr::lookups::DMRAffiliationLookup* Control::affiliations() @@ -809,3 +784,197 @@ void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, break; } } + +/* (RPC Handler) Permits a TGID on a non-authoritative host. */ + +void Control::RPC_permittedTG(json::object& req, json::object& reply) +{ + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + // validate slot is a integer within the JSON blob + if (!req["slot"].is()) { + g_RPC->defaultResponse(reply, "slot was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint8_t slot = (uint8_t)req["slot"].get(); + + if (slot == 0U || slot > 2U) { + g_RPC->defaultResponse(reply, "illegal DMR slot"); + return; + } + + // LogDebugEx(LOG_DMR, "Control::RPC_permittedTG()", "callback, dstId = %u, slot = %u", dstId, slot); + + permittedTG(dstId, slot); +} + +/* (RPC Handler) Releases a granted TG. */ + +void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) +{ + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID is an illegal TGID"); + return; + } + + // validate slot is a integer within the JSON blob + if (!req["slot"].is()) { + g_RPC->defaultResponse(reply, "slot was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint8_t slot = (uint8_t)req["slot"].get(); + + if (slot == 0U || slot > 2U) { + g_RPC->defaultResponse(reply, "illegal DMR slot"); + return; + } + + // LogDebugEx(LOG_DMR, "Control::RPC_releaseGrantTG()", "callback, dstId = %u, slot = %u", dstId, slot); + + switch (slot) { + case 1U: + m_slot1->releaseGrantTG(dstId); + break; + case 2U: + m_slot2->releaseGrantTG(dstId); + break; + default: + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); + break; + } +} + +/* (RPC Handler) Touchs a granted TG to keep a channel grant alive. */ + +void Control::RPC_touchGrantTG(json::object& req, json::object& reply) +{ + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID is an illegal TGID"); + return; + } + + // validate slot is a integer within the JSON blob + if (!req["slot"].is()) { + g_RPC->defaultResponse(reply, "slot was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint8_t slot = (uint8_t)req["slot"].get(); + + if (slot == 0U || slot > 2U) { + g_RPC->defaultResponse(reply, "illegal DMR slot"); + return; + } + + // LogDebugEx(LOG_DMR, "Control::RPC_touchGrantTG()", "callback, dstId = %u, slot = %u", dstId, slot); + + switch (slot) { + case 1U: + m_slot1->touchGrantTG(dstId); + break; + case 2U: + m_slot2->touchGrantTG(dstId); + break; + default: + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); + break; + } +} + +/* (RPC Handler) Helper to payload activate the slot carrying granted payload traffic. */ + +void Control::RPC_tsccPayloadActivate(json::object& req, json::object& reply) +{ + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["slot"].is()) { + g_RPC->defaultResponse(reply, "slot was not valid", network::RPC::INVALID_ARGS); + return; + } + + // validate clear flag is a boolean within the JSON blob + if (!req["clear"].is()) { + g_RPC->defaultResponse(reply, "clear flag was not valid", network::RPC::INVALID_ARGS); + return; + } + + uint8_t slot = req["slot"].get(); + bool clear = req["clear"].get(); + + if (slot == 0U || slot >= 3U) { + g_RPC->defaultResponse(reply, "invalid DMR slot number (slot == 0 or slot > 3)"); + return; + } + + if (clear) { + tsccClearActivatedSlot(slot); + } + else { + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not valid", network::RPC::INVALID_ARGS); + return; + } + + // validate destination ID is a integer within the JSON blob + if (!req["srcId"].is()) { + g_RPC->defaultResponse(reply, "source ID was not valid", network::RPC::INVALID_ARGS); + return; + } + + // validate group flag is a boolean within the JSON blob + if (!req["group"].is()) { + g_RPC->defaultResponse(reply, "group flag was not valid", network::RPC::INVALID_ARGS); + return; + } + + // validate voice flag is a boolean within the JSON blob + if (!req["voice"].is()) { + g_RPC->defaultResponse(reply, "voice flag was not valid", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + uint32_t srcId = req["srcId"].get(); + bool group = req["group"].get(); + bool voice = req["voice"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID was not valid"); + return; + } + + tsccActivateSlot(slot, dstId, srcId, group, voice); + } +} diff --git a/src/host/dmr/Control.h b/src/host/dmr/Control.h index 5e8f6931..22949b75 100644 --- a/src/host/dmr/Control.h +++ b/src/host/dmr/Control.h @@ -31,6 +31,7 @@ #include "common/lookups/IdenTableLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" +#include "common/network/RPC.h" #include "common/network/RTPFNEHeader.h" #include "common/network/Network.h" #include "common/yaml/Yaml.h" @@ -195,18 +196,6 @@ namespace dmr * @param grp Flag indicating group grant. */ void grantTG(uint32_t srcId, uint32_t dstId, uint8_t slot, bool grp); - /** - * @brief Releases a granted TG. - * @param dstId Destination ID. - * @param slot DMR slot number. - */ - void releaseGrantTG(uint32_t dstId, uint8_t slot); - /** - * @brief Touches a granted TG to keep a channel grant alive. - * @param dstId Destination ID. - * @param slot DMR slot number. - */ - void touchGrantTG(uint32_t dstId, uint8_t slot); /** @} */ /** @@ -350,6 +339,34 @@ namespace dmr * @param slotNo DMR slot. */ void processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo); + + /** @name Supervisory Control */ + /** + * @brief (RPC Handler) Permits a TGID on a non-authoritative host. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_permittedTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Releases a granted TG. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_releaseGrantTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Touches a granted TG to keep a channel grant alive. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_touchGrantTG(json::object& req, json::object& reply); + /** @} */ + + /** + * @brief (RPC Handler) Helper to payload activate the slot carrying granted payload traffic. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_tsccPayloadActivate(json::object& req, json::object& reply); }; } // namespace dmr diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 606eb48e..425613ad 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -19,9 +19,9 @@ #include "common/Utils.h" #include "dmr/Slot.h" #include "common/dmr/acl/AccessControl.h" -#include "remote/RESTClient.h" #include "ActivityLog.h" #include "HostMain.h" +#include "Host.h" using namespace dmr; using namespace dmr::defines; @@ -825,7 +825,10 @@ void Slot::permittedTG(uint32_t dstId) } if (m_verbose) { - LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId); + if (dstId == 0U) + LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG unpermit", m_slotNo); + else + LogMessage(LOG_DMR, "DMR Slot %u, non-authoritative TG permit, dstId = %u", m_slotNo, dstId); } m_permittedDstId = dstId; @@ -1035,8 +1038,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s bool clear = true; req["clear"].set(clear); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); + g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_DMR, "DMR Slot %u, CSBK, RAND (Random Access), failed to clear payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1046,14 +1048,11 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s if (m_authoritative && m_dmr->m_supervisor) { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_DMR; - req["state"].set(state); dstId = 0U; // clear TG value req["dstId"].set(dstId); req["slot"].set(slot); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_dmr->m_debug); + g_RPC->req(RPC_PERMIT_DMR_TG, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_DMR, "DMR Slot %u, CSBK, RAND (Random Access), failed to clear TG permit, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1258,17 +1257,27 @@ void Slot::notifyCC_ReleaseGrant(uint32_t dstId) // callback REST API to release the granted TG on the specified control channel json::object req = json::object(); - int state = modem::DVM_STATE::STATE_DMR; - req["state"].set(state); req["dstId"].set(dstId); uint8_t slot = m_slotNo; req["slot"].set(slot); - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); - } + g_RPC->req(RPC_RELEASE_DMR_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the release of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_DMR, "DMR Slot %u, RPC failed, %s", m_slotNo, retMsg.c_str()); + } + } + else + ::LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, released grant, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + }, m_controlChData.address(), m_controlChData.port()); m_rfLastDstId = 0U; m_rfLastSrcId = 0U; @@ -1294,17 +1303,28 @@ void Slot::notifyCC_TouchGrant(uint32_t dstId) // callback REST API to touch the granted TG on the specified control channel json::object req = json::object(); - int state = modem::DVM_STATE::STATE_DMR; - req["state"].set(state); req["dstId"].set(dstId); uint8_t slot = m_slotNo; req["slot"].set(slot); - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); - } + g_RPC->req(RPC_TOUCH_DMR_TG, req, [=](json::object& req, json::object& reply) { + // validate channelNo is a string within the JSON blob + if (!req["status"].is()) { + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_DMR, "DMR Slot %u, failed to notify the CC %s:%u of the touch of, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_DMR, "DMR Slot %u, RPC failed, %s", m_slotNo, retMsg.c_str()); + } + } + else + ::LogMessage(LOG_DMR, "DMR Slot %u, CC %s:%u, touched grant, dstId = %u", m_slotNo, m_controlChData.address().c_str(), m_controlChData.port(), dstId); + }, m_controlChData.address(), m_controlChData.port()); } /* Write data frame to the network. */ diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index fbb96099..7fc0805e 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -19,8 +19,8 @@ #include "dmr/lc/csbk/CSBK_DVM_GIT_HASH.h" #include "dmr/packet/ControlSignaling.h" #include "dmr/Slot.h" -#include "remote/RESTClient.h" #include "ActivityLog.h" +#include "Host.h" using namespace dmr; using namespace dmr::defines; @@ -950,29 +950,40 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ ::ActivityLog("DMR", true, "Slot %u group grant request from %u to TG %u", tscc->m_slotNo, srcId, dstId); } - // callback REST API to permit the granted TG on the specified voice channel + // callback RPC to permit the granted TG on the specified voice channel if (tscc->m_authoritative && tscc->m_supervisor && tscc->m_channelNo != chNo) { ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_DMR; - req["state"].set(state); req["dstId"].set(dstId); req["slot"].set(slot); - int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); - tscc->m_affiliations->releaseGrant(dstId, false); - if (!net) { - writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U); - m_slot->m_rfState = RS_RF_REJECTED; + bool requestFailed = false; + g_RPC->req(RPC_PERMIT_DMR_TG, req, [=, &requestFailed](json::object& req, json::object& reply) { + if (!req["status"].is()) { + return; } + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, RPC failed, %s", tscc->m_slotNo, retMsg.c_str()); + } + + tscc->m_affiliations->releaseGrant(dstId, false); + if (!net) { + writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U); + m_slot->m_rfState = RS_RF_REJECTED; + } + requestFailed = true; + } + }, voiceChData.address(), voiceChData.port()); + + if (requestFailed) return false; - } } else { ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1012,8 +1023,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ bool voice = true; req["voice"].set(voice); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); + g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1028,29 +1038,40 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ ::ActivityLog("DMR", true, "Slot %u individual grant request from %u to TG %u", tscc->m_slotNo, srcId, dstId); } - // callback REST API to permit the granted TG on the specified voice channel + // callback RPC to permit the granted TG on the specified voice channel if (tscc->m_authoritative && tscc->m_supervisor && tscc->m_channelNo != chNo) { ::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_DMR; - req["state"].set(state); req["dstId"].set(dstId); req["slot"].set(slot); - int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); - tscc->m_affiliations->releaseGrant(dstId, false); - if (!net) { - writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U); - m_slot->m_rfState = RS_RF_REJECTED; + bool requestFailed = false; + g_RPC->req(RPC_PERMIT_DMR_TG, req, [=, &requestFailed](json::object& req, json::object& reply) { + if (!req["status"].is()) { + return; } + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError((net) ? LOG_NET : LOG_RF, "DMR Slot %u, RPC failed, %s", tscc->m_slotNo, retMsg.c_str()); + } + + tscc->m_affiliations->releaseGrant(dstId, false); + if (!net) { + writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_DENY_RSN_TGT_BUSY, (grp) ? 1U : 0U); + m_slot->m_rfState = RS_RF_REJECTED; + } + requestFailed = true; + } + }, voiceChData.address(), voiceChData.port()); + + if (requestFailed) return false; - } } else { ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to permit TG for use, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1088,8 +1109,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ bool voice = true; req["voice"].set(voice); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); + g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1241,8 +1261,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u bool voice = false; req["voice"].set(voice); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); + g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); @@ -1288,8 +1307,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u bool voice = false; req["voice"].set(voice); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_DMR_TSCC_PAYLOAD_ACT, req, voiceChData.ssl(), REST_QUICK_WAIT, tscc->m_debug); + g_RPC->req(RPC_DMR_TSCC_PAYLOAD_ACT, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_RF, "DMR Slot %u, CSBK, RAND (Random Access), failed to activate payload channel, chNo = %u, slot = %u", tscc->m_slotNo, chNo, slot); diff --git a/src/host/network/RESTAPI.cpp b/src/host/network/RESTAPI.cpp index 828477dd..78537186 100644 --- a/src/host/network/RESTAPI.cpp +++ b/src/host/network/RESTAPI.cpp @@ -288,10 +288,6 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(GET_RELEASE_GRNTS).get(REST_API_BIND(RESTAPI::restAPI_GetReleaseGrants, this)); m_dispatcher.match(GET_RELEASE_AFFS).get(REST_API_BIND(RESTAPI::restAPI_GetReleaseAffs, this)); - m_dispatcher.match(PUT_REGISTER_CC_VC).put(REST_API_BIND(RESTAPI::restAPI_PutRegisterCCVC, this)); - m_dispatcher.match(PUT_RELEASE_TG).put(REST_API_BIND(RESTAPI::restAPI_PutReleaseGrant, this)); - m_dispatcher.match(PUT_TOUCH_TG).put(REST_API_BIND(RESTAPI::restAPI_PutTouchGrant, this)); - m_dispatcher.match(GET_RID_WHITELIST, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDWhitelist, this)); m_dispatcher.match(GET_RID_BLACKLIST, true).get(REST_API_BIND(RESTAPI::restAPI_GetRIDBlacklist, this)); @@ -305,7 +301,6 @@ void RESTAPI::initializeEndpoints() m_dispatcher.match(PUT_DMR_RID).put(REST_API_BIND(RESTAPI::restAPI_PutDMRRID, this)); m_dispatcher.match(GET_DMR_CC_DEDICATED).get(REST_API_BIND(RESTAPI::restAPI_GetDMRCCEnable, this)); m_dispatcher.match(GET_DMR_CC_BCAST).get(REST_API_BIND(RESTAPI::restAPI_GetDMRCCBroadcast, this)); - m_dispatcher.match(PUT_DMR_TSCC_PAYLOAD_ACT).put(REST_API_BIND(RESTAPI::restAPI_PutTSCCPayloadActivate, this)); m_dispatcher.match(GET_DMR_AFFILIATIONS).get(REST_API_BIND(RESTAPI::restAPI_GetDMRAffList, this)); /* @@ -970,266 +965,6 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& re } } -/* REST API endpoint; implements register CC/VC request. */ - -void RESTAPI::restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) -{ - if (!validateAuth(request, reply)) { - return; - } - - json::object req = json::object(); - if (!parseRequestBody(request, reply, req)) { - return; - } - - errorPayload(reply, "OK", HTTPPayload::OK); - - if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) { - errorPayload(reply, "Host is not a control channel, cannot register voice channel"); - return; - } - - // validate channelNo is a string within the JSON blob - if (!req["channelNo"].is()) { - errorPayload(reply, "channelNo was not a valid integer"); - return; - } - - uint32_t channelNo = req["channelNo"].get(); - - // validate peerId is a string within the JSON blob - if (!req["peerId"].is()) { - errorPayload(reply, "peerId was not a valid integer"); - return; - } - - uint32_t peerId = req["peerId"].get(); - - // LogDebugEx(LOG_REST, "RESTAPI::restAPI_PutRegisterCCVC()", "callback, channelNo = %u, peerId = %u", channelNo, peerId); - - // validate restAddress is a string within the JSON blob - if (!req["restAddress"].is()) { - errorPayload(reply, "restAddress was not a valid string"); - return; - } - - if (!req["restPort"].is()) { - errorPayload(reply, "restPort was not a valid integer"); - return; - } - - std::string restAddress = req["restAddress"].get(); - uint16_t restPort = (uint16_t)req["restPort"].get(); - - auto voiceChData = m_host->rfCh()->rfChDataTable(); - if (voiceChData.find(channelNo) != voiceChData.end()) { - ::lookups::VoiceChData voiceCh = m_host->rfCh()->getRFChData(channelNo); - - if (voiceCh.address() == "0.0.0.0") { - voiceCh.address(restAddress); - } - - if (voiceCh.port() == 0U || voiceCh.port() == REST_API_DEFAULT_PORT) { - voiceCh.port(restPort); - } - - m_host->rfCh()->setRFChData(channelNo, voiceCh); - - m_host->m_voiceChPeerId[channelNo] = peerId; - LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo); - LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port(), voiceCh.ssl()); - - g_fireCCVCNotification = true; // announce this registration immediately to the FNE - } else { - LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo); - } -} - -/* REST API endpoint; implements release grant request. */ - -void RESTAPI::restAPI_PutReleaseGrant(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) -{ - if (!validateAuth(request, reply)) { - return; - } - - json::object req = json::object(); - if (!parseRequestBody(request, reply, req)) { - return; - } - - errorPayload(reply, "OK", HTTPPayload::OK); - - if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) { - errorPayload(reply, "Host is not a control channel, cannot release TG grant"); - return; - } - - // validate state is a string within the JSON blob - if (!req["state"].is()) { - errorPayload(reply, "state was not a valid integer"); - return; - } - - DVM_STATE state = (DVM_STATE)req["state"].get(); - - // validate destination ID is a integer within the JSON blob - if (!req["dstId"].is()) { - errorPayload(reply, "destination ID was not a valid integer"); - return; - } - - uint32_t dstId = req["dstId"].get(); - - if (dstId == 0U) { - errorPayload(reply, "destination ID is an illegal TGID"); - return; - } - - // LogDebugEx(LOG_REST, "RESTAPI::restAPI_PutReleaseGrant()", "callback, state = %u, dstId = %u", state, dstId); - - switch (state) { - case STATE_DMR: - { - // validate slot is a integer within the JSON blob - if (!req["slot"].is()) { - errorPayload(reply, "slot was not a valid integer"); - return; - } - - uint8_t slot = (uint8_t)req["slot"].get(); - - if (slot == 0U || slot > 2U) { - errorPayload(reply, "illegal DMR slot"); - return; - } - - if (m_dmr != nullptr) { - m_dmr->releaseGrantTG(dstId, slot); - } - else { - errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - } - } - break; - case STATE_P25: - { - if (m_p25 != nullptr) { - m_p25->releaseGrantTG(dstId); - } - else { - errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - } - } - break; - case STATE_NXDN: - { - if (m_nxdn != nullptr) { - m_nxdn->releaseGrantTG(dstId); - } - else { - errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - } - } - break; - default: - errorPayload(reply, "invalid mode"); - } -} - -/* REST API endpoint; implements touch grant request. */ - -void RESTAPI::restAPI_PutTouchGrant(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) -{ - if (!validateAuth(request, reply)) { - return; - } - - json::object req = json::object(); - if (!parseRequestBody(request, reply, req)) { - return; - } - - errorPayload(reply, "OK", HTTPPayload::OK); - - if (!m_host->m_dmrTSCCData && !m_host->m_p25CCData && !m_host->m_nxdnCCData) { - errorPayload(reply, "Host is not a control channel, cannot touch TG grant"); - return; - } - - // validate state is a string within the JSON blob - if (!req["state"].is()) { - errorPayload(reply, "state was not a valid integer"); - return; - } - - DVM_STATE state = (DVM_STATE)req["state"].get(); - - // validate destination ID is a integer within the JSON blob - if (!req["dstId"].is()) { - errorPayload(reply, "destination ID was not a valid integer"); - return; - } - - uint32_t dstId = req["dstId"].get(); - - if (dstId == 0U) { - errorPayload(reply, "destination ID is an illegal TGID"); - return; - } - - // LogDebugEx(LOG_REST, "RESTAPI::restAPI_PutTouchGrant()", "callback, state = %u, dstId = %u", state, dstId); - - switch (state) { - case STATE_DMR: - { - // validate slot is a integer within the JSON blob - if (!req["slot"].is()) { - errorPayload(reply, "slot was not a valid integer"); - return; - } - - uint8_t slot = (uint8_t)req["slot"].get(); - - if (slot == 0U || slot > 2U) { - errorPayload(reply, "illegal DMR slot"); - return; - } - - if (m_dmr != nullptr) { - m_dmr->touchGrantTG(dstId, slot); - } - else { - errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - } - } - break; - case STATE_P25: - { - if (m_p25 != nullptr) { - m_p25->touchGrantTG(dstId); - } - else { - errorPayload(reply, "P25 mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - } - } - break; - case STATE_NXDN: - { - if (m_nxdn != nullptr) { - m_nxdn->touchGrantTG(dstId); - } - else { - errorPayload(reply, "NXDN mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - } - } - break; - default: - errorPayload(reply, "invalid mode"); - } -} - /* REST API endpoint; implements get RID whitelist request. */ void RESTAPI::restAPI_GetRIDWhitelist(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) @@ -1499,87 +1234,6 @@ void RESTAPI::restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& } } -/* REST API endpoint; implements trigger TSCC payload channel activation request. */ - -void RESTAPI::restAPI_PutTSCCPayloadActivate(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) -{ - if (!validateAuth(request, reply)) { - return; - } - - json::object req = json::object(); - if (!parseRequestBody(request, reply, req)) { - return; - } - - if (m_dmr == nullptr) { - errorPayload(reply, "DMR mode is not enabled", HTTPPayload::SERVICE_UNAVAILABLE); - return; - } - - // validate destination ID is a integer within the JSON blob - if (!req["slot"].is()) { - errorPayload(reply, "slot was not valid"); - return; - } - - // validate clear flag is a boolean within the JSON blob - if (!req["clear"].is()) { - errorPayload(reply, "clear flag was not valid"); - return; - } - - uint8_t slot = req["slot"].get(); - bool clear = req["clear"].get(); - - if (slot == 0U || slot >= 3U) { - errorPayload(reply, "invalid DMR slot number (slot == 0 or slot > 3)"); - return; - } - - errorPayload(reply, "OK", HTTPPayload::OK); - if (clear) { - m_dmr->tsccClearActivatedSlot(slot); - } - else { - // validate destination ID is a integer within the JSON blob - if (!req["dstId"].is()) { - errorPayload(reply, "destination ID was not valid"); - return; - } - - // validate destination ID is a integer within the JSON blob - if (!req["srcId"].is()) { - errorPayload(reply, "source ID was not valid"); - return; - } - - // validate group flag is a boolean within the JSON blob - if (!req["group"].is()) { - errorPayload(reply, "group flag was not valid"); - return; - } - - // validate voice flag is a boolean within the JSON blob - if (!req["voice"].is()) { - errorPayload(reply, "voice flag was not valid"); - return; - } - - uint32_t dstId = req["dstId"].get(); - uint32_t srcId = req["srcId"].get(); - bool group = req["group"].get(); - bool voice = req["voice"].get(); - - if (dstId == 0U) { - errorPayload(reply, "destination ID was not valid"); - return; - } - - m_dmr->tsccActivateSlot(slot, dstId, srcId, group, voice); - } -} - /* REST API endpoint; implements get DMR affiliations request. */ void RESTAPI::restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& reply, const RequestMatch& match) diff --git a/src/host/network/RESTAPI.h b/src/host/network/RESTAPI.h index 9eee7067..b1d9dfc4 100644 --- a/src/host/network/RESTAPI.h +++ b/src/host/network/RESTAPI.h @@ -225,28 +225,6 @@ private: */ void restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); - /** - * @brief REST API endpoint; implements register CC/VC request. - * @param request HTTP request. - * @param reply HTTP reply. - * @param match HTTP request matcher. - */ - void restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); - /** - * @brief REST API endpoint; implements release grant request. - * @param request HTTP request. - * @param reply HTTP reply. - * @param match HTTP request matcher. - */ - void restAPI_PutReleaseGrant(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); - /** - * @brief REST API endpoint; implements touch grant request. - * @param request HTTP request. - * @param reply HTTP reply. - * @param match HTTP request matcher. - */ - void restAPI_PutTouchGrant(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); - /** * @brief REST API endpoint; implements get RID whitelist request. * @param request HTTP request. @@ -308,13 +286,6 @@ private: * @param match HTTP request matcher. */ void restAPI_GetDMRCCBroadcast(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); - /** - * @brief REST API endpoint; implements trigger TSCC payload channel activation request. - * @param request HTTP request. - * @param reply HTTP reply. - * @param match HTTP request matcher. - */ - void restAPI_PutTSCCPayloadActivate(const HTTPPayload& request, HTTPPayload& reply, const network::rest::RequestMatch& match); /** * @brief REST API endpoint; implements get DMR affiliations request. * @param request HTTP request. diff --git a/src/host/network/RESTDefines.h b/src/host/network/RESTDefines.h index 046188a2..4659ffdf 100644 --- a/src/host/network/RESTDefines.h +++ b/src/host/network/RESTDefines.h @@ -65,10 +65,6 @@ #define GET_RELEASE_GRNTS "/release-grants" #define GET_RELEASE_AFFS "/release-affs" -#define PUT_REGISTER_CC_VC "/register-cc-vc" -#define PUT_RELEASE_TG "/release-tg-grant" -#define PUT_TOUCH_TG "/touch-tg-grant" - #define GET_RID_WHITELIST_BASE "/rid-whitelist/" #define GET_RID_WHITELIST GET_RID_WHITELIST_BASE"(\\d+)" #define GET_RID_BLACKLIST_BASE "/rid-blacklist/" @@ -82,7 +78,6 @@ #define PUT_DMR_RID "/dmr/rid" #define GET_DMR_CC_DEDICATED "/dmr/cc-enable" #define GET_DMR_CC_BCAST "/dmr/cc-broadcast" -#define PUT_DMR_TSCC_PAYLOAD_ACT "/dmr/payload-activate" #define GET_DMR_AFFILIATIONS "/dmr/report-affiliations" #define GET_P25_CC "/p25/cc" diff --git a/src/host/network/RPCDefines.h b/src/host/network/RPCDefines.h new file mode 100644 index 00000000..f8587997 --- /dev/null +++ b/src/host/network/RPCDefines.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Modem Host Software + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2025 Bryan Biedenkapp, N2PLL + * + */ +/** + * @defgroup host_rpc Host RPC + * @brief Implementation for the host RPC. + * @ingroup host + * + * @file RPCDefines.h + * @ingroup host_prc + */ +#if !defined(__RPC_DEFINES_H__) +#define __RPC_DEFINES_H__ + +#include "Defines.h" + +/** + * @addtogroup host_rpc + * @{ + */ + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +#define RPC_REGISTER_CC_VC 0x1000U + +#define RPC_RELEASE_P25_TG 0x0101U +#define RPC_RELEASE_DMR_TG 0x0102U +#define RPC_RELEASE_NXDN_TG 0x0103U +#define RPC_TOUCH_P25_TG 0x0201U +#define RPC_TOUCH_DMR_TG 0x0202U +#define RPC_TOUCH_NXDN_TG 0x0203U +#define RPC_PERMIT_P25_TG 0x0001U +#define RPC_PERMIT_DMR_TG 0x0002U +#define RPC_PERMIT_NXDN_TG 0x0003U + +#define RPC_DMR_TSCC_PAYLOAD_ACT 0x0010U + +/** @} */ + +#endif // __RPC_DEFINES_H__ diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index 85bfa618..f0d78597 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -20,8 +20,8 @@ #include "common/Log.h" #include "common/Utils.h" #include "nxdn/Control.h" -#include "remote/RESTClient.h" #include "ActivityLog.h" +#include "Host.h" using namespace nxdn; using namespace nxdn::defines; @@ -134,6 +134,11 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q lc::RCCH::setVerbose(dumpRCCHData); lc::RTCH::setVerbose(dumpRCCHData); + + // register RPC handlers + g_RPC->registerHandler(RPC_PERMIT_NXDN_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); + g_RPC->registerHandler(RPC_RELEASE_NXDN_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this)); + g_RPC->registerHandler(RPC_TOUCH_NXDN_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this)); } /* Finalizes a instance of the Control class. */ @@ -265,13 +270,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_siteData.channelNo()) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_NXDN; - req["state"].set(state); dstId = 0U; // clear TG value req["dstId"].set(dstId); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_debug); + g_RPC->req(RPC_PERMIT_NXDN_TG, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_NXDN, "NXDN, " NXDN_RTCH_MSG_TYPE_VCALL_RESP ", failed to clear TG permit, chNo = %u", chNo); @@ -309,6 +311,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw if (m_control->m_disableGrantSrcIdCheck) { LogInfo(" Disable Grant Source ID Check: yes"); } + if (m_supervisor) + LogMessage(LOG_DMR, "Host is configured to operate as a NXDN control channel, site controller mode."); } LogInfo(" Ignore Affiliation Check: %s", m_ignoreAffiliationCheck ? "yes" : "no"); @@ -728,7 +732,10 @@ void Control::permittedTG(uint32_t dstId) } if (m_verbose) { - LogMessage(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId); + if (dstId == 0U) + LogMessage(LOG_NXDN, "non-authoritative TG unpermit"); + else + LogMessage(LOG_NXDN, "non-authoritative TG permit, dstId = %u", dstId); } m_permittedDstId = dstId; @@ -749,52 +756,6 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, bool grp) m_control->writeRF_Message_Grant(srcId, dstId, 4U, grp); } -/* Releases a granted TG. */ - -void Control::releaseGrantTG(uint32_t dstId) -{ - if (!m_enableControl) { - return; - } - - if (m_verbose) { - LogMessage(LOG_NXDN, "VC request, release TG grant, dstId = %u", dstId); - } - - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); - - if (m_verbose) { - LogMessage(LOG_NXDN, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); - } - - m_affiliations.releaseGrant(dstId, false); - } -} - -/* Touches a granted TG to keep a channel grant alive. */ - -void Control::touchGrantTG(uint32_t dstId) -{ - if (!m_enableControl) { - return; - } - - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); - - if (m_verbose) { - LogMessage(LOG_NXDN, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); - } - - m_affiliations.touchGrant(dstId); - } -} - /* Clears the current operating RF state back to idle. */ void Control::clearRFReject() @@ -1103,15 +1064,25 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId) // callback REST API to release the granted TG on the specified control channel json::object req = json::object(); - int state = modem::DVM_STATE::STATE_NXDN; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); - } + g_RPC->req(RPC_RELEASE_NXDN_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_NXDN, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_NXDN, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + }, m_controlChData.address(), m_controlChData.port()); m_rfLastDstId = 0U; m_rfLastSrcId = 0U; @@ -1137,14 +1108,130 @@ void Control::notifyCC_TouchGrant(uint32_t dstId) // callback REST API to touch the granted TG on the specified control channel json::object req = json::object(); - int state = modem::DVM_STATE::STATE_NXDN; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + g_RPC->req(RPC_TOUCH_NXDN_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_NXDN, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_NXDN, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_NXDN, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + }, m_controlChData.address(), m_controlChData.port()); +} + +/* (RPC Handler) Permits a TGID on a non-authoritative host. */ + +void Control::RPC_permittedTG(json::object& req, json::object& reply) +{ + if (!m_enableControl) { + g_RPC->defaultResponse(reply, "not NXDN control channel", network::RPC::BAD_REQUEST); + return; + } + + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + // LogDebugEx(LOG_NXDN, "Control::RPC_permittedTG()", "callback, dstId = %u, dataPermit = %u", dstId, dataPermit); + + permittedTG(dstId); +} + +/* (RPC Handler) Releases a granted TG. */ + +void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) +{ + if (!m_enableControl) { + g_RPC->defaultResponse(reply, "not NXDN control channel", network::RPC::BAD_REQUEST); + return; + } + + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID is an illegal TGID"); + return; + } + + // LogDebugEx(LOG_NXDN, "Control::RPC_releaseGrantTG()", "callback, dstId = %u", dstId); + + if (m_verbose) { + LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); + } + + if (m_affiliations.isGranted(dstId)) { + uint32_t chNo = m_affiliations.getGrantedCh(dstId); + uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + + if (m_verbose) { + LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + } + + m_affiliations.releaseGrant(dstId, false); + } +} + +/* (RPC Handler) Touches a granted TG to keep a channel grant alive. */ + +void Control::RPC_touchGrantTG(json::object& req, json::object& reply) +{ + if (!m_enableControl) { + g_RPC->defaultResponse(reply, "not NXDN control channel"); + return; + } + + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID is an illegal TGID"); + return; + } + + // LogDebugEx(LOG_NXDN, "Control::RPC_touchGrantTG()", "callback, dstId = %u", dstId); + + if (m_affiliations.isGranted(dstId)) { + uint32_t chNo = m_affiliations.getGrantedCh(dstId); + uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + + if (m_verbose) { + LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + } + + m_affiliations.touchGrant(dstId); } } diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index b1d66ec6..e8427da4 100644 --- a/src/host/nxdn/Control.h +++ b/src/host/nxdn/Control.h @@ -31,6 +31,7 @@ #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" #include "common/lookups/AffiliationLookup.h" +#include "common/network/RPC.h" #include "common/network/RTPFNEHeader.h" #include "common/network/Network.h" #include "common/RingBuffer.h" @@ -194,16 +195,6 @@ namespace nxdn * @param grp Flag indicating group grant. */ void grantTG(uint32_t srcId, uint32_t dstId, bool grp); - /** - * @brief Releases a granted TG. - * @param dstId Destination ID. - */ - void releaseGrantTG(uint32_t dstId); - /** - * @brief Touches a granted TG to keep a channel grant alive. - * @param dstId Destination ID. - */ - void touchGrantTG(uint32_t dstId); /** @} */ /** @@ -390,6 +381,27 @@ namespace nxdn */ void notifyCC_TouchGrant(uint32_t dstId); + /** @name Supervisory Control */ + /** + * @brief (RPC Handler) Permits a TGID on a non-authoritative host. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_permittedTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Releases a granted TG. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_releaseGrantTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Touches a granted TG to keep a channel grant alive. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_touchGrantTG(json::object& req, json::object& reply); + /** @} */ + /** * @brief Helper to write control channel frame data. * @returns bool True, if control data is written, otherwise false. diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 28babb81..ca66ac1e 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -17,8 +17,8 @@ #include "common/Log.h" #include "common/Utils.h" #include "nxdn/packet/ControlSignaling.h" -#include "remote/RESTClient.h" #include "ActivityLog.h" +#include "Host.h" using namespace nxdn; using namespace nxdn::defines; @@ -560,28 +560,40 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin } } - // callback REST API to permit the granted TG on the specified voice channel + // callback RPC to permit the granted TG on the specified voice channel if (m_nxdn->m_authoritative && m_nxdn->m_supervisor) { ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.rfCh()->getRFChData(chNo); if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_nxdn->m_siteData.channelNo()) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_NXDN; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT / 2, m_nxdn->m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo); - m_nxdn->m_affiliations.releaseGrant(dstId, false); - if (!net) { - writeRF_Message_Deny(0U, srcId, CauseResponse::VD_QUE_GRP_BUSY, MessageType::RTCH_VCALL); - m_nxdn->m_rfState = RS_RF_REJECTED; + bool requestFailed = false; + std::string rcchStr = rcch->toString().c_str(); + g_RPC->req(RPC_PERMIT_NXDN_TG, req, [=, &requestFailed, &rcchStr](json::object& req, json::object& reply) { + if (!req["status"].is()) { + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcchStr.c_str(), chNo); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, RPC failed, %s", retMsg.c_str()); + } + + m_nxdn->m_affiliations.releaseGrant(dstId, false); + if (!net) { + writeRF_Message_Deny(0U, srcId, CauseResponse::VD_QUE_GRP_BUSY, MessageType::RTCH_VCALL); + m_nxdn->m_rfState = RS_RF_REJECTED; + } + requestFailed = true; } + }, voiceChData.address(), voiceChData.port()); + if (requestFailed) return false; - } } else { ::LogError((net) ? LOG_NET : LOG_RF, "NXDN, %s, failed to permit TG for use, chNo = %u", rcch->toString().c_str(), chNo); diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index ca285ae8..ef1ac8ff 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -18,9 +18,9 @@ #include "common/Utils.h" #include "p25/Control.h" #include "modem/ModemV24.h" -#include "remote/RESTClient.h" #include "ActivityLog.h" #include "HostMain.h" +#include "Host.h" using namespace p25::packet; using namespace p25::defines; @@ -159,6 +159,11 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q m_llaRS = new uint8_t[AUTH_KEY_LENGTH_BYTES]; m_llaCRS = new uint8_t[AUTH_KEY_LENGTH_BYTES]; m_llaKS = new uint8_t[AUTH_KEY_LENGTH_BYTES]; + + // register RPC handlers + g_RPC->registerHandler(RPC_PERMIT_P25_TG, RPC_FUNC_BIND(Control::RPC_permittedTG, this)); + g_RPC->registerHandler(RPC_RELEASE_P25_TG, RPC_FUNC_BIND(Control::RPC_releaseGrantTG, this)); + g_RPC->registerHandler(RPC_TOUCH_P25_TG, RPC_FUNC_BIND(Control::RPC_touchGrantTG, this)); } /* Finalizes a instance of the Control class. */ @@ -416,13 +421,10 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_siteData.channelNo()) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_P25; - req["state"].set(state); dstId = 0U; // clear TG value req["dstId"].set(dstId); - RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_DEFAULT_WAIT, m_debug); + g_RPC->req(RPC_PERMIT_P25_TG, req, nullptr, voiceChData.address(), voiceChData.port()); } else { ::LogError(LOG_P25, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Grant), failed to clear TG permit, chNo = %u", chNo); @@ -461,6 +463,8 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw if (m_control->m_disableGrantSrcIdCheck) { LogInfo(" Disable Grant Source ID Check: yes"); } + if (m_supervisor) + LogMessage(LOG_P25, "Host is configured to operate as a P25 control channel, site controller mode."); } if (m_controlOnly) { @@ -1050,7 +1054,10 @@ void Control::permittedTG(uint32_t dstId, bool dataPermit) } if (m_verbose) { - LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); + if (dstId == 0U) + LogMessage(LOG_P25, "non-authoritative TG unpermit"); + else + LogMessage(LOG_P25, "non-authoritative TG permit, dstId = %u", dstId); } m_permittedDstId = dstId; @@ -1076,52 +1083,6 @@ void Control::grantTG(uint32_t srcId, uint32_t dstId, bool grp) m_control->writeRF_TSDU_Grant(srcId, dstId, 4U, grp); } -/* Releases a granted TG. */ - -void Control::releaseGrantTG(uint32_t dstId) -{ - if (!m_enableControl) { - return; - } - - if (m_verbose) { - LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); - } - - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); - - if (m_verbose) { - LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); - } - - m_affiliations.releaseGrant(dstId, false); - } -} - -/* Touches a granted TG to keep a channel grant alive. */ - -void Control::touchGrantTG(uint32_t dstId) -{ - if (!m_enableControl) { - return; - } - - if (m_affiliations.isGranted(dstId)) { - uint32_t chNo = m_affiliations.getGrantedCh(dstId); - uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); - ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); - - if (m_verbose) { - LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); - } - - m_affiliations.touchGrant(dstId); - } -} - /* Clears the current operating RF state back to idle. */ void Control::clearRFReject() @@ -1624,15 +1585,25 @@ void Control::notifyCC_ReleaseGrant(uint32_t dstId) // callback REST API to release the granted TG on the specified control channel json::object req = json::object(); - int state = modem::DVM_STATE::STATE_P25; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_RELEASE_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); - } + g_RPC->req(RPC_RELEASE_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_P25, "failed to notify the CC %s:%u of the release of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "CC %s:%u, released grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + }, m_controlChData.address(), m_controlChData.port()); m_rfLastDstId = 0U; m_rfLastSrcId = 0U; @@ -1658,15 +1629,25 @@ void Control::notifyCC_TouchGrant(uint32_t dstId) // callback REST API to touch the granted TG on the specified control channel json::object req = json::object(); - int state = modem::DVM_STATE::STATE_P25; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(), - HTTP_PUT, PUT_TOUCH_TG, req, m_controlChData.ssl(), REST_QUICK_WAIT, m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); - } + g_RPC->req(RPC_TOUCH_P25_TG, req, [=](json::object& req, json::object& reply) { + if (!req["status"].is()) { + ::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u, invalid RPC response", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_P25, "failed to notify the CC %s:%u of the touch of, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_P25, "RPC failed, %s", retMsg.c_str()); + } + } + else + ::LogMessage(LOG_P25, "CC %s:%u, touched grant, dstId = %u", m_controlChData.address().c_str(), m_controlChData.port(), dstId); + }, m_controlChData.address(), m_controlChData.port()); } /* Helper to write control channel frame data. */ @@ -1809,6 +1790,118 @@ void Control::writeRF_TDU(bool noNetwork, bool imm) } } +/* (RPC Handler) Permits a TGID on a non-authoritative host. */ + +void Control::RPC_permittedTG(json::object& req, json::object& reply) +{ + if (!m_enableControl) { + g_RPC->defaultResponse(reply, "not P25 control channel", network::RPC::BAD_REQUEST); + return; + } + + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + bool dataPermit = false; + + // validate destination ID is a integer within the JSON blob + if (req["dataPermit"].is()) { + dataPermit = (bool)req["dataPermit"].get(); + } + + // LogDebugEx(LOG_P25, "Control::RPC_permittedTG()", "callback, dstId = %u, dataPermit = %u", dstId, dataPermit); + + permittedTG(dstId, dataPermit); +} + +/* (RPC Handler) Releases a granted TG. */ + +void Control::RPC_releaseGrantTG(json::object& req, json::object& reply) +{ + if (!m_enableControl) { + g_RPC->defaultResponse(reply, "not P25 control channel", network::RPC::BAD_REQUEST); + return; + } + + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID is an illegal TGID"); + return; + } + + // LogDebugEx(LOG_P25, "Control::RPC_releaseGrantTG()", "callback, dstId = %u", dstId); + + if (m_verbose) { + LogMessage(LOG_P25, "VC request, release TG grant, dstId = %u", dstId); + } + + if (m_affiliations.isGranted(dstId)) { + uint32_t chNo = m_affiliations.getGrantedCh(dstId); + uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + + if (m_verbose) { + LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + } + + m_affiliations.releaseGrant(dstId, false); + } +} + +/* (RPC Handler) Touches a granted TG to keep a channel grant alive. */ + +void Control::RPC_touchGrantTG(json::object& req, json::object& reply) +{ + if (!m_enableControl) { + g_RPC->defaultResponse(reply, "not P25 control channel"); + return; + } + + g_RPC->defaultResponse(reply, "OK", network::RPC::OK); + + // validate destination ID is a integer within the JSON blob + if (!req["dstId"].is()) { + g_RPC->defaultResponse(reply, "destination ID was not a valid integer", network::RPC::INVALID_ARGS); + return; + } + + uint32_t dstId = req["dstId"].get(); + + if (dstId == 0U) { + g_RPC->defaultResponse(reply, "destination ID is an illegal TGID"); + return; + } + + // LogDebugEx(LOG_P25, "Control::RPC_touchGrantTG()", "callback, dstId = %u", dstId); + + if (m_affiliations.isGranted(dstId)) { + uint32_t chNo = m_affiliations.getGrantedCh(dstId); + uint32_t srcId = m_affiliations.getGrantedSrcId(dstId); + ::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo); + + if (m_verbose) { + LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo); + } + + m_affiliations.touchGrant(dstId); + } +} + /* Helper to setup and generate LLA AM1 parameters. */ void Control::generateLLA_AM1_Parameters() diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index e5476eec..0cd0c790 100644 --- a/src/host/p25/Control.h +++ b/src/host/p25/Control.h @@ -31,6 +31,7 @@ #include "common/lookups/IdenTableLookup.h" #include "common/lookups/RadioIdLookup.h" #include "common/lookups/TalkgroupRulesLookup.h" +#include "common/network/RPC.h" #include "common/network/RTPFNEHeader.h" #include "common/network/Network.h" #include "common/p25/SiteData.h" @@ -211,16 +212,6 @@ namespace p25 * @param grp Flag indicating group grant. */ void grantTG(uint32_t srcId, uint32_t dstId, bool grp); - /** - * @brief Releases a granted TG. - * @param dstId Destination ID. - */ - void releaseGrantTG(uint32_t dstId); - /** - * @brief Touches a granted TG to keep a channel grant alive. - * @param dstId Destination ID. - */ - void touchGrantTG(uint32_t dstId); /** @} */ /** @@ -455,6 +446,27 @@ namespace p25 */ void writeRF_TDU(bool noNetwork, bool imm = false); + /** @name Supervisory Control */ + /** + * @brief (RPC Handler) Permits a TGID on a non-authoritative host. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_permittedTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Releases a granted TG. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_releaseGrantTG(json::object& req, json::object& reply); + /** + * @brief (RPC Handler) Touches a granted TG to keep a channel grant alive. + * @param req JSON request. + * @param reply JSON response. + */ + void RPC_touchGrantTG(json::object& req, json::object& reply); + /** @} */ + /** * @brief Helper to setup and generate LLA AM1 parameters. */ diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index b9d1c4b4..4269968b 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -22,9 +22,9 @@ #include "p25/packet/Voice.h" #include "p25/packet/ControlSignaling.h" #include "p25/lookups/P25AffiliationLookup.h" -#include "remote/RESTClient.h" #include "ActivityLog.h" #include "HostMain.h" +#include "Host.h" using namespace p25; using namespace p25::defines; @@ -2288,27 +2288,38 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); if (grp) { - // callback REST API to permit the granted TG on the specified voice channel + // callback RPC to permit the granted TG on the specified voice channel if (m_p25->m_authoritative && m_p25->m_supervisor) { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_p25->m_siteData.channelNo()) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_P25; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); - m_p25->m_affiliations.releaseGrant(dstId, false); - if (!net) { - writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true); - m_p25->m_rfState = RS_RF_REJECTED; + bool requestFailed = false; + g_RPC->req(RPC_PERMIT_P25_TG, req, [=, &requestFailed](json::object& req, json::object& reply) { + if (!req["status"].is()) { + return; } + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError((net) ? LOG_NET : LOG_RF, "P25, RPC failed, %s", retMsg.c_str()); + } + + m_p25->m_affiliations.releaseGrant(dstId, false); + if (!net) { + writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true); + m_p25->m_rfState = RS_RF_REJECTED; + } + requestFailed = true; + } + }, voiceChData.address(), voiceChData.port()); + + if (requestFailed) return false; - } } else { ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_GRP_VCH (Group Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); @@ -2342,27 +2353,38 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } else { - // callback REST API to permit the granted TG on the specified voice channel + // callback RPC to permit the granted TG on the specified voice channel if (m_p25->m_authoritative && m_p25->m_supervisor) { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_p25->m_siteData.channelNo()) { json::object req = json::object(); - int state = modem::DVM_STATE::STATE_P25; - req["state"].set(state); req["dstId"].set(dstId); - int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); - m_p25->m_affiliations.releaseGrant(dstId, false); - if (!net) { - writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true); - m_p25->m_rfState = RS_RF_REJECTED; + bool requestFailed = false; + g_RPC->req(RPC_PERMIT_P25_TG, req, [=, &requestFailed](json::object& req, json::object& reply) { + if (!req["status"].is()) { + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError((net) ? LOG_NET : LOG_RF, "P25, RPC failed, %s", retMsg.c_str()); + } + + m_p25->m_affiliations.releaseGrant(dstId, false); + if (!net) { + writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_PTT_BONK, (grp) ? TSBKO::IOSP_GRP_VCH : TSBKO::IOSP_UU_VCH, grp, true); + m_p25->m_rfState = RS_RF_REJECTED; + } + requestFailed = true; } + }, voiceChData.address(), voiceChData.port()); + if (requestFailed) return false; - } } else { ::LogError((net) ? LOG_NET : LOG_RF, P25_TSDU_STR ", TSBKO, IOSP_UU_VCH (Unit-to-Unit Voice Channel Request), failed to permit TG for use, chNo = %u", chNo); @@ -2540,7 +2562,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 if (chNo > 0U) { ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo); - // callback REST API to permit the granted TG on the specified voice channel + // callback RPC to permit the granted TG on the specified voice channel if (m_p25->m_authoritative && m_p25->m_supervisor) { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && chNo != m_p25->m_siteData.channelNo()) { @@ -2551,16 +2573,29 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 bool dataCh = true; req["dataPermit"].set(dataCh); - int ret = RESTClient::send(voiceChData.address(), voiceChData.port(), voiceChData.password(), - HTTP_PUT, PUT_PERMIT_TG, req, voiceChData.ssl(), REST_QUICK_WAIT, m_p25->m_debug); - if (ret != network::rest::http::HTTPPayload::StatusType::OK) { - ::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo); - m_p25->m_affiliations.releaseGrant(srcId, false); - writeRF_TSDU_Deny(srcId, srcId, ReasonCode::DENY_PTT_BONK, TSBKO::ISP_SNDCP_CH_REQ, false, true); - m_p25->m_rfState = RS_RF_REJECTED; + bool requestFailed = false; + g_RPC->req(RPC_PERMIT_P25_TG, req, [=, &requestFailed](json::object& req, json::object& reply) { + if (!req["status"].is()) { + return; + } + + int status = req["status"].get(); + if (status != network::RPC::OK) { + ::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo); + if (req["message"].is()) { + std::string retMsg = req["message"].get(); + ::LogError(LOG_RF, "P25, RPC failed, %s", retMsg.c_str()); + } + m_p25->m_affiliations.releaseGrant(srcId, false); + writeRF_TSDU_Deny(srcId, srcId, ReasonCode::DENY_PTT_BONK, TSBKO::ISP_SNDCP_CH_REQ, false, true); + m_p25->m_rfState = RS_RF_REJECTED; + requestFailed = true; + } + }, voiceChData.address(), voiceChData.port()); + + if (requestFailed) return false; - } } else { ::LogError(LOG_RF, P25_TSDU_STR ", TSBKO, ISP_SNDCP_CH_REQ (SNDCP Data Channel Request), failed to permit for use, chNo = %u", chNo); diff --git a/src/remote/RESTClientMain.cpp b/src/remote/RESTClientMain.cpp index d3d2b88a..be168f2b 100644 --- a/src/remote/RESTClientMain.cpp +++ b/src/remote/RESTClientMain.cpp @@ -71,7 +71,6 @@ #define RCD_RELEASE_GRANTS "rel-grnts" #define RCD_RELEASE_AFFS "rel-affs" -#define RCD_RELEASE_AFF "rel-aff" #define RCD_DMR_BEACON "dmr-beacon" #define RCD_P25_CC "p25-cc" @@ -233,7 +232,6 @@ void usage(const char* message, const char* arg) reply += "\r\n"; reply += " rel-grnts Forcibly releases all channel grants\r\n"; reply += " rel-affs Forcibly releases all group affiliations\r\n"; - reply += " rel-aff Forcibly releases specified group affiliations\r\n"; reply += "\r\n"; reply += " dmr-beacon Transmits a DMR beacon burst\r\n"; reply += " p25-cc Transmits a non-continous P25 CC burst\r\n"; @@ -558,21 +556,6 @@ int main(int argc, char** argv) else if (rcom == RCD_RELEASE_AFFS) { retCode = client->send(HTTP_GET, GET_RELEASE_AFFS, json::object(), response); } - else if (rcom == RCD_RELEASE_AFF) { - json::object req = json::object(); - int state = getArgInt32(args, 0U); - req["state"].set(state); - - uint32_t dstId = getArgInt32(args, 1U); - req["dstId"].set(dstId); - - if (state == 1) { - uint8_t slot = getArgInt8(args, 2U); - req["slot"].set(slot); - } - - retCode = client->send(HTTP_PUT, PUT_RELEASE_TG, req, response); - } /* ** Digital Mobile Radio diff --git a/src/sysview/NodeStatusWnd.h b/src/sysview/NodeStatusWnd.h index 01572da8..ec94486b 100644 --- a/src/sysview/NodeStatusWnd.h +++ b/src/sysview/NodeStatusWnd.h @@ -695,7 +695,7 @@ private: uint8_t channelId = peerObj["channelId"].get(); uint32_t channelNo = peerObj["channelNo"].get(); - VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string(), false); + VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string()); wdgt->channelId = channelId; wdgt->channelNo = channelNo; @@ -747,7 +747,7 @@ private: uint8_t channelId = peerObj["channelId"].get(); uint32_t channelNo = peerObj["channelNo"].get(); - VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string(), false); + VoiceChData data = VoiceChData(channelId, channelNo, std::string(), 9990U, std::string()); wdgt->channelId = channelId; wdgt->channelNo = channelNo;