You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dvmhost/src/common/lookups/AffiliationLookup.cpp

718 lines
17 KiB

// 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 <cassert>
// ---------------------------------------------------------------------------
// 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<uint32_t> srcToRel = std::vector<uint32_t>();
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<uint32_t> AffiliationLookup::clearGroupAff(uint32_t dstId, bool releaseAll)
{
std::vector<uint32_t> srcToRel = std::vector<uint32_t>();
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<uint32_t> gntsToRel = std::vector<uint32_t>();
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<uint32_t> gntsToRel = std::vector<uint32_t>();
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<uint32_t> unitsToDereg = std::vector<uint32_t>();
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);
}
}
}

Powered by TurnKey Linux.