recover Tier III grants for payload-channel re-key;

pull/133/head
Bryan Biedenkapp 1 month ago
parent 25dfc17d44
commit 33b9e25f55

@ -60,7 +60,6 @@ void CSBK_P_CLEAR::encode(uint8_t* data)
ulong64_t csbkValue = 0U;
csbkValue = (csbkValue << 12) + (m_logicalCh1 & 0xFFFU); // Logical Physical Channel 1
csbkValue = (csbkValue << 1) + 0U; // Reserved
csbkValue = (csbkValue << 3) + 0U; // Reserved
csbkValue = (csbkValue << 1) + ((m_GI) ? 1U : 0U); // Group/Individual Flag
csbkValue = (csbkValue << 24) + m_dstId; // Talkgroup ID

@ -924,6 +924,24 @@ void Control::RPC_touchGrantTG(json::object& req, json::object& reply)
return;
}
Slot* tscc = getTSCCSlot();
if (tscc != nullptr && tscc->m_enableTSCC && !tscc->s_affiliations->isGranted(dstId) &&
req["srcId"].is<int>() && req["group"].is<bool>() && req["chNo"].is<int>()) {
uint32_t srcId = req["srcId"].get<uint32_t>();
bool group = req["group"].get<bool>();
uint32_t chNo = req["chNo"].get<uint32_t>();
::lookups::VoiceChData voiceCh = tscc->s_affiliations->rfCh()->getRFChData(chNo);
if (!voiceCh.isValidCh()) {
g_RPC->defaultResponse(reply, "DMR payload channel is not configured", network::NetRPC::INVALID_ARGS);
return;
}
if (!tscc->m_control->writeRF_CSBK_Grant(srcId, dstId, 4U, group,
true, false, chNo, slot)) {
g_RPC->defaultResponse(reply, "failed to recreate DMR payload grant", network::NetRPC::BAD_REQUEST);
return;
}
}
// LogDebugEx(LOG_DMR, "Control::RPC_touchGrantTG()", "callback, dstId = %u, slot = %u", dstId, slot);
switch (slot) {

@ -962,6 +962,56 @@ void Slot::touchGrantTG(uint32_t dstId)
}
}
/* Reissues a missing grant for traffic beginning on a retained payload channel. */
bool Slot::ensureTSCCPayloadGrant(uint32_t srcId, uint32_t dstId, bool grp)
{
Slot* tscc = s_dmr->getTSCCSlot();
if (tscc == this)
return true;
// A normal RAND/grant activation already identifies this payload call.
// Only synthesize the grant when the subscriber re-keys directly on the
// retained traffic channel without returning to the TSCC.
if (m_tsccPayloadDstId != 0U)
return true;
bool granted = false;
if (tscc != nullptr && tscc->m_enableTSCC) {
granted = tscc->m_control->writeRF_CSBK_Grant(srcId, dstId, 4U, grp,
true, false, s_channelNo, (uint8_t)m_slotNo);
}
else if (m_notifyCC && !s_controlChData.address().empty() && s_controlChData.port() != 0U) {
json::object req = json::object();
req["dstId"].set<uint32_t>(dstId);
req["srcId"].set<uint32_t>(srcId);
uint8_t slot = (uint8_t)m_slotNo;
req["slot"].set<uint8_t>(slot);
req["group"].set<bool>(grp);
req["chNo"].set<uint32_t>(s_channelNo);
bool rpcAccepted = false;
bool sent = g_RPC->req(RPC_TOUCH_DMR_TG, req,
[=, &rpcAccepted](json::object& req, json::object& reply) {
(void)reply;
rpcAccepted = req["status"].is<int>() &&
req["status"].get<int>() == network::NetRPC::OK;
}, s_controlChData.address(), s_controlChData.port(), true);
granted = sent && rpcAccepted;
}
else {
// Conventional/non-trunked operation has no TSCC grant to maintain.
return true;
}
if (!granted) {
LogWarning(LOG_RF, "DMR Slot %u, unable to reissue missing payload grant, srcId = %u, dstId = %u",
m_slotNo, srcId, dstId);
}
return granted;
}
/* Clears the current operating RF state back to idle. */
void Slot::clearRFReject()
@ -1304,6 +1354,10 @@ void Slot::processFrameLoss(RPT_RF_LOSS_TYPE type)
notifyCC_ReleaseGrant(m_rfLC->getDstId());
}
}
else if (m_rfLC != nullptr) {
notifyCC_ReleaseGrant(m_rfLC->getDstId());
s_dmr->tsccClearActivatedSlot(m_slotNo);
}
if (m_rfTimeout) {
writeEndRF();

@ -215,6 +215,15 @@ namespace dmr
* @param dstId Destination ID.
*/
void touchGrantTG(uint32_t dstId);
/**
* @brief Reissues a missing Tier III grant when a subscriber starts
* traffic directly on a retained payload channel.
* @param srcId Source Radio ID.
* @param dstId Destination ID.
* @param grp Flag indicating group traffic.
* @returns bool True if no grant was needed or a grant was issued.
*/
bool ensureTSCCPayloadGrant(uint32_t srcId, uint32_t dstId, bool grp);
/** @} */
/**

@ -42,13 +42,22 @@ bool DMRAffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t gran
/* Helper to grant a channel and slot. */
bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t slot, uint32_t grantTimeout, bool grp, bool netGranted)
bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t slot, uint32_t grantTimeout, bool grp, bool netGranted, uint32_t chNo)
{
if (dstId == 0U) {
if (dstId == 0U || slot == 0U || slot > 2U) {
return false;
}
uint32_t chNo = getAvailableChannelForSlot(slot);
if (chNo != 0U) {
for (auto entry : m_grantChSlotTable) {
if (std::get<0>(entry.second) == chNo && std::get<1>(entry.second) == slot)
return false;
}
}
else {
chNo = getAvailableChannelForSlot(slot);
}
if (chNo == 0U) {
return false;
}
@ -290,53 +299,23 @@ uint8_t DMRAffiliationLookup::getAvailableSlotForChannel(uint32_t chNo) const
__spinlock();
uint8_t slot = 1U;
// lookup dynamic channel slot grant table entry
bool grantedSlot = false;
int slotCount = 0U;
bool slot1Busy = chNo == m_tsccChNo && m_tsccSlot == 1U;
bool slot2Busy = chNo == m_tsccChNo && m_tsccSlot == 2U;
for (auto entry : m_grantChSlotTable) {
uint32_t foundChNo = std::get<0>(entry.second);
if (foundChNo == chNo)
{
uint8_t foundSlot = std::get<1>(entry.second);
if (slot == foundSlot) {
switch (foundSlot) {
case 1U:
slot = 2U;
break;
case 2U:
slot = 1U;
break;
}
grantedSlot = true;
slotCount++;
}
}
}
if (slotCount == 2U) {
slot = 0U;
return slot;
}
if (foundChNo != chNo)
continue;
// are we trying to assign the TSCC slot?
if (chNo == m_tsccChNo && slot == m_tsccSlot) {
if (!grantedSlot) {
// since we didn't find a slot being granted out -- utilize the slot opposing the TSCC
switch (m_tsccSlot) {
case 1U:
slot = 2U;
break;
case 2U:
slot = 1U;
break;
}
} else {
slot = 0U; // TSCC is not assignable
}
uint8_t foundSlot = std::get<1>(entry.second);
if (foundSlot == 1U)
slot1Busy = true;
if (foundSlot == 2U)
slot2Busy = true;
}
return slot;
if (!slot1Busy)
return 1U;
if (!slot2Busy)
return 2U;
return 0U;
}

@ -68,7 +68,7 @@ namespace dmr
* @param netGranted Flag indicating the grant came from network traffic.
* @returns bool True, if the destination address has been granted to the source ID, otherwise false.
*/
bool grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t slot, uint32_t grantTimeout, bool grp, bool netGranted);
bool grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t slot, uint32_t grantTimeout, bool grp, bool netGranted, uint32_t chNo = 0U);
/**
* @brief Helper to release the channel grant for the destination ID.
* @param dstId Destination Address.

@ -832,7 +832,7 @@ void ControlSignaling::writeRF_CSBK_NACK_RSP(uint32_t dstId, uint8_t reason, uin
/* Helper to write a grant packet. */
bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net, bool skip, uint32_t chNo)
bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net, bool skip, uint32_t chNo, uint8_t preferredSlot)
{
Slot* tscc = m_slot->s_dmr->getTSCCSlot();
@ -887,7 +887,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
if (!tscc->s_affiliations->isGranted(dstId)) {
::lookups::TalkgroupRuleGroupVoice groupVoice = tscc->s_tidLookup->find(dstId);
slot = groupVoice.source().tgSlot();
slot = preferredSlot != 0U ? preferredSlot : groupVoice.source().tgSlot();
if (grp && !tscc->m_ignoreAffiliationCheck) {
// is this an affiliation required group?
@ -924,7 +924,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
}
uint32_t availChNo = tscc->s_affiliations->getAvailableChannelForSlot(slot);
uint32_t availChNo = chNo != 0U ? chNo : tscc->s_affiliations->getAvailableChannelForSlot(slot);
if (availChNo == 0U) {
if (grp) {
if (!net) {
@ -950,7 +950,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
}
else {
if (tscc->s_affiliations->grantChSlot(dstId, srcId, slot, GRANT_TIMER_TIMEOUT, grp, net)) {
if (tscc->s_affiliations->grantChSlot(dstId, srcId, slot, GRANT_TIMER_TIMEOUT, grp, net, availChNo)) {
chNo = tscc->s_affiliations->getGrantedCh(dstId);
slot = tscc->s_affiliations->getGrantedSlot(dstId);
//tscc->s_siteData.setChCnt(tscc->s_affiliations->getRFChCnt() + tscc->s_affiliations->getGrantedRFChCnt());

@ -156,7 +156,8 @@ namespace dmr
* @param skip Flag indicating normal grant checking is skipped.
* @param chNo Channel Number.
*/
bool writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net = false, bool skip = false, uint32_t chNo = 0U);
bool writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOptions, bool grp, bool net = false, bool skip = false,
uint32_t chNo = 0U, uint8_t preferredSlot = 0U);
/**
* @brief Helper to write a data grant packet.
* @param srcId Source Radio ID.

@ -83,9 +83,11 @@ bool Data::process(uint8_t* data, uint32_t len)
if (m_tscc != nullptr) {
if (m_tscc->m_enableTSCC) {
m_tscc->s_affiliations->releaseGrant(m_slot->m_rfLC->getDstId(), false);
m_slot->clearTSCCActivated();
}
}
else {
m_slot->notifyCC_ReleaseGrant(m_slot->m_rfLC->getDstId());
}
if (m_slot->m_rssi != 0U) {
::ActivityLog("DMR", true, "Slot %u RF end of voice transmission, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm",
@ -387,9 +389,11 @@ void Data::processNetwork(const data::NetData& dmrData)
if (m_tscc != nullptr) {
if (m_tscc->m_enableTSCC) {
m_tscc->s_affiliations->releaseGrant(m_slot->m_netLC->getDstId(), false);
m_slot->clearTSCCActivated();
}
}
else {
m_slot->notifyCC_ReleaseGrant(m_slot->m_netLC->getDstId());
}
// We've received the voice header and terminator haven't we?
m_slot->m_netFrames += 2U;

@ -86,6 +86,9 @@ bool Voice::process(uint8_t* data, uint32_t len)
if (checkRFTrafficCollision(dstId))
return false;
if (!m_slot->ensureTSCCPayloadGrant(srcId, dstId, flco == FLCO::GROUP))
return false;
if (m_slot->m_tsccPayloadDstId != 0U && m_slot->m_tsccPayloadActRetry.isRunning()) {
m_slot->confirmTSCCPayloadActive();
}
@ -587,6 +590,9 @@ bool Voice::process(uint8_t* data, uint32_t len)
if (checkRFTrafficCollision(dstId))
return false;
if (!m_slot->ensureTSCCPayloadGrant(srcId, dstId, flco == FLCO::GROUP))
return false;
// validate the source RID
if (!acl::AccessControl::validateSrcId(srcId)) {
LogWarning(LOG_RF, "DMR Slot %u, VOICE denial, RID rejection, srcId = %u", m_slot->m_slotNo, srcId);

@ -775,6 +775,39 @@ TEST_CASE("DMR processFrame accepts a synthetic RF voice call on the targeted sl
REQUIRE(harness.m_control->getLastDstId(1U) == 2001U);
}
TEST_CASE("DMR Tier III re-key on a retained payload channel recreates its grant", "[dmr][tier3][host][control][rf]")
{
DMRHostHarness harness;
REQUIRE(harness.m_chLookup.initializeRFCh(101U));
HostTestHooks::dmrEnableTier3(*harness.m_control, 101U, 1U);
dmr::Slot* payload = HostTestHooks::dmrSlot2(*harness.m_control);
REQUIRE(HostTestHooks::dmrStartRFVoiceCall(*payload, 1001U, 2001U));
CHECK(HostTestHooks::dmrIsGranted(*payload, 2001U));
CHECK(HostTestHooks::dmrPayloadDstId(*payload) == 2001U);
uint8_t terminator[dmr::defines::DMR_FRAME_LENGTH_BYTES + 2U] = { 0U };
terminator[0U] = modem::TAG_DATA;
terminator[1U] = dmr::defines::SYNC_DATA | dmr::defines::DataType::TERMINATOR_WITH_LC;
buildDMRTerminatorPayload(terminator + 2U, 1001U, 2001U, true);
REQUIRE(payload->processFrame(terminator, sizeof(terminator)));
CHECK_FALSE(HostTestHooks::dmrIsGranted(*payload, 2001U));
CHECK(HostTestHooks::dmrPayloadDstId(*payload) == 0U);
// Some subscribers remain on the payload channel during hangtime and
// begin transmitting without another RAND request on the TSCC.
REQUIRE(HostTestHooks::dmrStartRFVoiceCall(*payload, 1002U, 2001U));
CHECK(HostTestHooks::dmrIsGranted(*payload, 2001U));
CHECK(HostTestHooks::dmrPayloadDstId(*payload) == 2001U);
terminator[0U] = modem::TAG_DATA;
terminator[1U] = dmr::defines::SYNC_DATA | dmr::defines::DataType::TERMINATOR_WITH_LC;
buildDMRTerminatorPayload(terminator + 2U, 1002U, 2001U, true);
REQUIRE(payload->processFrame(terminator, sizeof(terminator)));
CHECK_FALSE(HostTestHooks::dmrIsGranted(*payload, 2001U));
CHECK(HostTestHooks::dmrPayloadDstId(*payload) == 0U);
}
TEST_CASE("DMR rfTGHang expiry ends active RF call and returns slot to listening", "[dmr][host][control][rf]")
{
DMRHostHarness harness;
@ -811,4 +844,4 @@ TEST_CASE("DMR rfTGHang zero fallback arms rfLossWatchdog and recovers RF state"
REQUIRE(HostTestHooks::dmrRFState(*slot) == RS_RF_LISTENING);
REQUIRE_FALSE(HostTestHooks::dmrRFLossWatchdog(*slot).isRunning());
}
}

@ -39,6 +39,25 @@ TEST_CASE("DMR Tier III maintenance preserves 24-bit addresses", "[dmr][tier3][c
CHECK(decoded.getSrcId() == 0xABCDEFU);
}
TEST_CASE("DMR Tier III maintenance round trip preserves field boundaries", "[dmr][tier3][csbk]")
{
CSBK_MAINT encoded;
encoded.setLastBlock(true);
encoded.setMaintKind(7U);
encoded.setDstId(0xFFFFFFU);
encoded.setSrcId(0x000001U);
uint8_t burst[DMR_FRAME_LENGTH_BYTES] = { 0U };
encoded.encode(burst);
CSBK_MAINT decoded;
decoded.setDataType(DataType::CSBK);
REQUIRE(decoded.decode(burst));
CHECK(decoded.getMaintKind() == 7U);
CHECK(decoded.getDstId() == 0xFFFFFFU);
CHECK(decoded.getSrcId() == 0x000001U);
}
TEST_CASE("DMR Tier III payload clear round trip carries TSCC return fields", "[dmr][tier3][csbk]")
{
CSBK_P_CLEAR encoded;
@ -60,6 +79,27 @@ TEST_CASE("DMR Tier III payload clear round trip carries TSCC return fields", "[
CHECK(decoded.getSrcId() == WUID_TSI);
}
TEST_CASE("DMR Tier III individual payload clear preserves maximum channel and addresses", "[dmr][tier3][csbk]")
{
CSBK_P_CLEAR encoded;
encoded.setLastBlock(true);
encoded.setLogicalCh1(0xFFFU);
encoded.setGI(false);
encoded.setDstId(0xFFFFFFU);
encoded.setSrcId(0x000001U);
uint8_t burst[DMR_FRAME_LENGTH_BYTES] = { 0U };
encoded.encode(burst);
CSBK_P_CLEAR decoded;
decoded.setDataType(DataType::CSBK);
REQUIRE(decoded.decode(burst));
CHECK(decoded.getLogicalCh1() == 0xFFFU);
CHECK_FALSE(decoded.getGI());
CHECK(decoded.getDstId() == 0xFFFFFFU);
CHECK(decoded.getSrcId() == 0x000001U);
}
TEST_CASE("DMR Tier III grants account for both slots without changing ChannelLookup", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
@ -94,3 +134,118 @@ TEST_CASE("DMR Tier III does not double-grant one slot", "[dmr][tier3][grant]")
REQUIRE(affiliations.grantChSlot(2001U, 1001U, 1U, 15U, true, false));
CHECK_FALSE(affiliations.grantChSlot(2002U, 1002U, 1U, 15U, true, false));
}
TEST_CASE("DMR Tier III rejects grants without a valid destination or RF resource", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
dmr::lookups::DMRAffiliationLookup affiliations(&channels, false);
CHECK_FALSE(affiliations.grantChSlot(0U, 1001U, 1U, 15U, true, false));
CHECK_FALSE(affiliations.grantChSlot(2001U, 1001U, 1U, 15U, true, false));
CHECK(affiliations.getGrantedRFChCnt() == 0U);
}
TEST_CASE("DMR Tier III spills an occupied slot onto another physical channel", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
REQUIRE(channels.initializeRFCh(101U));
REQUIRE(channels.initializeRFCh(102U));
dmr::lookups::DMRAffiliationLookup affiliations(&channels, false);
REQUIRE(affiliations.grantChSlot(2001U, 1001U, 1U, 15U, true, false));
REQUIRE(affiliations.grantChSlot(2002U, 1002U, 1U, 15U, true, false));
CHECK(affiliations.getGrantedCh(2001U) == 101U);
CHECK(affiliations.getGrantedCh(2002U) == 102U);
CHECK(affiliations.getGrantedSlot(2001U) == 1U);
CHECK(affiliations.getGrantedSlot(2002U) == 1U);
CHECK_FALSE(channels.isRFChAvailable());
}
TEST_CASE("DMR Tier III can recreate a grant on an exact remote payload channel", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
REQUIRE(channels.initializeRFCh(101U));
REQUIRE(channels.initializeRFCh(102U));
dmr::lookups::DMRAffiliationLookup affiliations(&channels, false);
affiliations.setSlotForChannelTSCC(101U, 1U);
REQUIRE(affiliations.grantChSlot(2001U, 1001U, 2U, 15U, true, true, 102U));
CHECK(affiliations.getGrantedCh(2001U) == 102U);
CHECK(affiliations.getGrantedSlot(2001U) == 2U);
CHECK_FALSE(affiliations.grantChSlot(2002U, 1002U, 2U, 15U, true, true, 102U));
}
TEST_CASE("DMR Tier III reserves the TSCC slot and permits its opposing payload slot", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
REQUIRE(channels.initializeRFCh(101U));
dmr::lookups::DMRAffiliationLookup affiliations(&channels, false);
affiliations.setSlotForChannelTSCC(101U, 1U);
CHECK_FALSE(channels.isRFChAvailable());
CHECK(affiliations.getAvailableSlotForChannel(101U) == 2U);
CHECK(affiliations.getAvailableChannelForSlot(1U) == 0U);
CHECK(affiliations.getAvailableChannelForSlot(2U) == 101U);
CHECK_FALSE(affiliations.isChBusy(101U));
REQUIRE(affiliations.grantChSlot(2001U, 1001U, 2U, 15U, true, false));
CHECK(affiliations.isChBusy(101U));
CHECK(affiliations.getAvailableSlotForChannel(101U) == 0U);
REQUIRE(affiliations.releaseGrant(2001U, false));
CHECK_FALSE(affiliations.isChBusy(101U));
CHECK_FALSE(channels.isRFChAvailable());
}
TEST_CASE("DMR Tier III release callback reports physical channel and slot", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
REQUIRE(channels.initializeRFCh(101U));
dmr::lookups::DMRAffiliationLookup affiliations(&channels, false);
uint32_t releasedCh = 0U;
uint32_t releasedSrc = 0U;
uint32_t releasedDst = 0U;
uint8_t releasedSlot = 0U;
affiliations.setReleaseGrantCallback(
[&](uint32_t chNo, uint32_t srcId, uint32_t dstId, uint8_t slot) {
releasedCh = chNo;
releasedSrc = srcId;
releasedDst = dstId;
releasedSlot = slot;
});
REQUIRE(affiliations.grantChSlot(2001U, 1001U, 2U, 15U, false, true));
CHECK_FALSE(affiliations.isGroup(2001U));
CHECK(affiliations.isNetGranted(2001U));
REQUIRE(affiliations.releaseGrant(2001U, false));
CHECK(releasedCh == 101U);
CHECK(releasedSrc == 1001U);
CHECK(releasedDst == 2001U);
CHECK(releasedSlot == 2U);
CHECK(affiliations.getGrantedRFChCnt() == 0U);
}
TEST_CASE("DMR Tier III bulk release clears grants across carriers", "[dmr][tier3][grant]")
{
::lookups::ChannelLookup channels;
REQUIRE(channels.initializeRFCh(101U));
REQUIRE(channels.initializeRFCh(102U));
dmr::lookups::DMRAffiliationLookup affiliations(&channels, false);
REQUIRE(affiliations.grantChSlot(2001U, 1001U, 1U, 15U, true, false));
REQUIRE(affiliations.grantChSlot(2002U, 1002U, 1U, 15U, true, false));
CHECK(affiliations.getGrantedRFChCnt() == 2U);
REQUIRE(affiliations.releaseGrant(0U, true));
CHECK_FALSE(affiliations.isGranted(2001U));
CHECK_FALSE(affiliations.isGranted(2002U));
CHECK(affiliations.getGrantedSlot(2001U) == 0U);
CHECK(affiliations.getGrantedSlot(2002U) == 0U);
CHECK(affiliations.getGrantedRFChCnt() == 0U);
CHECK(channels.isRFChAvailable());
}

@ -163,6 +163,29 @@ bool HostTestHooks::dmrStartRFVoiceCall(dmr::Slot& slot, uint32_t srcId, uint32_
return slot.processFrame(frame, sizeof(frame));
}
/* Configures a co-located DMR Tier III control channel. */
void HostTestHooks::dmrEnableTier3(dmr::Control& control, uint32_t channelNo, uint8_t tsccSlot)
{
control.m_enableTSCC = true;
control.m_tsccSlotNo = tsccSlot;
dmr::Slot::s_channelNo = channelNo;
dmr::Slot* slot = tsccSlot == 1U ? control.m_slot1 : control.m_slot2;
slot->setTSCC(true, false);
}
/* Gets the active DMR Tier III payload destination. */
uint32_t HostTestHooks::dmrPayloadDstId(const dmr::Slot& slot) { return slot.m_tsccPayloadDstId; }
/* Tests whether a DMR Tier III grant exists. */
bool HostTestHooks::dmrIsGranted(const dmr::Slot& slot, uint32_t dstId)
{
return slot.s_affiliations->isGranted(dstId);
}
/* Gets a P25 Phase 2 slot for tests. */
p25::phase2::Slot& HostTestHooks::p25P2Slot(p25::phase2::Control& control, uint32_t slotNo)

@ -121,6 +121,18 @@ public:
* @return bool True if frame processing succeeds.
*/
static bool dmrStartRFVoiceCall(dmr::Slot& slot, uint32_t srcId, uint32_t dstId, bool group = true);
/**
* @brief Configures one slot as a co-located Tier III control channel for tests.
*/
static void dmrEnableTier3(dmr::Control& control, uint32_t channelNo, uint8_t tsccSlot);
/**
* @brief Gets the active Tier III payload destination for a slot.
*/
static uint32_t dmrPayloadDstId(const dmr::Slot& slot);
/**
* @brief Tests whether the shared DMR affiliation table contains a grant.
*/
static bool dmrIsGranted(const dmr::Slot& slot, uint32_t dstId);
/** @} */
/**

Loading…
Cancel
Save

Powered by TurnKey Linux.