diff --git a/src/common/nxdn/NXDNUtils.cpp b/src/common/nxdn/NXDNUtils.cpp index dd59e611..5d67eee9 100644 --- a/src/common/nxdn/NXDNUtils.cpp +++ b/src/common/nxdn/NXDNUtils.cpp @@ -15,6 +15,7 @@ #include "Defines.h" #include "nxdn/NXDNDefines.h" #include "nxdn/NXDNUtils.h" +#include "Utils.h" using namespace nxdn; @@ -45,3 +46,19 @@ void NXDNUtils::scrambler(uint8_t* data) for (uint32_t i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) data[i] ^= SCRAMBLER[i]; } + +/// +/// Helper to add the post field bits on NXDN frame data. +/// +/// +void NXDNUtils::addPostBits(uint8_t* data) +{ + assert(data != nullptr); + + // post field + for (uint32_t i = 0U; i < NXDN_CAC_E_POST_FIELD_BITS; i++) { + uint32_t n = i + NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_CAC_FEC_LENGTH_BITS + NXDN_CAC_E_POST_FIELD_BITS; + bool b = READ_BIT(NXDN_PREAMBLE, i); + WRITE_BIT(data, n, b); + } +} diff --git a/src/common/nxdn/NXDNUtils.h b/src/common/nxdn/NXDNUtils.h index 6ac2b185..68ca8f63 100644 --- a/src/common/nxdn/NXDNUtils.h +++ b/src/common/nxdn/NXDNUtils.h @@ -28,6 +28,8 @@ namespace nxdn public: /// Helper to scramble the NXDN frame data. static void scrambler(uint8_t* data); + /// Helper to add the post field bits on NXDN frame data. + static void addPostBits(uint8_t* data); }; } // namespace nxdn diff --git a/src/fne/network/fne/TagDMRData.cpp b/src/fne/network/fne/TagDMRData.cpp index 9cc9ffed..27d74c00 100644 --- a/src/fne/network/fne/TagDMRData.cpp +++ b/src/fne/network/fne/TagDMRData.cpp @@ -11,8 +11,11 @@ * */ #include "fne/Defines.h" +#include "common/dmr/lc/csbk/CSBKFactory.h" #include "common/dmr/lc/LC.h" #include "common/dmr/lc/FullLC.h" +#include "common/dmr/SlotType.h" +#include "common/dmr/Sync.h" #include "common/Clock.h" #include "common/Log.h" #include "common/Utils.h" @@ -542,4 +545,68 @@ bool TagDMRData::validate(uint32_t peerId, data::Data& data, uint32_t streamId) } return true; -} \ No newline at end of file +} + +/// +/// Helper to write a NACK RSP packet. +/// +/// +/// +/// +/// +void TagDMRData::write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t reason, uint8_t service) +{ + std::unique_ptr csbk = std::make_unique(); + csbk->setServiceKind(service); + csbk->setReason(reason); + csbk->setSrcId(DMR_WUID_ALL); // hmmm... + csbk->setDstId(dstId); + + write_CSBK(peerId, csbk.get()); +} + +/// +/// Helper to write a network CSBK. +/// +/// +/// +void TagDMRData::write_CSBK(uint32_t peerId, lc::CSBK* csbk) +{ + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); + + SlotType slotType; + slotType.setColorCode(0U); + slotType.setDataType(DT_CSBK); + + // Regenerate the CSBK data + csbk->encode(data + 2U); + + // Regenerate the Slot Type + slotType.encode(data + 2U); + + // Convert the Data Sync to be from the BS or MS as needed + Sync::addDMRDataSync(data + 2U, true); + + data::Data dmrData; + dmrData.setSlotNo(1U); + dmrData.setDataType(DT_CSBK); + dmrData.setSrcId(csbk->getSrcId()); + dmrData.setDstId(csbk->getDstId()); + dmrData.setFLCO(csbk->getGI() ? FLCO_GROUP : FLCO_PRIVATE); + dmrData.setN(0U); + dmrData.setSeqNo(0U); + dmrData.setBER(0U); + dmrData.setRSSI(0U); + + dmrData.setData(data + 2U); + + uint32_t streamId = m_network->createStreamId(); + uint32_t messageLength = 0U; + UInt8Array message = m_network->createDMR_Message(messageLength, streamId, dmrData); + if (message == nullptr) { + return; + } + + m_network->writePeer(peerId, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_DMR }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true); +} diff --git a/src/fne/network/fne/TagDMRData.h b/src/fne/network/fne/TagDMRData.h index 8502c2b8..7551c02f 100644 --- a/src/fne/network/fne/TagDMRData.h +++ b/src/fne/network/fne/TagDMRData.h @@ -16,6 +16,7 @@ #include "fne/Defines.h" #include "common/dmr/DMRDefines.h" #include "common/dmr/data/Data.h" +#include "common/dmr/lc/CSBK.h" #include "common/Clock.h" #include "network/FNENetwork.h" @@ -75,6 +76,12 @@ namespace network bool isPeerPermitted(uint32_t peerId, dmr::data::Data& data, uint32_t streamId); /// Helper to validate the DMR call stream. bool validate(uint32_t peerId, dmr::data::Data& data, uint32_t streamId); + + /// Helper to write a NACK RSP packet. + void write_CSBK_NACK_RSP(uint32_t peerId, uint32_t dstId, uint8_t reason, uint8_t service); + + /// Helper to write a network CSBK. + void write_CSBK(uint32_t peerId, dmr::lc::CSBK* csbk); }; } // namespace fne } // namespace network diff --git a/src/fne/network/fne/TagNXDNData.cpp b/src/fne/network/fne/TagNXDNData.cpp index 4fcf8fd4..1bdb1838 100644 --- a/src/fne/network/fne/TagNXDNData.cpp +++ b/src/fne/network/fne/TagNXDNData.cpp @@ -12,6 +12,12 @@ */ #include "fne/Defines.h" #include "common/nxdn/NXDNDefines.h" +#include "common/nxdn/channel/LICH.h" +#include "common/nxdn/channel/CAC.h" +#include "common/nxdn/lc/rcch/RCCHFactory.h" +#include "common/nxdn/lc/RTCH.h" +#include "common/nxdn/NXDNUtils.h" +#include "common/nxdn/Sync.h" #include "common/Clock.h" #include "common/Log.h" #include "common/Utils.h" @@ -460,4 +466,86 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u } return true; -} \ No newline at end of file +} + +/// +/// Helper to write a deny packet. +/// +/// +/// +/// +/// +/// +void TagNXDNData::write_Message_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service) +{ + std::unique_ptr rcch = nullptr; + + switch (service) { + case RTCH_MESSAGE_TYPE_VCALL: + rcch = std::make_unique(); + rcch->setMessageType(RTCH_MESSAGE_TYPE_VCALL); + default: + return; + } + + rcch->setCauseResponse(reason); + rcch->setSrcId(srcId); + rcch->setDstId(dstId); + + if (m_network->m_verbose) { + LogMessage(LOG_RF, "NXDN, MSG_DENIAL (Message Denial), reason = $%02X, service = $%02X, srcId = %u, dstId = %u", + service, srcId, dstId); + } + + write_Message(peerId, rcch.get()); +} + +/// +/// Helper to write a network RCCH. +/// +/// +/// +void TagNXDNData::write_Message(uint32_t peerId, lc::RCCH* rcch) +{ + uint8_t data[NXDN_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, NXDN_FRAME_LENGTH_BYTES); + + Sync::addNXDNSync(data + 2U); + + // generate the LICH + channel::LICH lich; + lich.setRFCT(NXDN_LICH_RFCT_RCCH); + lich.setFCT(NXDN_LICH_CAC_OUTBOUND); + lich.setOption(NXDN_LICH_DATA_COMMON); + lich.setOutbound(true); + lich.encode(data + 2U); + + uint8_t buffer[NXDN_RCCH_LC_LENGTH_BYTES]; + ::memset(buffer, 0x00U, NXDN_RCCH_LC_LENGTH_BYTES); + + rcch->encode(buffer, NXDN_RCCH_LC_LENGTH_BITS); + + // generate the CAC + channel::CAC cac; + cac.setRAN(0U); + cac.setStructure(NXDN_SR_RCCH_SINGLE); + cac.setData(buffer); + cac.encode(data + 2U); + + NXDNUtils::scrambler(data + 2U); + NXDNUtils::addPostBits(data + 2U); + + lc::RTCH lc = lc::RTCH(); + lc.setMessageType(rcch->getMessageType()); + lc.setSrcId(rcch->getSrcId()); + lc.setDstId(rcch->getDstId()); + + uint32_t messageLength = 0U; + UInt8Array message = m_network->createNXDN_Message(messageLength, lc, data, NXDN_FRAME_LENGTH_BYTES + 2U); + if (message == nullptr) { + return; + } + + uint32_t streamId = m_network->createStreamId(); + m_network->writePeer(peerId, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_NXDN }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true); +} diff --git a/src/fne/network/fne/TagNXDNData.h b/src/fne/network/fne/TagNXDNData.h index 0bd15fd7..92aab94a 100644 --- a/src/fne/network/fne/TagNXDNData.h +++ b/src/fne/network/fne/TagNXDNData.h @@ -17,6 +17,7 @@ #include "common/Clock.h" #include "common/nxdn/NXDNDefines.h" #include "common/nxdn/lc/RTCH.h" +#include "common/nxdn/lc/RCCH.h" #include "network/FNENetwork.h" #include @@ -74,6 +75,12 @@ namespace network bool isPeerPermitted(uint32_t peerId, nxdn::lc::RTCH& lc, uint8_t messageType, uint32_t streamId); /// Helper to validate the NXDN call stream. bool validate(uint32_t peerId, nxdn::lc::RTCH& control, uint8_t messageType, uint32_t streamId); + + /// Helper to write a deny packet. + void write_Message_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service); + + /// Helper to write a network RCCH. + void write_Message(uint32_t peerId, nxdn::lc::RCCH* rcch); }; } // namespace fne } // namespace network diff --git a/src/fne/network/fne/TagP25Data.cpp b/src/fne/network/fne/TagP25Data.cpp index dc8c6cc1..0f41b80a 100644 --- a/src/fne/network/fne/TagP25Data.cpp +++ b/src/fne/network/fne/TagP25Data.cpp @@ -684,4 +684,97 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, uint8_t duid, const } return true; -} \ No newline at end of file +} + +/// +/// Helper to write a deny packet. +/// +/// +/// +/// +/// +/// +void TagP25Data::write_TSDU_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service, bool aiv) +{ + std::unique_ptr osp = std::make_unique(); + osp->setAIV(aiv); + osp->setSrcId(srcId); + osp->setDstId(dstId); + osp->setService(service); + osp->setResponse(reason); + + if (m_network->m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + } + + write_TSDU(peerId, osp.get()); +} + +/// +/// Helper to write a queue packet. +/// +/// +/// +/// +/// +/// +/// +void TagP25Data::write_TSDU_Queue(uint32_t peerId, uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service, bool aiv, bool grp) +{ + std::unique_ptr osp = std::make_unique(); + osp->setAIV(aiv); + osp->setSrcId(srcId); + osp->setDstId(dstId); + osp->setService(service); + osp->setResponse(reason); + osp->setGroup(grp); + + if (m_network->m_verbose) { + LogMessage(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, reason = $%02X, srcId = %u, dstId = %u", + osp->toString().c_str(), osp->getAIV(), reason, osp->getSrcId(), osp->getDstId()); + } + + write_TSDU(peerId, osp.get()); +} + +/// +/// Helper to write a network TSDU. +/// +/// +/// +void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) +{ + uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); + + // Generate Sync + Sync::addP25Sync(data + 2U); + + // Generate TSBK block + tsbk->setLastBlock(true); // always set last block -- this a Single Block TSDU + tsbk->encode(data + 2U); + + if (m_debug) { + LogDebug(LOG_RF, P25_TSDU_STR ", lco = $%02X, mfId = $%02X, lastBlock = %u, AIV = %u, EX = %u, srcId = %u, dstId = %u, sysId = $%03X, netId = $%05X", + tsbk->getLCO(), tsbk->getMFId(), tsbk->getLastBlock(), tsbk->getAIV(), tsbk->getEX(), tsbk->getSrcId(), tsbk->getDstId(), + tsbk->getSysId(), tsbk->getNetId()); + + Utils::dump(1U, "!!! *TSDU (SBF) TSBK Block Data", data + P25_PREAMBLE_LENGTH_BYTES + 2U, P25_TSBK_FEC_LENGTH_BYTES); + } + + lc::LC lc = lc::LC(); + lc.setLCO(tsbk->getLCO()); + lc.setMFId(tsbk->getMFId()); + lc.setSrcId(tsbk->getSrcId()); + lc.setDstId(tsbk->getDstId()); + + uint32_t messageLength = 0U; + UInt8Array message = m_network->createP25_TSDUMessage(messageLength, lc, data); + if (message == nullptr) { + return; + } + + uint32_t streamId = m_network->createStreamId(); + m_network->writePeer(peerId, { NET_FUNC_PROTOCOL, NET_PROTOCOL_SUBFUNC_P25 }, message.get(), messageLength, RTP_END_OF_CALL_SEQ, streamId, false, true); +} diff --git a/src/fne/network/fne/TagP25Data.h b/src/fne/network/fne/TagP25Data.h index c76adc78..e7d83928 100644 --- a/src/fne/network/fne/TagP25Data.h +++ b/src/fne/network/fne/TagP25Data.h @@ -84,6 +84,14 @@ namespace network bool isPeerPermitted(uint32_t peerId, p25::lc::LC& control, uint8_t duid, uint32_t streamId); /// Helper to validate the P25 call stream. bool validate(uint32_t peerId, p25::lc::LC& control, uint8_t duid, const p25::lc::TSBK* tsbk, uint32_t streamId); + + /// Helper to write a deny packet. + void write_TSDU_Deny(uint32_t peerId, uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service, bool aiv = false); + /// Helper to write a queue packet. + void write_TSDU_Queue(uint32_t peerId, uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service, bool aiv = false, bool group = true); + + /// Helper to write a network TSDU. + void write_TSDU(uint32_t peerId, p25::lc::TSBK* tsbk); }; } // namespace fne } // namespace network diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index b826f05e..81eacb76 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -442,36 +442,28 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) case SVC_KIND_IND_VOICE_CALL: writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 1U); - if (m_slot->m_authoritative) { - if (!m_slot->m_affiliations->isGranted(dstId)) { - writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), false, true); - } + if (!m_slot->m_affiliations->isGranted(dstId)) { + writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), false, true); } break; case SVC_KIND_GRP_VOICE_CALL: writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 1U); - if (m_slot->m_authoritative) { - if (!m_slot->m_affiliations->isGranted(dstId)) { - writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), true, true); - } + if (!m_slot->m_affiliations->isGranted(dstId)) { + writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), true, true); } break; case SVC_KIND_IND_DATA_CALL: case SVC_KIND_IND_UDT_DATA_CALL: writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 0U); - if (!m_slot->m_affiliations->isGranted(dstId)) { - writeRF_CSBK_Data_Grant(srcId, dstId, isp->getServiceOptions(), false, true); - } + writeRF_CSBK_Data_Grant(srcId, dstId, isp->getServiceOptions(), false, true); break; case SVC_KIND_GRP_DATA_CALL: case SVC_KIND_GRP_UDT_DATA_CALL: writeRF_CSBK_ACK_RSP(srcId, TS_WAIT_RSN, 0U); - if (!m_slot->m_affiliations->isGranted(dstId)) { - writeRF_CSBK_Data_Grant(srcId, dstId, isp->getServiceOptions(), true, true); - } + writeRF_CSBK_Data_Grant(srcId, dstId, isp->getServiceOptions(), true, true); break; case SVC_KIND_REG_SVC: break; @@ -725,7 +717,7 @@ void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool clearBeforeWrite, bool */ /// -/// Helper to write a deny packet. +/// Helper to write a ACK RSP packet. /// /// /// @@ -742,7 +734,7 @@ void ControlSignaling::writeRF_CSBK_ACK_RSP(uint32_t dstId, uint8_t reason, uint } /// -/// Helper to write a deny packet. +/// Helper to write a NACK RSP packet. /// /// /// diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index af6bc030..b6c53446 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -257,7 +257,7 @@ bool ControlSignaling::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& net uint16_t srcId = rcch->getSrcId(); uint16_t dstId = rcch->getDstId(); - // handle standard P25 reference opcodes + // handle standard NXDN message opcodes switch (rcch->getMessageType()) { case RTCH_MESSAGE_TYPE_VCALL: { @@ -277,6 +277,8 @@ bool ControlSignaling::processNetwork(uint8_t fct, uint8_t option, lc::RTCH& net } } return true; // don't allow this to write to the air + case RCCH_MESSAGE_TYPE_VCALL_CONN: + break; // the FNE may explicitly send these default: LogError(LOG_NET, "NXDN, unhandled message type, messageType = $%02X", rcch->getMessageType()); return false; @@ -395,8 +397,7 @@ void ControlSignaling::writeRF_Message(RCCH* rcch, bool noNetwork, bool clearBef data[1U] = 0x00U; NXDNUtils::scrambler(data + 2U); - - addPostBits(data + 2U); + NXDNUtils::addPostBits(data + 2U); if (!noNetwork) writeNetwork(data, NXDN_FRAME_LENGTH_BYTES + 2U); @@ -823,8 +824,7 @@ void ControlSignaling::writeRF_CC_Message_Site_Info() data[1U] = 0x00U; NXDNUtils::scrambler(data + 2U); - - addPostBits(data + 2U); + NXDNUtils::addPostBits(data + 2U); if (m_nxdn->m_duplex) { m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); @@ -868,26 +868,9 @@ void ControlSignaling::writeRF_CC_Message_Service_Info() data[1U] = 0x00U; NXDNUtils::scrambler(data + 2U); - - addPostBits(data + 2U); + NXDNUtils::addPostBits(data + 2U); if (m_nxdn->m_duplex) { m_nxdn->addFrame(data, NXDN_FRAME_LENGTH_BYTES + 2U); } } - -/// -/// Helper to add the post field bits on NXDN frame data. -/// -/// -void ControlSignaling::addPostBits(uint8_t* data) -{ - assert(data != nullptr); - - // post field - for (uint32_t i = 0U; i < NXDN_CAC_E_POST_FIELD_BITS; i++) { - uint32_t n = i + NXDN_FSW_LENGTH_BITS + NXDN_LICH_LENGTH_BITS + NXDN_CAC_FEC_LENGTH_BITS + NXDN_CAC_E_POST_FIELD_BITS; - bool b = READ_BIT(NXDN_PREAMBLE, i); - WRITE_BIT(data, n, b); - } -} diff --git a/src/host/nxdn/packet/ControlSignaling.h b/src/host/nxdn/packet/ControlSignaling.h index 2aa36268..323b3f9f 100644 --- a/src/host/nxdn/packet/ControlSignaling.h +++ b/src/host/nxdn/packet/ControlSignaling.h @@ -106,9 +106,6 @@ namespace nxdn void writeRF_CC_Message_Site_Info(); /// Helper to write a CC SRV_INFO broadcast packet on the RF interface. void writeRF_CC_Message_Service_Info(); - - /// Helper to add the post field bits on NXDN frame data. - void addPostBits(uint8_t* data); }; } // namespace packet } // namespace nxdn diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 1227786b..c562a753 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -894,12 +894,9 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr break; case TSBK_ISP_EMERG_ALRM_REQ: { - ISP_EMERG_ALRM_REQ* isp = static_cast(tsbk.get()); - // non-emergency mode is a TSBK_OSP_DENY_RSP - if (!isp->getEmergency()) { - // ignore a network deny command - return true; // don't allow this to write to the air + if (!tsbk->getEmergency()) { + break; // the FNE may explicitly send these } else { VERBOSE_LOG_TSBK_NET(tsbk->toString(true), srcId, dstId); return true; // don't allow this to write to the air @@ -915,9 +912,10 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr case TSBK_OSP_LOC_REG_RSP: // ignore a network location registration command return true; // don't allow this to write to the air + case TSBK_OSP_U_REG_CMD: + break; // the FNE may explicitly send these case TSBK_OSP_QUE_RSP: - // ignore a network queue command - return true; // don't allow this to write to the air + break; // the FNE may explicitly send these default: LogError(LOG_NET, P25_TSDU_STR ", unhandled LCO, mfId = $%02X, lco = $%02X", tsbk->getMFId(), tsbk->getLCO()); return false; diff --git a/src/host/p25/packet/ControlSignaling.h b/src/host/p25/packet/ControlSignaling.h index b566646f..dccc258a 100644 --- a/src/host/p25/packet/ControlSignaling.h +++ b/src/host/p25/packet/ControlSignaling.h @@ -218,7 +218,7 @@ namespace p25 bool writeNet_TSDU_Call_Term(uint32_t srcId, uint32_t dstId); /// Helper to write a network TSDU from the RF data queue. - void writeNet_TSDU_From_RF(lc::TSBK* tsbk, uint8_t * data); + void writeNet_TSDU_From_RF(lc::TSBK* tsbk, uint8_t* data); /// Helper to automatically inhibit a source ID on a denial. void denialInhibit(uint32_t srcId);