// SPDX-License-Identifier: GPL-2.0-only /* * 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. * * Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL * */ #include "lookups/AffiliationLookup.h" #include "Log.h" using namespace lookups; #include // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const uint32_t UNIT_REG_TIMEOUT = 43200U; // 12 hours // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /* Initializes a new instance of the AffiliationLookup class. */ AffiliationLookup::AffiliationLookup(const std::string name, ChannelLookup* channelLookup, bool verbose) : m_rfGrantChCnt(0U), m_unitRegTable(), m_unitRegTimers(), m_grpAffTable(), m_grantChTable(), m_grantSrcIdTable(), m_uuGrantedTable(), m_netGrantedTable(), m_grantTimers(), m_releaseGrant(nullptr), m_name(), m_chLookup(channelLookup), m_disableUnitRegTimeout(false), m_verbose(verbose) { m_name = name; m_unitRegTable.clear(); m_unitRegTimers.clear(); m_grpAffTable.clear(); m_grantChTable.clear(); m_grantSrcIdTable.clear(); m_grantTimers.clear(); } /* Finalizes a instance of the AffiliationLookup class. */ AffiliationLookup::~AffiliationLookup() = default; /* Helper to group affiliate a source ID. */ void AffiliationLookup::unitReg(uint32_t srcId) { if (isUnitReg(srcId)) { return; } __lock(); m_unitRegTable.push_back(srcId); m_unitRegTimers[srcId] = Timer(1000U, UNIT_REG_TIMEOUT); m_unitRegTimers[srcId].start(); if (m_verbose) { LogInfoEx(LOG_HOST, "%s, unit registration, srcId = %u", m_name.c_str(), srcId); } __unlock(); } /* Helper to group unaffiliate a source ID. */ bool AffiliationLookup::unitDereg(uint32_t srcId, bool automatic) { bool ret = false; if (!isUnitReg(srcId)) { return false; } groupUnaff(srcId); __lock(); if (m_verbose) { LogInfoEx(LOG_HOST, "%s, unit deregistration, srcId = %u", m_name.c_str(), srcId); } m_unitRegTimers[srcId].stop(); // remove dynamic unit registration table entry m_unitRegTable.lock(false); if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { auto it = std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId); m_unitRegTable.unlock(); m_unitRegTable.erase(it); ret = true; } m_unitRegTable.unlock(); if (ret) { if (m_unitDereg != nullptr) { m_unitDereg(srcId, automatic); } } __unlock(); return ret; } /* Helper to start the source ID registration timer. */ void AffiliationLookup::touchUnitReg(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) { if (srcId == 0U) { return 0U; } __spinlock(); if (isUnitReg(srcId)) { return m_unitRegTimers[srcId].getTimeout(); } return 0U; } /* Gets the current timer value for this unit registration. */ uint32_t AffiliationLookup::unitRegTimer(uint32_t srcId) { if (srcId == 0U) { return 0U; } __spinlock(); if (isUnitReg(srcId)) { return m_unitRegTimers[srcId].getTimer(); } return 0U; } /* Helper to determine if the source ID has unit registered. */ bool AffiliationLookup::isUnitReg(uint32_t srcId) const { __spinlock(); // lookup dynamic unit registration table entry m_unitRegTable.lock(false); if (std::find(m_unitRegTable.begin(), m_unitRegTable.end(), srcId) != m_unitRegTable.end()) { m_unitRegTable.unlock(); return true; } else { m_unitRegTable.unlock(); return false; } } /* Helper to release unit registrations. */ void AffiliationLookup::clearUnitReg() { __lock(); std::vector srcToRel = std::vector(); LogWarning(LOG_HOST, "%s, releasing all unit registrations", m_name.c_str()); m_unitRegTable.clear(); __unlock(); } /* Helper to group affiliate a source ID. */ void AffiliationLookup::groupAff(uint32_t srcId, uint32_t dstId) { if (!isGroupAff(srcId, dstId)) { __lock(); // update dynamic affiliation table m_grpAffTable[srcId] = dstId; if (m_verbose) { LogInfoEx(LOG_HOST, "%s, group affiliation, srcId = %u, dstId = %u", m_name.c_str(), srcId, dstId); } __unlock(); } } /* Helper to group unaffiliate a source ID. */ bool AffiliationLookup::groupUnaff(uint32_t srcId) { __lock(); // lookup dynamic affiliation table entry auto it = m_grpAffTable.find(srcId); if (it != m_grpAffTable.end()) { if (m_verbose) { LogInfoEx(LOG_HOST, "%s, group unaffiliation, srcId = %u, dstId = %u", m_name.c_str(), srcId, it->second); } } else { __unlock(); return false; } // remove dynamic affiliation table entry try { uint32_t entry = m_grpAffTable.at(srcId); // this value will get discarded (void)entry; // but some variants of C++ mark the unordered_map<>::at as nodiscard m_grpAffTable.erase(srcId); __unlock(); return true; } catch (...) { __unlock(); return false; } } /* Helper to determine if the group destination ID has any affiations. */ bool AffiliationLookup::hasGroupAff(uint32_t dstId) const { __spinlock(); // lookup dynamic affiliation table entry m_grpAffTable.lock(false); for (auto entry : m_grpAffTable) { if (entry.second == dstId) { m_grpAffTable.unlock(); return true; } } m_grpAffTable.unlock(); return false; } /* Helper to determine if the source ID has affiliated to the group destination ID. */ bool AffiliationLookup::isGroupAff(uint32_t srcId, uint32_t dstId) const { __spinlock(); // lookup dynamic affiliation table entry m_grpAffTable.lock(false); auto it = m_grpAffTable.find(srcId); if (it != m_grpAffTable.end()) { if (it->second == dstId) { m_grpAffTable.unlock(); return true; } } m_grpAffTable.unlock(); return false; } /* Helper to release group affiliations. */ std::vector AffiliationLookup::clearGroupAff(uint32_t dstId, bool releaseAll) { std::vector srcToRel = std::vector(); if (dstId == 0U && !releaseAll) { return srcToRel; } if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, releasing all group affiliations", m_name.c_str()); for (auto entry : m_grpAffTable) { uint32_t srcId = entry.first; srcToRel.push_back(srcId); } } else { LogWarning(LOG_HOST, "%s, releasing group affiliations, dstId = %u", m_name.c_str(), dstId); for (auto entry : m_grpAffTable) { uint32_t srcId = entry.first; uint32_t grpId = entry.second; if (grpId == dstId) { srcToRel.push_back(srcId); } } } __lock(); for (auto srcId : srcToRel) { m_grpAffTable.erase(srcId); } __unlock(); return srcToRel; } /* Helper to grant a channel. */ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout, bool grp, bool netGranted) { if (dstId == 0U) { return false; } if (m_chLookup == nullptr) { return false; } if (!m_chLookup->isRFChAvailable()) { return false; } uint32_t chNo = m_chLookup->getFirstRFChannel(); if (!m_chLookup->removeRFCh(chNo)) { return false; } __lock(); m_grantChTable[dstId] = chNo; m_grantSrcIdTable[dstId] = srcId; m_rfGrantChCnt++; m_uuGrantedTable[dstId] = !grp; m_netGrantedTable[dstId] = netGranted; m_grantTimers[dstId] = Timer(1000U, grantTimeout); m_grantTimers[dstId].start(); if (m_verbose) { LogInfoEx(LOG_HOST, "%s, granting channel, chNo = %u, dstId = %u, srcId = %u, group = %u", m_name.c_str(), chNo, dstId, srcId, grp); } __unlock(); return true; } /* Helper to start the destination ID grant timer. */ void AffiliationLookup::touchGrant(uint32_t dstId) { if (dstId == 0U) { return; } /* ** bryanb: this doesn't __spinlock(), this is dangerous but necessary ** otherwise an affiliations lookup lock can cause audio cuts if this is ** used in an audio processing chain */ m_grantTimers.lock(false); if (isGranted(dstId)) { m_grantTimers[dstId].start(); } m_grantTimers.unlock(); } /* Helper to release the channel grant for the destination ID. */ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll) { if (dstId == 0U && !releaseAll) { return false; } // are we trying to release all grants? if (dstId == 0U && releaseAll) { LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name.c_str()); m_grantChTable.lock(false); std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { uint32_t dstId = entry.first; gntsToRel.push_back(dstId); } m_grantChTable.unlock(); // release grants for (uint32_t dstId : gntsToRel) { releaseGrant(dstId, false); } return true; } if (isGranted(dstId)) { uint32_t chNo = 0U; m_grantChTable.lock(false); for (auto entry : m_grantChTable) { if (entry.first == dstId) { chNo = entry.second; break; } } m_grantChTable.unlock(); uint32_t srcId = getGrantedSrcId(dstId); if (m_verbose) { LogInfoEx(LOG_HOST, "%s, releasing channel grant, chNo = %u, dstId = %u", m_name.c_str(), chNo, dstId); } if (m_releaseGrant != nullptr) { m_releaseGrant(chNo, srcId, dstId, 0U); } __lock(); m_grantChTable.erase(dstId); m_grantSrcIdTable.erase(dstId); m_uuGrantedTable.erase(dstId); m_netGrantedTable.erase(dstId); if (m_chLookup != nullptr) { m_chLookup->addRFCh(chNo, true); } if (m_rfGrantChCnt > 0U) { m_rfGrantChCnt--; } else { m_rfGrantChCnt = 0U; } m_grantTimers[dstId].stop(); __unlock(); return true; } return false; } /* Helper to determine if the channel number is busy. */ bool AffiliationLookup::isChBusy(uint32_t chNo) const { if (chNo == 0U) { return false; } __spinlock(); // lookup dynamic channel grant table entry m_grantChTable.lock(false); for (auto entry : m_grantChTable) { if (entry.second == chNo) { m_grantChTable.unlock(); return true; } } m_grantChTable.unlock(); return false; } /* Helper to determine if the destination ID is already granted. */ bool AffiliationLookup::isGranted(uint32_t dstId) const { if (dstId == 0U) { return false; } /* ** bryanb: this doesn't __spinlock(), this is dangerous but necessary ** otherwise an affiliations lookup lock can cause audio cuts if this is ** used in an audio processing chain */ // lookup dynamic channel grant table entry m_grantChTable.lock(false); for (auto entry : m_grantChTable) { uint32_t gntDstId = entry.first; uint32_t chNo = entry.second; if (gntDstId == dstId && chNo != 0U) { m_grantChTable.unlock(); return true; } } m_grantChTable.unlock(); return false; } /* Helper to determine if the destination ID is network granted. */ bool AffiliationLookup::isGroup(uint32_t dstId) const { if (dstId == 0U) { return true; } __spinlock(); // lookup U-U grant flag table entry m_uuGrantedTable.lock(false); for (auto entry : m_uuGrantedTable) { uint32_t gntDstId = entry.first; bool uu = entry.second; if (gntDstId == dstId) { m_uuGrantedTable.unlock(); return !uu; } } m_uuGrantedTable.unlock(); return true; } /* Helper to determine if the destination ID is network granted. */ bool AffiliationLookup::isNetGranted(uint32_t dstId) const { if (dstId == 0U) { return false; } __spinlock(); // lookup net granted flag table entry m_netGrantedTable.lock(false); for (auto entry : m_netGrantedTable) { uint32_t gntDstId = entry.first; bool net = entry.second; if (gntDstId == dstId) { m_netGrantedTable.unlock(); return net; } } m_netGrantedTable.unlock(); return false; } /* Helper to get the channel granted for the given destination ID. */ uint32_t AffiliationLookup::getGrantedCh(uint32_t dstId) { if (dstId == 0U) { return 0U; } __spinlock(); if (isGranted(dstId)) { // lookup dynamic channel grant table entry m_grantChTable.lock(false); auto it = m_grantChTable.find(dstId); if (it != m_grantChTable.end()) { m_grantChTable.unlock(); return m_grantChTable[dstId]; } m_grantChTable.unlock(); } return 0U; } /* Helper to get the destination ID for the given channel. */ uint32_t AffiliationLookup::getGrantedDstByCh(uint32_t chNo) { __spinlock(); // lookup dynamic channel grant table entry m_grantChTable.lock(false); for (auto entry : m_grantChTable) { if (entry.second == chNo) { m_grantChTable.unlock(); return entry.first; } } m_grantChTable.unlock(); return 0U; } /* Helper to get the destination ID granted to the given source ID. */ uint32_t AffiliationLookup::getGrantedBySrcId(uint32_t srcId) { if (srcId == 0U) { return 0U; } __spinlock(); // lookup dynamic channel grant source table entry m_grantSrcIdTable.lock(false); for (auto entry : m_grantSrcIdTable) { if (entry.second == srcId) { m_grantSrcIdTable.unlock(); return entry.first; } } m_grantSrcIdTable.unlock(); return 0U; } /* Helper to get the source ID granted for the given destination ID. */ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId) { if (dstId == 0U) { return 0U; } __spinlock(); if (isGranted(dstId)) { // lookup dynamic channel grant source table entry m_grantSrcIdTable.lock(false); auto it = m_grantSrcIdTable.find(dstId); if (it != m_grantSrcIdTable.end()) { m_grantSrcIdTable.unlock(); return m_grantSrcIdTable[dstId]; } m_grantSrcIdTable.unlock(); } return 0U; } /* Updates the processor by the passed number of milliseconds. */ void AffiliationLookup::clock(uint32_t ms) { m_grantChTable.spinlock(); // clock all the grant timers m_grantChTable.lock(false); std::vector gntsToRel = std::vector(); for (auto entry : m_grantChTable) { uint32_t dstId = entry.first; auto it = m_grantTimers.find(dstId); if (it != m_grantTimers.end()) { it->second.clock(ms); if (it->second.isRunning() && it->second.hasExpired()) { gntsToRel.push_back(dstId); } } } m_grantChTable.unlock(); // release grants that have timed out for (uint32_t dstId : gntsToRel) { releaseGrant(dstId, false); } if (!m_disableUnitRegTimeout) { m_unitRegTable.spinlock(); // clock all the unit registration timers m_unitRegTable.lock(false); std::vector unitsToDereg = std::vector(); for (uint32_t srcId : m_unitRegTable) { auto it = m_unitRegTimers.find(srcId); if (it != m_unitRegTimers.end()) { it->second.clock(ms); if (it->second.isRunning() && it->second.hasExpired()) { unitsToDereg.push_back(srcId); } } } m_unitRegTable.unlock(); // release units registrations that have timed out for (uint32_t srcId : unitsToDereg) { unitDereg(srcId, true); } } }