make the network presence announcment (VC -> CC and CC -> FNE) timing configurable (this value is in seconds); refactor RF channel enumeration, RF channels are now enumerated in their own class ChannelLookup instead of being integrated into AffiliationLookup, this allows the flexibility to update and change channel information at runtime; add support for VC -> CC presence/registration to tell the CC what the REST information is for that VC, this makes the restAddress and restPort entries for the voiceChNo list in the config.yml optional, the only mandatory fields are channelId, channelNo and restPassword;

pull/51/head
Bryan Biedenkapp 2 years ago
parent f5a5d83f4b
commit e0b6da51fb

@ -372,6 +372,9 @@ system:
restSsl: false
# Flag indicating voice channels will notify the control channel of traffic status.
notifyEnable: true
# Amount of time between network presence announcements. (seconds)
# NOTE: This value applies to VC -> CC and CC -> FNE presence notification messages.
presence: 120
#
# Voice Channels

@ -7,7 +7,7 @@
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "lookups/AffiliationLookup.h"
@ -15,6 +15,8 @@
using namespace lookups;
#include <cassert>
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
@ -23,10 +25,9 @@ using namespace lookups;
/// Initializes a new instance of the AffiliationLookup class.
/// </summary>
/// <param name="name">Name of lookup table.</param>
/// <param name="channelLookup">Instance of the channel lookup class.</param>
/// <param name="verbose">Flag indicating whether verbose logging is enabled.</param>
AffiliationLookup::AffiliationLookup(const std::string name, bool verbose) :
m_rfChTable(),
m_rfChDataTable(),
AffiliationLookup::AffiliationLookup(const std::string name, ChannelLookup* channelLookup, bool verbose) :
m_rfGrantChCnt(0U),
m_unitRegTable(),
m_grpAffTable(),
@ -37,11 +38,12 @@ AffiliationLookup::AffiliationLookup(const std::string name, bool verbose) :
m_grantTimers(),
m_releaseGrant(nullptr),
m_name(),
m_chLookup(channelLookup),
m_verbose(verbose)
{
m_name = name;
assert(channelLookup != nullptr);
m_rfChTable.clear();
m_name = name;
m_unitRegTable.clear();
m_grpAffTable.clear();
@ -268,13 +270,12 @@ bool AffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTi
return false;
}
if (!isRFChAvailable()) {
if (!m_chLookup->isRFChAvailable()) {
return false;
}
uint32_t chNo = m_rfChTable.at(0);
auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
m_rfChTable.erase(it);
uint32_t chNo = m_chLookup->getFirstRFChannel();
m_chLookup->removeRFCh(chNo);
m_grantChTable[dstId] = chNo;
m_grantSrcIdTable[dstId] = srcId;
@ -355,7 +356,7 @@ bool AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
m_grantSrcIdTable.erase(dstId);
m_uuGrantedTable.erase(dstId);
m_netGrantedTable.erase(dstId);
m_rfChTable.push_back(chNo);
m_chLookup->addRFCh(chNo, true);
if (m_rfGrantChCnt > 0U) {
m_rfGrantChCnt--;
@ -514,27 +515,6 @@ uint32_t AffiliationLookup::getGrantedSrcId(uint32_t dstId)
return 0U;
}
/// <summary>
/// Helper to get RF channel data.
/// </summary>
/// <param name="chNo"></param>
/// <returns></returns>
VoiceChData AffiliationLookup::getRFChData(uint32_t chNo) const
{
if (chNo == 0U) {
return VoiceChData();
}
VoiceChData data;
try {
data = m_rfChDataTable.at(chNo);
} catch (...) {
data = VoiceChData();
}
return data;
}
/// <summary>
/// Updates the processor by the passed number of milliseconds.
/// </summary>

@ -14,6 +14,7 @@
#define __AFFILIATION_LOOKUP_H__
#include "common/Defines.h"
#include "common/lookups/ChannelLookup.h"
#include "common/Timer.h"
#include <cstdio>
@ -24,79 +25,6 @@
namespace lookups
{
// ---------------------------------------------------------------------------
// Class Declaration
// Represents voice channel data.
// ---------------------------------------------------------------------------
class HOST_SW_API VoiceChData {
public:
/// <summary>Initializes a new instance of the VoiceChData class.</summary>
VoiceChData() :
m_chId(0U),
m_chNo(0U),
m_address(),
m_port(),
m_password(),
m_ssl()
{
/* stub */
}
/// <summary>Initializes a new instance of the VoiceChData class.</summary>
/// <param name="chId">Voice Channel Identity.</param>
/// <param name="chNo">Voice Channel Number.</param>
/// <param name="address">REST API Address.</param>
/// <param name="port">REST API Port.</param>
/// <param name="password">REST API Password.</param>
/// <param name="ssl">Flag indicating REST is using SSL.</param>
VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) :
m_chId(chId),
m_chNo(chNo),
m_address(address),
m_port(port),
m_password(password),
m_ssl(ssl)
{
/* stub */
}
/// <summary>Equals operator.</summary>
/// <param name="data"></param>
/// <returns></returns>
VoiceChData & operator=(const VoiceChData & data)
{
if (this != &data) {
m_chId = data.m_chId;
m_chNo = data.m_chNo;
m_address = data.m_address;
m_port = data.m_port;
m_password = data.m_password;
m_ssl = data.m_ssl;
}
return *this;
}
/// <summary>Helper to determine if the channel identity is valid.</summary>
bool isValidChId() const { return m_chId != 0U; }
/// <summary>Helper to determine if the channel is valid.</summary>
bool isValidCh() const { return m_chNo != 0U; }
public:
/// <summary>Voice Channel Identity.</summary>
__READONLY_PROPERTY_PLAIN(uint8_t, chId);
/// <summary>Voice Channel Number.</summary>
__READONLY_PROPERTY_PLAIN(uint32_t, chNo);
/// <summary>REST API Address.</summary>
__READONLY_PROPERTY_PLAIN(std::string, address);
/// <summary>REST API Port.</summary>
__READONLY_PROPERTY_PLAIN(uint16_t, port);
/// <summary>REST API Password.</summary>
__READONLY_PROPERTY_PLAIN(std::string, password);
/// <summary>Flag indicating REST is using SSL.</summary>
__READONLY_PROPERTY_PLAIN(bool, ssl);
};
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a lookup table class that contains subscriber registration
@ -106,10 +34,11 @@ namespace lookups
class HOST_SW_API AffiliationLookup {
public:
/// <summary>Initializes a new instance of the AffiliationLookup class.</summary>
AffiliationLookup(const std::string name, bool verbose);
AffiliationLookup(const std::string name, ChannelLookup* chLookup, bool verbose);
/// <summary>Finalizes a instance of the AffiliationLookup class.</summary>
virtual ~AffiliationLookup();
/** Unit Registrations */
/// <summary>Gets the count of unit registrations.</summary>
uint8_t unitRegSize() const { return m_unitRegTable.size(); }
/// <summary>Gets the unit registration table.</summary>
@ -123,6 +52,7 @@ namespace lookups
/// <summary>Helper to release unit registrations.</summary>
virtual void clearUnitReg();
/** Group Affiliations */
/// <summary>Gets the count of affiliations.</summary>
uint8_t grpAffSize() const { return m_grpAffTable.size(); }
/// <summary>Gets the group affiliation table.</summary>
@ -138,6 +68,7 @@ namespace lookups
/// <summary>Helper to release group affiliations.</summary>
virtual std::vector<uint32_t> clearGroupAff(uint32_t dstId, bool releaseAll);
/** Channel Grants */
/// <summary>Gets the count of grants.</summary>
uint8_t grantSize() const { return m_grantChTable.size(); }
/// <summary>Gets the grant table.</summary>
@ -162,23 +93,12 @@ namespace lookups
virtual uint32_t getGrantedBySrcId(uint32_t srcId);
/// <summary>Helper to get the source ID granted for the given destination ID.</summary>
virtual uint32_t getGrantedSrcId(uint32_t dstId);
/// <summary>Helper to set RF channel data.</summary>
void setRFChData(const std::unordered_map<uint32_t, VoiceChData>& chData) { m_rfChDataTable = chData; }
/// <summary>Helper to get RF channel data.</summary>
VoiceChData getRFChData(uint32_t chNo) const;
/// <summary>Helper to add a RF channel.</summary>
void addRFCh(uint32_t chNo) { m_rfChTable.push_back(chNo); }
/// <summary>Helper to remove a RF channel.</summary>
void removeRFCh(uint32_t chNo) { m_rfChTable.push_back(chNo); }
/// <summary>Gets the count of RF channels.</summary>
uint8_t getRFChCnt() const { return m_rfChTable.size(); }
/// <summary>Helper to determine if there are any RF channels available..</summary>
bool isRFChAvailable() const { return !m_rfChTable.empty(); }
/// <summary>Gets the count of granted RF channels.</summary>
uint8_t getGrantedRFChCnt() const { return m_rfGrantChCnt; }
/// <summary>Gets the RF channel lookup class.</summary>
ChannelLookup* rfCh() const { return m_chLookup; }
/// <summary>Updates the processor by the passed number of milliseconds.</summary>
void clock(uint32_t ms);
@ -186,8 +106,6 @@ namespace lookups
void setReleaseGrantCallback(std::function<void(uint32_t, uint32_t, uint8_t)>&& callback) { m_releaseGrant = callback; }
protected:
std::vector<uint32_t> m_rfChTable;
std::unordered_map<uint32_t, VoiceChData> m_rfChDataTable;
uint8_t m_rfGrantChCnt;
std::vector<uint32_t> m_unitRegTable;
@ -203,6 +121,7 @@ namespace lookups
std::function<void(uint32_t, uint32_t, uint8_t)> m_releaseGrant;
std::string m_name;
ChannelLookup* m_chLookup;
bool m_verbose;
};

@ -0,0 +1,97 @@
// 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.
*
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#include "lookups/ChannelLookup.h"
#include "Log.h"
using namespace lookups;
// ---------------------------------------------------------------------------
// Public Class Members
// ---------------------------------------------------------------------------
/// <summary>
/// Initializes a new instance of the ChannelLookup class.
/// </summary>
ChannelLookup::ChannelLookup() :
m_rfChTable(),
m_rfChDataTable()
{
m_rfChTable.clear();
}
/// <summary>
/// Finalizes a instance of the ChannelLookup class.
/// </summary>
ChannelLookup::~ChannelLookup() = default;
/// <summary>
/// Helper to get RF channel data.
/// </summary>
/// <param name="chNo"></param>
/// <returns></returns>
VoiceChData ChannelLookup::getRFChData(uint32_t chNo) const
{
if (chNo == 0U) {
return VoiceChData();
}
VoiceChData data;
try {
data = m_rfChDataTable.at(chNo);
} catch (...) {
data = VoiceChData();
}
return data;
}
/// <summary>
/// Helper to add a RF channel.
/// </summary>
/// <param name="chNo"></param>
/// <param name="force"></param>
/// <returns></returns>
bool ChannelLookup::addRFCh(uint32_t chNo, bool force)
{
if (chNo == 0U) {
return false;
}
if (force) {
m_rfChTable.push_back(chNo);
return true;
}
auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
if (it == m_rfChTable.end()) {
m_rfChTable.push_back(chNo);
return true;
}
return false;
}
/// <summary>
/// Helper to remove a RF channel.
/// </summary>
/// <param name="chNo"></param>
/// <returns></returns>
void ChannelLookup::removeRFCh(uint32_t chNo)
{
if (chNo == 0U) {
return;
}
auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
m_rfChTable.erase(it);
}

@ -0,0 +1,143 @@
// 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.
*
* @package DVM / Common Library
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__CHANNEL_LOOKUP_H__)
#define __CHANNEL_LOOKUP_H__
#include "common/Defines.h"
#include "common/Timer.h"
#include <cstdio>
#include <unordered_map>
#include <algorithm>
#include <vector>
#include <functional>
namespace lookups
{
// ---------------------------------------------------------------------------
// Class Declaration
// Represents voice channel data.
// ---------------------------------------------------------------------------
class HOST_SW_API VoiceChData {
public:
/// <summary>Initializes a new instance of the VoiceChData class.</summary>
VoiceChData() :
m_chId(0U),
m_chNo(0U),
m_address(),
m_port(),
m_password(),
m_ssl()
{
/* stub */
}
/// <summary>Initializes a new instance of the VoiceChData class.</summary>
/// <param name="chId">Voice Channel Identity.</param>
/// <param name="chNo">Voice Channel Number.</param>
/// <param name="address">REST API Address.</param>
/// <param name="port">REST API Port.</param>
/// <param name="password">REST API Password.</param>
/// <param name="ssl">Flag indicating REST is using SSL.</param>
VoiceChData(uint8_t chId, uint32_t chNo, std::string address, uint16_t port, std::string password, bool ssl) :
m_chId(chId),
m_chNo(chNo),
m_address(address),
m_port(port),
m_password(password),
m_ssl(ssl)
{
/* stub */
}
/// <summary>Equals operator.</summary>
/// <param name="data"></param>
/// <returns></returns>
VoiceChData & operator=(const VoiceChData & data)
{
if (this != &data) {
m_chId = data.m_chId;
m_chNo = data.m_chNo;
m_address = data.m_address;
m_port = data.m_port;
m_password = data.m_password;
m_ssl = data.m_ssl;
}
return *this;
}
/// <summary>Helper to determine if the channel identity is valid.</summary>
bool isValidChId() const { return m_chId != 0U; }
/// <summary>Helper to determine if the channel is valid.</summary>
bool isValidCh() const { return m_chNo != 0U; }
public:
/// <summary>Voice Channel Identity.</summary>
__READONLY_PROPERTY_PLAIN(uint8_t, chId);
/// <summary>Voice Channel Number.</summary>
__READONLY_PROPERTY_PLAIN(uint32_t, chNo);
/// <summary>REST API Address.</summary>
__PROPERTY_PLAIN(std::string, address);
/// <summary>REST API Port.</summary>
__PROPERTY_PLAIN(uint16_t, port);
/// <summary>REST API Password.</summary>
__READONLY_PROPERTY_PLAIN(std::string, password);
/// <summary>Flag indicating REST is using SSL.</summary>
__PROPERTY_PLAIN(bool, ssl);
};
// ---------------------------------------------------------------------------
// Class Declaration
// Implements a lookup table class that contains RF channel information.
// ---------------------------------------------------------------------------
class HOST_SW_API ChannelLookup {
public:
/// <summary>Initializes a new instance of the ChannelLookup class.</summary>
ChannelLookup();
/// <summary>Finalizes a instance of the ChannelLookup class.</summary>
virtual ~ChannelLookup();
/** RF Channel Data */
/// <summary>Gets the count of RF channel data.</summary>
uint8_t rfChDataSize() const { return m_rfChDataTable.size(); }
/// <summary>Gets the RF channel data table.</summary>
std::unordered_map<uint32_t, VoiceChData> rfChDataTable() const { return m_rfChDataTable; }
/// <summary>Helper to set RF channel data.</summary>
void setRFChData(const std::unordered_map<uint32_t, VoiceChData>& chData) { m_rfChDataTable = chData; }
/// <summary>Helper to set RF channel data.</summary>
void setRFChData(uint32_t chNo, VoiceChData chData) { m_rfChDataTable[chNo] = chData; }
/// <summary>Helper to get RF channel data.</summary>
VoiceChData getRFChData(uint32_t chNo) const;
/// <summary>Helper to get first available channel number.</summary>
uint32_t getFirstRFChannel() const { return m_rfChTable.at(0); }
/// <summary>Gets the count of RF channels.</summary>
uint8_t rfChSize() const { return m_rfChTable.size(); }
/// <summary>Gets the RF channels table.</summary>
std::vector<uint32_t> rfChTable() const { return m_rfChTable; }
/// <summary>Helper to add a RF channel.</summary>
bool addRFCh(uint32_t chNo, bool force = false);
/// <summary>Helper to remove a RF channel.</summary>
void removeRFCh(uint32_t chNo);
/// <summary>Helper to determine if there are any RF channels available..</summary>
bool isRFChAvailable() const { return !m_rfChTable.empty(); }
private:
std::vector<uint32_t> m_rfChTable;
std::unordered_map<uint32_t, VoiceChData> m_rfChDataTable;
};
} // namespace lookups
#endif // __CHANNEL_LOOKUP_H__

