diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp index bffc2a32..06567f50 100644 --- a/src/host/p25/packet/ControlSignaling.cpp +++ b/src/host/p25/packet/ControlSignaling.cpp @@ -282,9 +282,11 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetPriority() & 0x07U); if (m_p25->m_affiliations->isU2UGranted(srcId, dstId)) { - // Control-channel continuation of an active message-trunked U2U call; + // control-channel continuation of an active message-trunked U2U call; // address the existing assignment to the requesting SU and do not // restart target availability checking + clearMFId(srcId, dstId, TSBKO::IOSP_UU_VCH); + clearMFId(dstId, srcId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(u2uTransactionKey(srcId, dstId)); m_u2uAvailabilityTransactions.erase(u2uTransactionKey(dstId, srcId)); if (m_p25->isGrantAuthoritative()) { @@ -299,14 +301,14 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrgetMFId()); writeRF_TSDU_UU_Ans_Req(srcId, dstId); } else { // some SUs use manual-answer signaling even when the FNE grants an unchecked // U2U request immediately; retain correlation state so a later PROCEED can // reissue a grant that expired while the called unit was ringing - startU2UAvailabilityTransaction(srcId, dstId, serviceOptions, true, false); + startU2UAvailabilityTransaction(srcId, dstId, serviceOptions, true, false, tsbk->getMFId()); if (m_p25->isGrantAuthoritative()) { writeRF_TSDU_Grant(srcId, dstId, serviceOptions, false); } else { @@ -348,11 +350,16 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(true).c_str(), srcId, dstId); + clearMFId(transaction.callerId, transaction.calledId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(transactionIt); writeNetworkRF(iosp, data + 2U, true); break; } + const uint8_t previousMFId = m_lastMFID; + m_lastMFID = transactionMFId(transaction.callerId, transaction.calledId, + TSBKO::IOSP_UU_VCH, previousMFId); + // handle the response for the unit-to-unit availability request if (iosp->getResponse() == ResponseCode::ANS_PROCEED) { if (m_p25->isGrantAuthoritative()) { @@ -363,11 +370,13 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrm_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true, transaction.serviceOptions); } + clearMFId(transaction.callerId, transaction.calledId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(transactionIt); } else if (iosp->getResponse() == ResponseCode::ANS_DENY) { // handle the denial response for the unit-to-unit availability request writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_TGT_UNIT_REFUSED, TSBKO::IOSP_UU_ANS); + clearMFId(transaction.callerId, transaction.calledId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(transactionIt); } else if (iosp->getResponse() == ResponseCode::ANS_WAIT) { @@ -381,6 +390,8 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrtoString(true).c_str(), iosp->getResponse(), srcId, dstId); } + m_lastMFID = previousMFId; + // preserve and forward the received ISP response; re-encoding // IOSP_UU_ANS would incorrectly produce the OSP request layout writeNetworkRF(iosp, data + 2U, true); @@ -644,6 +655,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrsecond.callerId == srcId || it->second.calledId == srcId) { + clearMFId(it->second.callerId, it->second.calledId, TSBKO::IOSP_UU_VCH); it = m_u2uAvailabilityTransactions.erase(it); } else { @@ -777,6 +789,7 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptrsecond.responseTimer.hasExpired()) { if (transactionIt != m_u2uAvailabilityTransactions.end()) { + clearMFId(transactionIt->second.callerId, transactionIt->second.calledId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(transactionIt); } @@ -1149,6 +1163,9 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr // process the response based on the received ISP code U2UAvailabilityTransaction& transaction = transactionIt->second; + const uint8_t previousMFId = m_lastMFID; + m_lastMFID = transactionMFId(transaction.callerId, transaction.calledId, + TSBKO::IOSP_UU_VCH, previousMFId); // handle the response based on the ISP code if (iosp->getResponse() == ResponseCode::ANS_PROCEED) { @@ -1161,11 +1178,13 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr m_p25->m_network->writeGrantReq(modem::DVM_STATE::STATE_P25, srcId, dstId, 0U, true, transaction.serviceOptions); } + clearMFId(transaction.callerId, transaction.calledId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(transactionIt); } else if (iosp->getResponse() == ResponseCode::ANS_DENY) { // handle the denial response by sending a deny packet to the source ID writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_TGT_UNIT_REFUSED, TSBKO::IOSP_UU_ANS); + clearMFId(transaction.callerId, transaction.calledId, TSBKO::IOSP_UU_VCH); m_u2uAvailabilityTransactions.erase(transactionIt); } else if (iosp->getResponse() == ResponseCode::ANS_WAIT) { @@ -1175,6 +1194,8 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr transaction.responseTimer.start(); } + m_lastMFID = previousMFId; + return true; } else { @@ -1188,7 +1209,7 @@ bool ControlSignaling::processNetwork(uint8_t* data, uint32_t len, lc::LC& contr const uint8_t serviceOptions = (tsbk->getEmergency() ? 0x80U : 0x00U) + (tsbk->getEncrypted() ? 0x40U : 0x00U) + (tsbk->getPriority() & 0x07U); - startU2UAvailabilityTransaction(srcId, dstId, serviceOptions, false, true); + startU2UAvailabilityTransaction(srcId, dstId, serviceOptions, false, true, tsbk->getMFId()); } } break; @@ -1441,9 +1462,12 @@ void ControlSignaling::writeRF_TSDU_Call_Alrt(uint32_t srcId, uint32_t dstId) iosp->setSrcId(srcId); iosp->setDstId(dstId); - if (m_lastMFID != MFG_STANDARD) { - iosp->setMFId(m_lastMFID); - m_lastMFID = MFG_STANDARD; + const bool retainedMFId = hasMFIdTransaction(srcId, dstId, TSBKO::IOSP_CALL_ALRT); + const uint8_t mfId = transactionMFId(srcId, dstId, TSBKO::IOSP_CALL_ALRT, m_lastMFID); + if (mfId != MFG_STANDARD) { + iosp->setMFId(mfId); + if (!retainedMFId) + m_lastMFID = MFG_STANDARD; } VERBOSE_LOG_TSBK(iosp->toString(), srcId, dstId); @@ -1479,9 +1503,12 @@ void ControlSignaling::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32 iosp->setSrcId(arg); iosp->setDstId(dstId); - if (m_lastMFID != MFG_STANDARD) { - iosp->setMFId(m_lastMFID); - m_lastMFID = MFG_STANDARD; + const bool retainedMFId = hasMFIdTransaction(arg, dstId, TSBKO::IOSP_EXT_FNCT); + const uint8_t mfId = transactionMFId(arg, dstId, TSBKO::IOSP_EXT_FNCT, m_lastMFID); + if (mfId != MFG_STANDARD) { + iosp->setMFId(mfId); + if (!retainedMFId) + m_lastMFID = MFG_STANDARD; } // class $02 is Motorola -- set the MFID properly @@ -1533,8 +1560,12 @@ void ControlSignaling::writeRF_TSDU_Grp_Aff_Q(uint32_t dstId) } m_groupAffQueryTransactions.erase(dstId); + clearMFId(WUID_FNE, dstId, TSBKO::OSP_GRP_AFF_Q); + const uint8_t transactionMFId = m_lastMFID; m_groupAffQueryTransactions.emplace(dstId, GroupAffQueryTransaction(GROUP_AFF_QUERY_TIMER_TIMEOUT)); writeRF_TSDU_Grp_Aff_Q_Once(dstId); + + storeMFId(WUID_FNE, dstId, TSBKO::OSP_GRP_AFF_Q, transactionMFId, GROUP_AFF_QUERY_TIMER_TIMEOUT * (GROUP_AFF_QUERY_MAX_RETRIES + 1U)); } /* Emits a group affiliation query without changing transaction state. */ @@ -1545,9 +1576,13 @@ void ControlSignaling::writeRF_TSDU_Grp_Aff_Q_Once(uint32_t dstId) osp->setSrcId(WUID_FNE); osp->setDstId(dstId); - if (m_lastMFID != MFG_STANDARD) { - osp->setMFId(m_lastMFID); - m_lastMFID = MFG_STANDARD; + const bool retainedMFId = hasMFIdTransaction(WUID_FNE, dstId, TSBKO::OSP_GRP_AFF_Q); + const uint8_t mfId = transactionMFId(WUID_FNE, dstId, TSBKO::OSP_GRP_AFF_Q, m_lastMFID); + if (mfId != MFG_STANDARD) { + osp->setMFId(mfId); + if (!retainedMFId) { + m_lastMFID = MFG_STANDARD; + } } VERBOSE_LOG_TSBK_DST(osp->toString(), dstId); @@ -1564,9 +1599,12 @@ void ControlSignaling::writeRF_TSDU_U_Reg_Cmd(uint32_t dstId) osp->setSrcId(WUID_FNE); osp->setDstId(dstId); - if (m_lastMFID != MFG_STANDARD) { - osp->setMFId(m_lastMFID); - m_lastMFID = MFG_STANDARD; + const bool retainedMFId = hasMFIdTransaction(WUID_FNE, dstId, TSBKO::OSP_U_REG_CMD); + const uint8_t mfId = transactionMFId(WUID_FNE, dstId, TSBKO::OSP_U_REG_CMD, m_lastMFID); + if (mfId != MFG_STANDARD) { + osp->setMFId(mfId); + if (!retainedMFId) + m_lastMFID = MFG_STANDARD; } VERBOSE_LOG_TSBK_DST(osp->toString(), dstId); @@ -1711,9 +1749,14 @@ void ControlSignaling::clock(uint32_t ms) // if the transaction requires an answer and originated from a site, send a deny response to the called ID if (it->second.answerRequired && it->second.originatingSite) { + const uint8_t previousMFId = m_lastMFID; + m_lastMFID = transactionMFId(it->second.callerId, it->second.calledId, + TSBKO::IOSP_UU_VCH, previousMFId); writeRF_TSDU_Deny(it->second.calledId, it->second.callerId, ReasonCode::DENY_TGT_UNIT_REFUSED, TSBKO::IOSP_UU_VCH); + m_lastMFID = previousMFId; } + clearMFId(it->second.callerId, it->second.calledId, TSBKO::IOSP_UU_VCH); it = m_u2uAvailabilityTransactions.erase(it); } else { @@ -1728,6 +1771,7 @@ void ControlSignaling::clock(uint32_t ms) if (it->second.responseTimer.isRunning() && it->second.responseTimer.hasExpired()) { if (it->second.retries >= GROUP_AFF_QUERY_MAX_RETRIES) { LogWarning(LOG_RF, P25_TSDU_STR ", group affiliation query response expired, dstId = %u", it->first); + clearMFId(WUID_FNE, it->first, TSBKO::OSP_GRP_AFF_Q); it = m_groupAffQueryTransactions.erase(it); continue; } @@ -1739,6 +1783,16 @@ void ControlSignaling::clock(uint32_t ms) ++it; } + + // Expire orphaned MFID records only after their owning procedures have + // had the opportunity to emit a final response using the retained value. + for (auto it = m_mfIdTransactions.begin(); it != m_mfIdTransactions.end(); ) { + it->second.lifetime.clock(ms); + if (it->second.lifetime.isRunning() && it->second.lifetime.hasExpired()) + it = m_mfIdTransactions.erase(it); + else + ++it; + } } /* Clears all pending unit-to-unit availability transactions. */ @@ -1746,6 +1800,7 @@ void ControlSignaling::clock(uint32_t ms) void ControlSignaling::resetU2UAvailabilityTransactions() { m_u2uAvailabilityTransactions.clear(); + clearMFIds(TSBKO::IOSP_UU_VCH); } /* Clears all pending group-affiliation query transactions. */ @@ -1753,6 +1808,7 @@ void ControlSignaling::resetU2UAvailabilityTransactions() void ControlSignaling::resetGroupAffQueryTransactions() { m_groupAffQueryTransactions.clear(); + clearMFIds(TSBKO::OSP_GRP_AFF_Q); } /* Write data processed from RF to the network. */ @@ -2858,6 +2914,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } if (grp) { + const uint8_t grantMFId = transactionMFId(srcId, dstId, TSBKO::IOSP_GRP_VCH, m_lastMFID); // callback RPC to permit the granted TG on the specified voice channel { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && @@ -2907,7 +2964,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (voiceChData.isExplicitCh()) { std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + osp->setMFId(grantMFId); osp->setSrcId(srcId); osp->setDstId(dstId); osp->setGrpVchId(voiceChData.chId()); @@ -2934,7 +2991,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } std::unique_ptr iosp = std::make_unique(); - iosp->setMFId(m_lastMFID); + iosp->setMFId(grantMFId); iosp->setSrcId(srcId); iosp->setDstId(dstId); iosp->setGrpVchId(voiceChData.chId()); @@ -2962,6 +3019,9 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } } else { + uint8_t grantMFId = transactionMFId(srcId, dstId, TSBKO::IOSP_UU_VCH, m_lastMFID); + if (!hasMFIdTransaction(srcId, dstId, TSBKO::IOSP_UU_VCH)) + grantMFId = transactionMFId(dstId, srcId, TSBKO::IOSP_UU_VCH, grantMFId); // callback RPC to permit the granted TG on the specified voice channel { if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 && @@ -3077,7 +3137,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ if (voiceChData.isExplicitCh()) { std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + osp->setMFId(grantMFId); osp->setSrcId(srcId); osp->setDstId(dstId); osp->setGrpVchId(voiceChData.chId()); @@ -3104,7 +3164,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_ } std::unique_ptr iosp = std::make_unique(); - iosp->setMFId(m_lastMFID); + iosp->setMFId(grantMFId); iosp->setSrcId(srcId); iosp->setDstId(dstId); iosp->setGrpVchId(voiceChData.chId()); @@ -3240,7 +3300,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 return false; std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + osp->setMFId(transactionMFId(srcId, srcId, TSBKO::ISP_SNDCP_CH_REQ, m_lastMFID)); osp->siteIdenEntry(m_p25->m_idenEntry); osp->setSrcId(srcId); osp->setDstId(srcId); @@ -3362,7 +3422,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3 void ControlSignaling::writeRF_TSDU_UU_Ans_Req(uint32_t srcId, uint32_t dstId) { std::unique_ptr iosp = std::make_unique(); - iosp->setMFId(m_lastMFID); + iosp->setMFId(transactionMFId(srcId, dstId, TSBKO::IOSP_UU_VCH, m_lastMFID)); iosp->setSrcId(srcId); iosp->setDstId(dstId); @@ -3403,7 +3463,10 @@ void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bo void ControlSignaling::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service, bool grp, bool aiv) { std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + uint8_t mfId = transactionMFId(srcId, dstId, service, m_lastMFID); + if (!hasMFIdTransaction(srcId, dstId, service)) + mfId = transactionMFId(dstId, srcId, service, mfId); + osp->setMFId(mfId); osp->setAIV(aiv); osp->setSrcId(srcId); osp->setDstId(dstId); @@ -3432,7 +3495,7 @@ void ControlSignaling::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId) { std::unique_ptr iosp = std::make_unique(); - iosp->setMFId(m_lastMFID); + iosp->setMFId(transactionMFId(srcId, dstId, TSBKO::IOSP_GRP_AFF, m_lastMFID)); iosp->setAnnounceGroup(m_announcementGroup); iosp->setSrcId(srcId); iosp->setDstId(dstId); @@ -3543,7 +3606,7 @@ uint8_t ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstI void ControlSignaling::writeRF_TSDU_U_Reg_Rsp(uint32_t srcId, uint32_t sysId) { std::unique_ptr iosp = std::make_unique(); - iosp->setMFId(m_lastMFID); + iosp->setMFId(transactionMFId(srcId, srcId, TSBKO::IOSP_U_REG, m_lastMFID)); iosp->setResponse(ResponseCode::ACCEPT); iosp->setSrcId(srcId); iosp->setDstId(srcId); @@ -3597,7 +3660,7 @@ void ControlSignaling::writeRF_TSDU_U_Dereg_Ack(uint32_t srcId) if (dereged) { std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + osp->setMFId(transactionMFId(srcId, WUID_FNE, TSBKO::ISP_U_DEREG_REQ, m_lastMFID)); osp->setSrcId(WUID_FNE); osp->setDstId(srcId); @@ -3619,7 +3682,10 @@ void ControlSignaling::writeRF_TSDU_U_Dereg_Ack(uint32_t srcId) void ControlSignaling::writeRF_TSDU_Queue(uint32_t srcId, uint32_t dstId, uint8_t reason, uint8_t service, bool grp, bool aiv) { std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + uint8_t mfId = transactionMFId(srcId, dstId, service, m_lastMFID); + if (!hasMFIdTransaction(srcId, dstId, service)) + mfId = transactionMFId(dstId, srcId, service, mfId); + osp->setMFId(mfId); osp->setAIV(aiv); osp->setSrcId(srcId); osp->setDstId(dstId); @@ -3643,7 +3709,7 @@ bool ControlSignaling::writeRF_TSDU_Loc_Reg_Rsp(uint32_t srcId, uint32_t dstId, bool ret = false; std::unique_ptr osp = std::make_unique(); - osp->setMFId(m_lastMFID); + osp->setMFId(transactionMFId(srcId, dstId, TSBKO::ISP_LOC_REG_REQ, m_lastMFID)); osp->setResponse(ResponseCode::ACCEPT); osp->setDstId(dstId); osp->setSrcId(srcId); @@ -3827,13 +3893,14 @@ uint64_t ControlSignaling::u2uTransactionKey(uint32_t callerId, uint32_t calledI /* Starts or replaces a pending U2U availability transaction. */ void ControlSignaling::startU2UAvailabilityTransaction(uint32_t callerId, uint32_t calledId, uint8_t serviceOptions, - bool originatingSite, bool answerRequired) + bool originatingSite, bool answerRequired, uint8_t mfId) { // remove any existing transactions involving either the caller or called ID before starting a new one for (auto it = m_u2uAvailabilityTransactions.begin(); it != m_u2uAvailabilityTransactions.end(); ) { const U2UAvailabilityTransaction& transaction = it->second; if (transaction.callerId == callerId || transaction.callerId == calledId || transaction.calledId == callerId || transaction.calledId == calledId) { + clearMFId(transaction.callerId, transaction.calledId, TSBKO::IOSP_UU_VCH); it = m_u2uAvailabilityTransactions.erase(it); } else { @@ -3845,4 +3912,58 @@ void ControlSignaling::startU2UAvailabilityTransaction(uint32_t callerId, uint32 const uint64_t key = u2uTransactionKey(callerId, calledId); m_u2uAvailabilityTransactions.emplace(key, U2UAvailabilityTransaction(callerId, calledId, serviceOptions, originatingSite, answerRequired, U2U_AVAILABILITY_TIMER_TIMEOUT)); + + storeMFId(callerId, calledId, TSBKO::IOSP_UU_VCH, mfId, U2U_AVAILABILITY_TIMER_TIMEOUT); +} + +/* Produces the ordered key for retained transaction MFID state. */ + +uint64_t ControlSignaling::mfIdTransactionKey(uint32_t srcId, uint32_t dstId, uint8_t lco) +{ + return (static_cast(lco & 0x3FU) << 48) | (static_cast(srcId & 0xFFFFFFU) << 24) | (dstId & 0xFFFFFFU); +} + +/* Stores retained MFID state for a control transaction. */ + +void ControlSignaling::storeMFId(uint32_t srcId, uint32_t dstId, uint8_t lco, uint8_t mfId, uint32_t timeoutSeconds) +{ + const uint64_t key = mfIdTransactionKey(srcId, dstId, lco); + m_mfIdTransactions.erase(key); + m_mfIdTransactions.emplace(key, MFIdTransaction(srcId, dstId, lco, mfId, timeoutSeconds)); +} + +/* Returns retained transaction MFID, or the supplied compatibility fallback. */ + +uint8_t ControlSignaling::transactionMFId(uint32_t srcId, uint32_t dstId, uint8_t lco, uint8_t fallback) const +{ + const auto transaction = m_mfIdTransactions.find(mfIdTransactionKey(srcId, dstId, lco)); + return transaction != m_mfIdTransactions.end() ? transaction->second.mfId : fallback; +} + +/* Tests whether retained MFID state exists for a control transaction. */ + +bool ControlSignaling::hasMFIdTransaction(uint32_t srcId, uint32_t dstId, uint8_t lco) const +{ + return m_mfIdTransactions.find(mfIdTransactionKey(srcId, dstId, lco)) != m_mfIdTransactions.end(); +} + +/* Clears retained MFID state for one control transaction. */ + +void ControlSignaling::clearMFId(uint32_t srcId, uint32_t dstId, uint8_t lco) +{ + m_mfIdTransactions.erase(mfIdTransactionKey(srcId, dstId, lco)); +} + +/* Clears retained MFID state for an LCO, optionally limited to a subscriber. */ + +void ControlSignaling::clearMFIds(uint8_t lco, uint32_t subscriberId) +{ + // iterate through all MFID transactions and erase those matching the LCO and optionally the subscriber ID + for (auto it = m_mfIdTransactions.begin(); it != m_mfIdTransactions.end(); ) { + const MFIdTransaction& transaction = it->second; + if (transaction.lco == lco && (subscriberId == 0U || transaction.srcId == subscriberId || transaction.dstId == subscriberId)) + it = m_mfIdTransactions.erase(it); + else + ++it; + } } diff --git a/src/host/p25/packet/ControlSignaling.h b/src/host/p25/packet/ControlSignaling.h index 8df21b1f..5436de5f 100644 --- a/src/host/p25/packet/ControlSignaling.h +++ b/src/host/p25/packet/ControlSignaling.h @@ -166,6 +166,28 @@ namespace p25 friend class p25::Control; friend class lookups::P25AffiliationLookup; + /** + * @brief Manufacturer identity retained for an asynchronous control transaction. + */ + struct MFIdTransaction { + uint32_t srcId; //!< Transaction source RID. + uint32_t dstId; //!< Transaction destination RID. + uint8_t lco; //!< LCO which originated the transaction. + uint8_t mfId; //!< Manufacturer identity from the originating message. + Timer lifetime; //!< Maximum lifetime of the retained identity. + + MFIdTransaction(uint32_t src, uint32_t dst, uint8_t opcode, uint8_t manufacturer, + uint32_t timeoutSeconds) : + srcId(src), + dstId(dst), + lco(opcode), + mfId(manufacturer), + lifetime(1000U, timeoutSeconds) + { + lifetime.start(); + } + }; + /** * @brief State of a pending unit-to-unit availability transaction. */ @@ -265,6 +287,7 @@ namespace p25 std::unordered_set m_telIntGrants; std::unordered_map m_u2uAvailabilityTransactions; std::unordered_map m_groupAffQueryTransactions; + std::unordered_map m_mfIdTransactions; uint8_t m_convFallbackPacketDelay; bool m_convFallback; @@ -543,7 +566,55 @@ namespace p25 * @param originatingSite Flag indicating this site received the original request. */ void startU2UAvailabilityTransaction(uint32_t callerId, uint32_t calledId, uint8_t serviceOptions, - bool originatingSite, bool answerRequired); + bool originatingSite, bool answerRequired, uint8_t mfId); + + /** + * @brief Produces the ordered key for retained transaction MFID state. + * @param srcId Source Radio ID. + * @param dstId Destination Radio ID. + * @param lco LCO which originated the transaction. + * @returns uint64_t Ordered 48-bit transaction key. + */ + static uint64_t mfIdTransactionKey(uint32_t srcId, uint32_t dstId, uint8_t lco); + /** + * @brief Stores retained MFID state for a control transaction. + * @param srcId Source Radio ID. + * @param dstId Destination Radio ID. + * @param lco LCO which originated the transaction. + * @param mfId MFID to remember. + * @param timeoutSeconds Timeout for the retained state. + */ + void storeMFId(uint32_t srcId, uint32_t dstId, uint8_t lco, uint8_t mfId, uint32_t timeoutSeconds); + /** + * @brief Returns retained transaction MFID, or the supplied compatibility fallback. + * @param srcId Source Radio ID. + * @param dstId Destination Radio ID. + * @param lco LCO which originated the transaction. + * @param fallback Compatibility fallback MFID. + * @returns uint8_t Retained MFID or the fallback. + */ + uint8_t transactionMFId(uint32_t srcId, uint32_t dstId, uint8_t lco, uint8_t fallback) const; + /** + * @brief Tests whether retained MFID state exists for a control transaction. + * @param srcId Source Radio ID. + * @param dstId Destination Radio ID. + * @param lco LCO which originated the transaction. + * @returns bool True if retained MFID state exists, false otherwise. + */ + bool hasMFIdTransaction(uint32_t srcId, uint32_t dstId, uint8_t lco) const; + /** + * @brief Clears retained MFID state for one control transaction. + * @param srcId Source Radio ID. + * @param dstId Destination Radio ID. + * @param lco LCO which originated the transaction. + */ + void clearMFId(uint32_t srcId, uint32_t dstId, uint8_t lco); + /** + * @brief Clears retained MFID state for an LCO, optionally limited to a subscriber. + * @param lco LCO which originated the transactions. + * @param subscriberId Subscriber ID to limit the clearing, or 0 for all. + */ + void clearMFIds(uint8_t lco, uint32_t subscriberId = 0U); }; } // namespace packet } // namespace p25 diff --git a/tests/host/HostTestHooks.cpp b/tests/host/HostTestHooks.cpp index c568f683..43a426ff 100644 --- a/tests/host/HostTestHooks.cpp +++ b/tests/host/HostTestHooks.cpp @@ -594,6 +594,58 @@ void HostTestHooks::p25ClockU2U(p25::Control& control, uint32_t ms) control.m_control->clock(ms); } +/* Stores an MFID transaction for a control-signaling test. */ + +void HostTestHooks::p25StoreMFId(p25::Control& control, uint32_t srcId, uint32_t dstId, + uint8_t lco, uint8_t mfId, uint32_t timeoutSeconds) +{ + control.m_control->storeMFId(srcId, dstId, lco, mfId, timeoutSeconds); +} + +/* Resolves an MFID transaction with a compatibility fallback. */ + +uint8_t HostTestHooks::p25TransactionMFId(const p25::Control& control, uint32_t srcId, + uint32_t dstId, uint8_t lco, uint8_t fallback) +{ + return control.m_control->transactionMFId(srcId, dstId, lco, fallback); +} + +/* Tests whether an exact MFID transaction exists. */ + +bool HostTestHooks::p25HasMFIdTransaction(const p25::Control& control, uint32_t srcId, + uint32_t dstId, uint8_t lco) +{ + return control.m_control->hasMFIdTransaction(srcId, dstId, lco); +} + +/* Clears one exact MFID transaction. */ + +void HostTestHooks::p25ClearMFId(p25::Control& control, uint32_t srcId, uint32_t dstId, uint8_t lco) +{ + control.m_control->clearMFId(srcId, dstId, lco); +} + +/* Clears MFID transactions by LCO and optional subscriber. */ + +void HostTestHooks::p25ClearMFIds(p25::Control& control, uint8_t lco, uint32_t subscriberId) +{ + control.m_control->clearMFIds(lco, subscriberId); +} + +/* Gets the legacy ambient MFID compatibility value. */ + +uint8_t HostTestHooks::p25LastMFId(const p25::Control& control) +{ + return control.m_control->m_lastMFID; +} + +/* Sets the legacy ambient MFID compatibility value. */ + +void HostTestHooks::p25SetLastMFId(p25::Control& control, uint8_t mfId) +{ + control.m_control->setLastMFId(mfId); +} + /* Releases a P25 grant for a steering-policy test. */ bool HostTestHooks::p25ReleaseGrant(p25::Control& control, uint32_t dstId) diff --git a/tests/host/HostTestHooks.h b/tests/host/HostTestHooks.h index 5cb924bf..7533d544 100644 --- a/tests/host/HostTestHooks.h +++ b/tests/host/HostTestHooks.h @@ -388,6 +388,37 @@ public: * @param ms Elapsed milliseconds. */ static void p25ClockU2U(p25::Control& control, uint32_t ms); + /** + * @brief Stores an MFID transaction for P25 control-signaling tests. + */ + static void p25StoreMFId(p25::Control& control, uint32_t srcId, uint32_t dstId, + uint8_t lco, uint8_t mfId, uint32_t timeoutSeconds); + /** + * @brief Resolves an MFID transaction with a caller-provided fallback. + */ + static uint8_t p25TransactionMFId(const p25::Control& control, uint32_t srcId, + uint32_t dstId, uint8_t lco, uint8_t fallback); + /** + * @brief Tests whether an exact MFID transaction exists. + */ + static bool p25HasMFIdTransaction(const p25::Control& control, uint32_t srcId, + uint32_t dstId, uint8_t lco); + /** + * @brief Clears one exact MFID transaction. + */ + static void p25ClearMFId(p25::Control& control, uint32_t srcId, uint32_t dstId, uint8_t lco); + /** + * @brief Clears MFID transactions by LCO and optional subscriber. + */ + static void p25ClearMFIds(p25::Control& control, uint8_t lco, uint32_t subscriberId = 0U); + /** + * @brief Gets the legacy ambient MFID compatibility value. + */ + static uint8_t p25LastMFId(const p25::Control& control); + /** + * @brief Sets the legacy ambient MFID compatibility value. + */ + static void p25SetLastMFId(p25::Control& control, uint8_t mfId); /** * @brief Releases a P25 grant for a steering-policy test. * @param control P25 control instance. diff --git a/tests/p25/HostControl_Tests.cpp b/tests/p25/HostControl_Tests.cpp index 93d8cbfb..c8d81596 100644 --- a/tests/p25/HostControl_Tests.cpp +++ b/tests/p25/HostControl_Tests.cpp @@ -925,6 +925,94 @@ 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 MFID transactions are isolated by subscriber pair and LCO", + "[p25][host][control][mfid]") +{ + P25HostHarness harness; + const uint32_t caller = 5519008U; + const uint32_t called = 5519007U; + const uint8_t u2uLco = p25::defines::TSBKO::IOSP_UU_VCH; + const uint8_t alertLco = p25::defines::TSBKO::IOSP_CALL_ALRT; + + HostTestHooks::p25StoreMFId(*harness.m_control, caller, called, u2uLco, 0x90U, 10U); + HostTestHooks::p25StoreMFId(*harness.m_control, caller, called, alertLco, 0xA4U, 10U); + HostTestHooks::p25StoreMFId(*harness.m_control, called, caller, u2uLco, 0xB0U, 10U); + + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, caller, called, u2uLco, 0x00U) == 0x90U); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, caller, called, alertLco, 0x00U) == 0xA4U); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, called, caller, u2uLco, 0x00U) == 0xB0U); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, caller, called + 1U, u2uLco, 0x55U) == 0x55U); +} + +TEST_CASE("P25 MFID transaction replacement does not disturb legacy fallback state", + "[p25][host][control][mfid][regression]") +{ + P25HostHarness harness; + const uint32_t srcId = 1001U; + const uint32_t dstId = 2001U; + const uint8_t lco = p25::defines::TSBKO::IOSP_GRP_VCH; + + HostTestHooks::p25SetLastMFId(*harness.m_control, 0x90U); + CHECK(HostTestHooks::p25LastMFId(*harness.m_control) == 0x90U); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, srcId, dstId, lco, + HostTestHooks::p25LastMFId(*harness.m_control)) == 0x90U); + + HostTestHooks::p25StoreMFId(*harness.m_control, srcId, dstId, lco, 0xA4U, 10U); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, srcId, dstId, lco, 0x90U) == 0xA4U); + CHECK(HostTestHooks::p25LastMFId(*harness.m_control) == 0x90U); + + HostTestHooks::p25StoreMFId(*harness.m_control, srcId, dstId, lco, 0xB0U, 10U); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, srcId, dstId, lco, 0x90U) == 0xB0U); + CHECK(HostTestHooks::p25LastMFId(*harness.m_control) == 0x90U); +} + +TEST_CASE("P25 MFID transaction cleanup is exact and subscriber scoped", + "[p25][host][control][mfid][cleanup]") +{ + P25HostHarness harness; + const uint8_t u2uLco = p25::defines::TSBKO::IOSP_UU_VCH; + const uint8_t alertLco = p25::defines::TSBKO::IOSP_CALL_ALRT; + + HostTestHooks::p25StoreMFId(*harness.m_control, 1001U, 2001U, u2uLco, 0x90U, 10U); + HostTestHooks::p25StoreMFId(*harness.m_control, 1002U, 2002U, u2uLco, 0xA4U, 10U); + HostTestHooks::p25StoreMFId(*harness.m_control, 1001U, 2001U, alertLco, 0xB0U, 10U); + + HostTestHooks::p25ClearMFId(*harness.m_control, 1001U, 2001U, u2uLco); + CHECK_FALSE(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1001U, 2001U, u2uLco)); + CHECK(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1002U, 2002U, u2uLco)); + CHECK(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1001U, 2001U, alertLco)); + + HostTestHooks::p25StoreMFId(*harness.m_control, 1001U, 2001U, u2uLco, 0x90U, 10U); + HostTestHooks::p25ClearMFIds(*harness.m_control, u2uLco, 2001U); + CHECK_FALSE(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1001U, 2001U, u2uLco)); + CHECK(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1002U, 2002U, u2uLco)); + CHECK(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1001U, 2001U, alertLco)); + + HostTestHooks::p25ClearMFIds(*harness.m_control, u2uLco); + CHECK_FALSE(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1002U, 2002U, u2uLco)); + CHECK(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, 1001U, 2001U, alertLco)); +} + +TEST_CASE("P25 orphaned MFID transactions expire without changing legacy fallback", + "[p25][host][control][mfid][expiry][regression]") +{ + P25HostHarness harness; + const uint32_t srcId = 1001U; + const uint32_t dstId = 2001U; + const uint8_t lco = p25::defines::TSBKO::IOSP_CALL_ALRT; + + HostTestHooks::p25SetLastMFId(*harness.m_control, 0x90U); + HostTestHooks::p25StoreMFId(*harness.m_control, srcId, dstId, lco, 0xA4U, 1U); + REQUIRE(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, srcId, dstId, lco)); + + HostTestHooks::p25ClockU2U(*harness.m_control, 2000U); + + CHECK_FALSE(HostTestHooks::p25HasMFIdTransaction(*harness.m_control, srcId, dstId, lco)); + CHECK(HostTestHooks::p25TransactionMFId(*harness.m_control, srcId, dstId, lco, + HostTestHooks::p25LastMFId(*harness.m_control)) == 0x90U); + CHECK(HostTestHooks::p25LastMFId(*harness.m_control) == 0x90U); +} + TEST_CASE("P25 conventional host accepts and acknowledges an emergency alarm TSBK", "[p25][host][control][emergency][conventional]") {