correct P25 group affiliation query handling, correlate and retry affiliation queries, refresh WUID validity only for acknowledged valid transactions and complete group affiliation query support;

pull/134/head
Bryan Biedenkapp 4 days ago
parent a14bb62b0b
commit d2ac635b9d

@ -149,6 +149,21 @@ void AffiliationLookup::touchUnitReg(uint32_t srcId)
}
}
/* Restarts only the source ID registration timer. */
void AffiliationLookup::touchUnitRegTimer(uint32_t srcId)
{
if (srcId == 0U) {
return;
}
__spinlock();
if (isUnitReg(srcId)) {
m_unitRegTimers[srcId].start();
}
}
/* Gets the current timer timeout for this unit registration. */
uint32_t AffiliationLookup::unitRegTimeout(uint32_t srcId)

@ -87,6 +87,11 @@ namespace lookups
* @param srcId Source Radio ID.
*/
virtual void touchUnitReg(uint32_t srcId);
/**
* @brief Restarts only the source ID registration timer.
* @param srcId Source Radio ID.
*/
virtual void touchUnitRegTimer(uint32_t srcId);
/**
* @brief Gets the current timer timeout for this unit registration.
* @param srcId Source Radio ID.

@ -44,6 +44,7 @@ bool ISP_GRP_AFF_Q_RSP::decode(const uint8_t* data, bool rawTSBK)
ulong64_t tsbkValue = TSBK::toValue(tsbk);
m_emergency = ((tsbkValue >> 56) & 0x80U) != 0U; // Emergency Flag
m_announceGroup = (uint32_t)((tsbkValue >> 40) & 0xFFFFU); // Announcement Group Address
m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFU); // Talkgroup Address
m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address
@ -57,7 +58,13 @@ void ISP_GRP_AFF_Q_RSP::encode(uint8_t* data, bool rawTSBK, bool noTrellis)
{
assert(data != nullptr);
/* stub */
ulong64_t tsbkValue = m_emergency ? 0x80U : 0x00U; // Emergency Flag
tsbkValue = (tsbkValue << 16) + m_announceGroup; // Announcement Group Address
tsbkValue = (tsbkValue << 16) + m_dstId; // Talkgroup Address
tsbkValue = (tsbkValue << 24) + m_srcId; // Source Radio Address
std::unique_ptr<uint8_t[]> tsbk = TSBK::fromValue(tsbkValue);
TSBK::encode(data, tsbk.get(), rawTSBK, noTrellis);
}
/* Returns a string that represents the current TSBK. */

@ -34,7 +34,16 @@ bool OSP_GRP_AFF_Q::decode(const uint8_t* data, bool rawTSBK)
{
assert(data != nullptr);
/* stub */
uint8_t tsbk[P25_TSBK_LENGTH_BYTES + 1U];
::memset(tsbk, 0x00U, P25_TSBK_LENGTH_BYTES);
bool ret = TSBK::decode(data, tsbk, rawTSBK);
if (!ret)
return false;
ulong64_t tsbkValue = TSBK::toValue(tsbk);
m_dstId = (uint32_t)((tsbkValue >> 24) & 0xFFFFFFU); // Target Radio Address
m_srcId = (uint32_t)(tsbkValue & 0xFFFFFFU); // Source Radio Address
return true;
}

@ -225,6 +225,7 @@ void Control::reset()
if (m_control != nullptr) {
m_control->resetU2UAvailabilityTransactions();
m_control->resetGroupAffQueryTransactions();
}
m_txImmQueue.clear();