@ -24,6 +24,8 @@ using namespace network::udp;
#include <cerrno>
#include <cstring>
#include <ifaddrs.h>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
@ -578,15 +580,15 @@ void Socket::setPresharedKey(const uint8_t* presharedKey)
/// </summary>
/// <param name="hostname">String containing hostname to resolve.</param>
/// <param name="port">Numeric port number of service to resolve.</param>
/// <param name="addr">Socket address structure.</param>
/// <param name="address">Socket address structure.</param>
/// <param name="addrLen"></param>
/// <returns>Zero if no error during lookup, otherwise error.</returns>
int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& addr, uint32_t& addrLen)
int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& address, uint32_t& addrLen)
{
struct addrinfo hints;
::memset(&hints, 0, sizeof(hints));
return lookup(hostname, port, addr, addrLen, hints);
return lookup(hostname, port, address, addrLen, hints);
}
/// <summary>
@ -594,11 +596,11 @@ int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage&
/// </summary>
/// <param name="hostname">String containing hostname to resolve.</param>
/// <param name="port">Numeric port number of service to resolve.</param>
/// <param name="addr">Socket address structure.</param>
/// <param name="address">Socket address structure.</param>
/// <param name="addrLen"></param>
/// <param name="hints"></param>
/// <returns>Zero if no error during lookup, otherwise error.</returns>
int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& addr, uint32_t& addrLen, struct addrinfo& hints)
int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage& address, uint32_t& addrLen, struct addrinfo& hints)
{
std::string portstr = std::to_string(port);
struct addrinfo* res;
@ -608,7 +610,7 @@ int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage&
int err = getaddrinfo(hostname.empty() ? NULL : hostname.c_str(), portstr.c_str(), &hints, &res);
if (err != 0) {
sockaddr_in* paddr = (sockaddr_in*)& addr;
sockaddr_in* paddr = (sockaddr_in*)& address;
::memset(paddr, 0x00U, addrLen = sizeof(sockaddr_in));
paddr->sin_family = AF_INET;
paddr->sin_port = htons(port);
@ -617,13 +619,56 @@ int Socket::lookup(const std::string& hostname, uint16_t port, sockaddr_storage&
return err;
}
::memcpy(&addr, res->ai_addr, addrLen = res->ai_addrlen);
::memcpy(&address, res->ai_addr, addrLen = res->ai_addrlen);
freeaddrinfo(res);
return 0;
}
/// <summary>
///
/// </summary>
/// <returns>Zero if no error during lookup, otherwise error.</returns>
std::string Socket::getLocalAddress()
{
struct ifaddrs *ifaddr, *ifa;
int n;
char host[NI_MAXHOST];
std::string address = std::string();
int err = -1;
if ((err = getifaddrs(&ifaddr)) == -1) {
LogError(LOG_NET, "Cannot retreive system network interfaces");
return "0.0.0.0";
}
for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
if (ifa->ifa_addr == NULL)
continue;
int family = ifa->ifa_addr->sa_family;
if (family == AF_INET || family == AF_INET6) {
err = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (err != 0) {
LogError(LOG_NET, "Cannot retreive system network interfaces, err: %d", errno);
break;
}
address = std::string(host);
if (address == "127.0.0.1" || address == "::1")
continue;
else
break;
}
}
freeifaddrs(ifaddr);
return address;
}
/// <summary>
///
/// </summary>

