// 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) 2015,2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2017-2025 Bryan Biedenkapp, N2PLL * */ #include "Defines.h" #include "common/dmr/acl/AccessControl.h" #include "common/dmr/lc/CSBK.h" #include "common/dmr/lc/csbk/CSBKFactory.h" #include "common/RC4Crypto.h" #include "common/Log.h" #include "dmr/Control.h" #include "Host.h" using namespace dmr; using namespace dmr::defines; #include #include #include #include // --------------------------------------------------------------------------- // Global Functions // --------------------------------------------------------------------------- /** * @brief Calculates the registration authentication response. * @param challenge Registration challenge. * @param key Authentication key. * @return Calculated registration authentication response. */ uint32_t calculateRegistrationAuthResponse(uint32_t challenge, const uint8_t key[16U]) { uint8_t rc4Key[19U]; rc4Key[0U] = (uint8_t)((challenge >> 16U) & 0xFFU); rc4Key[1U] = (uint8_t)((challenge >> 8U) & 0xFFU); rc4Key[2U] = (uint8_t)(challenge & 0xFFU); ::memcpy(rc4Key + 3U, key, 16U); crypto::RC4 rc4; std::unique_ptr stream(rc4.keystream(259U, rc4Key, sizeof(rc4Key))); return ((uint32_t)stream[256U] << 16U) | ((uint32_t)stream[257U] << 8U) | stream[258U]; } // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /* Initializes a new instance of the Control class. */ Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly, bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) : m_authoritative(authoritative), m_colorCode(colorCode), m_modem(modem), m_network(network), m_slot1(nullptr), m_slot2(nullptr), m_idenTable(idenTable), m_ridLookup(ridLookup), m_tidLookup(tidLookup), m_enableTSCC(false), m_requireAuthForReg(false), m_tsccCnt(0U), m_tsccCntInterval(1000U, 0U, DMR_SLOT_TIME / 2U), m_tsccSlotNo(0U), m_tsccPayloadActive(false), m_ccRunning(false), m_ccHalted(false), m_dumpCSBKData(dumpCSBKData), m_verbose(verbose), m_debug(debug) { assert(modem != nullptr); assert(chLookup != nullptr); assert(ridLookup != nullptr); assert(tidLookup != nullptr); assert(idenTable != nullptr); assert(rssiMapper != nullptr); acl::AccessControl::init(m_ridLookup, m_tidLookup); Slot::init(this, authoritative, colorCode, SiteData(), embeddedLCOnly, dumpTAData, callHang, modem, network, duplex, chLookup, m_ridLookup, m_tidLookup, m_idenTable, rssiMapper, jitter, verbose); lc::CSBK::setVerbose(m_dumpCSBKData); m_slot1 = new Slot(1U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, dumpCSBKData, debug, verbose); 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. */ Control::~Control() { delete m_slot2; delete m_slot1; } /* Helper to set DMR configuration options. */ void Control::setOptions(yaml::Node& conf, ::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node dmrProtocol = conf["protocols"]["dmr"]; Slot::s_verifyReg = dmrProtocol["verifyReg"].as(false); uint8_t nRandWait = (uint8_t)dmrProtocol["nRandWait"].as(DEFAULT_NRAND_WAIT); if (nRandWait > 15U) nRandWait = 15U; uint8_t backOff = (uint8_t)dmrProtocol["backOff"].as(1U); if (backOff > 15U) backOff = 15U; yaml::Node control = dmrProtocol["control"]; m_requireAuthForReg = control["requireAuthForReg"].as(false); if (m_requireAuthForReg && m_network != nullptr) { m_network->setKEKKeyResponseCallback([this](uint32_t srcId, p25::kmm::KeyItem key, uint8_t algId, uint8_t keyLength) { processRegistrationAuthKey(srcId, key, algId, keyLength); }); } else if (m_requireAuthForReg) { LogWarning(LOG_DMR, "DMR registration authentication disabled; no FNE network is configured"); m_requireAuthForReg = false; } bool enableTSCC = control["enable"].as(false); bool dedicatedTSCC = false; if (enableTSCC) { dedicatedTSCC = control["dedicated"].as(false); } else { dedicatedTSCC = false; } bool ccDebug = control["debug"].as(false); Slot::setSiteData(controlChData, netId, siteId, channelId, channelNo, dedicatedTSCC); Slot::setAlohaConfig(nRandWait, backOff); bool disableGrantSourceIdCheck = control["disableGrantSourceIdCheck"].as(false); if (enableTSCC) { m_tsccSlotNo = (uint8_t)control["slot"].as(0U); switch (m_tsccSlotNo) { case 1U: m_slot1->setTSCC(enableTSCC, dedicatedTSCC); m_slot1->setDisableSourceIDGrantCheck(disableGrantSourceIdCheck); m_slot1->setCCDebug(ccDebug); break; case 2U: m_slot2->setTSCC(enableTSCC, dedicatedTSCC); m_slot2->setDisableSourceIDGrantCheck(disableGrantSourceIdCheck); m_slot2->setCCDebug(ccDebug); break; default: LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); break; } } m_enableTSCC = enableTSCC; yaml::Node rfssConfig = systemConf["config"]; uint32_t defaultNetIdleTalkgroup = (uint32_t)rfssConfig["defaultNetIdleTalkgroup"].as(0U); m_slot1->setDefaultNetIdleTG(defaultNetIdleTalkgroup); m_slot2->setDefaultNetIdleTG(defaultNetIdleTalkgroup); yaml::Node controlCh = rfssConfig["controlCh"]; bool notifyCC = controlCh["notifyEnable"].as(false); m_slot1->setNotifyCC(notifyCC); m_slot2->setNotifyCC(notifyCC); bool disableGrpAffTimeout = dmrProtocol["disableGrpAffTimeout"].as(false); m_slot1->s_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); m_slot2->s_affiliations->setDisableGrpAffTimeout(disableGrpAffTimeout); bool disableUnitRegTimeout = dmrProtocol["disableUnitRegTimeout"].as(false); m_slot1->s_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); m_slot2->s_affiliations->setDisableUnitRegTimeout(disableUnitRegTimeout); /* ** Voice Silence and Frame Loss Thresholds */ uint32_t silenceThreshold = dmrProtocol["silenceThreshold"].as(DEFAULT_SILENCE_THRESHOLD); if (silenceThreshold > MAX_DMR_VOICE_ERRORS) { LogWarning(LOG_DMR, "Silence threshold > %u, defaulting to %u", MAX_DMR_VOICE_ERRORS, DEFAULT_SILENCE_THRESHOLD); silenceThreshold = DEFAULT_SILENCE_THRESHOLD; } // either MAX_DMR_VOICE_ERRORS or 0 will disable the threshold logic if (silenceThreshold == 0U) { LogWarning(LOG_DMR, "Silence threshold set to zero, defaulting to %u", MAX_DMR_VOICE_ERRORS); silenceThreshold = MAX_DMR_VOICE_ERRORS; } m_slot1->setSilenceThreshold(silenceThreshold); m_slot2->setSilenceThreshold(silenceThreshold); uint8_t frameLossThreshold = (uint8_t)dmrProtocol["frameLossThreshold"].as(DEFAULT_FRAME_LOSS_THRESHOLD); if (frameLossThreshold == 0U) { frameLossThreshold = 1U; } if (frameLossThreshold > DEFAULT_FRAME_LOSS_THRESHOLD * 2U) { LogWarning(LOG_DMR, "Frame loss threshold may be excessive, default is %u, configured is %u", DEFAULT_FRAME_LOSS_THRESHOLD, frameLossThreshold); } m_slot1->setFrameLossThreshold(frameLossThreshold); m_slot2->setFrameLossThreshold(frameLossThreshold); bool ignoreAffiliationCheck = dmrProtocol["ignoreAffiliationCheck"].as(true); m_slot1->m_ignoreAffiliationCheck = ignoreAffiliationCheck; m_slot2->m_ignoreAffiliationCheck = ignoreAffiliationCheck; bool legacyGroupReg = dmrProtocol["legacyGroupReg"].as(false); m_slot1->setLegacyGroupReg(legacyGroupReg); m_slot2->setLegacyGroupReg(legacyGroupReg); // set the In-Call Control function callback if (m_network != nullptr) { m_network->setDMRICCCallback([=](network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo, uint32_t peerId, uint32_t ssrc, uint32_t streamId) { processInCallCtrl(command, dstId, slotNo); }); } bool convNetGrantDemand = dmrProtocol["convNetGrantDemand"].as(false); m_slot1->m_convNetGrantDemand = convNetGrantDemand; m_slot2->m_convNetGrantDemand = convNetGrantDemand; if (printOptions) { if (enableTSCC) { LogInfo(" TSCC Slot: %u", m_tsccSlotNo); LogInfo(" TSCC Aloha Random Access Wait: %u", nRandWait); LogInfo(" TSCC Aloha Backoff: %u", backOff); if (disableGrantSourceIdCheck) { LogInfo(" TSCC Disable Grant Source ID Check: yes"); } LogInfoEx(LOG_DMR, "Host is configured to operate as a DMR TSCC."); } if (defaultNetIdleTalkgroup != 0U) { LogInfo(" Default Network Idle Talkgroup: %u", defaultNetIdleTalkgroup); } if (disableGrpAffTimeout) { LogInfo(" Disable Group Affiliation Timeout: yes"); } if (disableUnitRegTimeout) { LogInfo(" Disable Unit Registration Timeout: yes"); } LogInfo(" Ignore Affiliation Check: %s", ignoreAffiliationCheck ? "yes" : "no"); LogInfo(" Legacy Group Registration: %s", legacyGroupReg ? "yes" : "no"); LogInfo(" Notify Control: %s", notifyCC ? "yes" : "no"); LogInfo(" Silence Threshold: %u (%.1f%%)", silenceThreshold, float(silenceThreshold) / 1.41F); LogInfo(" Frame Loss Threshold: %u", frameLossThreshold); LogInfo(" Verify Registration: %s", Slot::s_verifyReg ? "yes" : "no"); LogInfo(" Conventional Network Grant Demand: %s", convNetGrantDemand ? "yes" : "no"); } } /* Sets a flag indicating whether the DMR control channel is running. */ void Control::setCCRunning(bool ccRunning) { if (!m_enableTSCC) { m_ccRunning = false; return; } m_ccRunning = ccRunning; switch (m_tsccSlotNo) { case 1U: m_slot1->setCCRunning(ccRunning); break; case 2U: m_slot2->setCCRunning(ccRunning); break; default: LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); break; } } /* Sets a flag indicating whether the DMR control channel is halted. */ void Control::setCCHalted(bool ccHalted) { if (!m_enableTSCC) { m_ccHalted = true; return; } m_ccHalted = ccHalted; switch (m_tsccSlotNo) { case 1U: m_slot1->setCCHalted(ccHalted); break; case 2U: m_slot2->setCCHalted(ccHalted); break; default: LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo); break; } } /* Helper to process wakeup frames from the RF interface. */ bool Control::processWakeup(const uint8_t* data) { assert(data != nullptr); // wakeups always come in on slot 1 if (data[0U] != modem::TAG_DATA || data[1U] != (IDLE_RX | SYNC_DATA | DataType::CSBK)) return false; // generate a new CSBK and check validity std::unique_ptr csbk = lc::csbk::CSBKFactory::createCSBK(data + 2U, DataType::CSBK); if (csbk == nullptr) return false; uint8_t csbko = csbk->getCSBKO(); if (csbko != CSBKO::BSDWNACT) return false; uint32_t srcId = csbk->getSrcId(); // check the srcId against the ACL control bool ret = acl::AccessControl::validateSrcId(srcId); if (!ret) { LogError(LOG_RF, "DMR, invalid CSBKO, BSDWNACT, srcId = %u", srcId); return false; } if (m_verbose) { LogInfoEx(LOG_RF, "DMR, CSBKO, BSDWNACT, srcId = %u", srcId); } return true; } /* Process a data frame for slot, from the RF interface. */ bool Control::processFrame(uint32_t slotNo, uint8_t *data, uint32_t len) { assert(data != nullptr); switch (slotNo) { case 1U: return m_slot1->processFrame(data, len); case 2U: return m_slot2->processFrame(data, len); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); return false; } } /* Get the frame data length for the next frame in the data ring buffer. */ uint32_t Control::peekFrameLength(uint32_t slotNo) { switch (slotNo) { case 1U: return m_slot1->peekFrameLength(); case 2U: return m_slot2->peekFrameLength(); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); return 0U; } } /* Helper to determine whether or not the internal frame queue is full. */ bool Control::isQueueFull(uint32_t slotNo) { switch (slotNo) { case 1U: return m_slot1->isQueueFull(); case 2U: return m_slot2->isQueueFull(); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); return true; } } /* Get a data frame for slot, from data ring buffer. */ uint32_t Control::getFrame(uint32_t slotNo, uint8_t* data, bool *imm) { assert(data != nullptr); switch (slotNo) { case 1U: return m_slot1->getFrame(data, imm); case 2U: return m_slot2->getFrame(data, imm); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); return 0U; } } /* Updates the processor. */ void Control::clock() { if (m_network != nullptr) { processNetwork(); } m_slot1->clock(); m_slot2->clock(); } /* Updates the adj. site tables. */ void Control::clockSiteData(uint32_t ms) { m_slot1->clockSiteData(ms); m_slot2->clockSiteData(ms); } /* Permits a TGID on a non-authoritative host. */ void Control::permittedTG(uint32_t dstId, uint8_t slot) { switch (slot) { case 1U: m_slot1->permittedTG(dstId); break; case 2U: m_slot2->permittedTG(dstId); break; default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); break; } } /* Grants a TGID on a non-authoritative host. */ void Control::grantTG(uint32_t srcId, uint32_t dstId, uint8_t slot, bool grp) { switch (slot) { case 1U: m_slot1->grantTG(srcId, dstId, grp); break; case 2U: m_slot2->grantTG(srcId, dstId, grp); break; default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slot); break; } } /* Gets instance of the AffiliationLookup class. */ dmr::lookups::DMRAffiliationLookup* Control::affiliations() { switch (m_tsccSlotNo) { case 1U: return m_slot1->s_affiliations; case 2U: return m_slot2->s_affiliations; default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", m_tsccSlotNo); break; } return nullptr; } /* Returns the current operating RF state of the DMR controller. */ RPT_RF_STATE Control::getRFState(uint32_t slotNo) const { switch (slotNo) { case 1U: return m_slot1->getRFState(); case 2U: return m_slot2->getRFState(); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); break; } return RS_RF_INVALID; } /* Clears the current operating RF state back to idle (with no data reset!). */ void Control::clearRFReject(uint32_t slotNo) { switch (slotNo) { case 1U: return m_slot1->clearRFReject(); case 2U: return m_slot2->clearRFReject(); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); break; } } /* Helper to return the slot carrying the TSCC. */ Slot* Control::getTSCCSlot() const { switch (m_tsccSlotNo) { case 1U: return m_slot1; break; case 2U: return m_slot2; break; default: return nullptr; } } /* Helper to payload activate the slot carrying granted payload traffic. */ void Control::tsccActivateSlot(uint32_t slotNo, uint32_t dstId, uint32_t srcId, bool group, bool voice) { if (m_verbose) { LogInfoEx(LOG_DMR, "DMR Slot %u, payload activation, srcId = %u, group = %u, dstId = %u", slotNo, srcId, group, dstId); } // never allow the TSCC to become payload activated if (m_tsccSlotNo == slotNo) { LogError(LOG_DMR, "DMR, cowardly refusing to, TSCC payload activation, slotNo = %u", slotNo); return; } switch (slotNo) { case 1U: m_tsccPayloadActive = true; m_slot1->setTSCCActivated(dstId, srcId, group, voice); break; case 2U: m_tsccPayloadActive = true; m_slot2->setTSCCActivated(dstId, srcId, group, voice); break; default: LogError(LOG_DMR, "DMR, invalid slot, TSCC payload activation, slotNo = %u", slotNo); break; } } /* Helper to clear an activated payload slot. */ void Control::tsccClearActivatedSlot(uint32_t slotNo) { if (m_verbose) { LogInfoEx(LOG_DMR, "DMR Slot %u, payload activation clear", slotNo); } switch (slotNo) { case 1U: m_modem->clearDMRFrame1(); m_slot1->clearTSCCActivated(); break; case 2U: m_modem->clearDMRFrame2(); m_slot2->clearTSCCActivated(); break; default: LogError(LOG_DMR, "DMR, invalid slot, TSCC payload activation, slotNo = %u", slotNo); break; } if (m_tsccPayloadActive && m_slot1->m_tsccPayloadDstId == 0U && m_slot2->m_tsccPayloadDstId == 0U) { m_tsccPayloadActive = false; } } /* Helper to write a DMR extended function packet on the RF interface. */ void Control::writeRF_Ext_Func(uint32_t slotNo, uint32_t func, uint32_t arg, uint32_t dstId) { switch (slotNo) { case 1U: m_slot1->control()->writeRF_Ext_Func(func, arg, dstId); break; case 2U: m_slot2->control()->writeRF_Ext_Func(func, arg, dstId); break; default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); break; } } /* Helper to write a DMR call alert packet on the RF interface. */ void Control::writeRF_Call_Alrt(uint32_t slotNo, uint32_t srcId, uint32_t dstId) { switch (slotNo) { case 1U: m_slot1->control()->writeRF_Call_Alrt(srcId, dstId); break; case 2U: m_slot2->control()->writeRF_Call_Alrt(srcId, dstId); break; default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); break; } } /* Flag indicating whether the processor or is busy or not. */ bool Control::isBusy() const { return (m_slot1->m_rfState != RS_RF_LISTENING || m_slot1->m_netState != RS_NET_IDLE) || (m_slot2->m_rfState != RS_RF_LISTENING || m_slot2->m_netState != RS_NET_IDLE); } /* Helper to change the debug and verbose state. */ void Control::setDebugVerbose(bool debug, bool verbose) { m_debug = debug; m_verbose = verbose; m_slot1->setDebugVerbose(debug, verbose); m_slot2->setDebugVerbose(debug, verbose); } /* Helper to change the CSBK verbose state. */ void Control::setCSBKVerbose(bool verbose) { m_dumpCSBKData = verbose; lc::CSBK::setVerbose(verbose); } /* Helper to get the last transmitted destination ID. */ uint32_t Control::getLastDstId(uint32_t slotNo) const { switch (slotNo) { case 1U: return m_slot1->getLastDstId(); case 2U: return m_slot2->getLastDstId(); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); break; } return 0U; } /* Helper to get the last transmitted source ID. */ uint32_t Control::getLastSrcId(uint32_t slotNo) const { switch (slotNo) { case 1U: return m_slot1->getLastSrcId(); case 2U: return m_slot2->getLastSrcId(); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); break; } return 0U; } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /* Process a data frames from the network. */ void Control::processNetwork() { uint32_t length = 0U; bool ret = false; UInt8Array buffer = m_network->readDMR(ret, length); if (!ret) return; if (length == 0U) return; if (buffer == nullptr) { return; } data::NetData data; // process network message header uint8_t seqNo = buffer[4U]; uint32_t srcId = GET_UINT24(buffer, 5U); uint32_t dstId = GET_UINT24(buffer, 8U); uint8_t controlByte = buffer[14U]; FLCO::E flco = (buffer[15U] & 0x40U) == 0x40U ? FLCO::PRIVATE : FLCO::GROUP; uint32_t slotNo = (buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; if (slotNo > 3U) { LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); return; } // DMO mode slot disabling if (slotNo == 1U && !m_network->getDuplex()) { LogError(LOG_DMR, "DMR/DMO, invalid slot, slotNo = %u", slotNo); return; } // Individual slot disabling if (slotNo == 1U && !m_network->getSlot1()) { LogError(LOG_DMR, "DMR, invalid slot, slot 1 disabled, slotNo = %u", slotNo); return; } if (slotNo == 2U && !m_network->getSlot2()) { LogError(LOG_DMR, "DMR, invalid slot, slot 2 disabled, slotNo = %u", slotNo); return; } data.setSeqNo(seqNo); data.setSlotNo(slotNo); data.setSrcId(srcId); data.setDstId(dstId); data.setFLCO(flco); data.setControl(controlByte); bool dataSync = (buffer[15U] & 0x20U) == 0x20U; bool voiceSync = (buffer[15U] & 0x10U) == 0x10U; if (m_debug) { LogDebug(LOG_NET, "DMR, seqNo = %u, srcId = %u, dstId = %u, flco = $%02X, slotNo = %u, len = %u", seqNo, srcId, dstId, flco, slotNo, length); } // process raw DMR data bytes if (dataSync) { DataType::E dataType = (DataType::E)(buffer[15U] & 0x0FU); data.setData(buffer.get() + 20U); data.setDataType(dataType); data.setN(0U); } else if (voiceSync) { data.setData(buffer.get() + 20U); data.setDataType(DataType::VOICE_SYNC); data.setN(0U); } else { uint8_t n = buffer[15U] & 0x0FU; data.setData(buffer.get() + 20U); data.setDataType(DataType::VOICE); data.setN(n); } // are we TSCC enabled? if (m_enableTSCC) { Slot* tscc = getTSCCSlot(); if (tscc != nullptr) { if (!tscc->s_affiliations->isGranted(dstId)) { tscc->m_control->writeRF_CSBK_Grant(srcId, dstId, 4U, (flco == FLCO::GROUP) ? true : false, true); } } } // forward onto the specific slot for final processing and delivery switch (slotNo) { case 1U: m_slot1->processNetwork(data); break; case 2U: m_slot2->processNetwork(data); break; } } /* Helper to process a registration authentication key. */ void Control::processRegistrationAuthKey(uint32_t srcId, const p25::kmm::KeyItem& key, uint8_t algId, uint8_t keyLength) { (void)algId; // The FNE labels this shared 16-byte key as the P25 AES-128 KEK type. Slot* tscc = getTSCCSlot(); if (tscc == nullptr) return; auto it = tscc->m_authTransactions.find(srcId); if (it == tscc->m_authTransactions.end()) return; if (keyLength != 16U) { tscc->m_authTransactions.erase(it); tscc->m_control->writeRF_CSBK_NACK_RSP(srcId, ReasonCode::TS_DENY_RSN_REG_DENIED, ServiceKind::REG_SVC); return; } uint8_t keyData[16U]; ::memset(keyData, 0U, 16U); key.getKey(keyData); static std::random_device random; std::uniform_int_distribution distribution(0U, 0xFFFCDFU); it->second.challenge = distribution(random); it->second.expectedResponse = calculateRegistrationAuthResponse(it->second.challenge, keyData); it->second.challengeSent = true; it->second.expires = std::chrono::steady_clock::now() + std::chrono::seconds(30); tscc->m_control->writeRF_CSBK_AHOY(srcId, ServiceKind::REG_SVC, it->second.challenge, false, 0U); } /* Helper to process an In-Call Control message. */ void Control::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId, uint8_t slotNo) { switch (slotNo) { case 1U: return m_slot1->processInCallCtrl(command, dstId); case 2U: return m_slot2->processInCallCtrl(command, dstId); default: LogError(LOG_DMR, "DMR, invalid slot, slotNo = %u", slotNo); 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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::INVALID_ARGS); return; } uint8_t slot = (uint8_t)req["slot"].get(); if (slot == 0U || slot > 2U) { g_RPC->defaultResponse(reply, "illegal DMR slot"); return; } Slot* tscc = getTSCCSlot(); if (tscc != nullptr && tscc->m_enableTSCC && !tscc->s_affiliations->isGranted(dstId) && req["srcId"].is() && req["group"].is() && req["chNo"].is()) { uint32_t srcId = req["srcId"].get(); bool group = req["group"].get(); uint32_t chNo = req["chNo"].get(); ::lookups::VoiceChData voiceCh = tscc->s_affiliations->rfCh()->getRFChData(chNo); if (!voiceCh.isValidCh()) { g_RPC->defaultResponse(reply, "DMR payload channel is not configured", network::NetRPC::INVALID_ARGS); return; } if (!tscc->m_control->writeRF_CSBK_Grant(srcId, dstId, 4U, group, true, false, chNo, slot)) { g_RPC->defaultResponse(reply, "failed to recreate DMR payload grant", network::NetRPC::BAD_REQUEST); 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::NetRPC::OK); // validate destination ID is a integer within the JSON blob if (!req["slot"].is()) { g_RPC->defaultResponse(reply, "slot was not valid", network::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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::NetRPC::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); } }