@ -153,6 +153,8 @@ const uint32_t TSDU_CTRL_BURST_COUNT = 2U;
const uint32_t TSBK_MBF_CNT = 3U;
const uint32_t GRANT_TIMER_TIMEOUT = 15U;
const uint32_t U2U_AVAILABILITY_TIMER_TIMEOUT = 15U;
const uint32_t GROUP_AFF_QUERY_TIMER_TIMEOUT = 4U;
const uint8_t GROUP_AFF_QUERY_MAX_RETRIES = 4U;
const uint8_t CONV_FALLBACK_PACKET_DELAY = 8U;
// ---------------------------------------------------------------------------
@ -230,7 +232,6 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
return false;
}
m_p25->m_affiliations->touchUnitReg(srcId);
m_lastMFID = tsbk->getMFId();
// handle standard P25 reference opcodes
@ -763,14 +764,10 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
IS_SUPPORT_CONTROL_CHECK(tsbk->toString(true), TSBKO::ISP_GRP_AFF_Q_RSP, srcId);
// validate the source RID
VALID_SRCID(tsbk->toString(true), TSBKO::IOSP_ACK_RSP, srcId);
// validate the target RID
VALID_DSTID(tsbk->toString(true), TSBKO::IOSP_ACK_RSP, srcId, dstId);
VALID_SRCID(tsbk->toString(true), TSBKO::ISP_GRP_AFF_Q_RSP, srcId);
if (m_p25->m_ackTSBKRequests) {
writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_GRP_AFF_Q_RSP, true, true);
}
// verify the source RID is registered
VERIFY_SRCID_REG(tsbk->toString(true), TSBKO::ISP_GRP_AFF_Q_RSP, srcId);
ISP_GRP_AFF_Q_RSP* isp = static_cast<ISP_GRP_AFF_Q_RSP*>(tsbk.get());
if (m_verbose) {
@ -778,15 +775,44 @@ bool ControlSignaling::process(uint8_t* data, uint32_t len, std::unique_ptr<lc::
tsbk->toString(true).c_str(), srcId, dstId, isp->getAnnounceGroup());
}
::ActivityLog("P25", true, "group affiliation query response from %u to %s %u", srcId, "TG ", dstId);
// look for a pending group affiliation query transaction for this source ID
auto query = m_groupAffQueryTransactions.find(srcId);
if (query == m_groupAffQueryTransactions.end()) {
LogWarning(LOG_RF, P25_TSDU_STR ", unsolicited group affiliation query response, srcId = %u", srcId);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
if (!m_p25->m_affiliations->isGroupAff(srcId, dstId)) {
// update dynamic affiliation table
m_p25->m_affiliations->groupAff(srcId, dstId);
m_groupAffQueryTransactions.erase(query);
if (m_p25->m_network != nullptr)
m_p25->m_network->announceGroupAffiliation(srcId, dstId);
// check if the announced group matches the expected announcement group and if the source is affiliated with the target group
if (isp->getAnnounceGroup() != m_announcementGroup || !m_p25->m_affiliations->isGroupAff(srcId, dstId)) {
LogWarning(LOG_RF, P25_TSDU_STR ", affiliation query mismatch, srcId = %u, dstId = %u, anncId = %u", srcId, dstId, isp->getAnnounceGroup());
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_TGT_GROUP_NOT_VALID, TSBKO::ISP_GRP_AFF_Q_RSP);
writeRF_TSDU_U_Reg_Cmd(srcId);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
// preserve the normal affiliation path's null-group exception
if (dstId != 0U) {
// validate the talkgroup ID
VALID_TGID(tsbk->toString(true), TSBKO::ISP_GRP_AFF_Q_RSP, srcId, dstId);
// check if the target group ID is non-preferred
if (acl::AccessControl::tgidNonPreferred(dstId)) {
LogWarning(LOG_RF, P25_TSDU_STR ", %s non-preferred on this site, TGID rejection, dstId = %u", tsbk->toString(true).c_str(), dstId);
writeRF_TSDU_Deny(srcId, dstId, ReasonCode::DENY_TGT_GROUP_NOT_VALID, TSBKO::ISP_GRP_AFF_Q_RSP);
m_p25->m_rfState = RS_RF_REJECTED;
return false;
}
}
writeRF_TSDU_ACK_FNE(srcId, TSBKO::ISP_GRP_AFF_Q_RSP, false, true);
m_p25->m_affiliations->touchUnitReg(srcId);
::ActivityLog("P25", true, "group affiliation query response from %u to %s %u", srcId, "TG ", dstId);
}
break;
case TSBKO::ISP_U_DEREG_REQ:
@ -1500,6 +1526,19 @@ void ControlSignaling::writeRF_TSDU_Ext_Func(uint32_t func, uint32_t arg, uint32
/* Helper to write a group affiliation query packet. */
void ControlSignaling::writeRF_TSDU_Grp_Aff_Q(uint32_t dstId)
{
if (dstId == 0U) {
return;
}
m_groupAffQueryTransactions.erase(dstId);
m_groupAffQueryTransactions.emplace(dstId, GroupAffQueryTransaction(GROUP_AFF_QUERY_TIMER_TIMEOUT));
writeRF_TSDU_Grp_Aff_Q_Once(dstId);
}
/* Emits a group affiliation query without changing transaction state. */
void ControlSignaling::writeRF_TSDU_Grp_Aff_Q_Once(uint32_t dstId)
{
std::unique_ptr<OSP_GRP_AFF_Q> osp = std::make_unique<OSP_GRP_AFF_Q>();
osp->setSrcId(WUID_FNE);
@ -1680,6 +1719,25 @@ void ControlSignaling::clock(uint32_t ms)
++it;
}
}
// TIA-102.AABD-B 6.7.3 permits the RFSS to repeat the query while waiting
// for the response. Nretry is four per Table 17-1.
for (auto it = m_groupAffQueryTransactions.begin(); it != m_groupAffQueryTransactions.end(); ) {
it->second.responseTimer.clock(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);
it = m_groupAffQueryTransactions.erase(it);
continue;
}
++it->second.retries;
writeRF_TSDU_Grp_Aff_Q_Once(it->first);
it->second.responseTimer.start();
}
++it;
}
}
/* Clears all pending unit-to-unit availability transactions. */
@ -1689,6 +1747,13 @@ void ControlSignaling::resetU2UAvailabilityTransactions()
m_u2uAvailabilityTransactions.clear();
}
/* Clears all pending group-affiliation query transactions. */
void ControlSignaling::resetGroupAffQueryTransactions()
{
m_groupAffQueryTransactions.clear();
}
/* Write data processed from RF to the network. */
void ControlSignaling::writeNetworkRF(lc::TSBK* tsbk, const uint8_t* data, bool autoReset)
@ -3056,6 +3121,10 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
m_p25->m_rfTGHang.start();
}
if (!net) {
m_p25->m_affiliations->touchUnitRegTimer(srcId);
}
return true;
}
@ -3265,6 +3334,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, bool skip, uint3
}
}
m_p25->m_affiliations->touchUnitRegTimer(srcId);
return true;
}
@ -3283,7 +3353,7 @@ 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 dstId, uint32_t service, bool extended, bool noNetwork)
void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork, bool refreshRegistration)
{
std::unique_ptr<IOSP_ACK_RSP> iosp = std::make_unique<IOSP_ACK_RSP>();
iosp->setAIV(true);
@ -3303,6 +3373,10 @@ void ControlSignaling::writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bo
}
writeRF_TSDU_SBF_Imm(iosp.get(), noNetwork);
if (refreshRegistration) {
m_p25->m_affiliations->touchUnitRegTimer(dstId);
}
}
/* Helper to write a deny packet. */
@ -3327,7 +3401,7 @@ void ControlSignaling::writeRF_TSDU_Deny(uint32_t srcId, uint32_t dstId, uint8_t
// are deny responses disabled?
if (!m_p25->m_dedicatedControl && !m_p25->m_controlOnly && m_p25->m_disableDenyResponse) {
// at least ACK the request to try to silence the sender
writeRF_TSDU_ACK_FNE(srcId, service, aiv, false);
writeRF_TSDU_ACK_FNE(srcId, service, aiv, false, false);
return;
}

@ -209,6 +209,26 @@ namespace p25
}
};
/**
* @brief Pending group-affiliation query transaction.
* @ingroup host_p25
*/
struct GroupAffQueryTransaction {
uint8_t retries; //!< Number of retransmissions already sent.
Timer responseTimer; //!< TACK response timer.
/**
* @brief Initializes a pending group-affiliation query transaction.
* @param timeoutSeconds Response timeout in seconds.
*/
explicit GroupAffQueryTransaction(uint32_t timeoutSeconds) :
retries(0U),
responseTimer(1000U, timeoutSeconds)
{
responseTimer.start();
}
};
Control* m_p25;
uint32_t m_patchSuperGroup;
@ -243,6 +263,7 @@ namespace p25
bool m_telephoneInterconnect;
std::unordered_set<uint32_t> m_telIntGrants;
std::unordered_map<uint64_t, U2UAvailabilityTransaction> m_u2uAvailabilityTransactions;
std::unordered_map<uint32_t, GroupAffQueryTransaction> m_groupAffQueryTransactions;
uint8_t m_convFallbackPacketDelay;
bool m_convFallback;
@ -291,6 +312,11 @@ namespace p25
*/
void resetU2UAvailabilityTransactions();
/**
* @brief Clears all pending group-affiliation query transactions.
*/
void resetGroupAffQueryTransactions();
/**
* @brief Write data processed from RF to the network.
* @param tsbk TSBK to write to network.
@ -417,8 +443,16 @@ namespace p25
* @param service Service being acknowledged.
* @param extended Flag indicating this is an extended acknowledgement.
* @param noNetwork Flag indicating the acknowledgement must not be forwarded to the network.
* @param refreshRegistration Flag indicating the acknowledgement confirms a valid WUID transaction.
*/
void writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork,
bool refreshRegistration = true);
/**
* @brief Emits a group-affiliation query without changing transaction state.
* @param dstId Destination Radio ID.
*/
void writeRF_TSDU_ACK_FNE(uint32_t dstId, uint32_t service, bool extended, bool noNetwork);
void writeRF_TSDU_Grp_Aff_Q_Once(uint32_t dstId);
/**
* @brief Helper to write a deny packet.
* @param srcId Source Radio ID.

@ -10,6 +10,8 @@
#include "host/Defines.h"
#include "common/p25/lc/tsbk/OSP_SCCB.h"
#include "common/p25/lc/tsbk/OSP_SYS_SRV_BCAST.h"
#include "common/p25/lc/tsbk/OSP_GRP_AFF_Q.h"
#include "common/p25/lc/tsbk/ISP_GRP_AFF_Q_RSP.h"
#include "common/p25/lc/tsbk/OSP_TSBK_RAW.h"
#include "common/p25/lc/tsbk/TSBKFactory.h"
#include "common/p25/data/DataBlock.h"
@ -64,6 +66,58 @@ TEST_CASE("TSBK", "[p25][tsbk]") {
TSBK::setSiteData(previousSiteData);
}
SECTION("Group_Affiliation_Query_Known_Bytes") {
OSP_GRP_AFF_Q query;
query.setDstId(0x123456U);
query.setSrcId(WUID_FNE);
uint8_t raw[P25_TSBK_LENGTH_BYTES];
::memset(raw, 0x00U, sizeof(raw));
query.encode(raw, true, true);
CHECK(raw[2U] == 0x00U);
CHECK(raw[3U] == 0x00U);
CHECK(raw[4U] == 0x12U);
CHECK(raw[5U] == 0x34U);
CHECK(raw[6U] == 0x56U);
CHECK(raw[7U] == 0xFFU);
CHECK(raw[8U] == 0xFFU);
CHECK(raw[9U] == 0xFCU);
OSP_GRP_AFF_Q decoded;
REQUIRE(decoded.decode(raw, true));
CHECK(decoded.getDstId() == 0x123456U);
CHECK(decoded.getSrcId() == WUID_FNE);
}
SECTION("Group_Affiliation_Query_Response_Known_Bytes") {
ISP_GRP_AFF_Q_RSP response;
response.setEmergency(true);
response.setAnnounceGroup(0x2468U);
response.setDstId(0x1357U);
response.setSrcId(0x123456U);
uint8_t raw[P25_TSBK_LENGTH_BYTES];
::memset(raw, 0x00U, sizeof(raw));
response.encode(raw, true, true);
CHECK(raw[2U] == 0x80U);
CHECK(raw[3U] == 0x24U);
CHECK(raw[4U] == 0x68U);
CHECK(raw[5U] == 0x13U);
CHECK(raw[6U] == 0x57U);
CHECK(raw[7U] == 0x12U);
CHECK(raw[8U] == 0x34U);
CHECK(raw[9U] == 0x56U);
ISP_GRP_AFF_Q_RSP decoded;
REQUIRE(decoded.decode(raw, true));
CHECK(decoded.getEmergency());
CHECK(decoded.getAnnounceGroup() == 0x2468U);
CHECK(decoded.getDstId() == 0x1357U);
CHECK(decoded.getSrcId() == 0x123456U);
}
SECTION("Emergency_Alarm_Request_Roundtrip") {
ISP_EMERG_ALRM_REQ source;
source.setSpecialInfo1(0x01U);

Loading…
Cancel
Save

Powered by TurnKey Linux.