@ -136,6 +136,9 @@ namespace network
/// <summary>Helper to lookup a hostname and resolve it to an IP address.</summary>
static int lookup(const std::string& hostName, uint16_t port, sockaddr_storage& address, uint32_t& addrLen, struct addrinfo& hints);
/// <summary></summary>
static std::string getLocalAddress();
/// <summary></summary>
static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type = IMT_ADDRESS_AND_PORT);

@ -1092,7 +1092,8 @@ void FNENetwork::createPeerAffiliations(uint32_t peerId, std::string peerName)
erasePeerAffiliations(peerId);
std::lock_guard<std::mutex> lock(m_peerMutex);
m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, m_verbose);
lookups::ChannelLookup* chLookup = new lookups::ChannelLookup();
m_peerAffiliations[peerId] = new lookups::AffiliationLookup(peerName, chLookup, m_verbose);
}
/// <summary>
@ -1106,8 +1107,12 @@ bool FNENetwork::erasePeerAffiliations(uint32_t peerId)
auto it = std::find_if(m_peerAffiliations.begin(), m_peerAffiliations.end(), [&](PeerAffiliationMapPair x) { return x.first == peerId; });
if (it != m_peerAffiliations.end()) {
lookups::AffiliationLookup* aff = m_peerAffiliations[peerId];
if (aff != nullptr)
if (aff != nullptr) {
lookups::ChannelLookup* rfCh = aff->rfCh();
if (rfCh != nullptr)
delete rfCh;
delete aff;
}
m_peerAffiliations.erase(peerId);
return true;

@ -139,6 +139,7 @@ bool Host::readParams()
** Channel Configuration
*/
yaml::Node rfssConfig = systemConf["config"];
m_channelLookup = new ChannelLookup();
m_channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);
if (m_channelId > 15U) { // clamp to 15
m_channelId = 15U;
@ -188,6 +189,7 @@ bool Host::readParams()
uint16_t restApiPort = (uint16_t)controlCh["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = controlCh["restPassword"].as<std::string>();
bool restSsl = controlCh["restSsl"].as<bool>(false);
m_presenceTime = controlCh["presence"].as<uint32_t>(120U);
VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword, restSsl);
m_controlChData = data;
@ -232,7 +234,7 @@ bool Host::readParams()
chNo = 4095U;
}
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
std::string restApiAddress = channel["restAddress"].as<std::string>("0.0.0.0");
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
std::string restApiPassword = channel["restPassword"].as<std::string>();
bool restSsl = channel["restSsl"].as<bool>(false);
@ -240,14 +242,15 @@ bool Host::readParams()
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", chId, chNo, restApiAddress.c_str(), restApiPort, restSsl);
VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword, restSsl);
m_voiceChData[chNo] = data;
m_voiceChNo.push_back(chNo);
m_channelLookup->setRFChData(chNo, data);
m_channelLookup->addRFCh(chNo);
}
std::string strVoiceChNo = "";
for (auto it = m_voiceChNo.begin(); it != m_voiceChNo.end(); ++it) {
std::vector<uint32_t> voiceChNo = m_channelLookup->rfChTable();
for (auto it = voiceChNo.begin(); it != voiceChNo.end(); ++it) {
uint32_t chNo = ::atoi(std::to_string(*it).c_str());
::lookups::VoiceChData voiceChData = m_voiceChData[chNo];
::lookups::VoiceChData voiceChData = m_channelLookup->getRFChData(chNo);
char hexStr[29];
@ -816,6 +819,8 @@ bool Host::createNetwork()
// initialize network remote command
if (restApiEnable) {
m_restAddress = restApiAddress;
m_restPort = restApiPort;
m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, restApiSSLKey, restApiSSLCert, restApiEnableSSL, this, restApiDebug);
m_RESTAPI->setLookups(m_ridLookup, m_tidLookup);
bool ret = m_RESTAPI->open();
@ -827,6 +832,8 @@ bool Host::createNetwork()
}
}
else {
m_restAddress = "0.0.0.0";
m_restPort = REST_API_DEFAULT_PORT;
m_RESTAPI = nullptr;
}

