diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index bc46a707..f4b638e9 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -87,6 +87,8 @@ master: disallowExtAdjStsBcast: true # Flag indicating whether or not a conventional site can override affiliation rules. allowConvSiteAffOverride: true + # Flag indicating whether or not In-Call Control feedback is enabled. + enableInCallCtrl: true # Flag indicating that traffic headers will be filtered by destination ID (i.e. valid RID or valid TGID). filterHeaders: true diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 0892b689..2e650027 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -63,6 +63,7 @@ namespace network PONG = 0x75U, //! Pong GRANT_REQ = 0x7AU, //! Grant Request + INCALL_CTRL = 0x7BU, //! In-Call Control ACK = 0x7EU, //! Packet Acknowledge NAK = 0x7FU, //! Packet Negative Acknowledge @@ -111,6 +112,19 @@ namespace network }; }; + /** + * @brief Network In-Call Control + * @ingroup network_core + */ + namespace NET_ICC { + enum ENUM : uint8_t { + NOP = 0xFFU, //! No Operation Sub-Function + + BUSY_DENY = 0x00U, //! Busy Deny + REJECT_TRAFFIC = 0x01U, //! Reject Active Traffic + }; + }; + namespace frame { // --------------------------------------------------------------------------- diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 8c2ff3ca..585a4dd5 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -124,6 +124,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) m_disallowAdjStsBcast = conf["disallowAdjStsBcast"].as(false); m_disallowExtAdjStsBcast = conf["disallowExtAdjStsBcast"].as(true); m_allowConvSiteAffOverride = conf["allowConvSiteAffOverride"].as(true); + m_enableInCallCtrl = conf["enableInCallCtrl"].as(true); m_softConnLimit = conf["connectionLimit"].as(MAX_HARD_CONN_CAP); if (m_softConnLimit > MAX_HARD_CONN_CAP) { @@ -179,6 +180,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) LogInfo(" Dump Packet Data: %s", m_dumpDataPacket ? "yes" : "no"); LogInfo(" Disable P25 ADJ_STS_BCAST to external peers: %s", m_disallowExtAdjStsBcast ? "yes" : "no"); LogInfo(" Allow conventional sites to override affiliation and receive all traffic: %s", m_allowConvSiteAffOverride ? "yes" : "no"); + LogInfo(" Enable In-Call Control: %s", m_enableInCallCtrl ? "yes" : "no"); LogInfo(" Restrict grant response by affiliation: %s", m_restrictGrantToAffOnly ? "yes" : "no"); LogInfo(" Traffic Headers Filtered by Destination ID: %s", m_filterHeaders ? "yes" : "no"); LogInfo(" Traffic Terminators Filtered by Destination ID: %s", m_filterTerminators ? "yes" : "no"); @@ -2243,6 +2245,24 @@ void FNENetwork::writePeerList(uint32_t peerId) return; } +/* Helper to send a In-Call Control command to the specified peer. */ + +bool FNENetwork::writePeerICC(uint32_t peerId, NET_SUBFUNC::ENUM subFunc, NET_ICC::ENUM command, uint8_t slotNo) +{ + assert(peerId > 0); + if (!m_enableInCallCtrl) + return false; + + uint8_t buffer[DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, DATA_PACKET_LENGTH); + + __SET_UINT32(peerId, buffer, 6U); // Peer ID + buffer[10U] = (uint8_t)command; // In-Call Control Command + buffer[11U] = slotNo; + + return writePeer(peerId, { NET_FUNC::INCALL_CTRL, subFunc }, buffer, 12U, RTP_END_OF_CALL_SEQ, false, true); +} + /* Helper to send a data message to the specified peer. */ bool FNENetwork::writePeer(uint32_t peerId, FrameQueue::OpcodePair opcode, const uint8_t* data, @@ -2379,7 +2399,7 @@ bool FNENetwork::writePeerNAK(uint32_t peerId, const char* tag, NET_CONN_NAK_REA __SET_UINT16B((uint16_t)reason, buffer, 10U); // Reason logPeerNAKReason(peerId, tag, reason); - return writePeer(peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 10U, RTP_END_OF_CALL_SEQ, false, true); + return writePeer(peerId, { NET_FUNC::NAK, NET_SUBFUNC::NOP }, buffer, 12U, RTP_END_OF_CALL_SEQ, false, true); } /* Helper to send a NAK response to the specified peer. */ diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 779c8558..af6c2c04 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -457,6 +457,7 @@ namespace network bool m_disallowExtAdjStsBcast; bool m_allowConvSiteAffOverride; bool m_restrictGrantToAffOnly; + bool m_enableInCallCtrl; bool m_filterHeaders; bool m_filterTerminators; @@ -566,6 +567,16 @@ namespace network * @param peerId Peer ID. */ void writePeerList(uint32_t peerId); + + /** + * @brief Helper to send a In-Call Control command to the specified peer. + * @param peerId Peer ID. + * @param subFunc Network Sub-Function. + * @param command In-Call Control Command. + * @param slotNo DMR slot. + */ + bool writePeerICC(uint32_t peerId, NET_SUBFUNC::ENUM subFunc = NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, + NET_ICC::ENUM command = NET_ICC::NOP, uint8_t slotNo = 0U); /** * @brief Helper to send a data message to the specified peer. diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 315a1170..857bd723 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -738,6 +738,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getSlotNo()); return false; } } @@ -789,6 +790,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getSlotNo()); return false; } @@ -808,6 +810,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getSlotNo()); return false; } @@ -826,6 +829,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getSlotNo()); return false; } } diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index ed0c51a2..8dc858f0 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -552,6 +552,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC); return false; } } @@ -603,6 +604,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC); return false; } @@ -620,6 +622,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC); return false; } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 4db93cbd..30f67a30 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1034,6 +1034,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC); return false; } } @@ -1113,6 +1114,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC); return false; } @@ -1130,6 +1132,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC); return false; } diff --git a/src/host/dmr/Control.cpp b/src/host/dmr/Control.cpp index a0565223..95e0fc48 100644 --- a/src/host/dmr/Control.cpp +++ b/src/host/dmr/Control.cpp @@ -176,6 +176,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChDa m_slot1->m_ignoreAffiliationCheck = ignoreAffiliationCheck; m_slot2->m_ignoreAffiliationCheck = ignoreAffiliationCheck; + // set the In-Call Control function callback + if (m_network != nullptr) { + m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint8_t slotNo) { processInCallCtrl(command, slotNo); }); + } + if (printOptions) { if (enableTSCC) { LogInfo(" TSCC Slot: %u", m_tsccSlotNo); @@ -737,3 +742,18 @@ void Control::processNetwork() break; } } + +/* Helper to process an In-Call Control message. */ + +void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint8_t slotNo) +{ + switch (slotNo) { + case 1U: + return m_slot1->processInCallCtrl(command); + case 2U: + return m_slot2->processInCallCtrl(command); + default: + LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); + break; + } +} diff --git a/src/host/dmr/Control.h b/src/host/dmr/Control.h index 3d1e56d1..1fdab63f 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/RTPFNEHeader.h" #include "common/yaml/Yaml.h" #include "dmr/lookups/DMRAffiliationLookup.h" #include "dmr/Slot.h" @@ -330,6 +331,12 @@ namespace dmr * @brief Process a data frames from the network. */ void processNetwork(); + /** + * @brief Helper to process an In-Call Control message. + * @param command In-Call Control Command. + * @param slotNo DMR slot. + */ + void processInCallCtrl(network::NET_ICC::ENUM command, uint8_t slotNo); }; } // namespace dmr diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 4cbe363d..c17eb9e1 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -453,6 +453,27 @@ void Slot::processNetwork(const data::NetData& dmrData) } } +/* Helper to process an In-Call Control message. */ + +void Slot::processInCallCtrl(network::NET_ICC::ENUM command) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + { + processFrameLoss(); + + m_rfLastDstId = 0U; + m_rfLastSrcId = 0U; + m_rfTGHang.stop(); + m_rfState = RS_RF_REJECTED; + } + break; + + default: + break; + } +} + /* Updates the DMR slot processor. */ void Slot::clock() diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index 185de43f..490dd3ec 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -157,6 +157,14 @@ namespace dmr void processNetwork(const data::NetData& data); /** @} */ + /** @name In-Call Control */ + /** + * @brief Helper to process an In-Call Control message. + * @param command In-Call Control Command. + */ + void processInCallCtrl(network::NET_ICC::ENUM command); + /** @} */ + /** @name Data Clocking */ /** * @brief Updates the processor. diff --git a/src/host/network/Network.cpp b/src/host/network/Network.cpp index 23c3a2f9..e0735c2b 100644 --- a/src/host/network/Network.cpp +++ b/src/host/network/Network.cpp @@ -71,7 +71,10 @@ Network::Network(const std::string& address, uint16_t port, uint16_t localPort, m_restApiPort(0), m_conventional(false), m_remotePeerId(0U), - m_promiscuousPeer(false) + m_promiscuousPeer(false), + m_dmrInCallCallback(nullptr), + m_p25InCallCallback(nullptr), + m_nxdnInCallCallback(nullptr) { assert(!address.empty()); assert(port > 0U); @@ -486,6 +489,42 @@ void Network::clock(uint32_t ms) } break; + case NET_FUNC::INCALL_CTRL: + { + 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]; + uint8_t slot = buffer[11U]; + + if (m_dmrInCallCallback != nullptr) { + m_dmrInCallCallback(command, 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]; + + if (m_p25InCallCallback != nullptr) { + m_p25InCallCallback(command); + } + } + } + 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]; + + if (m_nxdnInCallCallback != nullptr) { + m_nxdnInCallCallback(command); + } + } + } + else { + Utils::dump("unknown protocol opcode from the master", buffer.get(), length); + } + } + break; + case NET_FUNC::NAK: // Master Negative Ack { // DVM 3.6 adds support to respond with a NAK reason, as such we just check if the NAK response is greater diff --git a/src/host/network/Network.h b/src/host/network/Network.h index 64dc2704..7698178f 100644 --- a/src/host/network/Network.h +++ b/src/host/network/Network.h @@ -32,6 +32,7 @@ #include #include +#include namespace network { @@ -152,6 +153,22 @@ namespace network */ void enable(bool enabled); + /** + * @brief Helper to set the DMR In-Call Control callback. + * @param callback + */ + void setDMRICCCallback(std::function&& callback) { m_dmrInCallCallback = callback; } + /** + * @brief Helper to set the P25 In-Call Control callback. + * @param callback + */ + void setP25ICCCallback(std::function&& callback) { m_p25InCallCallback = callback; } + /** + * @brief Helper to set the NXDN In-Call Control callback. + * @param callback + */ + void setNXDNICCCallback(std::function&& callback) { m_nxdnInCallCallback = callback; } + public: /** * @brief Last received RTP sequence number. @@ -213,6 +230,10 @@ namespace network bool m_promiscuousPeer; + std::function m_dmrInCallCallback; + std::function m_p25InCallCallback; + std::function m_nxdnInCallCallback; + /** * @brief User overrideable handler that allows user code to process network packets not handled by this class. * @param peerId Peer ID. diff --git a/src/host/nxdn/Control.cpp b/src/host/nxdn/Control.cpp index bd1ccbbe..7b699f7d 100644 --- a/src/host/nxdn/Control.cpp +++ b/src/host/nxdn/Control.cpp @@ -296,6 +296,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw } } + // set the In-Call Control function callback + if (m_network != nullptr) { + m_network->setNXDNICCCallback([=](network::NET_ICC::ENUM command) { processInCallCtrl(command); }); + } + if (printOptions) { LogInfo(" Silence Threshold: %u (%.1f%%)", m_voice->m_silenceThreshold, float(m_voice->m_silenceThreshold) / 12.33F); LogInfo(" Frame Loss Threshold: %u", m_frameLossThreshold); @@ -1032,6 +1037,27 @@ void Control::processFrameLoss() m_rfLC.reset(); } +/* Helper to process an In-Call Control message. */ + +void Control::processInCallCtrl(network::NET_ICC::ENUM command) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + { + processFrameLoss(); + + m_rfLastDstId = 0U; + m_rfLastSrcId = 0U; + m_rfTGHang.stop(); + m_rfState = RS_RF_REJECTED; + } + break; + + default: + break; + } +} + /* Helper to send a REST API request to the CC to release a channel grant at the end of a call. */ void Control::notifyCC_ReleaseGrant(uint32_t dstId) diff --git a/src/host/nxdn/Control.h b/src/host/nxdn/Control.h index e29f4cc5..bf943ae2 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/RTPFNEHeader.h" #include "common/RingBuffer.h" #include "common/StopWatch.h" #include "common/Timer.h" @@ -361,6 +362,11 @@ namespace nxdn * @brief Helper to process loss of frame stream from modem. */ void processFrameLoss(); + /** + * @brief Helper to process an In-Call Control message. + * @param command In-Call Control Command. + */ + void processInCallCtrl(network::NET_ICC::ENUM command); /** * @brief Helper to send a REST API request to the CC to release a channel grant at the end of a call. diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index 106caf49..944cb036 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -439,6 +439,11 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw } }); + // set the In-Call Control function callback + if (m_network != nullptr) { + m_network->setP25ICCCallback([=](network::NET_ICC::ENUM command) { processInCallCtrl(command); }); + } + if (printOptions) { LogInfo(" Silence Threshold: %u (%.1f%%)", m_voice->m_silenceThreshold, float(m_voice->m_silenceThreshold) / 12.33F); LogInfo(" Frame Loss Threshold: %u", m_frameLossThreshold); @@ -1534,6 +1539,27 @@ void Control::processFrameLoss() } } +/* Helper to process an In-Call Control message. */ + +void Control::processInCallCtrl(network::NET_ICC::ENUM command) +{ + switch (command) { + case network::NET_ICC::REJECT_TRAFFIC: + { + processFrameLoss(); + + m_rfLastDstId = 0U; + m_rfLastSrcId = 0U; + m_rfTGHang.stop(); + m_rfState = RS_RF_REJECTED; + } + break; + + default: + break; + } +} + /* Helper to send a REST API request to the CC to release a channel grant at the end of a call. */ void Control::notifyCC_ReleaseGrant(uint32_t dstId) diff --git a/src/host/p25/Control.h b/src/host/p25/Control.h index f81ead09..02242a6e 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/RTPFNEHeader.h" #include "common/p25/SiteData.h" #include "common/RingBuffer.h" #include "common/StopWatch.h" @@ -397,6 +398,11 @@ namespace p25 * @brief Helper to process loss of frame stream from modem. */ void processFrameLoss(); + /** + * @brief Helper to process an In-Call Control message. + * @param command In-Call Control Command. + */ + void processInCallCtrl(network::NET_ICC::ENUM command); /** * @brief Helper to send a REST API request to the CC to release a channel grant at the end of a call.