/** * Digital Voice Modem - Common Library * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * @package DVM / Common Library * */ /* * Copyright (C) 2023-2024 by Bryan Biedenkapp N2PLL * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "lookups/TalkgroupRulesLookup.h" #include "Log.h" #include "Timer.h" using namespace lookups; #include #include #include #include #include #include #include // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the TalkgroupRulesLookup class. /// /// Full-path to the routing rules file. /// Interval of time to reload the routing rules. /// TalkgroupRulesLookup::TalkgroupRulesLookup(const std::string& filename, uint32_t reloadTime, bool acl) : Thread(), m_rulesFile(filename), m_reloadTime(reloadTime), m_rules(), m_acl(acl), m_groupHangTime(5U), m_sendTalkgroups(false), m_groupVoice() { /* stub */ } /// /// Finalizes a instance of the TalkgroupRulesLookup class. /// TalkgroupRulesLookup::~TalkgroupRulesLookup() { /* stub */ } /// /// /// void TalkgroupRulesLookup::entry() { if (m_reloadTime == 0U) { return; } Timer timer(1U, 60U * m_reloadTime); timer.start(); while (!m_stop) { sleep(1000U); timer.clock(); if (timer.hasExpired()) { load(); timer.start(); } } } /// /// Stops and unloads this lookup table. /// void TalkgroupRulesLookup::stop() { if (m_reloadTime == 0U) { delete this; return; } m_stop = true; wait(); } /// /// Reads the lookup table from the specified lookup table file. /// /// True, if lookup table was read, otherwise false. bool TalkgroupRulesLookup::read() { bool ret = load(); if (m_reloadTime > 0U) run(); return ret; } /// /// Clears all entries from the lookup table. /// void TalkgroupRulesLookup::clear() { m_mutex.lock(); { m_groupVoice.clear(); } m_mutex.unlock(); } /// /// Adds a new entry to the lookup table by the specified unique ID. /// /// 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) { TalkgroupRuleGroupVoiceSource source; TalkgroupRuleConfig config; source.tgId(id); source.tgSlot(slot); config.active(enabled); m_mutex.lock(); { auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; } return x.source().tgId() == id; }); if (it != m_groupVoice.end()) { source = it->source(); source.tgId(id); source.tgSlot(slot); config = it->config(); config.active(enabled); TalkgroupRuleGroupVoice entry = *it; entry.config(config); entry.source(source); m_groupVoice[it - m_groupVoice.begin()] = entry; } else { TalkgroupRuleGroupVoice entry; entry.config(config); entry.source(source); m_groupVoice.push_back(entry); } } m_mutex.unlock(); } /// /// Adds a new entry to the lookup table by the specified unique ID. /// /// void TalkgroupRulesLookup::addEntry(TalkgroupRuleGroupVoice groupVoice) { if (groupVoice.isInvalid()) return; TalkgroupRuleGroupVoice entry = groupVoice; uint32_t id = entry.source().tgId(); uint8_t slot = entry.source().tgSlot(); m_mutex.lock(); { auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; } return x.source().tgId() == id; }); if (it != m_groupVoice.end()) { m_groupVoice[it - m_groupVoice.begin()] = entry; } else { m_groupVoice.push_back(entry); } } m_mutex.unlock(); } /// /// Erases an existing entry from the lookup table by the specified unique ID. /// /// Unique ID to erase. /// DMR slot this talkgroup is valid on. void TalkgroupRulesLookup::eraseEntry(uint32_t id, uint8_t slot) { m_mutex.lock(); { auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { return x.source().tgId() == id && x.source().tgSlot() == slot; }); if (it != m_groupVoice.end()) { m_groupVoice.erase(it); } } m_mutex.unlock(); } /// /// Finds a table entry in this lookup table. /// /// Unique identifier for table entry. /// DMR slot this talkgroup is valid on. /// Table entry. TalkgroupRuleGroupVoice TalkgroupRulesLookup::find(uint32_t id, uint8_t slot) { TalkgroupRuleGroupVoice entry; m_mutex.lock(); { auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { if (slot != 0U) { return x.source().tgId() == id && x.source().tgSlot() == slot; } return x.source().tgId() == id; }); if (it != m_groupVoice.end()) { entry = *it; } else { entry = TalkgroupRuleGroupVoice(); } } m_mutex.unlock(); return entry; } /// /// Finds a table entry in this lookup table. /// /// Unique identifier for table entry. /// Unique identifier for table entry. /// DMR slot this talkgroup is valid on. /// Table entry. TalkgroupRuleGroupVoice TalkgroupRulesLookup::findByMutation(uint32_t peerId, uint32_t id, uint8_t slot) { TalkgroupRuleGroupVoice entry; m_mutex.lock(); { auto it = std::find_if(m_groupVoice.begin(), m_groupVoice.end(), [&](TalkgroupRuleGroupVoice x) { if (x.config().mutation().size() == 0) return false; auto innerIt = std::find_if(x.config().mutation().begin(), x.config().mutation().end(), [&](TalkgroupRuleMutation y) { if (slot != 0U) { return y.peerId() == peerId && y.tgId() == id && y.tgSlot() == slot; } return y.peerId() == peerId && y.tgId() == id; }); if (innerIt != x.config().mutation().end()) return true; return false; }); if (it != m_groupVoice.end()) { entry = *it; } else { entry = TalkgroupRuleGroupVoice(); } } m_mutex.unlock(); return entry; } /// /// Flag indicating whether talkgroup ID access control is enabled or not. /// /// True, if talkgroup ID access control is enabled, otherwise false. bool TalkgroupRulesLookup::getACL() { return m_acl; } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /// /// Loads the table from the passed lookup table file. /// /// True, if lookup table was loaded, otherwise false. bool TalkgroupRulesLookup::load() { if (m_rulesFile.length() <= 0) { return false; } try { bool ret = yaml::Parse(m_rules, m_rulesFile.c_str()); if (!ret) { LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s", m_rulesFile.c_str()); return false; } } catch (yaml::OperationException const& e) { LogError(LOG_HOST, "Cannot open the talkgroup rules lookup file - %s (%s)", m_rulesFile.c_str(), e.message()); return false; } // clear table clear(); m_mutex.lock(); { yaml::Node& groupVoiceList = m_rules["groupVoice"]; if (groupVoiceList.size() == 0U) { ::LogError(LOG_HOST, "No group voice rules list defined!"); return false; } for (size_t i = 0; i < groupVoiceList.size(); i++) { TalkgroupRuleGroupVoice groupVoice = TalkgroupRuleGroupVoice(groupVoiceList[i]); m_groupVoice.push_back(groupVoice); std::string groupName = groupVoice.name(); uint32_t tgId = groupVoice.source().tgId(); uint8_t tgSlot = groupVoice.source().tgSlot(); bool active = groupVoice.config().active(); bool parrot = groupVoice.config().parrot(); uint32_t incCount = groupVoice.config().inclusion().size(); uint32_t excCount = groupVoice.config().exclusion().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", groupName.c_str(), tgId, tgSlot, active, parrot, incCount, excCount); } } m_mutex.unlock(); size_t size = m_groupVoice.size(); if (size == 0U) return false; LogInfoEx(LOG_HOST, "Loaded %u entries into lookup table", size); return true; }