@ -15,6 +15,7 @@
*/
#include "Defines.h"
#include "common/lookups/RSSIInterpolator.h"
#include "common/network/udp/Socket.h"
#include "common/Log.h"
#include "common/StopWatch.h"
#include "common/Thread.h"
@ -86,8 +87,7 @@ Host::Host(const std::string& confFile) :
m_txFrequency(0U),
m_channelId(0U),
m_channelNo(0U),
m_voiceChNo(),
m_voiceChData(),
m_channelLookup(),
m_voiceChPeerId(),
m_controlChData(),
m_idenTable(nullptr),
@ -102,6 +102,7 @@ Host::Host(const std::string& confFile) :
m_nxdnCCData(false),
m_nxdnCtrlChannel(false),
m_nxdnCtrlBroadcast(false),
m_presenceTime(120U),
m_siteId(1U),
m_sysId(1U),
m_dmrNetId(1U),
@ -120,6 +121,8 @@ Host::Host(const std::string& confFile) :
m_nxdnBcastDurationTimer(1000U),
m_activeTickDelay(5U),
m_idleTickDelay(5U),
m_restAddress("0.0.0.0"),
m_restPort(REST_API_DEFAULT_PORT),
m_RESTAPI(nullptr)
{
/* stub */
@ -404,9 +407,9 @@ int Host::run()
}
dmr = std::make_unique<dmr::Control>(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes,
embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup,
embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup,
m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket, dmrDumpCsbkData, dmrDebug, dmrVerbose);
dmr->setOptions(m_conf, m_supervisor, m_voiceChNo, m_voiceChData, m_controlChData, m_dmrNetId, m_siteId, m_channelId,
dmr->setOptions(m_conf, m_supervisor, m_controlChData, m_dmrNetId, m_siteId, m_channelId,
m_channelNo, true);
if (dmrCtrlChannel) {
@ -475,9 +478,9 @@ int Host::run()
}
p25 = std::make_unique<p25::Control>(m_authoritative, m_p25NAC, callHang, m_p25QueueSizeBytes, m_modem,
m_network, m_timeout, m_rfTalkgroupHang, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket,
m_network, m_timeout, m_rfTalkgroupHang, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket,
p25RepeatDataPacket, p25DumpTsbkData, p25Debug, p25Verbose);
p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_controlChData,
p25->setOptions(m_conf, m_supervisor, m_cwCallsign, m_controlChData,
m_p25NetId, m_sysId, m_p25RfssId, m_siteId, m_channelId, m_channelNo, true);
if (p25CtrlChannel) {
@ -537,9 +540,9 @@ int Host::run()
}
nxdn = std::make_unique<nxdn::Control>(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes,
m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi,
m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_channelLookup, m_ridLookup, m_tidLookup, m_idenTable, rssi,
nxdnDumpRcchData, nxdnDebug, nxdnVerbose);
nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_voiceChNo, m_voiceChData, m_controlChData, m_siteId,
nxdn->setOptions(m_conf, m_supervisor, m_cwCallsign, m_controlChData, m_siteId,
m_sysId, m_channelId, m_channelNo, true);
if (nxdnCtrlChannel) {
@ -897,10 +900,91 @@ int Host::run()
nxdnFrameWriteThread.run();
nxdnFrameWriteThread.setName("nxdn:frame-w");
Timer ccRegisterTimer(1000U, 120U);
ccRegisterTimer.start();
/** Network Presence Notification */
ThreadFunc presenceThread([&, this]() {
if (g_killed)
return;
Timer presenceNotifyTimer(1000U, m_presenceTime);
presenceNotifyTimer.start();
bool hasInitialRegistered = false;
StopWatch stopWatch;
stopWatch.start();
while (!g_killed) {
// scope is intentional
{
uint32_t ms = stopWatch.elapsed();
stopWatch.start();
presenceNotifyTimer.clock(ms);
// VC -> CC presence registration
if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr && m_RESTAPI != nullptr) {
if ((presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) || !hasInitialRegistered) {
LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", m_controlChData.address().c_str(), m_controlChData.port(), m_network->getPeerId());
hasInitialRegistered = true;
std::string localAddress = network::udp::Socket::getLocalAddress();
if (m_restAddress == "0.0.0.0") {
m_restAddress = localAddress;
}
// callback REST API to release the granted TG on the specified control channel
json::object req = json::object();
req["channelNo"].set<uint32_t>(m_channelNo);
uint32_t peerId = m_network->getPeerId();
req["peerId"].set<uint32_t>(peerId);
req["restAddress"].set<std::string>(m_restAddress);
req["restPort"].set<uint16_t>(m_restPort);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_REGISTER_CC_VC, req, m_controlChData.ssl(), REST_QUICK_WAIT, false);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", m_controlChData.address().c_str(), m_controlChData.port());
}
presenceNotifyTimer.start();
}
}
// CC -> FNE registered VC announcement
if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) {
if (presenceNotifyTimer.isRunning() && presenceNotifyTimer.hasExpired()) {
if (m_network != nullptr && m_voiceChPeerId.size() > 0) {
LogMessage(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", m_network->getPeerId());
std::vector<uint32_t> peers;
for (auto it : m_voiceChPeerId) {
peers.push_back(it.second);
}
m_network->announceSiteVCs(peers);
}
presenceNotifyTimer.start();
}
}
}
if (m_state != STATE_IDLE)
Thread::sleep(m_activeTickDelay);
if (m_state == STATE_IDLE)
Thread::sleep(m_idleTickDelay);
}
});
if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr) {
presenceThread.run();
presenceThread.setName("host:presence");
}
if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) {
presenceThread.run();
presenceThread.setName("host:presence");
}
// main execution loop
while (!killed) {
if (m_modem->hasLockout() && m_state != HOST_STATE_LOCKOUT)
@ -1049,47 +1133,6 @@ int Host::run()
if (nxdn != nullptr)
nxdn->clock(ms);
ccRegisterTimer.clock(ms);
// VC -> CC presence registration
if (!m_controlChData.address().empty() && m_controlChData.port() != 0 && m_network != nullptr) {
if ((ccRegisterTimer.isRunning() && ccRegisterTimer.hasExpired()) || !hasInitialRegistered) {
LogMessage(LOG_HOST, "CC %s:%u, notifying CC of VC registration, peerId = %u", m_controlChData.address().c_str(), m_controlChData.port(), m_network->getPeerId());
hasInitialRegistered = true;
// callback REST API to release the granted TG on the specified control channel
json::object req = json::object();
req["channelNo"].set<uint32_t>(m_channelNo);
uint32_t peerId = m_network->getPeerId();
req["peerId"].set<uint32_t>(peerId);
int ret = RESTClient::send(m_controlChData.address(), m_controlChData.port(), m_controlChData.password(),
HTTP_PUT, PUT_REGISTER_CC_VC, req, m_controlChData.ssl(), REST_QUICK_WAIT, false);
if (ret != network::rest::http::HTTPPayload::StatusType::OK) {
::LogError(LOG_HOST, "failed to notify the CC %s:%u of VC registration", m_controlChData.address().c_str(), m_controlChData.port());
}
ccRegisterTimer.start();
}
}
// CC -> FNE registered VC announcement
if (m_dmrCtrlChannel || m_p25CtrlChannel || m_nxdnCtrlChannel) {
if (ccRegisterTimer.isRunning() && ccRegisterTimer.hasExpired()) {
if (m_network != nullptr && m_voiceChPeerId.size() > 0) {
LogMessage(LOG_HOST, "notifying FNE of VC registrations, peerId = %u", m_network->getPeerId());
std::vector<uint32_t> peers;
for (auto it : m_voiceChPeerId) {
peers.push_back(it.second);
}
m_network->announceSiteVCs(peers);
}
ccRegisterTimer.start();
}
}
// ------------------------------------------------------
// -- Timer Clocking --
// ------------------------------------------------------
@ -1610,6 +1653,10 @@ void Host::setState(uint8_t state)
delete m_RESTAPI;
}
if (m_channelLookup != nullptr) {
delete m_channelLookup;
}
if (m_tidLookup != nullptr) {
m_tidLookup->stop();
delete m_tidLookup;

@ -9,7 +9,7 @@
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX
* Copyright (C) 2017-2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2017-2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__HOST_H__)
@ -18,6 +18,7 @@
#include "Defines.h"
#include "common/Timer.h"
#include "common/lookups/AffiliationLookup.h"
#include "common/lookups/ChannelLookup.h"
#include "common/lookups/IdenTableLookup.h"
#include "common/lookups/RadioIdLookup.h"
#include "common/lookups/TalkgroupRulesLookup.h"
@ -54,10 +55,8 @@ public:
/// <summary>Executes the main modem host processing loop.</summary>
int run();
/// <summary>Gets the voice channel number list.</summary>
std::vector<uint32_t> getVoiceChNo() const { return m_voiceChNo; }
/// <summary>Gets the voice channel data.</summary>
std::unordered_map<uint32_t, lookups::VoiceChData> getVoiceChData() const { return m_voiceChData; }
/// <summary>Gets the RF channel lookup class.</summary>
lookups::ChannelLookup* rfCh() const { return m_channelLookup; }
private:
const std::string& m_confFile;
@ -107,8 +106,7 @@ private:
uint8_t m_channelId;
uint32_t m_channelNo;
std::vector<uint32_t> m_voiceChNo;
std::unordered_map<uint32_t, lookups::VoiceChData> m_voiceChData;
lookups::ChannelLookup* m_channelLookup;
std::unordered_map<uint32_t, uint32_t> m_voiceChPeerId;
lookups::VoiceChData m_controlChData;
@ -126,6 +124,8 @@ private:
bool m_nxdnCtrlChannel;
bool m_nxdnCtrlBroadcast;
uint32_t m_presenceTime;
uint8_t m_siteId;
uint32_t m_sysId;
uint32_t m_dmrNetId;
@ -150,6 +150,8 @@ private:
uint8_t m_idleTickDelay;
friend class RESTAPI;
std::string m_restAddress;
uint16_t m_restPort;
RESTAPI *m_RESTAPI;
/// <summary>Modem port open callback.</summary>

