diff --git a/configs/talkgroup_rules.example.yml b/configs/talkgroup_rules.example.yml
index f7467983..9ebdd639 100644
--- a/configs/talkgroup_rules.example.yml
+++ b/configs/talkgroup_rules.example.yml
@@ -24,6 +24,10 @@ groupVoice:
exclusion: []
# List of peer talkgroup rewrites.
rewrite: []
+ # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access,
+ # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming).
+ # If this list is empty *all* peers a preferred. (Trunking Only)
+ preferred: []
#
# Source Configuration
#
@@ -51,6 +55,10 @@ groupVoice:
exclusion: []
# List of peer talkgroup rewrites.
rewrite: []
+ # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access,
+ # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming).
+ # If this list is empty *all* peers a preferred. (Trunking Only)
+ preferred: []
#
# Source Configuration
#
@@ -84,6 +92,10 @@ groupVoice:
tgid: 9999
# DMR slot number.
slot: 1
+ # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access,
+ # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming).
+ # If this list is empty *all* peers a preferred. (Trunking Only)
+ preferred: []
#
# Source Configuration
#
@@ -109,6 +121,10 @@ groupVoice:
exclusion: []
# List of peer talkgroup rewrites.
rewrite: []
+ # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access,
+ # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming).
+ # If this list is empty *all* peers a preferred. (Trunking Only)
+ preferred: []
#
# Source Configuration
#
@@ -134,6 +150,10 @@ groupVoice:
exclusion: []
# List of peer talkgroup rewrites.
rewrite: []
+ # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access,
+ # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming).
+ # If this list is empty *all* peers a preferred. (Trunking Only)
+ preferred: []
#
# Source Configuration
#
@@ -159,6 +179,10 @@ groupVoice:
exclusion: []
# List of peer talkgroup rewrites.
rewrite: []
+ # List of site CC peer IDs defining talkgroup access preference (peers listed here will be preferred for access,
+ # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming).
+ # If this list is empty *all* peers a preferred. (Trunking Only)
+ preferred: []
#
# Source Configuration
#
diff --git a/src/common/dmr/acl/AccessControl.cpp b/src/common/dmr/acl/AccessControl.cpp
index b5e7daa7..c0d4e890 100644
--- a/src/common/dmr/acl/AccessControl.cpp
+++ b/src/common/dmr/acl/AccessControl.cpp
@@ -10,7 +10,7 @@
*
* Copyright (C) 2016 Simon Rune, G7RZU
* Copyright (C) 2016,2017 Jonathan Naylor, G4KLX
-* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL
+* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@@ -95,3 +95,28 @@ bool AccessControl::validateTGId(uint32_t slotNo, uint32_t id)
return true;
}
}
+
+///
+/// Helper to determine if a talkgroup ID is non-preferred.
+///
+/// DMR slot number.
+/// Talkgroup ID.
+/// True, if talkgroup ID is valid, otherwise false.
+bool AccessControl::tgidNonPreferred(uint32_t slotNo, uint32_t id)
+{
+ // TG0 is never valid
+ if (id == 0U)
+ return false;
+
+ // check if TID ACLs are enabled
+ if (!m_tidLookup->getACL()) {
+ return false;
+ }
+
+ // lookup TID and perform test for validity
+ TalkgroupRuleGroupVoice tid = m_tidLookup->find(id);
+ if (tid.config().nonPreferred())
+ return true;
+
+ return false;
+}
diff --git a/src/common/dmr/acl/AccessControl.h b/src/common/dmr/acl/AccessControl.h
index 41542799..18ff6ac5 100644
--- a/src/common/dmr/acl/AccessControl.h
+++ b/src/common/dmr/acl/AccessControl.h
@@ -10,7 +10,7 @@
*
* Copyright (C) 2016 Simon Rune, G7RZU
* Copyright (C) 2016,2017 Jonathan Naylor, G4KLX
-* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL
+* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__DMR_ACL__ACCESS_CONTROL_H__)
@@ -41,6 +41,9 @@ namespace dmr
/// Helper to validate a talkgroup ID.
static bool validateTGId(uint32_t slotNo, uint32_t id);
+ /// Helper to determine if a talkgroup ID is non-preferred.
+ static bool tgidNonPreferred(uint32_t slotNo, uint32_t id);
+
private:
static RadioIdLookup* m_ridLookup;
static TalkgroupRulesLookup* m_tidLookup;
diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp
index a0404c85..81899dd5 100644
--- a/src/common/lookups/TalkgroupRulesLookup.cpp
+++ b/src/common/lookups/TalkgroupRulesLookup.cpp
@@ -121,13 +121,15 @@ void TalkgroupRulesLookup::clear()
/// Unique ID to add.
/// DMR slot this talkgroup is valid on.
/// Flag indicating if talkgroup ID is enabled or not.
-void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled)
+/// Flag indicating if the talkgroup ID is non-preferred.
+void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled, bool nonPreferred)
{
TalkgroupRuleGroupVoiceSource source;
TalkgroupRuleConfig config;
source.tgId(id);
source.tgSlot(slot);
config.active(enabled);
+ config.nonPreferred(nonPreferred);
std::lock_guard lock(m_mutex);
auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(),
@@ -146,6 +148,7 @@ void TalkgroupRulesLookup::addEntry(uint32_t id, uint8_t slot, bool enabled)
config = it->config();
config.active(enabled);
+ config.nonPreferred(nonPreferred);
TalkgroupRuleGroupVoice entry = *it;
entry.config(config);
@@ -345,12 +348,13 @@ bool TalkgroupRulesLookup::load()
uint32_t incCount = groupVoice.config().inclusion().size();
uint32_t excCount = groupVoice.config().exclusion().size();
uint32_t rewrCount = groupVoice.config().rewrite().size();
+ uint32_t prefCount = groupVoice.config().preferred().size();
if (incCount > 0 && excCount > 0) {
::LogWarning(LOG_HOST, "Talkgroup (%s) defines both inclusions and exclusions! Inclusions take precedence and exclusions will be ignored.", groupName.c_str());
}
- ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u PARROT: %u INCLUSIONS: %u EXCLUSIONS: %u REWRITES: %u", groupName.c_str(), tgId, tgSlot, active, parrot, incCount, excCount, rewrCount);
+ ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u PARROT: %u INCLUSIONS: %u EXCLUSIONS: %u REWRITES: %u PREFERRED: %u", groupName.c_str(), tgId, tgSlot, active, parrot, incCount, excCount, rewrCount, prefCount);
}
size_t size = m_groupVoice.size();
diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h
index e1ade8bc..0d3d2027 100644
--- a/src/common/lookups/TalkgroupRulesLookup.h
+++ b/src/common/lookups/TalkgroupRulesLookup.h
@@ -142,7 +142,9 @@ namespace lookups
m_parrot(false),
m_inclusion(),
m_exclusion(),
- m_rewrite()
+ m_rewrite(),
+ m_preferred(),
+ m_nonPreferred(false)
{
/* stub */
}
@@ -178,6 +180,14 @@ namespace lookups
m_rewrite.push_back(rewrite);
}
}
+
+ yaml::Node& preferredList = node["preferred"];
+ if (preferredList.size() > 0U) {
+ for (size_t i = 0; i < preferredList.size(); i++) {
+ uint32_t peerId = preferredList[i].as(0U);
+ m_preferred.push_back(peerId);
+ }
+ }
}
/// Equals operator. Copies this TalkgroupRuleConfig to another TalkgroupRuleConfig.
@@ -190,6 +200,8 @@ namespace lookups
m_inclusion = data.m_inclusion;
m_exclusion = data.m_exclusion;
m_rewrite = data.m_rewrite;
+ m_preferred = data.m_preferred;
+ m_nonPreferred = data.m_nonPreferred;
}
return *this;
@@ -201,6 +213,8 @@ namespace lookups
uint8_t exclusionSize() const { return m_exclusion.size(); }
/// Gets the count of rewrites.
uint8_t rewriteSize() const { return m_rewrite.size(); }
+ /// Gets the count of rewrites.
+ uint8_t preferredSize() const { return m_preferred.size(); }
/// Return the YAML structure for this TalkgroupRuleConfig.
void getYaml(yaml::Node &node)
@@ -237,6 +251,15 @@ namespace lookups
}
}
node["rewrite"] = rewriteList;
+
+ yaml::Node preferredList;
+ if (m_preferred.size() > 0U) {
+ for (auto pref : m_preferred) {
+ yaml::Node& newPref = preferredList.push_back();
+ newPref = __INT_STR(pref);
+ }
+ }
+ node["preferred"] = preferredList;
}
public:
@@ -252,6 +275,11 @@ namespace lookups
__PROPERTY_PLAIN(std::vector, exclusion);
/// List of rewrites performed by this rule.
__PROPERTY_PLAIN(std::vector, rewrite);
+ /// List of peer IDs preferred by this rule.
+ __PROPERTY_PLAIN(std::vector, preferred);
+
+ /// Flag indicating whether or not the talkgroup is a non-preferred.
+ __PROPERTY_PLAIN(bool, nonPreferred);
};
// ---------------------------------------------------------------------------
@@ -348,7 +376,7 @@ namespace lookups
void clear();
/// Adds a new entry to the lookup table.
- void addEntry(uint32_t id, uint8_t slot, bool enabled);
+ void addEntry(uint32_t id, uint8_t slot, bool enabled, bool nonPreferred = false);
/// Adds a new entry to the lookup table.
void addEntry(TalkgroupRuleGroupVoice groupVoice);
/// Erases an existing entry from the lookup table by the specified unique ID.
diff --git a/src/common/nxdn/acl/AccessControl.cpp b/src/common/nxdn/acl/AccessControl.cpp
index a089243a..54bf0738 100644
--- a/src/common/nxdn/acl/AccessControl.cpp
+++ b/src/common/nxdn/acl/AccessControl.cpp
@@ -10,7 +10,7 @@
*
* Copyright (C) 2016 Simon Rune, G7RZU
* Copyright (C) 2016,2017 Jonathan Naylor, G4KLX
-* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL
+* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@@ -64,7 +64,6 @@ bool AccessControl::validateSrcId(uint32_t id)
///
/// Helper to validate a talkgroup ID.
///
-/// DMR slot number.
/// Talkgroup ID.
/// True, if talkgroup ID is valid, otherwise false.
bool AccessControl::validateTGId(uint32_t id)
@@ -88,3 +87,27 @@ bool AccessControl::validateTGId(uint32_t id)
return true;
}
+
+///
+/// Helper to determine if a talkgroup ID is non-preferred.
+///
+/// Talkgroup ID.
+/// True, if talkgroup ID is valid, otherwise false.
+bool AccessControl::tgidNonPreferred(uint32_t id)
+{
+ // TG0 is never valid
+ if (id == 0U)
+ return false;
+
+ // check if TID ACLs are enabled
+ if (!m_tidLookup->getACL()) {
+ return false;
+ }
+
+ // lookup TID and perform test for validity
+ TalkgroupRuleGroupVoice tid = m_tidLookup->find(id);
+ if (tid.config().nonPreferred())
+ return true;
+
+ return false;
+}
diff --git a/src/common/nxdn/acl/AccessControl.h b/src/common/nxdn/acl/AccessControl.h
index c95ec233..2edfb64f 100644
--- a/src/common/nxdn/acl/AccessControl.h
+++ b/src/common/nxdn/acl/AccessControl.h
@@ -10,7 +10,7 @@
*
* Copyright (C) 2016 Simon Rune, G7RZU
* Copyright (C) 2016,2017 Jonathan Naylor, G4KLX
-* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL
+* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__NXDN_ACL__ACCESS_CONTROL_H__)
@@ -41,6 +41,9 @@ namespace nxdn
/// Helper to validate a talkgroup ID.
static bool validateTGId(uint32_t id);
+ /// Helper to determine if a talkgroup ID is non-preferred.
+ static bool tgidNonPreferred(uint32_t id);
+
private:
static RadioIdLookup* m_ridLookup;
static TalkgroupRulesLookup* m_tidLookup;
diff --git a/src/common/p25/acl/AccessControl.cpp b/src/common/p25/acl/AccessControl.cpp
index 7adf45a7..03d8f65b 100644
--- a/src/common/p25/acl/AccessControl.cpp
+++ b/src/common/p25/acl/AccessControl.cpp
@@ -10,7 +10,7 @@
*
* Copyright (C) 2016 Simon Rune, G7RZU
* Copyright (C) 2016,2017 Jonathan Naylor, G4KLX
-* Copyright (C) 2017,2019,2022 Bryan Biedenkapp, N2PLL
+* Copyright (C) 2017,2019,2022,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
@@ -64,7 +64,6 @@ bool AccessControl::validateSrcId(uint32_t id)
///
/// Helper to validate a talkgroup ID.
///
-/// DMR slot number.
/// Talkgroup ID.
/// True, if talkgroup ID is valid, otherwise false.
bool AccessControl::validateTGId(uint32_t id)
@@ -88,3 +87,27 @@ bool AccessControl::validateTGId(uint32_t id)
return true;
}
+
+///
+/// Helper to determine if a talkgroup ID is non-preferred.
+///
+/// Talkgroup ID.
+/// True, if talkgroup ID is valid, otherwise false.
+bool AccessControl::tgidNonPreferred(uint32_t id)
+{
+ // TG0 is never valid
+ if (id == 0U)
+ return false;
+
+ // check if TID ACLs are enabled
+ if (!m_tidLookup->getACL()) {
+ return false;
+ }
+
+ // lookup TID and perform test for validity
+ TalkgroupRuleGroupVoice tid = m_tidLookup->find(id);
+ if (tid.config().nonPreferred())
+ return true;
+
+ return false;
+}
diff --git a/src/common/p25/acl/AccessControl.h b/src/common/p25/acl/AccessControl.h
index e5e0d784..94a62368 100644
--- a/src/common/p25/acl/AccessControl.h
+++ b/src/common/p25/acl/AccessControl.h
@@ -10,7 +10,7 @@
*
* Copyright (C) 2016 Simon Rune, G7RZU
* Copyright (C) 2016,2017 Jonathan Naylor, G4KLX
-* Copyright (C) 2017,2019 Bryan Biedenkapp, N2PLL
+* Copyright (C) 2017,2019,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__P25_ACL__ACCESS_CONTROL_H__)
@@ -41,6 +41,9 @@ namespace p25
/// Helper to validate a talkgroup ID.
static bool validateTGId(uint32_t id);
+ /// Helper to determine if a talkgroup ID is non-preferred.
+ static bool tgidNonPreferred(uint32_t id);
+
private:
static RadioIdLookup* m_ridLookup;
static TalkgroupRulesLookup* m_tidLookup;
diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp
index e1206110..6290d9cf 100644
--- a/src/fne/network/FNENetwork.cpp
+++ b/src/fne/network/FNENetwork.cpp
@@ -1295,6 +1295,7 @@ void FNENetwork::writeTGIDs(uint32_t peerId)
for (auto entry : groupVoice) {
std::vector inclusion = entry.config().inclusion();
std::vector exclusion = entry.config().exclusion();
+ std::vector preferred = entry.config().preferred();
// peer inclusion lists take priority over exclusion lists
if (inclusion.size() > 0) {
@@ -1313,9 +1314,25 @@ void FNENetwork::writeTGIDs(uint32_t peerId)
}
}
}
-
+
+ // determine if the peer is non-preferred
+ bool nonPreferred = false;
+ if (preferred.size() > 0) {
+ auto it = std::find(preferred.begin(), preferred.end(), peerId);
+ if (it == preferred.end()) {
+ nonPreferred = true;
+ }
+ }
+
if (entry.config().active()) {
- tgidList.push_back({ entry.source().tgId(), entry.source().tgSlot() });
+ uint8_t slotNo = entry.source().tgSlot();
+
+ // set upper bit of the slot number to flag non-preferred
+ if (nonPreferred) {
+ slotNo = 0x80U + (slotNo & 0x03U);
+ }
+
+ tgidList.push_back({ entry.source().tgId(), slotNo });
}
}
diff --git a/src/host/network/Network.cpp b/src/host/network/Network.cpp
index d76e03b0..ee68dd11 100644
--- a/src/host/network/Network.cpp
+++ b/src/host/network/Network.cpp
@@ -397,8 +397,10 @@ void Network::clock(uint32_t ms)
m_ridLookup->toggleEntry(id, true);
offs += 4U;
}
+
LogMessage(LOG_NET, "Network Announced %u whitelisted RIDs", len);
- // Save to file if enabled and we got RIDs
+
+ // save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) {
m_ridLookup->commit();
}
@@ -419,8 +421,10 @@ void Network::clock(uint32_t ms)
m_ridLookup->toggleEntry(id, false);
offs += 4U;
}
+
LogMessage(LOG_NET, "Network Announced %u blacklisted RIDs", len);
- // Save to file if enabled and we got RIDs
+
+ // save to file if enabled and we got RIDs
if (m_saveLookup && len > 0) {
m_ridLookup->commit();
}
@@ -438,22 +442,35 @@ void Network::clock(uint32_t ms)
uint32_t offs = 11U;
for (uint32_t i = 0; i < len; i++) {
uint32_t id = __GET_UINT16(buffer, offs);
- uint8_t slot = (buffer[offs + 3U]);
+ uint8_t slot = (buffer[offs + 3U]) & 0x03U;
+ bool nonPreferred = (buffer[offs + 3U] & 0x80U) == 0x80U;
lookups::TalkgroupRuleGroupVoice tid = m_tidLookup->find(id, slot);
+
+ // if the TG is marked as non-preferred, and the TGID exists in the local entries
+ // erase the local and overwrite with the FNE data
+ if (nonPreferred) {
+ if (!tid.isInvalid()) {
+ m_tidLookup->eraseEntry(id, slot);
+ tid = m_tidLookup->find(id, slot);
+ }
+ }
+
if (tid.isInvalid()) {
if (!tid.config().active()) {
m_tidLookup->eraseEntry(id, slot);
}
- LogMessage(LOG_NET, "Activated TG %u TS %u in TGID table", id, slot);
- m_tidLookup->addEntry(id, slot, true);
+ LogMessage(LOG_NET, "Activated%s TG %u TS %u in TGID table", (nonPreferred) ? " non-preferred" : "", id, slot);
+ m_tidLookup->addEntry(id, slot, true, nonPreferred);
}
offs += 5U;
}
+
LogMessage(LOG_NET, "Activated %u TGs; loaded %u entries into lookup table", len, m_tidLookup->groupVoice().size());
- // Save if saving from network is enabled
+
+ // save if saving from network is enabled
if (m_saveLookup && len > 0) {
m_tidLookup->commit();
}
@@ -481,8 +498,10 @@ void Network::clock(uint32_t ms)
offs += 5U;
}
+
LogMessage(LOG_NET, "Deactivated %u TGs; loaded %u entries into lookup table", len, m_tidLookup->groupVoice().size());
- // Save if saving from network is enabled
+
+ // save if saving from network is enabled
if (m_saveLookup && len > 0) {
m_tidLookup->commit();
}
diff --git a/src/host/p25/packet/ControlSignaling.cpp b/src/host/p25/packet/ControlSignaling.cpp
index 037b3038..60c9cedc 100644
--- a/src/host/p25/packet/ControlSignaling.cpp
+++ b/src/host/p25/packet/ControlSignaling.cpp
@@ -2671,6 +2671,14 @@ bool ControlSignaling::writeRF_TSDU_Grp_Aff_Rsp(uint32_t srcId, uint32_t dstId)
iosp->setResponse(P25_RSP_DENY);
noNet = true;
}
+
+ // deny affiliation if the TG is non-preferred on this site/CC
+ if (acl::AccessControl::tgidNonPreferred(dstId)) {
+ LogWarning(LOG_RF, P25_TSDU_STR ", %s non-preferred on this site, TGID rejection, dstId = %u", iosp->toString().c_str(), dstId);
+ ::ActivityLog("P25", true, "group affiliation request from %u to %s %u denied", srcId, "TG ", dstId);
+ iosp->setResponse(P25_RSP_DENY);
+ noNet = true;
+ }
}
if (iosp->getResponse() == P25_RSP_ACCEPT) {