diff --git a/src/common/network/BaseNetwork.cpp b/src/common/network/BaseNetwork.cpp index 91ee8b1d..d124a5b2 100644 --- a/src/common/network/BaseNetwork.cpp +++ b/src/common/network/BaseNetwork.cpp @@ -102,7 +102,8 @@ BaseNetwork::~BaseNetwork() /* Writes grant request to the network. */ -bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit) +bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, + const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -118,6 +119,7 @@ bool BaseNetwork::writeGrantReq(const uint8_t mode, const uint32_t srcId, const buffer[19U] |= 0x80U; buffer[20U] = mode; // DVM Mode State + buffer[21U] = serviceOptions; // Service Options return writeMaster({ NET_FUNC::GRANT_REQ, NET_SUBFUNC::NOP }, buffer, MSG_HDR_SIZE, RTP_END_OF_CALL_SEQ, 0U); } @@ -903,7 +905,7 @@ bool BaseNetwork::writeP25TDU(const p25::lc::LC& control, const p25::data::LowSp /* Writes P25 TSDU frame data to the network. */ -bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data) +bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte) { if (m_status != NET_STAT_RUNNING && m_status != NET_STAT_MST_RUNNING) return false; @@ -913,7 +915,7 @@ bool BaseNetwork::writeP25TSDU(const p25::lc::LC& control, const uint8_t* data) } uint32_t messageLength = 0U; - UInt8Array message = createP25_TSDUMessage(messageLength, control, data); + UInt8Array message = createP25_TSDUMessage(messageLength, control, data, controlByte); if (message == nullptr) { return false; } @@ -1606,7 +1608,7 @@ UInt8Array BaseNetwork::createP25_TDUMessage(uint32_t& length, const p25::lc::LC /* Creates an P25 TSDU frame message. */ -UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data) +UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte) { using namespace p25::defines; assert(data != nullptr); @@ -1617,6 +1619,7 @@ UInt8Array BaseNetwork::createP25_TSDUMessage(uint32_t& length, const p25::lc::L // construct P25 message header p25::data::LowSpeedData lsd = p25::data::LowSpeedData(); createP25_MessageHdr(buffer, DUID::TSDU, control, lsd, FrameType::TERMINATOR); + buffer[14U] = controlByte; // pack raw P25 TSDU bytes ::memcpy(buffer + 24U, data, P25_TSDU_FRAME_LENGTH_BYTES); diff --git a/src/common/network/BaseNetwork.h b/src/common/network/BaseNetwork.h index 77df4a39..77040d83 100644 --- a/src/common/network/BaseNetwork.h +++ b/src/common/network/BaseNetwork.h @@ -154,6 +154,7 @@ namespace network NET_CTRL_GRANT_DEMAND = 0x80U, //!< Grant Demand NET_CTRL_GRANT_DENIAL = 0x40U, //!< Grant Denial NET_CTRL_SWITCH_OVER = 0x20U, //!< Call Source RID Switch Over + NET_CTRL_ISP = 0x10U, //!< P25 inbound service packet direction NET_CTRL_GRANT_ENCRYPT = 0x08U, //!< Grant Encrypt NET_CTRL_U2U = 0x01U, //!< Unit-to-Unit }; @@ -449,7 +450,7 @@ namespace network * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Destination ID | Reserved |S| Reserverd | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Mode | Reserved | + * | Mode | Service Options| Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \endcode * @param mode Digital Mode. @@ -457,9 +458,11 @@ namespace network * @param dstId Destination Radio ID. * @param slot DMR slot number (if DMR). * @param unitToUnit Flag indicating if this grant is unit-to-unit. + * @param serviceOptions Protocol service-options octet for the requested grant. * @returns bool True, if grant request was sent, otherwise false. */ - bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, const uint8_t slot, const bool unitToUnit); + bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, + const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions); /** * @brief Writes a enc. key request to the network. @@ -913,9 +916,10 @@ namespace network * @brief Writes P25 TSDU frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] data Buffer containing P25 TSDU data to send. + * @param[in] controlByte DVM control byte, including the TSBK direction marker. * @returns bool True, if message was sent, otherwise false. */ - virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data); + virtual bool writeP25TSDU(const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte = 0U); /** * @brief Writes P25 TDULC frame data to the network. * @param[in] control Instance of p25::lc::LC containing link control data. @@ -1239,9 +1243,10 @@ namespace network * @param[out] length Length of network message buffer. * @param[in] control Instance of p25::lc::LC containing link control data. * @param[in] data Buffer containing P25 TSDU data to send. + * @param[in] controlByte DVM control byte, including the TSBK direction marker. * @returns UInt8Array Buffer containing the built network message. */ - UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data); + UInt8Array createP25_TSDUMessage(uint32_t& length, const p25::lc::LC& control, const uint8_t* data, const uint8_t controlByte); /** * @brief Creates an P25 TDULC frame message. diff --git a/src/common/network/Network.cpp b/src/common/network/Network.cpp index cc73972a..c75b207e 100644 --- a/src/common/network/Network.cpp +++ b/src/common/network/Network.cpp @@ -118,12 +118,12 @@ Network::~Network() /* Writes a grant request unless unit-to-unit network traffic is disabled. */ bool Network::writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, - const uint8_t slot, const bool unitToUnit) + const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions) { if (m_disallowUnitToUnit && unitToUnit) return false; - return BaseNetwork::writeGrantReq(mode, srcId, dstId, slot, unitToUnit); + return BaseNetwork::writeGrantReq(mode, srcId, dstId, slot, unitToUnit, serviceOptions); } /* Resets the DMR ring buffer for the given slot. */ diff --git a/src/common/network/Network.h b/src/common/network/Network.h index 2014a01d..cdd5303e 100644 --- a/src/common/network/Network.h +++ b/src/common/network/Network.h @@ -180,10 +180,11 @@ namespace network * @param dstId Destination subscriber or talkgroup ID. * @param slot DMR slot number, or zero for protocols without slots. * @param unitToUnit Flag indicating an individual-call grant request. + * @param serviceOptions Protocol service-options octet for the requested grant. * @return True if the request was written; otherwise false. */ bool writeGrantReq(const uint8_t mode, const uint32_t srcId, const uint32_t dstId, - const uint8_t slot, const bool unitToUnit); + const uint8_t slot, const bool unitToUnit, const uint8_t serviceOptions); /** * @brief Resets the DMR ring buffer for the given slot. diff --git a/src/common/p25/P25Defines.h b/src/common/p25/P25Defines.h index 548965c3..b25b5a4a 100644 --- a/src/common/p25/P25Defines.h +++ b/src/common/p25/P25Defines.h @@ -246,6 +246,8 @@ namespace p25 /** @brief FNE Working Unit ID */ const uint32_t WUID_FNE = 0xFFFFFCU; + /** @brief System-default Working Unit ID used by FNE call-processing responses. */ + const uint32_t WUID_SYSTEM = 0xFFFFFDU; /** @brief Registration Working Unit ID */ const uint32_t WUID_REG = 0xFFFFFEU; /** @brief All-call Working Unit ID */ diff --git a/src/common/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp b/src/common/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp index 51a1c469..e2e6306c 100644 --- a/src/common/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp +++ b/src/common/p25/lc/tsbk/ISP_CAN_SRV_REQ.cpp @@ -58,7 +58,13 @@ void ISP_CAN_SRV_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) { assert(data != nullptr); - /* stub */ + ulong64_t tsbkValue = (m_aivFlag ? 0x80U : 0x00U) + (m_service & 0x3FU); // AIV and Service Type + tsbkValue = (tsbkValue << 8) + m_response; // Reason + tsbkValue = (tsbkValue << 24) + (m_dstId & 0xFFFFFFU); // Additional Information + tsbkValue = (tsbkValue << 24) + (m_srcId & 0xFFFFFFU); // Source Radio Address + + std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); + TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } /* Returns a string that represents the current TSBK. */ diff --git a/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp b/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp index 62d5f2da..4d00a9d3 100644 --- a/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp +++ b/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.cpp @@ -23,7 +23,9 @@ using namespace p25::lc::tsbk; /* Initializes a new instance of the ISP_EMERG_ALRM_REQ class. */ -ISP_EMERG_ALRM_REQ::ISP_EMERG_ALRM_REQ() : TSBK() +ISP_EMERG_ALRM_REQ::ISP_EMERG_ALRM_REQ() : TSBK(), + m_specialInfo1(0U), + m_specialInfo2(0U) { m_lco = TSBKO::ISP_EMERG_ALRM_REQ; } @@ -43,34 +45,17 @@ bool ISP_EMERG_ALRM_REQ::decode(const uint8_t* data, bool rawTSBK) ulong64_t tsbkValue = TSBK::toValue(tsbk); - /* - ** bryanb: this is a bit of a hack -- because the EMERG ALRM and DENY have the same - ** opcode; the following are used by TSBK_OSP_DENY_RSP; best way to check is for m_response > 0 - */ - uint8_t si1 = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Emerg. Special Info 1 - bool manDown = si1 & 0x01U; // Man Down Flag - uint8_t si2 = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Emerg. Special Info 2 - - // if we have no special info, this is a defacto emergency button press - if (si1 == 0U && si2 == 0U) { - m_emergency = true; - } - - // if we have a man down flag set and no special info 2, this is a man-down emergency - if (manDown && si2 == 0U) { - m_emergency = true; - } - - // all other emergency alarms aren't supported and are ignored (and infact most code will treat that as - // OSP_DENY_RSP) - - m_aivFlag = (((tsbkValue >> 56) & 0xFFU) & 0x80U) == 0x80U; // Additional Info. Flag - m_service = (uint8_t)((tsbkValue >> 56) & 0x3FU); // Service Type - m_response = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Reason - - m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Target Radio Address + m_specialInfo1 = (uint8_t)((tsbkValue >> 56) & 0xFFU); // Emergency Special Information 1 + m_specialInfo2 = (uint8_t)((tsbkValue >> 48) & 0xFFU); // Emergency Special Information 2 + m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Group Address m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address + // Emergency Alarm Request and Denial Response share an opcode. Retain the + // direction discriminator used by network-originated TSBKs; the RF receive + // path independently identifies this opcode as an inbound alarm request. + m_emergency = (m_specialInfo2 == 0U) && + (m_specialInfo1 == 0U || (m_specialInfo1 & 0x01U) == 0x01U); + return true; } @@ -80,7 +65,14 @@ void ISP_EMERG_ALRM_REQ::encode(uint8_t* data, bool rawTSBK, bool noTrellis) { assert(data != nullptr); - /* stub */ + ulong64_t tsbkValue = m_specialInfo1; // Emergency Special Information 1 + tsbkValue = (tsbkValue << 8) + m_specialInfo2; // Emergency Special Information 2 + tsbkValue = (tsbkValue << 8) + 0U; // Reserved + tsbkValue = (tsbkValue << 16) + (m_dstId & 0xFFFFU); // Group Address + tsbkValue = (tsbkValue << 24) + (m_srcId & 0xFFFFFFU); // Source Radio Address + + std::unique_ptr tsbk = TSBK::fromValue(tsbkValue); + TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis); } /* Returns a string that represents the current TSBK. */ diff --git a/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h b/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h index 3b115c70..8a3e7841 100644 --- a/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h +++ b/src/common/p25/lc/tsbk/ISP_EMERG_ALRM_REQ.h @@ -60,6 +60,16 @@ namespace p25 * @returns std::string String representation of the TSBK. */ std::string toString(bool isp = false) override; + + public: + /** + * @brief Emergency special-information octet 1. + */ + DECLARE_PROPERTY(uint8_t, specialInfo1, SpecialInfo1); + /** + * @brief Emergency special-information octet 2. + */ + DECLARE_PROPERTY(uint8_t, specialInfo2, SpecialInfo2); }; } // namespace tsbk } // namespace lc diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index fbeeeb47..2b546336 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -734,7 +734,8 @@ bool TagDMRData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId /* Process a grant request frame from the network. */ -bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, + uint8_t serviceOptions, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { if (unitToUnit && (!m_network->isUnitToUnitPeerAllowed(peerId) || !m_network->isUnitToUnitRouteAvailable(dstId))) { return false; @@ -789,11 +790,13 @@ bool TagDMRData::processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, b continue; if (unitToUnit && !m_network->isUnitToUnitPeerAllowed(peer.first)) continue; - write_CSBK_Grant(peer.first, srcId, dstId, 4U, !unitToUnit); + + write_CSBK_Grant(peer.first, srcId, dstId, serviceOptions, !unitToUnit); } } } + // check if the destination has been granted globally if (!m_network->m_globalAff->isGranted(dstId)) { if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) { return true; diff --git a/src/fne/network/callhandler/TagDMRData.h b/src/fne/network/callhandler/TagDMRData.h index cff48805..8cb5ec27 100644 --- a/src/fne/network/callhandler/TagDMRData.h +++ b/src/fne/network/callhandler/TagDMRData.h @@ -92,12 +92,14 @@ namespace network * @param dstId Destination ID. * @param slot DMR slot number. * @param unitToUnit Flag indicating whether or not this grant is for unit-to-unit traffic. + * @param serviceOptions DMR service-options octet. * @param peerId Peer ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @returns bool True, if the grant was processed, otherwise false. */ - bool processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + bool processGrantReq(uint32_t srcId, uint32_t dstId, uint8_t slot, bool unitToUnit, + uint8_t serviceOptions, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); /** * @brief Helper to trigger a call takeover from a In-Call control event. diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 89870784..1efa590c 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -744,7 +744,8 @@ bool TagNXDNData::processFrame(const uint8_t* data, uint32_t len, uint32_t peerI /* Process a grant request frame from the network. */ -bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions, + uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { if (unitToUnit && (!m_network->isUnitToUnitPeerAllowed(peerId) || !m_network->isUnitToUnitRouteAvailable(dstId))) { return false; @@ -799,11 +800,13 @@ bool TagNXDNData::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUni continue; if (unitToUnit && !m_network->isUnitToUnitPeerAllowed(peer.first)) continue; - write_Message_Grant(peer.first, srcId, dstId, 4U, !unitToUnit); + + write_Message_Grant(peer.first, srcId, dstId, serviceOptions, !unitToUnit); } } } + // check if the destination has been granted globally if (!m_network->m_globalAff->isGranted(dstId)) { if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) { return true; diff --git a/src/fne/network/callhandler/TagNXDNData.h b/src/fne/network/callhandler/TagNXDNData.h index 287a5c25..e269060c 100644 --- a/src/fne/network/callhandler/TagNXDNData.h +++ b/src/fne/network/callhandler/TagNXDNData.h @@ -91,12 +91,14 @@ namespace network * @param srcId Source Radio ID. * @param dstId Destination ID. * @param unitToUnit Flag indicating whether or not this grant is for unit-to-unit traffic. + * @param serviceOptions NXDN service-options octet. * @param peerId Peer ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @returns bool True, if the grant was processed, otherwise false. */ - bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions, + uint32_t peerId, uint16_t pktSeq, uint32_t streamId); /** * @brief Helper to trigger a call takeover from a In-Call control event. diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 3e483c86..2674a641 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -870,7 +870,8 @@ bool TagP25Data::processFrame(const uint8_t* data, uint32_t len, uint32_t peerId /* Process a grant request frame from the network. */ -bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId) +bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions, + uint32_t peerId, uint16_t pktSeq, uint32_t streamId) { if (unitToUnit && (!m_network->isUnitToUnitPeerAllowed(peerId) || !m_network->isUnitToUnitRouteAvailable(dstId))) { return false; @@ -925,11 +926,13 @@ bool TagP25Data::processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit continue; if (unitToUnit && !m_network->isUnitToUnitPeerAllowed(peer.first)) continue; - write_TSDU_Grant(peer.first, srcId, dstId, 4U, !unitToUnit); + + write_TSDU_Grant(peer.first, srcId, dstId, serviceOptions, !unitToUnit); } } } + // check if the destination has been granted globally if (!m_network->m_globalAff->isGranted(dstId)) { if (m_network->m_globalAff->grantCh(dstId, srcId, peerId, fne_lookups::GRANT_TIMER_TIMEOUT, !unitToUnit)) { return true; @@ -2277,7 +2280,7 @@ void TagP25Data::write_TSDU(uint32_t peerId, lc::TSBK* tsbk) lc.setDstId(tsbk->getDstId()); uint32_t messageLength = 0U; - UInt8Array message = m_network->createP25_TSDUMessage(messageLength, lc, data); + UInt8Array message = m_network->createP25_TSDUMessage(messageLength, lc, data, 0U); if (message == nullptr) { return; } diff --git a/src/fne/network/callhandler/TagP25Data.h b/src/fne/network/callhandler/TagP25Data.h index 104d5be4..bf50cf8a 100644 --- a/src/fne/network/callhandler/TagP25Data.h +++ b/src/fne/network/callhandler/TagP25Data.h @@ -97,12 +97,14 @@ namespace network * @param srcId Source Radio ID. * @param dstId Destination ID. * @param unitToUnit Flag indicating whether or not this grant is for unit-to-unit traffic. + * @param serviceOptions P25 service-options octet. * @param peerId Peer ID. * @param pktSeq RTP packet sequence. * @param streamId Stream ID. * @returns bool True, if the grant was processed, otherwise false. */ - bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint32_t peerId, uint16_t pktSeq, uint32_t streamId); + bool processGrantReq(uint32_t srcId, uint32_t dstId, bool unitToUnit, uint8_t serviceOptions, + uint32_t peerId, uint16_t pktSeq, uint32_t streamId); /** * @brief Helper to trigger a call takeover from a In-Call control event. diff --git a/src/fne/network/packet/GrantRequest.cpp b/src/fne/network/packet/GrantRequest.cpp index 808018e6..5c854ff1 100644 --- a/src/fne/network/packet/GrantRequest.cpp +++ b/src/fne/network/packet/GrantRequest.cpp @@ -41,11 +41,13 @@ void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPac bool unitToUnit = (req->buffer[19U] & 0x80U) == 0x80U; DVM_STATE state = (DVM_STATE)req->buffer[20U]; // DVM Mode State + uint8_t serviceOptions = req->buffer[21U]; // Service Options switch (state) { case STATE_DMR: if (network->m_dmrEnabled) { if (network->m_tagDMR != nullptr) { - network->m_tagDMR->processGrantReq(srcId, dstId, slot, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagDMR->processGrantReq(srcId, dstId, slot, unitToUnit, serviceOptions, + peerId, req->rtpHeader.getSequence(), streamId); } else { network->writePeerNAK(peerId, streamId, TAG_DMR_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } @@ -54,7 +56,8 @@ void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPac case STATE_P25: if (network->m_p25Enabled) { if (network->m_tagP25 != nullptr) { - network->m_tagP25->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagP25->processGrantReq(srcId, dstId, unitToUnit, serviceOptions, + peerId, req->rtpHeader.getSequence(), streamId); } else { network->writePeerNAK(peerId, streamId, TAG_P25_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } @@ -63,7 +66,8 @@ void TrafficNetwork::PacketHandler::grantRequest(TrafficNetwork* network, NetPac case STATE_NXDN: if (network->m_nxdnEnabled) { if (network->m_tagNXDN != nullptr) { - network->m_tagNXDN->processGrantReq(srcId, dstId, unitToUnit, peerId, req->rtpHeader.getSequence(), streamId); + network->m_tagNXDN->processGrantReq(srcId, dstId, unitToUnit, serviceOptions, + peerId, req->rtpHeader.getSequence(), streamId); } else { network->writePeerNAK(peerId, streamId, TAG_NXDN_DATA, NET_CONN_NAK_MODE_NOT_ENABLED); } diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index d999a3cd..3f46795d 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -213,7 +213,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) } else { writeRF_CSBK_ACK_RSP(srcId, ReasonCode::TS_WAIT_RSN, 0U); if (m_slot->s_network != nullptr) - m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, true); + m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, + srcId, dstId, m_slot->m_slotNo, true, isp->getServiceOptions()); } break; case ServiceKind::CANCEL_CALL: @@ -238,7 +239,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len) writeRF_CSBK_Grant(srcId, dstId, isp->getServiceOptions(), true); } else { if (m_slot->s_network != nullptr) - m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, m_slot->m_slotNo, false); + m_slot->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, + srcId, dstId, m_slot->m_slotNo, false, isp->getServiceOptions()); } break; case ServiceKind::IND_DATA_CALL: @@ -1261,8 +1263,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_ // sites; ask the FNE to demand a site-local grant for the called // subscriber rather than forwarding this site's channel number if (!net && tscc->s_network != nullptr) { - tscc->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, - srcId, dstId, slot, true); + tscc->s_network->writeGrantReq(modem::DVM_STATE::STATE_DMR, srcId, dstId, slot, true, serviceOptions); } // if the channel granted isn't the same as the TSCC; remote activate the payload channel diff --git a/src/host/nxdn/packet/ControlSignaling.cpp b/src/host/nxdn/packet/ControlSignaling.cpp index 8f0a8e24..7baf5b53 100644 --- a/src/host/nxdn/packet/ControlSignaling.cpp +++ b/src/host/nxdn/packet/ControlSignaling.cpp @@ -192,7 +192,7 @@ bool ControlSignaling::process(FuncChannelType::E fct, ChOption::E option, uint8 writeRF_Message_Grant(srcId, dstId, serviceOptions, group); } else { if (m_nxdn->m_network != nullptr) - m_nxdn->m_network->writeGrantReq(modem::DVM_STATE::STATE_NXDN, srcId, dstId, 0U, !group); + m_nxdn->m_network->writeGrantReq(modem::DVM_STATE::STATE_NXDN, srcId, dstId, 0U, !group, serviceOptions); } } break; diff --git a/src/host/p25/Control.cpp b/src/host/p25/Control.cpp index f610addb..ccf3e386 100644 --- a/src/host/p25/Control.cpp +++ b/src/host/p25/Control.cpp @@ -1503,6 +1503,7 @@ void Control::processNetwork() bool grantDenial = (buffer[14U] & network::NET_CTRL_GRANT_DENIAL) == network::NET_CTRL_GRANT_DENIAL; bool grantEncrypt = (buffer[14U] & network::NET_CTRL_GRANT_ENCRYPT) == network::NET_CTRL_GRANT_ENCRYPT; bool unitToUnit = (buffer[14U] & network::NET_CTRL_U2U) == network::NET_CTRL_U2U; + bool inbound = (buffer[14U] & network::NET_CTRL_ISP) == network::NET_CTRL_ISP; // process network message header DUID::E duid = (DUID::E)buffer[22U]; @@ -1811,7 +1812,7 @@ void Control::processNetwork() break; case DUID::TSDU: - m_control->processNetwork(data.get(), frameLength, control, lsd, duid); + m_control->processNetwork(data.get(), frameLength, control, lsd, duid, inbound); break; default: diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index 336f8d6b..2e128e21 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -166,9 +166,6 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_enableControl) - return false; - DUID::E duid = DUID::HDU; if (preDecodedTSBK == nullptr) { // Decode the NID @@ -222,6 +219,17 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetSrcId(); uint32_t dstId = tsbk->getDstId(); + // check if control signaling is enabled and if the TSBK is not one of the allowed exceptions + // if control signaling is disabled and the TSBK is not an allowed exception, ignore it + if (!m_p25->m_enableControl && + tsbk->getLCO() != TSBKO::IOSP_CALL_ALRT && + tsbk->getLCO() != TSBKO::IOSP_ACK_RSP && + tsbk->getLCO() != TSBKO::ISP_EMERG_ALRM_REQ && + tsbk->getLCO() != TSBKO::ISP_CAN_SRV_REQ) { + m_p25->m_rfState = prevRfState; + return false; + } + m_p25->m_affiliations->touchUnitReg(srcId); m_lastMFID = tsbk->getMFId(); @@ -242,15 +250,16 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(true), TSBKO::IOSP_GRP_VCH, srcId, dstId); VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); - if (m_p25->m_authoritative) { - uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag - (tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag - (tsbk->getPriority() & 0x07U); // Priority + uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + // Emergency Flag + (tsbk->getEncrypted() ? 0x40U : 0x00U) + // Encrypted Flag + (tsbk->getPriority() & 0x07U); // Priority + if (m_p25->m_authoritative) { writeRF_TSDU_Grant(srcId, dstId, serviceOptions, true); } else { if (m_p25->m_network != nullptr) - m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, false); + m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, + srcId, dstId, 0U, false, serviceOptions); } } break; @@ -287,7 +296,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_network != nullptr) { m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, - srcId, dstId, 0U, true); + srcId, dstId, 0U, true, serviceOptions); } break; } @@ -305,7 +314,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_network != nullptr) - m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true); + m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, + srcId, dstId, 0U, true, serviceOptions); } } } @@ -356,8 +366,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_network != nullptr) - m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, - srcId, dstId, 0U, true); + m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true, transaction.serviceOptions); } m_u2uAvailabilityTransactions.erase(transactionIt); @@ -591,6 +600,42 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetService(); + if (service == TSBKO::ISP_EMERG_ALRM_REQ) { + VALID_SRCID(tsbk->toString(true), TSBKO::ISP_CAN_SRV_REQ, srcId); + + if (m_p25->m_enableControl && !m_p25->m_affiliations->isUnitReg(srcId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, emergency alarm cancellation from unregistered RID, srcId = %u", + tsbk->toString(true).c_str(), srcId); + writeRF_TSDU_Deny(srcId, WUID_FNE, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, + TSBKO::ISP_EMERG_ALRM_REQ); + writeRF_TSDU_U_Reg_Cmd(srcId); + m_p25->m_rfState = RS_RF_REJECTED; + return false; + } + + // validate the emergency alarm cancellation request + const uint32_t additionalInfo = isp->getDstId(); + const bool validAlarmCancel = isp->getAIV() && isp->getResponse() == 0U && (additionalInfo & 0x800000U) == 0x800000U; + + // extract the group ID from the additional info field + const uint32_t groupId = additionalInfo & 0xFFFFU; + if (!validAlarmCancel || !acl::AccessControl::validateTGId(groupId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, invalid emergency alarm cancellation, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), srcId, groupId); + writeRF_TSDU_Deny(srcId, groupId, ReasonCode::DENY_TGT_GROUP_NOT_VALID, TSBKO::ISP_EMERG_ALRM_REQ, true, true); + m_p25->m_rfState = RS_RF_REJECTED; + return false; + } + + RF_TO_WRITE_NET(isp); + + writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_EMERG_ALRM_REQ, false, true); + + ::ActivityLog("P25", true, "emergency alarm cancellation from %u for TG %u", srcId, groupId); + break; + } + + // handle telephone interconnect services if (m_telephoneInterconnect && (service == TSBKO::IOSP_TELE_INT_DIAL || service == TSBKO::ISP_TELE_INT_PSTN_REQ || service == TSBKO::IOSP_TELE_INT_ANS)) { RF_TO_WRITE_NET(isp); @@ -659,15 +704,40 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr(tsbk.get()); - if (isp->getEmergency()) { - VERBOSE_LOG_TSBK(tsbk->toString(true), srcId, dstId); + isp->setEmergency(true); - ::ActivityLog("P25", true, "emergency alarm request request from %u", srcId); + // validate the source RID + VALID_SRCID(tsbk->toString(true), TSBKO::ISP_EMERG_ALRM_REQ, srcId); - // emergency functions are expressly not supported by DVM -- DVM will *ACKNOWLEDGE* the request but will not do any - // further processing with it - writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_EMERG_ALRM_REQ, false, true); + // validate the target RID + VALID_TGID(tsbk->toString(true), TSBKO::ISP_EMERG_ALRM_REQ, srcId, dstId); + + // check if control is enabled and the source unit is registered + if (m_p25->m_enableControl) { + if (!m_p25->m_affiliations->isUnitReg(srcId)) { + LogWarning(LOG_RF, P25_TSDU_STR ", %s denial, emergency alarm from unregistered RID, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), srcId, dstId); + + writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_REQ_UNIT_NOT_AUTH, TSBKO::ISP_EMERG_ALRM_REQ, true, true); + writeRF_TSDU_U_Reg_Cmd(srcId); + m_p25->m_rfState = RS_RF_REJECTED; + return false; + } + + VERIFY_SRCID_AFF(tsbk->toString(true), TSBKO::ISP_EMERG_ALRM_REQ, srcId, dstId); } + + if (m_verbose) { + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, specialInfo1 = $%02X, specialInfo2 = $%02X, srcId = %u, dstId = %u", + tsbk->toString(true).c_str(), isp->getSpecialInfo1(), isp->getSpecialInfo2(), srcId, dstId); + } + + RF_TO_WRITE_NET(isp); + + writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_EMERG_ALRM_REQ, false, true); + + ::ActivityLog("P25", true, "emergency alarm request from %u for TG %u, specialInfo1 = $%02X, specialInfo2 = $%02X", + srcId, dstId, isp->getSpecialInfo1(), isp->getSpecialInfo2()); } break; case TSBKO::IOSP_GRP_AFF: @@ -865,9 +935,10 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_enableControl) + if (!m_p25->m_enableControl && duid != DUID::TSDU) return false; if (m_p25->m_rfState != RS_RF_LISTENING && m_p25->m_netState == RS_NET_IDLE) return false; @@ -882,6 +953,23 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr return false; } + // handle emergency alarm requests and cancellations + if (inbound && tsbk->getLCO() == TSBKO::ISP_EMERG_ALRM_REQ) { + tsbk->setEmergency(true); + } + + // if control is not enabled, only allow emergency alarm and cancel requests + if (!m_p25->m_enableControl) { + const bool ack = inbound && tsbk->getLCO() == TSBKO::IOSP_ACK_RSP; + const bool callAlert = inbound && tsbk->getLCO() == TSBKO::IOSP_CALL_ALRT; + const bool emergencyAlarm = inbound && tsbk->getLCO() == TSBKO::ISP_EMERG_ALRM_REQ; + const bool emergencyCancel = inbound && tsbk->getLCO() == TSBKO::ISP_CAN_SRV_REQ && tsbk->getService() == TSBKO::ISP_EMERG_ALRM_REQ; + if (!ack && !callAlert && !emergencyAlarm && !emergencyCancel) { + return false; + } + } + + // reject network unit-to-unit signaling if disallowed by the network if (m_p25->m_network != nullptr && m_p25->m_network->disallowUnitToUnit()) { switch (tsbk->getLCO()) { case TSBKO::IOSP_UU_VCH: @@ -1044,8 +1132,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr writeRF_TSDU_Grant(srcId, dstId, transaction.serviceOptions, false, false, false, 0U, false, srcId); } else if (m_p25->m_network != nullptr) { - m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, - srcId, dstId, 0U, true); + m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true, transaction.serviceOptions); } m_u2uAvailabilityTransactions.erase(transactionIt); @@ -1223,12 +1310,11 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr break; case TSBKO::ISP_EMERG_ALRM_REQ: { - // non-emergency mode is a TSBKO::OSP_DENY_RSP - if (!tsbk->getEmergency()) { - break; // the FNE may explicitly send these - } else { + if (inbound) { VERBOSE_LOG_TSBK_NET(tsbk->toString(true), srcId, dstId); - return true; // don't allow this to write to the air + if (m_p25->m_enableControl) { + return true; // do not repeat an inbound request on a trunking control channel + } } } break; @@ -1633,7 +1719,7 @@ void ControlSignaling::writeNetworkRF(lc::TSBK* tsbk, const uint8_t* data, bool lc.setSrcId(tsbk->getSrcId()); lc.setDstId(tsbk->getDstId()); - m_p25->m_network->writeP25TSDU(lc, data); + m_p25->m_network->writeP25TSDU(lc, data, network::NET_CTRL_ISP); if (autoReset) m_p25->m_network->resetP25(); } @@ -1756,11 +1842,12 @@ void ControlSignaling::writeNet_TDULC(lc::TDULC* lc) void ControlSignaling::writeRF_TSDU_SBF(lc::TSBK* tsbk, bool noNetwork, bool forceSingle, bool imm) { - if (!m_p25->m_enableControl) - return; - assert(tsbk != nullptr); + if (!m_p25->m_enableControl && tsbk->getLCO() != TSBKO::IOSP_ACK_RSP && + tsbk->getLCO() != TSBKO::ISP_EMERG_ALRM_REQ) + return; + uint8_t data[P25_TSDU_FRAME_LENGTH_BYTES + 2U]; ::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES); @@ -3196,10 +3283,13 @@ void ControlSignaling::writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId) /* Helper to write a acknowledge packet. */ -void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool extended, bool noNetwork) +void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork) { std::unique_ptr iosp = std::make_unique(); - iosp->setSrcId(srcId); + iosp->setAIV(true); + iosp->setEX(false); + iosp->setSrcId(dstId); + iosp->setDstId(WUID_SYSTEM); iosp->setService(service); if (extended) { @@ -3208,8 +3298,8 @@ void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bo } if (m_verbose) { - LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u", - iosp->toString().c_str(), iosp->getAIV(), iosp->getEX(), iosp->getService(), srcId); + LogInfoEx(LOG_RF, P25_TSDU_STR ", %s, AIV = %u, EX = %u, serviceType = $%02X, srcId = %u, dstId = %u", + iosp->toString().c_str(), iosp->getAIV(), iosp->getEX(), iosp->getService(), WUID_SYSTEM, dstId); } writeRF_TSDU_SBF_Imm(iosp.get(), noNetwork); diff --git a/src/host/p25/packet/ControlSignaling.h b/src/host/p25/packet/ControlSignaling.h index 43370277..b737040e 100644 --- a/src/host/p25/packet/ControlSignaling.h +++ b/src/host/p25/packet/ControlSignaling.h @@ -72,9 +72,11 @@ namespace p25 * @param control Link Control Data. * @param lsd Low Speed Data. * @param duid Data Unit ID. + * @param inbound Flag indicating an inbound service packet received from RF. * @returns bool True, if data frame is processed, otherwise false. */ - bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, defines::DUID::E& duid); + bool processNetwork(uint8_t* data, uint32_t len, lc::LC& control, data::LowSpeedData& lsd, + defines::DUID::E& duid, bool inbound = false); /** @} */ /** @@ -414,9 +416,9 @@ namespace p25 * @param srcId Source Radio ID. * @param service Service being acknowledged. * @param extended Flag indicating this is an extended acknowledgement. - * @param noActivityLog + * @param noNetwork Flag indicating the acknowledgement must not be forwarded to the network. */ - void writeRF_TSDU_ACK_FNE(uint32_t srcId, uint32_t service, bool extended, bool noActivityLog); + void writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork); /** * @brief Helper to write a deny packet. * @param srcId Source Radio ID. diff --git a/tests/fne/FNETestHooks.cpp b/tests/fne/FNETestHooks.cpp index fed666b4..99d4599e 100644 --- a/tests/fne/FNETestHooks.cpp +++ b/tests/fne/FNETestHooks.cpp @@ -303,19 +303,19 @@ bool FNETestHooks::validateP25IndividualCall(TrafficNetwork& network, uint32_t p /* Processes a synthetic DMR FNE grant request. */ bool FNETestHooks::processDMRGrantRequest(TrafficNetwork& network, uint32_t peerId, - uint32_t sourceId, uint32_t destinationId, bool unitToUnit) + uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions) { return network.m_tagDMR->processGrantReq( - sourceId, destinationId, 1U, unitToUnit, peerId, 1U, 105U); + sourceId, destinationId, 1U, unitToUnit, serviceOptions, peerId, 1U, 105U); } /* Processes a synthetic P25 FNE grant request. */ bool FNETestHooks::processP25GrantRequest(TrafficNetwork& network, uint32_t peerId, - uint32_t sourceId, uint32_t destinationId, bool unitToUnit) + uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions) { return network.m_tagP25->processGrantReq( - sourceId, destinationId, unitToUnit, peerId, 1U, 107U); + sourceId, destinationId, unitToUnit, serviceOptions, peerId, 1U, 107U); } /* Processes a synthetic P25 Phase 2 private voice frame. */ @@ -336,10 +336,10 @@ bool FNETestHooks::processP25P2PrivateFrame(TrafficNetwork& network, uint32_t pe /* Processes a synthetic NXDN FNE grant request. */ bool FNETestHooks::processNXDNGrantRequest(TrafficNetwork& network, uint32_t peerId, - uint32_t sourceId, uint32_t destinationId, bool unitToUnit) + uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions) { return network.m_tagNXDN->processGrantReq( - sourceId, destinationId, unitToUnit, peerId, 1U, 106U); + sourceId, destinationId, unitToUnit, serviceOptions, peerId, 1U, 106U); } /* Sets whether unit-to-unit traffic is globally disabled. */ diff --git a/tests/fne/FNETestHooks.h b/tests/fne/FNETestHooks.h index f5fba211..a1824415 100644 --- a/tests/fne/FNETestHooks.h +++ b/tests/fne/FNETestHooks.h @@ -181,10 +181,11 @@ public: * @param sourceId Calling subscriber ID. * @param destinationId Called subscriber or talkgroup ID. * @param unitToUnit Whether the request is an individual call. + * @param serviceOptions DMR service-options octet. * @return True when the request is accepted. */ static bool processDMRGrantRequest(network::TrafficNetwork& network, uint32_t peerId, - uint32_t sourceId, uint32_t destinationId, bool unitToUnit); + uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions = 4U); /** * @brief Processes a synthetic P25 FNE grant request. * @param network The TrafficNetwork instance. @@ -192,10 +193,11 @@ public: * @param sourceId Calling subscriber ID. * @param destinationId Called subscriber or talkgroup ID. * @param unitToUnit Whether the request is an individual call. + * @param serviceOptions P25 service-options octet. * @return True when the request is accepted. */ static bool processP25GrantRequest(network::TrafficNetwork& network, uint32_t peerId, - uint32_t sourceId, uint32_t destinationId, bool unitToUnit); + uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions = 4U); /** * @brief Processes a synthetic P25 Phase 2 private voice frame. * @param network The TrafficNetwork instance. @@ -214,10 +216,11 @@ public: * @param sourceId Calling subscriber ID. * @param destinationId Called subscriber or talkgroup ID. * @param unitToUnit Whether the request is an individual call. + * @param serviceOptions NXDN service-options octet. * @return True when the request is accepted. */ static bool processNXDNGrantRequest(network::TrafficNetwork& network, uint32_t peerId, - uint32_t sourceId, uint32_t destinationId, bool unitToUnit); + uint32_t sourceId, uint32_t destinationId, bool unitToUnit, uint8_t serviceOptions = 4U); /** * @brief Sets whether unit-to-unit traffic is globally disabled. * @param network The TrafficNetwork instance. diff --git a/tests/p25/HostControl_Tests.cpp b/tests/p25/HostControl_Tests.cpp index 0a60afd6..8ad6a6e9 100644 --- a/tests/p25/HostControl_Tests.cpp +++ b/tests/p25/HostControl_Tests.cpp @@ -195,6 +195,46 @@ void buildCancelServiceRequest(uint32_t srcId, uint8_t serviceType, uint8_t* raw edac::CRC::addCCITT162(raw, p25::defines::P25_TSBK_LENGTH_BYTES); } +/** + * @brief Builds an independently encoded emergency alarm request ISP TSBK. + * @param srcId RID declaring the emergency alarm. + * @param dstId Group associated with the emergency alarm. + * @param specialInfo1 Emergency Special Information octet 1. + * @param specialInfo2 Emergency Special Information octet 2. + * @param raw Buffer to store the constructed TSBK. + */ +void buildEmergencyAlarmRequest(uint32_t srcId, uint16_t dstId, uint8_t specialInfo1, + uint8_t specialInfo2, uint8_t* raw) +{ + ::memset(raw, 0x00U, p25::defines::P25_TSBK_LENGTH_BYTES); + raw[0U] = 0x80U | p25::defines::TSBKO::ISP_EMERG_ALRM_REQ; + raw[1U] = p25::defines::MFG_STANDARD; + raw[2U] = specialInfo1; + raw[3U] = specialInfo2; + SET_UINT16(dstId, raw, 5U); + SET_UINT24(srcId, raw, 7U); + edac::CRC::addCCITT162(raw, p25::defines::P25_TSBK_LENGTH_BYTES); +} + +/** + * @brief Builds an independently encoded emergency alarm cancellation ISP TSBK. + * @param srcId RID cancelling its emergency alarm. + * @param dstId Group associated with the emergency alarm. + * @param raw Buffer to store the constructed TSBK. + */ +void buildEmergencyAlarmCancel(uint32_t srcId, uint16_t dstId, uint8_t* raw) +{ + ::memset(raw, 0x00U, p25::defines::P25_TSBK_LENGTH_BYTES); + raw[0U] = 0x80U | p25::defines::TSBKO::ISP_CAN_SRV_REQ; + raw[1U] = p25::defines::MFG_STANDARD; + raw[2U] = 0x80U | p25::defines::TSBKO::ISP_EMERG_ALRM_REQ; + raw[3U] = 0x00U; + raw[4U] = 0x80U; + SET_UINT16(dstId, raw, 5U); + SET_UINT24(srcId, raw, 7U); + edac::CRC::addCCITT162(raw, p25::defines::P25_TSBK_LENGTH_BYTES); +} + /** Builds an independently encoded UU ANS request OSP TSBK. */ void buildUUAnswerRequest(uint32_t callerId, uint32_t calledId, uint8_t* raw) { @@ -805,6 +845,62 @@ TEST_CASE("P25 active unit-to-unit call continues with a grant instead of anothe CHECK(HostTestHooks::p25U2UGrantTarget(*harness.m_control, caller, called) == called); } +TEST_CASE("P25 conventional host accepts and acknowledges an emergency alarm TSBK", + "[p25][host][control][emergency][conventional]") +{ + P25HostHarness harness; + const uint32_t srcId = 1001U; + const uint16_t dstId = 2001U; + uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES]; + uint8_t response[p25::defines::P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + + buildEmergencyAlarmRequest(srcId, dstId, 0x00U, 0x01U, raw); + REQUIRE(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw)); + + CHECK(harness.m_control->getFrame(response) > 0U); +} + +TEST_CASE("P25 trunking host rejects an emergency alarm from an unregistered unit", + "[p25][host][control][emergency][trunking]") +{ + P25HostHarness harness; + const uint32_t srcId = 1001U; + const uint16_t dstId = 2001U; + uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES]; + + HostTestHooks::p25ConfigureU2U(*harness.m_control, false); + buildEmergencyAlarmRequest(srcId, dstId, 0x00U, 0x00U, raw); + CHECK_FALSE(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw)); +} + +TEST_CASE("P25 conventional host accepts and acknowledges emergency alarm cancellation", + "[p25][host][control][emergency][conventional]") +{ + P25HostHarness harness; + const uint32_t srcId = 1001U; + const uint16_t dstId = 2001U; + uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES]; + uint8_t response[p25::defines::P25_TSDU_FRAME_LENGTH_BYTES + 2U]; + + buildEmergencyAlarmCancel(srcId, dstId, raw); + REQUIRE(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw)); + CHECK(harness.m_control->getFrame(response) > 0U); +} + +TEST_CASE("P25 trunking host accepts an emergency alarm from a registered unit", + "[p25][host][control][emergency][trunking]") +{ + P25HostHarness harness; + const uint32_t srcId = 1001U; + const uint16_t dstId = 2001U; + uint8_t raw[p25::defines::P25_TSBK_LENGTH_BYTES]; + + HostTestHooks::p25ConfigureU2U(*harness.m_control, false); + HostTestHooks::p25RegisterUnit(*harness.m_control, srcId); + buildEmergencyAlarmRequest(srcId, dstId, 0x01U, 0x00U, raw); + CHECK(HostTestHooks::p25ProcessRawTSBK(*harness.m_control, raw)); +} + TEST_CASE("P25 unit-to-unit cancellation cleanup releases only private call state", "[p25][host][control][u2u][procedure][cancel]") { diff --git a/tests/p25/TSBK_Tests.cpp b/tests/p25/TSBK_Tests.cpp index 51ee86c4..ba6a5893 100644 --- a/tests/p25/TSBK_Tests.cpp +++ b/tests/p25/TSBK_Tests.cpp @@ -33,6 +33,79 @@ TEST_CASE("TSBK", "[p25][tsbk]") { REQUIRE(P25_TSBK_FEC_LENGTH_BITS == (P25_TSBK_FEC_LENGTH_BYTES * 8 - 4)); // 196 bits (Trellis) } + SECTION("Emergency_Alarm_Request_Roundtrip") { + ISP_EMERG_ALRM_REQ source; + source.setSpecialInfo1(0x01U); + source.setSpecialInfo2(0x00U); + source.setDstId(0x2345U); + source.setSrcId(0x654321U); + + uint8_t raw[P25_TSBK_LENGTH_BYTES]; + ::memset(raw, 0x00U, sizeof(raw)); + source.encode(raw, true, true); + + const uint16_t groupId = GET_UINT16(raw, 5U); + const uint32_t srcId = GET_UINT24(raw, 7U); + REQUIRE((raw[0U] & 0x3FU) == TSBKO::ISP_EMERG_ALRM_REQ); + REQUIRE(raw[2U] == 0x01U); + REQUIRE(raw[3U] == 0x00U); + REQUIRE(raw[4U] == 0x00U); + REQUIRE(groupId == 0x2345U); + REQUIRE(srcId == 0x654321U); + + std::unique_ptr decoded = TSBKFactory::createTSBK(raw, true); + REQUIRE(decoded != nullptr); + ISP_EMERG_ALRM_REQ* alarm = dynamic_cast(decoded.get()); + REQUIRE(alarm != nullptr); + CHECK(alarm->getEmergency()); + CHECK(alarm->getSpecialInfo1() == 0x01U); + CHECK(alarm->getSpecialInfo2() == 0x00U); + CHECK(alarm->getDstId() == 0x2345U); + CHECK(alarm->getSrcId() == 0x654321U); + } + + SECTION("Emergency_Alarm_Cancel_Roundtrip") { + ISP_CAN_SRV_REQ source; + source.setAIV(true); + source.setService(TSBKO::ISP_EMERG_ALRM_REQ); + source.setResponse(0U); + source.setDstId(0x800000U | 0x2345U); + source.setSrcId(0x654321U); + + uint8_t raw[P25_TSBK_LENGTH_BYTES]; + ::memset(raw, 0x00U, sizeof(raw)); + source.encode(raw, true, true); + + std::unique_ptr decoded = TSBKFactory::createTSBK(raw, true); + REQUIRE(decoded != nullptr); + ISP_CAN_SRV_REQ* cancel = dynamic_cast(decoded.get()); + REQUIRE(cancel != nullptr); + CHECK(cancel->getAIV()); + CHECK(cancel->getService() == TSBKO::ISP_EMERG_ALRM_REQ); + CHECK(cancel->getResponse() == 0U); + CHECK(cancel->getDstId() == (0x800000U | 0x2345U)); + CHECK(cancel->getSrcId() == 0x654321U); + } + + SECTION("Emergency_Alarm_ACK_Uses_System_Default_Source") { + IOSP_ACK_RSP ack; + ack.setAIV(true); + ack.setEX(false); + ack.setService(TSBKO::ISP_EMERG_ALRM_REQ); + ack.setDstId(WUID_SYSTEM); + ack.setSrcId(0x654321U); + + uint8_t raw[P25_TSBK_LENGTH_BYTES]; + ::memset(raw, 0x00U, sizeof(raw)); + ack.encode(raw, true, true); + + const uint32_t sourceId = GET_UINT24(raw, 4U); + const uint32_t targetId = GET_UINT24(raw, 7U); + CHECK((raw[2U] & 0xC0U) == 0x80U); + CHECK(targetId == 0x654321U); + CHECK(sourceId == WUID_SYSTEM); + } + SECTION("Standard_Unconfirmed_MBT_TELE_INT_DIAL_REQ") { const uint8_t counts[] = { 1U, 10U, 11U, 34U }; for (uint8_t count : counts) {