@ -42,6 +42,7 @@ using namespace dmr;
/// <param name="modem">Instance of the Modem class.</param>
/// <param name="network">Instance of the BaseNetwork class.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="chLookup">Instance of the ChannelLookup class.</param>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
@ -53,7 +54,7 @@ using namespace dmr;
/// <param name="debug">Flag indicating whether DMR debug is enabled.</param>
/// <param name="verbose">Flag indicating whether DMR verbose logging is enabled.</param>
Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly,
bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex,
bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup,
::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper,
uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) :
m_authoritative(authoritative),
@ -78,13 +79,14 @@ Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint
m_debug(debug)
{
assert(modem != nullptr);
assert(chLookup != nullptr);
assert(ridLookup != nullptr);
assert(tidLookup != nullptr);
assert(idenTable != nullptr);
assert(rssiMapper != nullptr);
acl::AccessControl::init(m_ridLookup, m_tidLookup);
Slot::init(this, authoritative, colorCode, SiteData(), embeddedLCOnly, dumpTAData, callHang, modem, network, duplex, m_ridLookup, m_tidLookup, m_idenTable, rssiMapper, jitter, verbose);
Slot::init(this, authoritative, colorCode, SiteData(), embeddedLCOnly, dumpTAData, callHang, modem, network, duplex, chLookup, m_ridLookup, m_tidLookup, m_idenTable, rssiMapper, jitter, verbose);
lc::CSBK::setVerbose(m_dumpCSBKData);
m_slot1 = new Slot(1U, timeout, tgHang, queueSize, dumpDataPacket, repeatDataPacket, dumpCSBKData, debug, verbose);
@ -107,16 +109,14 @@ Control::~Control()
/// </summary>
/// <param name="conf">Instance of the ConfigINI class.</param>
/// <param name="supervisor">Flag indicating whether the DMR has supervisory functions.</param>
/// <param name="voiceChNo">Voice Channel Number list.</param>
/// <param name="voiceChData">Voice Channel data map.</param>
/// <param name="controlChData">Control Channel data.</param>
/// <param name="netId">DMR Network ID.</param>
/// <param name="siteId">DMR Site ID.</param>
/// <param name="channelId">Channel ID.</param>
/// <param name="channelNo">Channel Number.</param>
/// <param name="printOptions"></param>
void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> voiceChData,
::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions)
void Control::setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChData controlChData,
uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions)
{
yaml::Node systemConf = conf["system"];
yaml::Node dmrProtocol = conf["protocols"]["dmr"];
@ -142,7 +142,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::vector<ui
dedicatedTSCC = false;
}
Slot::setSiteData(voiceChNo, voiceChData, controlChData, netId, siteId, channelId, channelNo, dedicatedTSCC);
Slot::setSiteData(controlChData, netId, siteId, channelId, channelNo, dedicatedTSCC);
Slot::setAlohaConfig(nRandWait, backOff);
bool disableGrantSourceIdCheck = control["disableGrantSourceIdCheck"].as<bool>(false);
@ -481,7 +481,7 @@ void Control::touchGrantTG(uint32_t dstId, uint8_t slot)
/// <summary>
/// Gets instance of the AffiliationLookup class.
/// </summary>
dmr::lookups::DMRAffiliationLookup Control::affiliations()
dmr::lookups::DMRAffiliationLookup* Control::affiliations()
{
switch (m_tsccSlotNo) {
case 1U:
@ -493,7 +493,7 @@ dmr::lookups::DMRAffiliationLookup Control::affiliations()
break;
}
return 0; // ??
return nullptr;
}
/// <summary>

@ -45,15 +45,15 @@ namespace dmr
public:
/// <summary>Initializes a new instance of the Control class.</summary>
Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly,
bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex,
bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup,
::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssi,
uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose);
/// <summary>Finalizes a instance of the Control class.</summary>
~Control();
/// <summary>Helper to set DMR configuration options.</summary>
void setOptions(yaml::Node& conf, bool supervisor, const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> voiceChData,
::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions);
void setOptions(yaml::Node& conf, bool supervisor, ::lookups::VoiceChData controlChData,
uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions);
/// <summary>Gets a flag indicating whether the DMR control channel is running.</summary>
bool getCCRunning() const { return m_ccRunning; }
@ -87,7 +87,7 @@ namespace dmr
void touchGrantTG(uint32_t dstId, uint8_t slot);
/// <summary>Gets instance of the DMRAffiliationLookup class.</summary>
lookups::DMRAffiliationLookup affiliations();
lookups::DMRAffiliationLookup* affiliations();
/// <summary>Helper to return the slot carrying the TSCC.</summary>
Slot* getTSCCSlot() const;

@ -740,7 +740,7 @@ void Slot::releaseGrantTG(uint32_t dstId)
if (m_affiliations->isGranted(dstId)) {
uint32_t chNo = m_affiliations->getGrantedCh(dstId);
uint32_t srcId = m_affiliations->getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_DMR, "DMR Slot %u, VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
@ -763,7 +763,7 @@ void Slot::touchGrantTG(uint32_t dstId)
if (m_affiliations->isGranted(dstId)) {
uint32_t chNo = m_affiliations->getGrantedCh(dstId);
uint32_t srcId = m_affiliations->getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceCh = m_affiliations->rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_DMR, "DMR Slot %u, VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", m_slotNo, voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
@ -870,6 +870,7 @@ uint32_t Slot::getLastSrcId() const
/// <param name="modem">Instance of the Modem class.</param>
/// <param name="network">Instance of the BaseNetwork class.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="chLookup">Instance of the ChannelLookup class.</param>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
@ -877,11 +878,12 @@ uint32_t Slot::getLastSrcId() const
/// <param name="jitter"></param>
/// <param name="verbose"></param>
void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem,
network::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup,
network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup,
::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose)
{
assert(dmr != nullptr);
assert(modem != nullptr);
assert(chLookup != nullptr);
assert(ridLookup != nullptr);
assert(tidLookup != nullptr);
assert(idenTable != nullptr);
@ -906,7 +908,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
m_idenTable = idenTable;
m_ridLookup = ridLookup;
m_tidLookup = tidLookup;
m_affiliations = new dmr::lookups::DMRAffiliationLookup(verbose);
m_affiliations = new dmr::lookups::DMRAffiliationLookup(chLookup, verbose);
// set the grant release callback
m_affiliations->setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) {
@ -917,7 +919,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
return;
}
::lookups::VoiceChData voiceChData = tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
req["slot"].set<uint8_t>(slot);
@ -973,16 +975,13 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
/// <summary>
/// Sets local configured site data.
/// </summary>
/// <param name="voiceChNo">Voice Channel Number list.</param>
/// <param name="voiceChData">Voice Channel data map.</param>
/// <param name="controlChData">Control Channel data.</param>
/// <param name="netId">DMR Network ID.</param>
/// <param name="siteId">DMR Site ID.</param>
/// <param name="channelId">Channel ID.</param>
/// <param name="channelNo">Channel Number.</param>
/// <param name="requireReg"></param>
void Slot::setSiteData(const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> voiceChData,
::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg)
void Slot::setSiteData(::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg)
{
m_siteData = SiteData(SITE_MODEL_SMALL, netId, siteId, 3U, requireReg);
m_channelNo = channelNo;
@ -995,13 +994,6 @@ void Slot::setSiteData(const std::vector<uint32_t> voiceChNo, const std::unorder
}
}
for (uint32_t chNo : voiceChNo) {
m_affiliations->addRFCh(chNo);
}
std::unordered_map<uint32_t, ::lookups::VoiceChData> chData = std::unordered_map<uint32_t, ::lookups::VoiceChData>(voiceChData);
m_affiliations->setRFChData(chData);
m_controlChData = controlChData;
lc::CSBK::setSiteData(m_siteData);

@ -131,11 +131,10 @@ namespace dmr
/// <summary>Helper to initialize the slot processor.</summary>
static void init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem,
network::Network* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup,
network::Network* network, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupRulesLookup* tidLookup,
::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose);
/// <summary>Sets local configured site data.</summary>
static void setSiteData(const std::vector<uint32_t> voiceChNo, const std::unordered_map<uint32_t, ::lookups::VoiceChData> voiceChData,
::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq);
static void setSiteData(::lookups::VoiceChData controlChData, uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq);
/// <summary>Sets TSCC Aloha configuration.</summary>
static void setAlohaConfig(uint8_t nRandWait, uint8_t backOff);

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL
*
*/
#include "common/Log.h"
@ -24,8 +24,9 @@ using namespace dmr::lookups;
/// <summary>
/// Initializes a new instance of the DMRAffiliationLookup class.
/// </summary>
/// <param name="channelLookup">Instance of the channel lookup class.</param>
/// <param name="verbose">Flag indicating whether verbose logging is enabled.</param>
DMRAffiliationLookup::DMRAffiliationLookup(bool verbose) : ::lookups::AffiliationLookup("DMR Affiliation", verbose),
DMRAffiliationLookup::DMRAffiliationLookup(::lookups::ChannelLookup* chLookup, bool verbose) : ::lookups::AffiliationLookup("DMR Affiliation", chLookup, verbose),
m_grantChSlotTable(),
m_tsccChNo(0U),
m_tsccSlot(0U)
@ -49,7 +50,7 @@ DMRAffiliationLookup::~DMRAffiliationLookup() = default;
/// <returns></returns>
bool DMRAffiliationLookup::grantCh(uint32_t dstId, uint32_t srcId, uint32_t grantTimeout, bool grp, bool netGranted)
{
uint32_t chNo = m_rfChTable.at(0);
uint32_t chNo = m_chLookup->getFirstRFChannel();
uint8_t slot = getAvailableSlotForChannel(chNo);
if (slot == 0U) {
@ -75,18 +76,17 @@ bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint32_t srcId, uint8_t s
return false;
}
if (!isRFChAvailable()) {
if (!m_chLookup->isRFChAvailable()) {
return false;
}
uint32_t chNo = m_rfChTable.at(0);
uint32_t chNo = m_chLookup->getFirstRFChannel();
if (chNo == m_tsccChNo && slot == m_tsccSlot) {
return false;
}
if (getAvailableSlotForChannel(chNo) == 0U || chNo == m_tsccChNo) {
auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
m_rfChTable.erase(it);
m_chLookup->removeRFCh(chNo);
}
m_grantChTable[dstId] = chNo;
@ -156,10 +156,7 @@ bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
m_grantChSlotTable.erase(dstId);
m_netGrantedTable.erase(dstId);
auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
if (it == m_rfChTable.end()) {
m_rfChTable.push_back(chNo);
}
m_chLookup->addRFCh(chNo);
if (m_rfGrantChCnt > 0U) {
m_rfGrantChCnt--;

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
* Copyright (C) 2023-2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__DMR_AFFILIATION_LOOKUP_H__)
@ -15,6 +15,7 @@
#include "Defines.h"
#include "common/lookups/AffiliationLookup.h"
#include "common/lookups/ChannelLookup.h"
#include <tuple>
@ -31,7 +32,7 @@ namespace dmr
class HOST_SW_API DMRAffiliationLookup : public ::lookups::AffiliationLookup {
public:
/// <summary>Initializes a new instance of the DMRAffiliationLookup class.</summary>
DMRAffiliationLookup(bool verbose);
DMRAffiliationLookup(::lookups::ChannelLookup* chLookup, bool verbose);
/// <summary>Finalizes a instance of the DMRAffiliationLookup class.</summary>
~DMRAffiliationLookup() override;

@ -905,7 +905,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
if (!m_tscc->m_affiliations->isGranted(dstId)) {
if (!m_tscc->m_affiliations->isRFChAvailable()) {
if (!m_tscc->m_affiliations->rfCh()->isRFChAvailable()) {
if (grp) {
if (!net) {
LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call) queued, no channels available, dstId = %u", m_tscc->m_slotNo, dstId);
@ -981,7 +981,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
// callback REST API to permit the granted TG on the specified voice channel
if (m_tscc->m_authoritative && m_tscc->m_supervisor &&
m_tscc->m_channelNo != chNo) {
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
@ -1030,7 +1030,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
// if the channel granted isn't the same as the TSCC; remote activate the payload channel
if (chNo != m_tscc->m_channelNo) {
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
req["dstId"].set<uint32_t>(dstId);
@ -1059,7 +1059,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
// callback REST API to permit the granted TG on the specified voice channel
if (m_tscc->m_authoritative && m_tscc->m_supervisor &&
m_tscc->m_channelNo != chNo) {
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
int state = modem::DVM_STATE::STATE_DMR;
@ -1106,7 +1106,7 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
// if the channel granted isn't the same as the TSCC; remote activate the payload channel
if (chNo != m_tscc->m_channelNo) {
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
req["dstId"].set<uint32_t>(dstId);
@ -1196,7 +1196,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
}
if (!m_tscc->m_affiliations->isGranted(dstId)) {
if (!m_tscc->m_affiliations->isRFChAvailable()) {
if (!m_tscc->m_affiliations->rfCh()->isRFChAvailable()) {
if (grp) {
if (!net) {
LogWarning(LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_DATA_CALL (Group Data Call) queued, no channels available, dstId = %u", m_tscc->m_slotNo, dstId);
@ -1264,7 +1264,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
// if the channel granted isn't the same as the TSCC; remote activate the payload channel
if (chNo != m_tscc->m_channelNo) {
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
req["dstId"].set<uint32_t>(dstId);
@ -1311,7 +1311,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
// if the channel granted isn't the same as the TSCC; remote activate the payload channel
if (chNo != m_tscc->m_channelNo) {
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
json::object req = json::object();
req["dstId"].set<uint32_t>(dstId);

@ -662,8 +662,9 @@ void RESTAPI::restAPI_GetVoiceCh(const HTTPPayload& request, HTTPPayload& reply,
setResponseDefaultStatus(response);
json::array channels = json::array();
if (m_host->m_voiceChData.size() > 0) {
for (auto entry : m_host->m_voiceChData) {
if (m_host->rfCh()->rfChDataSize() > 0) {
auto voiceChData = m_host->rfCh()->rfChDataTable();
for (auto entry : voiceChData) {
uint32_t chNo = entry.first;
lookups::VoiceChData data = entry.second;
@ -1109,7 +1110,8 @@ void RESTAPI::restAPI_GetReleaseGrants(const HTTPPayload& request, HTTPPayload&
errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) {
m_dmr->affiliations().releaseGrant(0, true);
if (m_dmr->affiliations() != nullptr)
m_dmr->affiliations()->releaseGrant(0, true);
}
if (m_p25 != nullptr) {
@ -1135,7 +1137,8 @@ void RESTAPI::restAPI_GetReleaseAffs(const HTTPPayload& request, HTTPPayload& re
errorPayload(reply, "OK", HTTPPayload::OK);
if (m_dmr != nullptr) {
m_dmr->affiliations().clearGroupAff(0, true);
if (m_dmr->affiliations() != nullptr)
m_dmr->affiliations()->clearGroupAff(0, true);
}
if (m_p25 != nullptr) {
@ -1179,7 +1182,7 @@ void RESTAPI::restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& r
uint32_t channelNo = req["channelNo"].get<uint32_t>();
// validate channelNo is a string within the JSON blob
// validate peerId is a string within the JSON blob
if (!req["peerId"].is<int>()) {
errorPayload(reply, "peerId was not a valid integer");
return;
@ -1189,11 +1192,39 @@ void RESTAPI::restAPI_PutRegisterCCVC(const HTTPPayload& request, HTTPPayload& r
// LogDebug(LOG_REST, "restAPI_PutRegisterCCVC(): callback, channelNo = %u, peerId = %u", channelNo, peerId);
if (m_host->m_voiceChData.find(channelNo) != m_host->m_voiceChData.end()) {
::lookups::VoiceChData voiceCh = m_host->m_voiceChData[channelNo];
// validate restAddress is a string within the JSON blob
if (!req["restAddress"].is<std::string>()) {
errorPayload(reply, "restAddress was not a valid string");
return;
}
if (!req["restPort"].is<int>()) {
errorPayload(reply, "restPort was not a valid integer");
return;
}
std::string restAddress = req["restAddress"].get<std::string>();
uint16_t restPort = (uint16_t)req["restPort"].get<int>();
auto voiceChData = m_host->rfCh()->rfChDataTable();
if (voiceChData.find(channelNo) != voiceChData.end()) {
::lookups::VoiceChData voiceCh = m_host->rfCh()->getRFChData(channelNo);
if (voiceCh.address() == "0.0.0.0") {
voiceCh.address(restAddress);
}
if (voiceCh.port() == 0U || voiceCh.port() == REST_API_DEFAULT_PORT) {
voiceCh.port(restPort);
}
m_host->rfCh()->setRFChData(channelNo, voiceCh);
m_host->m_voiceChPeerId[channelNo] = peerId;
LogMessage(LOG_REST, "VC %s:%u, registration notice, peerId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), peerId, voiceCh.chId(), channelNo);
LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u SSL %u", voiceCh.chId(), channelNo, voiceCh.address().c_str(), voiceCh.port(), voiceCh.ssl());
} else {
LogMessage(LOG_REST, "VC, registration rejected, peerId = %u, chNo = %u, VC wasn't a defined member of the CC voice channel list", peerId, channelNo);
}
}
@ -1790,7 +1821,8 @@ void RESTAPI::restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& rep
setResponseDefaultStatus(response);
json::array affs = json::array();
std::unordered_map<uint32_t, uint32_t> affTable = m_dmr->affiliations().grpAffTable();
if (m_dmr->affiliations() != nullptr) {
std::unordered_map<uint32_t, uint32_t> affTable = m_dmr->affiliations()->grpAffTable();
if (affTable.size() > 0) {
for (auto entry : affTable) {
uint32_t srcId = entry.first;
@ -1803,6 +1835,7 @@ void RESTAPI::restAPI_GetDMRAffList(const HTTPPayload& request, HTTPPayload& rep
affs.push_back(json::value(aff));
}
}
}
response["affiliations"].set<json::array>(affs);
reply.payload(response);

@ -62,6 +62,7 @@ const uint8_t SCRAMBLER[] = {
/// <param name="modem">Instance of the Modem class.</param>
/// <param name="network">Instance of the BaseNetwork class.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="chLookup">Instance of the ChannelLookup class.</param>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
@ -70,7 +71,7 @@ const uint8_t SCRAMBLER[] = {
/// <param name="debug">Flag indicating whether P25 debug is enabled.</param>
/// <param name="verbose">Flag indicating whether P25 verbose logging is enabled.</param>
Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang,
modem::Modem* modem, network::Network* network, bool duplex, lookups::RadioIdLookup* ridLookup,
modem::Modem* modem, network::Network* network, bool duplex, lookups::ChannelLookup* chLookup, lookups::RadioIdLookup* ridLookup,
lookups::TalkgroupRulesLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper,
bool dumpRCCHData, bool debug, bool verbose) :
m_voice(nullptr),
@ -94,7 +95,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q
m_idenTable(idenTable),
m_ridLookup(ridLookup),
m_tidLookup(tidLookup),
m_affiliations("NXDN Affiliations", verbose),
m_affiliations("NXDN Affiliations", chLookup, verbose),
m_controlChData(),
m_idenEntry(),
m_txImmQueue(queueSize, "NXDN Imm Frame"),
@ -131,6 +132,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q
m_verbose(verbose),
m_debug(debug)
{
assert(chLookup != nullptr);
assert(ridLookup != nullptr);
assert(tidLookup != nullptr);
assert(idenTable != nullptr);
@ -197,16 +199,13 @@ void Control::reset()
/// <param name="conf">Instance of the yaml::Node class.</param>
/// <param name="supervisor">Flag indicating whether the DMR has supervisory functions.</param>
/// <param name="cwCallsign">CW callsign of this host.</param>
/// <param name="voiceChNo">Voice Channel Number list.</param>
/// <param name="voiceChData">Voice Channel data map.</param>
/// <param name="controlChData">Control Channel data.</param>
/// <param name="siteId">NXDN Site Code.</param>
/// <param name="sysId">NXDN System Code.</param>
/// <param name="channelId">Channel ID.</param>
/// <param name="channelNo">Channel Number.</param>
/// <param name="printOptions"></param>
void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector<uint32_t> voiceChNo,
const std::unordered_map<uint32_t, lookups::VoiceChData> voiceChData, lookups::VoiceChData controlChData,
void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, lookups::VoiceChData controlChData,
uint16_t siteId, uint32_t sysId, uint8_t channelId, uint32_t channelNo, bool printOptions)
{
yaml::Node systemConf = conf["system"];
@ -274,20 +273,13 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
m_siteData = SiteData(locId, channelId, (channelNo & 0x3FF), serviceClass, false);
m_siteData.setCallsign(cwCallsign);
for (uint32_t ch : voiceChNo) {
m_affiliations.addRFCh(ch);
}
std::unordered_map<uint32_t, ::lookups::VoiceChData> chData = std::unordered_map<uint32_t, ::lookups::VoiceChData>(voiceChData);
m_affiliations.setRFChData(chData);
m_controlChData = controlChData;
// set the grant release callback
m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) {
// callback REST API to clear TG permit for the granted TG on the specified voice channel
if (m_authoritative && m_supervisor) {
::lookups::VoiceChData voiceChData = m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_affiliations.rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_siteData.channelNo()) {
json::object req = json::object();
@ -747,7 +739,7 @@ void Control::releaseGrantTG(uint32_t dstId)
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_NXDN, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
@ -770,7 +762,7 @@ void Control::touchGrantTG(uint32_t dstId)
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_NXDN, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);

@ -56,7 +56,7 @@ namespace nxdn
public:
/// <summary>Initializes a new instance of the Control class.</summary>
Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t timeout, uint32_t tgHang,
modem::Modem* modem, network::Network* network, bool duplex, lookups::RadioIdLookup* ridLookup,
modem::Modem* modem, network::Network* network, bool duplex, lookups::ChannelLookup* chLookup, lookups::RadioIdLookup* ridLookup,
lookups::TalkgroupRulesLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper,
bool dumpRCCHData, bool debug, bool verbose);
/// <summary>Finalizes a instance of the Control class.</summary>
@ -66,8 +66,7 @@ namespace nxdn
void reset();
/// <summary>Helper to set NXDN configuration options.</summary>
void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector<uint32_t> voiceChNo,
const std::unordered_map<uint32_t, lookups::VoiceChData> voiceChData, lookups::VoiceChData controlChData,
void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, lookups::VoiceChData controlChData,
uint16_t siteId, uint32_t sysId, uint8_t channelId, uint32_t channelNo, bool printOptions);
/// <summary>Gets a flag indicating whether the NXDN control channel is running.</summary>

@ -518,7 +518,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin
}
if (!m_nxdn->m_affiliations.isGranted(dstId)) {
if (!m_nxdn->m_affiliations.isRFChAvailable()) {
if (!m_nxdn->m_affiliations.rfCh()->isRFChAvailable()) {
if (grp) {
if (!net) {
LogWarning(LOG_RF, "NXDN, %s queued, no channels available, dstId = %u", rcch->toString().c_str(), dstId);
@ -593,7 +593,7 @@ bool ControlSignaling::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uin
// callback REST API to permit the granted TG on the specified voice channel
if (m_nxdn->m_authoritative && m_nxdn->m_supervisor) {
::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_nxdn->m_siteData.channelNo()) {
json::object req = json::object();

@ -55,6 +55,7 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U;
/// <param name="timeout">Transmit timeout.</param>
/// <param name="tgHang">Amount of time to hang on the last talkgroup mode from RF.</param>
/// <param name="duplex">Flag indicating full-duplex operation.</param>
/// <param name="chLookup">Instance of the ChannelLookup class.</param>
/// <param name="ridLookup">Instance of the RadioIdLookup class.</param>
/// <param name="tidLookup">Instance of the TalkgroupRulesLookup class.</param>
/// <param name="idenTable">Instance of the IdenTableLookup class.</param>
@ -65,7 +66,7 @@ const uint32_t MAX_PREAMBLE_TDU_CNT = 64U;
/// <param name="debug">Flag indicating whether P25 debug is enabled.</param>
/// <param name="verbose">Flag indicating whether P25 verbose logging is enabled.</param>
Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::Network* network,
uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::RadioIdLookup* ridLookup,
uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup,
::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper,
bool dumpPDUData, bool repeatPDU, bool dumpTSBKData, bool debug, bool verbose) :
m_voice(nullptr),
@ -93,7 +94,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_idenTable(idenTable),
m_ridLookup(ridLookup),
m_tidLookup(tidLookup),
m_affiliations(this, verbose),
m_affiliations(this, chLookup, verbose),
m_controlChData(),
m_idenEntry(),
m_txImmQueue(queueSize, "P25 Imm Frame"),
@ -140,6 +141,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_verbose(verbose),
m_debug(debug)
{
assert(chLookup != nullptr);
assert(ridLookup != nullptr);
assert(tidLookup != nullptr);
assert(idenTable != nullptr);
@ -207,8 +209,6 @@ void Control::reset()
/// <param name="conf">Instance of the yaml::Node class.</param>
/// <param name="supervisor">Flag indicating whether the DMR has supervisory functions.</param>
/// <param name="cwCallsign">CW callsign of this host.</param>
/// <param name="voiceChNo">Voice Channel Number list.</param>
/// <param name="voiceChData">Voice Channel data map.</param>
/// <param name="controlChData">Control Channel data.</param>
/// <param name="pSuperGroup"></param>
/// <param name="netId">P25 Network ID.</param>
@ -218,8 +218,7 @@ void Control::reset()
/// <param name="channelId">Channel ID.</param>
/// <param name="channelNo">Channel Number.</param>
/// <param name="printOptions"></param>
void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector<uint32_t> voiceChNo,
const std::unordered_map<uint32_t, ::lookups::VoiceChData> voiceChData, const ::lookups::VoiceChData controlChData,
void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const ::lookups::VoiceChData controlChData,
uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions)
{
yaml::Node systemConf = conf["system"];
@ -409,13 +408,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
}
}
m_siteData.setChCnt((uint8_t)voiceChNo.size());
for (uint32_t ch : voiceChNo) {
m_affiliations.addRFCh(ch);
}
std::unordered_map<uint32_t, ::lookups::VoiceChData> chData = std::unordered_map<uint32_t, ::lookups::VoiceChData>(voiceChData);
m_affiliations.setRFChData(chData);
m_siteData.setChCnt((uint8_t)m_affiliations.rfCh()->rfChSize());
m_controlChData = controlChData;
@ -423,7 +416,7 @@ void Control::setOptions(yaml::Node& conf, bool supervisor, const std::string cw
m_affiliations.setReleaseGrantCallback([=](uint32_t chNo, uint32_t dstId, uint8_t slot) {
// callback REST API to clear TG permit for the granted TG on the specified voice channel
if (m_authoritative && m_supervisor) {
::lookups::VoiceChData voiceChData = m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_affiliations.rfCh()->getRFChData(chNo);
if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
chNo != m_siteData.channelNo()) {
json::object req = json::object();
@ -999,7 +992,7 @@ void Control::releaseGrantTG(uint32_t dstId)
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, TG grant released, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);
@ -1022,7 +1015,7 @@ void Control::touchGrantTG(uint32_t dstId)
if (m_affiliations.isGranted(dstId)) {
uint32_t chNo = m_affiliations.getGrantedCh(dstId);
uint32_t srcId = m_affiliations.getGrantedSrcId(dstId);
::lookups::VoiceChData voiceCh = m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceCh = m_affiliations.rfCh()->getRFChData(chNo);
if (m_verbose) {
LogMessage(LOG_P25, "VC %s:%u, call in progress, srcId = %u, dstId = %u, chId = %u, chNo = %u", voiceCh.address().c_str(), voiceCh.port(), srcId, dstId, voiceCh.chId(), chNo);

@ -57,7 +57,7 @@ namespace p25
public:
/// <summary>Initializes a new instance of the Control class.</summary>
Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t queueSize, modem::Modem* modem, network::Network* network,
uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::RadioIdLookup* ridLookup,
uint32_t timeout, uint32_t tgHang, bool duplex, ::lookups::ChannelLookup* chLookup, ::lookups::RadioIdLookup* ridLookup,
::lookups::TalkgroupRulesLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper,
bool dumpPDUData, bool repeatPDU, bool dumpTSBKData, bool debug, bool verbose);
/// <summary>Finalizes a instance of the Control class.</summary>
@ -67,8 +67,7 @@ namespace p25
void reset();
/// <summary>Helper to set P25 configuration options.</summary>
void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const std::vector<uint32_t> voiceChNo,
const std::unordered_map<uint32_t, ::lookups::VoiceChData> voiceChData, const ::lookups::VoiceChData controlChData,
void setOptions(yaml::Node& conf, bool supervisor, const std::string cwCallsign, const ::lookups::VoiceChData controlChData,
uint32_t netId, uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions);
/// <summary>Gets a flag indicating whether the P25 control channel is running.</summary>

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
*
*/
#include "common/Log.h"
@ -24,8 +24,9 @@ using namespace p25::lookups;
/// Initializes a new instance of the P25AffiliationLookup class.
/// </summary>
/// <param name="name">Name of lookup table.</param>
/// <param name="channelLookup">Instance of the channel lookup class.</param>
/// <param name="verbose">Flag indicating whether verbose logging is enabled.</param>
P25AffiliationLookup::P25AffiliationLookup(Control* p25, bool verbose) : ::lookups::AffiliationLookup("P25 Affiliation", verbose),
P25AffiliationLookup::P25AffiliationLookup(Control* p25, ::lookups::ChannelLookup* chLookup, bool verbose) : ::lookups::AffiliationLookup("P25 Affiliation", chLookup, verbose),
m_p25(p25)
{
/* stub */
@ -46,10 +47,10 @@ bool P25AffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
bool ret = ::lookups::AffiliationLookup::releaseGrant(dstId, releaseAll);
if (ret) {
if (m_rfGrantChCnt > 0U) {
m_p25->m_siteData.setChCnt(getRFChCnt() + m_rfGrantChCnt);
m_p25->m_siteData.setChCnt(m_chLookup->rfChSize() + m_rfGrantChCnt);
}
else {
m_p25->m_siteData.setChCnt(getRFChCnt());
m_p25->m_siteData.setChCnt(m_chLookup->rfChSize());
}
}

@ -7,7 +7,7 @@
* @package DVM / Modem Host Software
* @license GPLv2 License (https://opensource.org/licenses/GPL-2.0)
*
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022,2024 Bryan Biedenkapp, N2PLL
*
*/
#if !defined(__P25_AFFILIATION_LOOKUP_H__)
@ -15,6 +15,7 @@
#include "Defines.h"
#include "common/lookups/AffiliationLookup.h"
#include "common/lookups/ChannelLookup.h"
namespace p25
{
@ -35,7 +36,7 @@ namespace p25
class HOST_SW_API P25AffiliationLookup : public ::lookups::AffiliationLookup {
public:
/// <summary>Initializes a new instance of the P25AffiliationLookup class.</summary>
P25AffiliationLookup(Control* p25, bool verbose);
P25AffiliationLookup(Control* p25, ::lookups::ChannelLookup* chLookup, bool verbose);
/// <summary>Finalizes a instance of the P25AffiliationLookup class.</summary>
~P25AffiliationLookup() override;

@ -2201,7 +2201,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
if (!m_p25->m_affiliations.isGranted(dstId)) {
if (!m_p25->m_affiliations.isRFChAvailable()) {
if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) {
if (grp) {
if (!net) {
LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_IOSP_GRP_VCH (Group Voice Channel Request) queued, no channels available, dstId = %u", dstId);
@ -2234,7 +2234,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
else {
if (m_p25->m_affiliations.grantCh(dstId, srcId, GRANT_TIMER_TIMEOUT, grp, net)) {
chNo = m_p25->m_affiliations.getGrantedCh(dstId);
m_p25->m_siteData.setChCnt(m_p25->m_affiliations.getRFChCnt() + m_p25->m_affiliations.getGrantedRFChCnt());
m_p25->m_siteData.setChCnt(m_p25->m_affiliations.rfCh()->rfChSize() + m_p25->m_affiliations.getGrantedRFChCnt());
}
}
}
@ -2274,7 +2274,7 @@ bool ControlSignaling::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_
}
if (chNo > 0U) {
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
if (grp) {
if (!net) {
@ -2418,7 +2418,7 @@ void ControlSignaling::writeRF_TSDU_Grant_Update()
uint32_t chNo = entry.second;
bool grp = m_p25->m_affiliations.isGroup(dstId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
if (chNo == 0U) {
noData = true;
@ -2503,7 +2503,7 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId,
}
if (!m_p25->m_affiliations.isGranted(srcId)) {
if (!m_p25->m_affiliations.isRFChAvailable()) {
if (!m_p25->m_affiliations.rfCh()->isRFChAvailable()) {
if (!net) {
LogWarning(LOG_RF, P25_TSDU_STR ", TSBK_ISP_SNDCP_CH_REQ (SNDCP Data Channel Request) queued, no channels available, srcId = %u", srcId);
writeRF_TSDU_Queue(srcId, dstId, P25_QUE_RSN_CHN_RESOURCE_NOT_AVAIL, TSBK_ISP_SNDCP_CH_REQ);
@ -2517,18 +2517,18 @@ bool ControlSignaling::writeRF_TSDU_SNDCP_Grant(uint32_t srcId, uint32_t dstId,
else {
if (m_p25->m_affiliations.grantCh(srcId, srcId, GRANT_TIMER_TIMEOUT, false, net)) {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(srcId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
osp->setGrpVchId(voiceChData.chId());
osp->setGrpVchNo(chNo);
osp->setDataChnNo(chNo);
m_p25->m_siteData.setChCnt(m_p25->m_affiliations.getRFChCnt() + m_p25->m_affiliations.getGrantedRFChCnt());
m_p25->m_siteData.setChCnt(m_p25->m_affiliations.rfCh()->rfChSize() + m_p25->m_affiliations.getGrantedRFChCnt());
}
}
}
else {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(srcId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
osp->setGrpVchId(voiceChData.chId());
osp->setGrpVchNo(chNo);

@ -413,7 +413,7 @@ bool Voice::process(uint8_t* data, uint32_t len)
// if voice on control; insert grant updates before voice traffic
if (m_p25->m_voiceOnControl) {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
bool grp = m_p25->m_affiliations.isGroup(dstId);
std::unique_ptr<lc::TSBK> osp;
@ -516,7 +516,7 @@ bool Voice::process(uint8_t* data, uint32_t len)
// if voice on control; insert group voice channel updates directly after HDU but before LDUs
if (m_p25->m_voiceOnControl) {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
bool grp = m_p25->m_affiliations.isGroup(dstId);
std::unique_ptr<lc::TSBK> osp;
@ -1676,7 +1676,7 @@ void Voice::writeNet_LDU1()
// if voice on control; insert grant updates before voice traffic
if (m_p25->m_voiceOnControl) {
uint32_t chNo = m_p25->m_affiliations.getGrantedCh(dstId);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
::lookups::VoiceChData voiceChData = m_p25->m_affiliations.rfCh()->getRFChData(chNo);
bool grp = m_p25->m_affiliations.isGroup(dstId);
std::unique_ptr<lc::TSBK> osp;

@ -182,6 +182,9 @@ int RESTClient::send(const std::string& address, uint32_t port, const std::strin
if (address.empty()) {
return ERRNO_NO_ADDRESS;
}
if (address == "0.0.0.0") {
return ERRNO_NO_ADDRESS;
}
if (port <= 0U) {
return ERRNO_NO_ADDRESS;
}

Loading…
Cancel
Save

Powered by TurnKey Linux.