Merge pull request #2 from n7tae/main

Add the ability to put and get information from a existing distributed hash table
pull/8/head
nostar 3 years ago committed by GitHub
commit 715365f737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -51,6 +51,29 @@ sudo apt install build-essential
sudo apt install nlohmann-json3-dev
```
### DVIN support (optional, but highly recommended)
**DVIN**, the Digital Voice Information Network, is implemented using a distributed hash table provided by OpenDHT.
OpenDHT is available [here](https://github./com/savoirfairelinux/opendht.git). Building and installing instructions are in the [OpenDHT Wiki](https://github.com/savoirfairelinux/opendht/wiki/Build-the-library). Pascal support and proxy-server support (RESTinio) is not required for urfd and so can be considered optional. With this in mind, this should work on Debian/Ubuntu-based systems:
```bash
# Install OpenDHT dependencies
sudo apt install libncurses5-dev libreadline-dev nettle-dev libgnutls28-dev libargon2-0-dev libmsgpack-dev libssl-dev libfmt-dev libjsoncpp-dev libhttp-parser-dev libasio-dev cmake pkg-config libcppunit-dev
# clone the repo
git clone https://github.com/savoirfairelinux/opendht.git
# build and install
cd opendht
mkdir build && cd build
cmake -DOPENDHT_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install
```
Please note that there is no easy way to uninstall OpenDHT once it's been installed.
### Download and build the repository and
```bash

@ -12,6 +12,14 @@ Country = GB
Sponsor = My Home Club
# DashboardURL is where your dashboard for this reflector is avaiable on the WWW.
DashboardUrl = https://YourDashboard.net
# Bootstrap is the internet address of any existing DHT client.
# Usually, choose a frequented, or close, reflector if it's already a DHT
# client. If that's not possible, you can use xrf757.openquad.net
Bootstrap = xrf757.openquad.net
[IP Addresses]
# Binding addresses are usually the 'any' address
IPv4Binding = 0.0.0.0

@ -1,14 +1,20 @@
##############################################################################
# URFD interlink file
#
# one line per entry
# each entry specifies a remote XLX or XRF to peer with
# format:
# <URF callsign> <ip> <list of modules shared>
# example:
# URF270 158.64.26.132 ACD
# One line per entry.
# Each entry specifies a remote URF to peer with.
# If no Port is specified, 10017 will be used.
# If DHT is enabled and the target is also DHT-enabled, then you only
# need to specify the URF-Callsign and the Shared-Modules.
# Format:
# <URF-Callsign> <IP-Address> <Shared-Mdoules> <Port>
# Examples:
# URF270 158.64.26.132 EF
# URF280 ABC
#
# note: the remote URFD must list this in its interlink file
# for the link to be established
# Brandmeister links use three params, no port is specified. Example:
# BM3104 162.248.88.117 E
#
# note: Remote URFD must list this in its interlink file
# for the link to be established.
#############################################################################

@ -4,3 +4,6 @@ BINDIR = /usr/local/bin
# besides making an executable that gdb can use,
# this will also provide some additional log messsage
debug = false
# To disable DHT support, set DHT to false.
DHT = true

@ -288,16 +288,16 @@ void CBMProtocol::HandlePeerLinks(void)
CBuffer buffer;
// get the list of peers
CPeerCallsignList *list = g_GateKeeper.GetPeerList();
auto ilmap = g_GateKeeper.GetInterlinkMap();
CPeers *peers = g_Reflector.GetPeers();
// check if all our connected peers are still listed by gatekeeper
// if not, disconnect
auto pit = peers->begin();
std::shared_ptr<CPeer>peer = nullptr;
while ( (peer = peers->FindNextPeer(EProtocol::bm, pit)) != nullptr )
while ( nullptr != (peer = peers->FindNextPeer(EProtocol::bm, pit)) )
{
if ( list->FindListItem(peer->GetCallsign()) == nullptr )
if ( nullptr == ilmap->FindMapItem(peer->GetCallsign().GetBase()) )
{
// send disconnect packet
EncodeDisconnectPacket(&buffer);
@ -310,23 +310,21 @@ void CBMProtocol::HandlePeerLinks(void)
// check if all ours peers listed by gatekeeper are connected
// if not, connect or reconnect
for ( auto it=list->begin(); it!=list->end(); it++ )
for ( auto it=ilmap->begin(); it!=ilmap->end(); it++ )
{
CCallsign cs = it->GetCallsign();
if (cs.HasSameCallsignWithWildcard(CCallsign("BM*")) && (nullptr==peers->FindPeer(cs, EProtocol::bm)))
const auto cs = it->first;
if (0 == cs.substr(0, 2).compare("BM") && (nullptr==peers->FindPeer(CCallsign(cs), EProtocol::bm)))
{
// resolve again peer's IP in case it's a dynamic IP
it->ResolveIp();
// send connect packet to re-initiate peer link
EncodeConnectPacket(&buffer, it->GetModules());
Send(buffer, it->GetIp(), m_Port);
std::cout << "Sending connect packet to BM peer " << cs << " @ " << it->GetIp() << " for modules " << it->GetModules() << std::endl;
EncodeConnectPacket(&buffer, it->second.GetModules().c_str());
Send(buffer, it->second.GetIp(), m_Port);
std::cout << "Sending connect packet to BM peer " << cs << " @ " << it->second.GetIp() << " for modules " << it->second.GetModules() << std::endl;
}
}
// done
g_Reflector.ReleasePeers();
g_GateKeeper.ReleasePeerList();
g_GateKeeper.ReleaseInterlinkMap();
}

@ -0,0 +1,193 @@
//
// ccallsignlist.cpp
// m17ref
//
// Created by Jean-Luc Deltombe (LX3JL) on 30/12/2015.
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// Copyright © 2020 Thomas A. Early, N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of m17ref.
//
// m17ref is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// m17ref is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#include <iostream>
#include <fstream>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "BlackWhiteSet.h"
////////////////////////////////////////////////////////////////////////////////////////
// file io
bool CBlackWhiteSet::LoadFromFile(const std::string &filename)
{
bool ok = false;
char sz[256];
// and load
std::ifstream file(filename);
if ( file.is_open() )
{
Lock();
// empty list
m_Callsigns.clear();
// fill with file content
while ( file.getline(sz, sizeof(sz)).good() )
{
// remove leading & trailing spaces
char *szt = TrimWhiteSpaces(sz);
// crack it
if ( (strlen(szt) > 0) && (szt[0] != '#') )
{
// 1st token is callsign
if ( (szt = strtok(szt, " ,\t")) != nullptr )
{
std::string cs(ToUpper(szt));
if (m_Callsigns.end() == m_Callsigns.find(cs))
{
m_Callsigns.insert(cs);
}
else
{
std::cerr << "Duplicate ," << cs << " in " << filename << " will be ignored." << std::endl;
}
}
}
}
// close file
file.close();
// keep file path
m_Filename = filename;
// update time
GetLastModTime(&m_LastModTime);
// and done
Unlock();
ok = true;
std::cout << "Gatekeeper loaded " << m_Callsigns.size() << " lines from " << filename << std::endl;
}
else
{
std::cout << "Gatekeeper cannot find " << filename << std::endl;
}
return ok;
}
bool CBlackWhiteSet::ReloadFromFile(void)
{
bool ok = false;
if ( ! m_Filename.empty() )
{
ok = LoadFromFile(m_Filename);
}
return ok;
}
bool CBlackWhiteSet::NeedReload(void)
{
bool needReload = false;
time_t time;
if ( GetLastModTime(&time) )
{
needReload = time != m_LastModTime;
}
return needReload;
}
////////////////////////////////////////////////////////////////////////////////////////
// compare
bool CBlackWhiteSet::IsMatched(const std::string &cs) const
{
for ( const auto &item : m_Callsigns )
{
auto pos = item.find('*');
switch (pos)
{
case 0:
return true;
case std::string::npos:
if (0 == item.compare(cs))
return true;
break;
default:
if (0 == item.compare(0, pos, cs, 0, pos))
return true;
break;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////
// helpers
char *CBlackWhiteSet::TrimWhiteSpaces(char *str)
{
char *end;
// Trim leading space & tabs
while((*str == ' ') || (*str == '\t')) str++;
// All spaces?
if(*str == 0)
return str;
// Trim trailing space, tab or lf
end = str + ::strlen(str) - 1;
while((end > str) && ((*end == ' ') || (*end == '\t') || (*end == '\r'))) end--;
// Write new null terminator
*(end+1) = 0;
return str;
}
bool CBlackWhiteSet::GetLastModTime(time_t *time)
{
bool ok = false;
if ( !m_Filename.empty() )
{
struct stat fileStat;
if( ::stat(m_Filename.c_str(), &fileStat) != -1 )
{
*time = fileStat.st_mtime;
ok = true;
}
}
return ok;
}
char *CBlackWhiteSet::ToUpper(char *str)
{
constexpr auto diff = 'a' - 'A';
for (char *p=str; *p; p++)
{
if (*p >= 'a' && *p <= 'z')
*p -= diff;
}
return str;
}

@ -0,0 +1,61 @@
//
// Copyright © 2020 Thomas A. Early, N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of m17ref.
//
// m17ref is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// m17ref is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#pragma once
#include <set>
#include <string>
#include <mutex>
////////////////////////////////////////////////////////////////////////////////////////
// class
class CBlackWhiteSet
{
public:
// constructor
CBlackWhiteSet() : m_LastModTime(0) {}
// locks
void Lock(void) const { m_Mutex.lock(); }
void Unlock(void) const { m_Mutex.unlock(); }
// file io
bool LoadFromFile(const std::string &filename);
bool ReloadFromFile(void);
bool NeedReload(void);
// pass-through
bool empty() const { return m_Callsigns.empty(); }
// compare
bool IsMatched(const std::string &) const;
protected:
bool GetLastModTime(time_t *);
char *TrimWhiteSpaces(char *);
char *ToUpper(char *str);
// data
mutable std::mutex m_Mutex;
std::string m_Filename;
time_t m_LastModTime;
std::set<std::string> m_Callsigns;
};

@ -1,49 +0,0 @@
// Copyright © 2015 Jean-Luc. All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <queue>
#include <mutex>
#include "Notification.h"
class CNotificationQueue
{
public:
// constructor
CNotificationQueue() {}
// destructor
~CNotificationQueue() {}
// lock
void Lock() { m_Mutex.lock(); }
void Unlock() { m_Mutex.unlock(); }
// pass thru
CNotification front() { return queue.front(); }
void pop() { queue.pop(); }
void push(CNotification note) { queue.push(note); }
bool empty() const { return queue.empty(); }
protected:
// data
std::mutex m_Mutex;
std::queue<CNotification> queue;
};

@ -349,6 +349,16 @@ UCallsign CCallsign::GetKey() const
return rval;
}
std::string CCallsign::GetBase() const
{
auto u = GetKey();
std::string rval(u.c, CALLSIGN_LEN);
auto pos = rval.find(' ');
if (std::string::npos != pos)
rval.resize(pos);
return rval;
}
void CCallsign::GetCallsign(uint8_t *buffer) const
{
memcpy(buffer, m_Callsign.c, CALLSIGN_LEN);
@ -404,16 +414,6 @@ bool CCallsign::HasSameCallsignWithWildcard(const CCallsign &cs) const
return same;
}
bool CCallsign::HasLowerCallsign(const CCallsign &cs) const
{
return (memcmp(m_Callsign.c, cs.m_Callsign.c, CALLSIGN_LEN) < 0);
}
bool CCallsign::HasSameModule(const CCallsign &Callsign) const
{
return (m_Module == Callsign.m_Module);
}
////////////////////////////////////////////////////////////////////////////////////////
// operators

@ -39,6 +39,23 @@ union USuffix
uint32_t u;
};
// functions for unordered containers
struct CCallsignHash
{
std::size_t operator() (const UCallsign &ucs) const
{
std::hash<uint64_t> hash;
return hash(ucs.l);
}
};
struct CCallsignEqual
{
bool operator() (const UCallsign &ucs1, const UCallsign &ucs2) const
{
return ucs1.l == ucs2.l;
}
};
////////////////////////////////////////////////////////////////////////////////////////
// class
@ -46,7 +63,7 @@ union USuffix
class CCallsign
{
public:
// contructors
// constructors
CCallsign();
CCallsign(const UCallsign &cs); // no id lookup
CCallsign(const CCallsign &cs);
@ -73,6 +90,7 @@ public:
// get
UCallsign GetKey() const;
std::string GetBase() const;
void GetCallsign(uint8_t *) const;
void GetCallsignString(char *) const;
const std::string GetCS() const;
@ -84,8 +102,6 @@ public:
// compare
bool HasSameCallsign(const CCallsign &) const;
bool HasSameCallsignWithWildcard(const CCallsign &) const;
bool HasLowerCallsign(const CCallsign &) const;
bool HasSameModule(const CCallsign &) const;
// operators
CCallsign &operator = (const CCallsign &cs);

@ -1,223 +0,0 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <fstream>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "CallsignList.h"
////////////////////////////////////////////////////////////////////////////////////////
// constructor
CCallsignList::CCallsignList()
{
memset(&m_LastModTime, 0, sizeof(time_t));
}
////////////////////////////////////////////////////////////////////////////////////////
// file io
bool CCallsignList::LoadFromFile(const std::string &filename)
{
bool ok = false;
char sz[256];
char szStar[2] = "*";
// and load
std::ifstream file (filename);
if ( file.is_open() )
{
Lock();
// empty list
m_Callsigns.clear();
// fill with file content
while ( file.getline(sz, sizeof(sz)).good() )
{
// remove leading & trailing spaces
char *szt = TrimWhiteSpaces(sz);
// crack it
if ( (::strlen(szt) > 0) && (szt[0] != '#') )
{
// 1st token is callsign
if ( (szt = ::strtok(szt, " ,\t")) != nullptr )
{
CCallsign callsign(szt);
// 2nd token is modules list
szt = ::strtok(nullptr, " ,\t");
// if token absent, use wildcard
if ( szt == nullptr )
{
szt = szStar;
}
// and add to list
m_Callsigns.push_back(CCallsignListItem(callsign, CIp(), szt));
}
}
}
// close file
file.close();
// keep file path
m_Filename.assign(filename);
// update time
GetLastModTime(&m_LastModTime);
// and done
Unlock();
ok = true;
std::cout << "Gatekeeper loaded " << m_Callsigns.size() << " lines from " << filename << std::endl;
}
else
{
std::cout << "Gatekeeper cannot find " << filename << std::endl;
}
return ok;
}
bool CCallsignList::ReloadFromFile(void)
{
bool ok = false;
if (! m_Filename.empty())
{
ok = LoadFromFile(m_Filename);
}
return ok;
}
bool CCallsignList::NeedReload(void)
{
bool needReload = false;
time_t time;
if ( GetLastModTime(&time) )
{
needReload = time != m_LastModTime;
}
return needReload;
}
////////////////////////////////////////////////////////////////////////////////////////
// compare
bool CCallsignList::IsCallsignListedWithWildcard(const CCallsign &callsign) const
{
for ( const auto &item : m_Callsigns )
{
if (item.HasSameCallsignWithWildcard(callsign))
return true;
}
return false;
}
bool CCallsignList::IsCallsignListedWithWildcard(const CCallsign &callsign, char module) const
{
for ( const auto &item : m_Callsigns )
{
if (item.HasSameCallsignWithWildcard(callsign) && ((module == ' ') || item.HasModuleListed(module)) )
return true;
}
return false;
}
bool CCallsignList::IsCallsignListed(const CCallsign &callsign, char module) const
{
for ( const auto &item : m_Callsigns )
{
if (item.HasSameCallsign(callsign) && item.HasModuleListed(module))
return true;
}
return false;
}
bool CCallsignList::IsCallsignListed(const CCallsign &callsign, char *modules) const
{
for ( const auto &item : m_Callsigns )
{
if (item.HasSameCallsign(callsign) && item.CheckListedModules(modules))
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////
// find
CCallsignListItem *CCallsignList::FindListItem(const CCallsign &Callsign)
{
for ( auto &item : m_Callsigns )
{
if ( item.GetCallsign().HasSameCallsign(Callsign) )
{
return &item;
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////
// helpers
char *CCallsignList::TrimWhiteSpaces(char *str)
{
char *end;
// Trim leading space & tabs
while((*str == ' ') || (*str == '\t')) str++;
// All spaces?
if(*str == 0)
return str;
// Trim trailing space, tab or lf
end = str + ::strlen(str) - 1;
while((end > str) && ((*end == ' ') || (*end == '\t') || (*end == '\r'))) end--;
// Write new null terminator
*(end+1) = 0;
return str;
}
bool CCallsignList::GetLastModTime(time_t *time)
{
bool ok = false;
if (! m_Filename.empty())
{
struct stat fileStat;
if( ::stat(m_Filename.c_str(), &fileStat) != -1 )
{
*time = fileStat.st_mtime;
ok = true;
}
}
return ok;
}

@ -1,67 +0,0 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <list>
#include <mutex>
#include "CallsignListItem.h"
////////////////////////////////////////////////////////////////////////////////////////
// class
class CCallsignList
{
public:
// constructor
CCallsignList();
// locks
void Lock(void) const { m_Mutex.lock(); }
void Unlock(void) const { m_Mutex.unlock(); }
// file io
virtual bool LoadFromFile(const std::string &str);
bool ReloadFromFile(void);
bool NeedReload(void);
// compare
bool IsCallsignListedWithWildcard(const CCallsign &) const;
bool IsCallsignListedWithWildcard(const CCallsign &, char) const;
bool IsCallsignListed(const CCallsign &, char) const;
bool IsCallsignListed(const CCallsign &, char*) const;
// pass-thru
bool empty() const { return m_Callsigns.empty(); }
std::list<CCallsignListItem>::iterator begin() { return m_Callsigns.begin(); }
std::list<CCallsignListItem>::iterator end() { return m_Callsigns.end(); }
// find
CCallsignListItem *FindListItem(const CCallsign &);
protected:
bool GetLastModTime(time_t *);
char *TrimWhiteSpaces(char *);
// data
mutable std::mutex m_Mutex;
std::string m_Filename;
time_t m_LastModTime;
std::list<CCallsignListItem> m_Callsigns;
};

@ -1,136 +0,0 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <string.h>
#include "Global.h"
#include "CallsignListItem.h"
////////////////////////////////////////////////////////////////////////////////////////
// constructor
CCallsignListItem::CCallsignListItem()
{
memset(m_Modules, 0, sizeof(m_Modules));
memset(m_szUrl, 0, sizeof(m_szUrl));
}
CCallsignListItem::CCallsignListItem(const CCallsign &callsign, const CIp &ip, const char *modules)
{
const std::string mods(g_Configure.GetString(g_Keys.modules.modules));
m_Callsign = callsign;
memset(m_szUrl, 0, sizeof(m_szUrl));
m_Ip = ip;
if ( modules != nullptr )
{
memset(m_Modules, 0, sizeof(m_Modules));
if ( modules[0] == '*' )
{
memcpy(m_Modules, mods.c_str(), mods.size());
}
else
{
int n = MIN(::strlen(modules), sizeof(m_Modules)-1);
for (int i=0, j=0; i<n; i++)
{
if (std::string::npos != mods.find(modules[i]))
{
m_Modules[j++] = modules[i];
}
}
}
}
}
CCallsignListItem::CCallsignListItem(const CCallsign &callsign, const char *url, const char *modules)
{
const std::string mods(g_Configure.GetString(g_Keys.modules.modules));
m_Callsign = callsign;
::strncpy(m_szUrl, url, URL_MAXLEN);
m_Ip = CIp(m_szUrl);
if ( modules != nullptr )
{
memset(m_Modules, 0, sizeof(m_Modules));
if ( modules[0] == '*' )
{
memcpy(m_Modules, mods.c_str(), mods.size());
}
else
{
int n = MIN(::strlen(modules), sizeof(m_Modules)-1);
for (int i=0, j=0; i<n; i++)
{
if (std::string::npos != mods.find(modules[i]))
{
m_Modules[j++] = modules[i];
}
}
}
}
}
CCallsignListItem::CCallsignListItem(const CCallsignListItem &item)
{
m_Callsign = item.m_Callsign;
memcpy(m_szUrl, item.m_szUrl, sizeof(m_szUrl));
m_Ip = item.m_Ip;
memcpy(m_Modules, item.m_Modules, sizeof(m_Modules));
}
////////////////////////////////////////////////////////////////////////////////////////
// compare
bool CCallsignListItem::HasSameCallsign(const CCallsign &callsign) const
{
return m_Callsign.HasSameCallsign(callsign);
}
bool CCallsignListItem::HasSameCallsignWithWildcard(const CCallsign &callsign) const
{
return m_Callsign.HasSameCallsignWithWildcard(callsign);
}
bool CCallsignListItem::HasModuleListed(char module) const
{
return (::strchr(m_Modules, (int)module) != nullptr);
}
bool CCallsignListItem::CheckListedModules(char *Modules) const
{
bool listed = false;
if ( Modules != nullptr )
{
// build a list of common modules
char list[27];
list[0] = 0;
//
for ( unsigned i = 0; i < ::strlen(Modules); i++ )
{
if ( HasModuleListed(Modules[i]) )
{
::strncat(list, &(Modules[i]), 1);
listed = true;
}
}
::strcpy(Modules, list);
}
return listed;
}

@ -1,65 +0,0 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include "Callsign.h"
#include "IP.h"
////////////////////////////////////////////////////////////////////////////////////////
// define
#define URL_MAXLEN 256
////////////////////////////////////////////////////////////////////////////////////////
// class
class CCallsignListItem
{
public:
// constructor
CCallsignListItem();
CCallsignListItem(const CCallsign &, const CIp &, const char *);
CCallsignListItem(const CCallsign &, const char *, const char *);
CCallsignListItem(const CCallsignListItem &);
// destructor
virtual ~CCallsignListItem() {}
// compare
bool HasSameCallsign(const CCallsign &) const;
bool HasSameCallsignWithWildcard(const CCallsign &) const;
bool HasModuleListed(char) const;
bool CheckListedModules(char*) const;
// get
const CCallsign &GetCallsign(void) const { return m_Callsign; }
const CIp &GetIp(void) const { return m_Ip; }
const char *GetModules(void) { return m_Modules; }
// update
void ResolveIp(void) { m_Ip = CIp(m_szUrl); }
protected:
// data
CCallsign m_Callsign;
char m_szUrl[URL_MAXLEN+1];
CIp m_Ip;
char m_Modules[27];
};

@ -50,6 +50,8 @@ public:
char GetCSModule(void) const { return m_Callsign.GetCSModule(); }
bool HasReflectorModule(void) const { return m_ReflectorModule != ' '; }
char GetReflectorModule(void) const { return m_ReflectorModule; }
std::time_t GetConnectTime(void) const { return m_ConnectTime; }
std::time_t GetLastHeardTime(void) const { return m_LastHeardTime; }
// set
void SetCSModule(char c) { m_Callsign.SetCSModule(c); }

@ -73,7 +73,7 @@ void CClients::RemoveClient(std::shared_ptr<CClient> client)
bool found = false;
for ( auto it=begin(); it!=end(); it++ )
{
// compare objetc pointers
// compare object pointers
if ( *it == client )
{
// found it !

@ -47,7 +47,7 @@ public:
void RemoveClient(std::shared_ptr<CClient>);
bool IsClient(std::shared_ptr<CClient>) const;
// pass-thru
// pass-through
std::list<std::shared_ptr<CClient>>::iterator begin() { return m_Clients.begin(); }
std::list<std::shared_ptr<CClient>>::iterator end() { return m_Clients.end(); }
std::list<std::shared_ptr<CClient>>::const_iterator cbegin() const { return m_Clients.cbegin(); }

@ -50,7 +50,7 @@ public:
void Thread(void);
void Task(void);
// pass-thru
// pass-through
void Push(std::unique_ptr<CPacket> p) { m_Queue.Push(std::move(p)); }
protected:

@ -33,9 +33,11 @@
// ini file keywords
#define JAUTOLINKMODULE "AutoLinkModule"
#define JBLACKLISTPATH "BlacklistPath"
#define JBOOTSTRAP "Bootstrap"
#define JBRANDMEISTER "Brandmeister"
#define JCALLSIGN "Callsign"
#define JCOUNTRY "Country"
#define JDASHBOARDURL "DashboardUrl"
#define JDCS "DCS"
#define JDEFAULTID "DefaultId"
#define JDEFAULTRXFREQ "DefaultRxFreq"
@ -250,6 +252,10 @@ bool CConfigure::ReadData(const std::string &path)
case ESection::names:
if (0 == key.compare(JCALLSIGN))
data[g_Keys.names.callsign] = value;
else if (0 == key.compare(JBOOTSTRAP))
data[g_Keys.names.bootstrap] = value;
else if (0 == key.compare(JDASHBOARDURL))
data[g_Keys.names.url] = value;
else if (0 == key.compare(JSYSOPEMAIL))
data[g_Keys.names.email] = value;
else if (0 == key.compare(JCOUNTRY))
@ -487,7 +493,7 @@ bool CConfigure::ReadData(const std::string &path)
badParam(key);
break;
default:
std::cout << "WARNING: parameter '" << line << "' defined befor any [section]" << std::endl;
std::cout << "WARNING: parameter '" << line << "' defined before any [section]" << std::endl;
}
}
@ -506,6 +512,10 @@ bool CConfigure::ReadData(const std::string &path)
}
}
#ifndef NO_DHT
isDefined(ErrorLevel::fatal, JNAMES, JBOOTSTRAP, g_Keys.names.bootstrap, rval);
#endif
isDefined(ErrorLevel::fatal, JNAMES, JDASHBOARDURL, g_Keys.names.url, rval);
isDefined(ErrorLevel::mild, JNAMES, JSYSOPEMAIL, g_Keys.names.email, rval);
isDefined(ErrorLevel::mild, JNAMES, JCOUNTRY, g_Keys.names.country, rval);
isDefined(ErrorLevel::mild, JNAMES, JSPONSOR, g_Keys.names.sponsor, rval);
@ -526,7 +536,7 @@ bool CConfigure::ReadData(const std::string &path)
}
else
{
std::cerr << "ERROR: specifed IPv4 external address, " << v4 << ", is malformed" << std::endl;
std::cerr << "ERROR: specified IPv4 external address, " << v4 << ", is malformed" << std::endl;
rval = true;
}
}
@ -564,7 +574,7 @@ bool CConfigure::ReadData(const std::string &path)
}
else
{
std::cerr << "ERROR: the specifed IPv6 address [" << v6 << "] is malformed" << std::endl;
std::cerr << "ERROR: the specified IPv6 address [" << v6 << "] is malformed" << std::endl;
rval = true;
}
}

@ -449,7 +449,7 @@ bool CDmrplusProtocol::IsValidDvFramePacket(const CIp &Ip, const CBuffer &Buffer
uint8_t dmrsync[7];
// get the 33 bytes ambe
memcpy(dmrframe, &(Buffer.data()[26]), 33);
// handle endianess
// handle endianness
SwapEndianess(dmrframe, sizeof(dmrframe));
// extract the 3 ambe frames
memcpy(dmr3ambe, dmrframe, 14);

@ -390,7 +390,7 @@ void CG3Protocol::Task(void)
{
BaseIp = &ClIp;
client->Alive();
// supress host checks - no ping needed to trigger potential ICMPs
// suppress host checks - no ping needed to trigger potential ICMPs
// the regular data flow will do it
m_LastKeepaliveTime.start();
break;
@ -444,7 +444,7 @@ void CG3Protocol::HandleQueue(void)
{
while (! m_Queue.IsEmpty())
{
// supress host checks
// suppress host checks
m_LastKeepaliveTime.start();
// get the packet

@ -44,9 +44,9 @@ bool CGateKeeper::Init(void)
{
// load lists from files
m_NodeWhiteList.LoadFromFile(g_Configure.GetString(g_Keys.files.white));
m_NodeBlackList.LoadFromFile(g_Configure.GetString(g_Keys.files.black));
m_PeerList.LoadFromFile(g_Configure.GetString(g_Keys.files.interlink));
m_WhiteSet.LoadFromFile(g_Configure.GetString(g_Keys.files.white));
m_BlackSet.LoadFromFile(g_Configure.GetString(g_Keys.files.black));
m_InterlinkMap.LoadFromFile(g_Configure.GetString(g_Keys.files.interlink));
// reset run flag
keep_running = true;
@ -72,7 +72,8 @@ void CGateKeeper::Close(void)
bool CGateKeeper::MayLink(const CCallsign &callsign, const CIp &ip, EProtocol protocol, char *modules) const
{
bool ok = true;
bool ok;
const std::string base(callsign.GetBase());
switch (protocol)
{
@ -88,19 +89,17 @@ bool CGateKeeper::MayLink(const CCallsign &callsign, const CIp &ip, EProtocol pr
case EProtocol::usrp:
case EProtocol::nxdn:
case EProtocol::g3:
// first check is IP & callsigned listed OK
ok &= IsNodeListedOk(callsign, ip);
// todo: then apply any protocol specific authorisation for the operation
// is callsign listed OK
ok = IsNodeListedOk(base);
break;
// URF and BM interlinks
case EProtocol::bm:
case EProtocol::urf:
ok &= IsPeerListedOk(callsign, ip, modules);
ok = IsPeerListedOk(base, ip, modules);
break;
// unsupported
case EProtocol::none:
default:
ok = false;
break;
@ -118,7 +117,9 @@ bool CGateKeeper::MayLink(const CCallsign &callsign, const CIp &ip, EProtocol pr
bool CGateKeeper::MayTransmit(const CCallsign &callsign, const CIp &ip, const EProtocol protocol, char module) const
{
bool ok = true;
bool ok;
const std::string base(callsign.GetBase());
switch (protocol)
{
@ -136,14 +137,14 @@ bool CGateKeeper::MayTransmit(const CCallsign &callsign, const CIp &ip, const EP
case EProtocol::usrp:
case EProtocol::g3:
// first check is IP & callsigned listed OK
ok = ok && IsNodeListedOk(callsign, ip, module);
ok = IsNodeListedOk(base);
// todo: then apply any protocol specific authorisation for the operation
break;
// URF interlinks
case EProtocol::urf:
case EProtocol::bm:
ok = ok && IsPeerListedOk(callsign, ip, module);
ok = IsPeerListedOk(base, module);
break;
// unsupported
@ -175,17 +176,17 @@ void CGateKeeper::Thread()
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
// have lists files changed ?
if ( m_NodeWhiteList.NeedReload() )
if ( m_WhiteSet.NeedReload() )
{
m_NodeWhiteList.ReloadFromFile();
m_WhiteSet.ReloadFromFile();
}
if ( m_NodeBlackList.NeedReload() )
if ( m_BlackSet.NeedReload() )
{
m_NodeBlackList.ReloadFromFile();
m_BlackSet.ReloadFromFile();
}
if ( m_PeerList.NeedReload() )
if ( m_InterlinkMap.NeedReload() )
{
m_PeerList.ReloadFromFile();
m_InterlinkMap.ReloadFromFile();
}
}
}
@ -193,30 +194,28 @@ void CGateKeeper::Thread()
////////////////////////////////////////////////////////////////////////////////////////
// operation helpers
bool CGateKeeper::IsNodeListedOk(const CCallsign &callsign, const CIp &ip, char module) const
bool CGateKeeper::IsNodeListedOk(const std::string &callsign) const
{
bool ok = true;
// first check IP
// next, check callsign
if ( ok )
{
// first check if callsign is in white list
// note if white list is empty, everybody is authorized
m_NodeWhiteList.Lock();
if ( !m_NodeWhiteList.empty() )
m_WhiteSet.Lock();
if ( ! m_WhiteSet.empty() )
{
ok = m_NodeWhiteList.IsCallsignListedWithWildcard(callsign, module);
ok = m_WhiteSet.IsMatched(callsign);
}
m_NodeWhiteList.Unlock();
m_WhiteSet.Unlock();
// then check if not blacklisted
if (ok)
{
m_NodeBlackList.Lock();
ok = !m_NodeBlackList.IsCallsignListedWithWildcard(callsign);
m_NodeBlackList.Unlock();
m_BlackSet.Lock();
ok = ! m_BlackSet.IsMatched(callsign);
m_BlackSet.Unlock();
}
}
@ -225,7 +224,7 @@ bool CGateKeeper::IsNodeListedOk(const CCallsign &callsign, const CIp &ip, char
}
bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char module) const
bool CGateKeeper::IsPeerListedOk(const std::string &callsign, char module) const
{
bool ok = true;
@ -235,19 +234,19 @@ bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char
if ( ok )
{
// look for an exact match in the list
m_PeerList.Lock();
if ( !m_PeerList.empty() )
m_InterlinkMap.Lock();
if ( !m_InterlinkMap.empty() )
{
ok = m_PeerList.IsCallsignListed(callsign, module);
ok = m_InterlinkMap.IsCallsignListed(callsign, module);
}
m_PeerList.Unlock();
m_InterlinkMap.Unlock();
}
// done
return ok;
}
bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char *modules) const
bool CGateKeeper::IsPeerListedOk(const std::string &callsign, const CIp &ip, char *modules) const
{
bool ok = true;
@ -257,12 +256,12 @@ bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char
if ( ok )
{
// look for an exact match in the list
m_PeerList.Lock();
if ( !m_PeerList.empty() )
m_InterlinkMap.Lock();
if ( ! m_InterlinkMap.empty() )
{
ok = m_PeerList.IsCallsignListed(callsign, modules);
ok = m_InterlinkMap.IsCallsignListed(callsign, ip, modules);
}
m_PeerList.Unlock();
m_InterlinkMap.Unlock();
}
// done

@ -22,8 +22,8 @@
#include "Defines.h"
#include "Callsign.h"
#include "IP.h"
#include "CallsignList.h"
#include "PeerCallsignList.h"
#include "BlackWhiteSet.h"
#include "InterlinkMap.h"
////////////////////////////////////////////////////////////////////////////////////////
// class
@ -41,29 +41,27 @@ public:
bool Init(void);
void Close(void);
CInterlinkMap *GetInterlinkMap(void) { m_InterlinkMap.Lock(); return &m_InterlinkMap; }
void ReleaseInterlinkMap(void) { m_InterlinkMap.Unlock(); }
// authorizations
bool MayLink(const CCallsign &, const CIp &, const EProtocol, char * = nullptr) const;
bool MayTransmit(const CCallsign &, const CIp &, EProtocol = EProtocol::any, char = ' ') const;
// peer list handeling
CPeerCallsignList *GetPeerList(void) { m_PeerList.Lock(); return &m_PeerList; }
void ReleasePeerList(void) { m_PeerList.Unlock(); }
protected:
// thread
void Thread();
// operation helpers
bool IsNodeListedOk(const CCallsign &, const CIp &, char = ' ') const;
bool IsPeerListedOk(const CCallsign &, const CIp &, char) const;
bool IsPeerListedOk(const CCallsign &, const CIp &, char *) const;
bool IsNodeListedOk(const std::string &) const;
bool IsPeerListedOk(const std::string &, char) const;
bool IsPeerListedOk(const std::string &, const CIp &, char *) const;
const std::string ProtocolName(EProtocol) const;
protected:
// data
CCallsignList m_NodeWhiteList;
CCallsignList m_NodeBlackList;
CPeerCallsignList m_PeerList;
CBlackWhiteSet m_WhiteSet, m_BlackSet;
CInterlinkMap m_InterlinkMap;
// thread
std::atomic<bool> keep_running;

@ -219,7 +219,7 @@ unsigned int CGolay2087::getSyndrome1987(unsigned int pattern)
* Compute the syndrome corresponding to the given pattern, i.e., the
* remainder after dividing the pattern (when considering it as the vector
* representation of a polynomial) by the generator polynomial, GENPOL.
* In the program this pattern has several meanings: (1) pattern = infomation
* In the program this pattern has several meanings: (1) pattern = information
* bits, when constructing the encoding table; (2) pattern = error pattern,
* when constructing the decoding table; and (3) pattern = received vector, to
* obtain its syndrome in decoding.

@ -1055,7 +1055,7 @@ static unsigned int get_syndrome_23127(unsigned int pattern)
* Compute the syndrome corresponding to the given pattern, i.e., the
* remainder after dividing the pattern (when considering it as the vector
* representation of a polynomial) by the generator polynomial, GENPOL.
* In the program this pattern has several meanings: (1) pattern = infomation
* In the program this pattern has several meanings: (1) pattern = information
* bits, when constructing the encoding table; (2) pattern = error pattern,
* when constructing the decoding table; and (3) pattern = received vector, to
* obtain its syndrome in decoding.

@ -224,7 +224,7 @@ bool CHamming::decode1393(bool* d)
d[12] = !d[12];
return true;
// Data bit erros
// Data bit errors
case 0x0FU:
d[0] = !d[0];
return true;
@ -303,7 +303,7 @@ bool CHamming::decode1063(bool* d)
d[9] = !d[9];
return true;
// Data bit erros
// Data bit errors
case 0x07U:
d[0] = !d[0];
return true;

@ -0,0 +1,261 @@
//
// ccallsignlist.cpp
// m17ref
//
// Created by Jean-Luc Deltombe (LX3JL) on 30/12/2015.
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// Copyright © 2020,2022 Thomas A. Early, N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of m17ref.
//
// m17ref is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// m17ref is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#include <fstream>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "Global.h"
#include "InterlinkMap.h"
CInterlinkMap::CInterlinkMap()
{
m_Filename.clear();
::memset(&m_LastModTime, 0, sizeof(time_t));
}
bool CInterlinkMap::LoadFromFile(const std::string &filename)
{
bool ok = false;
char line[256];
// and load
std::ifstream file(filename);
if ( file.is_open() )
{
Lock();
// empty list
m_InterlinkMap.clear();
// fill with file content
while ( file.getline(line, sizeof(line)).good() )
{
char *token[4];
// remove leading & trailing spaces
token[0] = ToUpper(TrimWhiteSpaces(line));
// crack it
if ( (strlen(token[0]) > 0) && (token[0][0] != '#') )
{
const char *delim = " \t\r";
// 1st token is callsign
if ( (token[0] = strtok(token[0], delim)) != nullptr )
{
if (strcmp(token[0], g_Configure.GetString(g_Keys.names.callsign).c_str()))
{
// the default port depends on the protocol type (URF or BM)
int default_port = (0 == memcmp(token[0], "URF", 3)) ? 10017 : 10002;
if (m_InterlinkMap.end() == m_InterlinkMap.find(token[0]))
{
// read remaining tokens
// 1=IP 2=Modules 3=Port Port is optional and defaults to 10017
// OR... 1=Modules and the dht will be used
for (int i=1; i<4; i++)
{
token[i] = strtok(nullptr, delim);
}
if (token[2])
{
int port = default_port;
if (token[3])
{
port = std::atoi(token[3]);
if (port < 1024 || port > 49000)
{
std::cout << token[0] << " Port " << port << " is out of range, resetting to " << default_port << std::endl;
port = default_port;
}
}
m_InterlinkMap[token[0]] = CInterlinkMapItem(token[1], token[2], (uint16_t)port);
}
#ifndef NO_DHT
else if (token[1])
{
m_InterlinkMap[token[0]] = CInterlinkMapItem(token[1]);
}
#endif
else
{
std::cout << token[0] << " has insufficient parameters!" << std::endl;
}
}
else
{
std::cerr << "Duplicate found: " << token[0] << " in " << filename << std::endl;
}
}
else
{
std::cerr << "Self linking is not allowed! You cannot use " << token[0] << " in " << filename << std::endl;
}
}
}
}
// close file
file.close();
// keep file path
m_Filename.assign(filename);
// update time
GetLastModTime(&m_LastModTime);
// and done
Unlock();
ok = true;
std::cout << "Gatekeeper loaded " << m_InterlinkMap.size() << " lines from " << filename << std::endl;
}
else
{
std::cout << "Gatekeeper cannot find " << filename << std::endl;
}
return ok;
}
bool CInterlinkMap::ReloadFromFile(void)
{
bool ok = false;
if ( ! m_Filename.empty() )
{
ok = LoadFromFile(m_Filename);
}
return ok;
}
bool CInterlinkMap::NeedReload(void)
{
bool needReload = false;
time_t time;
if ( GetLastModTime(&time) )
{
needReload = time != m_LastModTime;
}
return needReload;
}
bool CInterlinkMap::IsCallsignListed(const std::string &callsign, char module) const
{
const auto item = m_InterlinkMap.find(callsign);
if (m_InterlinkMap.cend() == item)
return false;
else
return item->second.HasModuleListed(module);
}
bool CInterlinkMap::IsCallsignListed(const std::string &callsign, const CIp &ip, const char *modules) const
{
const auto item = m_InterlinkMap.find(callsign);
if (m_InterlinkMap.cend() != item)
{
if ( item->second.CheckListedModules(modules) )
{
if ( ip == item->second.GetIp() )
{
return true;
}
}
}
else
{
std::cout << "'" << callsign << "' not found in interlink map\n";
}
return false;
}
CInterlinkMapItem *CInterlinkMap::FindMapItem(const std::string &cs)
{
auto it = m_InterlinkMap.find(cs);
if (m_InterlinkMap.end() == it)
return nullptr;
return &it->second;
}
char *CInterlinkMap::TrimWhiteSpaces(char *str)
{
char *end;
// Trim leading space & tabs
while((*str == ' ') || (*str == '\t')) str++;
// All spaces?
if(*str == 0)
return str;
// Trim trailing space, tab or lf
end = str + ::strlen(str) - 1;
while((end > str) && ((*end == ' ') || (*end == '\t') || (*end == '\r'))) end--;
// Write new null terminator
*(end+1) = 0;
return str;
}
bool CInterlinkMap::GetLastModTime(time_t *time)
{
bool ok = false;
if ( ! m_Filename.empty() )
{
struct stat fileStat;
if( ::stat(m_Filename.c_str(), &fileStat) != -1 )
{
*time = fileStat.st_mtime;
ok = true;
}
}
return ok;
}
char *CInterlinkMap::ToUpper(char *str)
{
constexpr auto diff = 'a' - 'A';
for (char *p=str; *p; p++)
{
if (*p >= 'a' && *p <= 'z')
*p -= diff;
}
return str;
}
#ifndef NO_DHT
void CInterlinkMap::Update(const std::string &cs, const std::string &cmods, const std::string &ipv4, const std::string &ipv6, uint16_t port, const std::string &emods)
{
auto it = m_InterlinkMap.find(cs);
if (m_InterlinkMap.end() == it)
{
std::cerr << "Can't Update CInterlinkMap item '" << cs << "' because it doesn't exist!";
}
else
{
it->second.UpdateItem(cmods, ipv4, ipv6, port, emods);
}
}
#endif

@ -0,0 +1,79 @@
//
// Created by Jean-Luc Deltombe (LX3JL) on 30/12/2015.
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// Copyright © 2020 Thomas A. Early, N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of m17ref.
//
// m17ref is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// m17ref is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#pragma once
#include <mutex>
#include <map>
#include "InterlinkMapItem.h"
////////////////////////////////////////////////////////////////////////////////////////
// class
class CInterlinkMap
{
public:
// constructor
CInterlinkMap();
// destructor
virtual ~CInterlinkMap() {}
// locks
void Lock(void) const { m_Mutex.lock(); }
void Unlock(void) const { m_Mutex.unlock(); }
// file io
virtual bool LoadFromFile(const std::string &filename);
bool ReloadFromFile(void);
bool NeedReload(void);
#ifndef NO_DHT
void Update(const std::string &cs, const std::string &mods, const std::string &ipv4, const std::string &ipv6, uint16_t port, const std::string &tcmods);
#endif
// compare
bool IsCallsignListed(const std::string &, const char) const;
bool IsCallsignListed(const std::string &, const CIp &ip, const char*) const;
// pass-through
bool empty() const { return m_InterlinkMap.empty(); }
std::map<std::string, CInterlinkMapItem>::iterator begin() { return m_InterlinkMap.begin(); }
std::map<std::string, CInterlinkMapItem>::iterator end() { return m_InterlinkMap.end(); }
std::map<std::string, CInterlinkMapItem>::const_iterator cbegin() { return m_InterlinkMap.cbegin(); }
std::map<std::string, CInterlinkMapItem>::const_iterator cend() { return m_InterlinkMap.cend(); }
// find
CInterlinkMapItem *FindMapItem(const std::string &);
protected:
bool GetLastModTime(time_t *);
char *TrimWhiteSpaces(char *);
char *ToUpper(char *str);
// data
mutable std::mutex m_Mutex;
std::string m_Filename;
time_t m_LastModTime;
std::map<std::string, CInterlinkMapItem> m_InterlinkMap;
};

@ -0,0 +1,143 @@
//
// ccallsignlistitem.cpp
// m17ref
//
// Created by Jean-Luc Deltombe (LX3JL) on 31/01/2016.
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// Copyright © 2020,2022 Thomas A. Early N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of m17ref.
//
// m17ref is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// m17ref is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#include <string.h>
#include "Configure.h"
#include "InterlinkMapItem.h"
#include "Reflector.h"
////////////////////////////////////////////////////////////////////////////////////////
// constructor
#ifdef NO_DHT
CInterlinkMapItem::CInterlinkMapItem()
{
m_UsesDHT = false;
}
#else
CInterlinkMapItem::CInterlinkMapItem()
{
m_UsesDHT = false;
m_Updated = false;
}
#endif
#ifndef NO_DHT
CInterlinkMapItem::CInterlinkMapItem(const char *mods)
{
m_UsesDHT = true;
m_Updated = false;
m_Mods.assign(mods);
}
#endif
CInterlinkMapItem::CInterlinkMapItem(const char *addr, const char *mods, uint16_t port) : CInterlinkMapItem()
{
m_Mods.assign(mods);
m_Ip.Initialize(strchr(addr, ':') ? AF_INET6 : AF_INET, port, addr);
}
////////////////////////////////////////////////////////////////////////////////////////
// compare
bool CInterlinkMapItem::HasModuleListed(char module) const
{
return m_Mods.npos != m_Mods.find(module);
}
bool CInterlinkMapItem::HasSameIp(const CIp &ip)
{
return ip == m_Ip;
}
bool CInterlinkMapItem::CheckListedModules(const char *mods) const
{
if (mods == nullptr)
return false;
// make sure every mods character is matched in m_Mods
const auto count = m_Mods.size();
bool found[count];
for (unsigned i=0; i<count; i++)
found[i] = false;
for (auto p=mods; *p; p++)
{
auto pos = m_Mods.find(*p);
if (pos == m_Mods.npos)
return false;
else
found[pos] = true;
}
for (unsigned i=0; i<count; i++)
{
if (! found[i])
return false;
}
return true;
}
#ifndef NO_DHT
void CInterlinkMapItem::UpdateItem(const std::string &cmods, const std::string &ipv4, const std::string &ipv6, uint16_t port, const std::string &tcmods)
{
if (m_CMods.compare(cmods))
{
m_CMods.assign(cmods);
m_Updated = true;
}
if (m_IPv4.compare(ipv4))
{
m_IPv4.assign(ipv4);
m_Updated = true;
}
if (m_IPv6.compare(ipv6))
{
m_IPv6.assign(ipv6);
m_Updated = true;
}
if (m_Port != port)
{
m_Port = port;
m_Updated = true;
}
if (m_TCMods.compare(tcmods))
{
m_TCMods.assign(tcmods);
m_Updated = true;
}
}
void CInterlinkMapItem::UpdateIP(bool IPv6NotConfigured)
{
if (m_Updated)
{
if (IPv6NotConfigured || m_IPv6.empty())
m_Ip.Initialize(AF_INET, m_Port, m_IPv4.c_str());
else
m_Ip.Initialize(AF_INET6, m_Port, m_IPv6.c_str());
m_Updated = false;
}
}
#endif

@ -0,0 +1,74 @@
//
// Created by Jean-Luc Deltombe (LX3JL) on 31/01/2016.
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// Copyright © 2020 Thomas A. Early N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of m17ref.
//
// m17ref is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// m17ref is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#pragma once
#include <string>
#include "Callsign.h"
#include "IP.h"
class CInterlinkMapItem
{
public:
// constructor
CInterlinkMapItem();
#ifndef NO_DHT
CInterlinkMapItem(const char *mods);
#endif
CInterlinkMapItem(const char *addr, const char *mods, uint16_t port);
// Update things
#ifndef NO_DHT
void UpdateIP(bool IPv6NotConfigured);
void UpdateItem(const std::string &cmods, const std::string &ipv4, const std::string &ipv6, uint16_t port, const std::string &tcmods);
#endif
// compare
bool HasSameIp(const CIp &ip);
bool HasModuleListed(char) const;
bool CheckListedModules(const char*) const;
// get
const CIp &GetIp(void) const { return m_Ip; }
const std::string &GetModules(void) const { return m_Mods; }
bool UsesDHT(void) const { return m_UsesDHT; }
uint16_t GetPort(void) const { return m_Port; }
#ifndef NO_DHT
const std::string &GetIPv4(void) const { return m_IPv4; }
const std::string &GetIPv6(void) const { return m_IPv6; }
const std::string &GetTCMods(void) const { return m_TCMods; }
const std::string &GetCMods(void) const { return m_CMods; }
#endif
private:
// data
CIp m_Ip;
std::string m_Mods;
uint16_t m_Port;
bool m_UsesDHT;
#ifndef NO_DHT
bool m_Updated;
std::string m_CMods, m_TCMods, m_IPv4, m_IPv6;
#endif
};

@ -39,8 +39,8 @@ struct SJsonKeys {
struct MMDVM { const std::string port, defaultid; }
mmdvm { "MMDVMPort", "mmdvmdefaultid" };
struct NAMES { const std::string callsign, email, country, sponsor; }
names { "Callsign", "SysopEmail", "Country", "Sponsor" };
struct NAMES { const std::string callsign, bootstrap, url, email, country, sponsor; }
names { "Callsign", "bootstrap", "DashboardUrl", "SysopEmail", "Country", "Sponsor" };
struct IP { const std::string ipv4bind, ipv4address, ipv6bind, ipv6address, transcoder; }
ip { "ipv4bind", "IPv4Address", "ipv6bind", "IPv6Address", "tcaddress" };

@ -27,26 +27,6 @@
enum class Eaction { normal, parse, error_only };
enum class Esource { http, file };
// compare function for std::map::find
struct CCallsignHash
{
std::size_t operator() (const UCallsign &ucs) const
{
std::hash<uint64_t> hash;
return hash(ucs.l);
}
};
struct CCallsignEqual
{
bool operator() (const UCallsign &ucs1, const UCallsign &ucs2) const
{
return ucs1.l == ucs2.l;
}
};
////////////////////////////////////////////////////////////////////////////////////////
class CLookup

@ -29,7 +29,7 @@
* Description
* -----------
* The source file contains routines which calculate the CCITT CRC
* values for an incomming byte string.
* values for an incoming byte string.
*/
#include <stdlib.h>

@ -29,7 +29,7 @@
* Description
* -----------
* The source file contains routines which calculate the CCITT CRC
* values for an incomming byte string.
* values for an incoming byte string.
*/
#pragma once

@ -28,7 +28,7 @@ SJsonKeys g_Keys;
CReflector g_Reflector;
CGateKeeper g_GateKeeper;
CConfigure g_Configure;
CVersion g_Version(3,0,1); // The major byte should only change if the interlink packet changes!
CVersion g_Version(3,1,0); // The major byte should only change if the interlink packet changes!
CLookupDmr g_LDid;
CLookupNxdn g_LNid;
CLookupYsf g_LYtr;
@ -39,7 +39,7 @@ int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "No configuration file specifed! Usage: " << argv[0] << " /pathname/to/configuration/file" << std::endl;
std::cerr << "No configuration file specified! Usage: " << argv[0] << " /pathname/to/configuration/file" << std::endl;
return EXIT_FAILURE;
}
@ -98,13 +98,13 @@ static void usage(std::ostream &os, const char *name)
" ysf : The Callsign => Tx/Rx frequency database.\n"
"SOURCE (choose one)\n"
" file : The file specified by the FilePath ini parameter.\n"
" http : The URL specified by the URL ini paramater.\n"
" http : The URL specified by the URL ini parameter.\n"
"ACTION (choose one)\n"
" print : Print all lines from the SOURCE that are syntactically correct.\n"
" error : Print only the lines with failed syntax.\n"
"INIFILE : an error-free urfd ini file (check it first with inicheck).\n\n"
"Only the first character of DATABASE, SOURCE and ACTION is read.\n"
"Example: " << name << " y f e urfd.ini # Check your YSF Tx/Rx database file specifed in urfd.ini for syntax errors.\n\n";
"Example: " << name << " y f e urfd.ini # Check your YSF Tx/Rx database file specified in urfd.ini for syntax errors.\n\n";
}
enum class Edb { none, dmr, nxdn, ysf };

@ -27,13 +27,19 @@ DBUTIL = dbutil
include urfd.mk
ifeq ($(debug), true)
CFLAGS = -ggdb3 -DDEBUG -W -Werror -std=c++11 -MMD -MD
CFLAGS = -ggdb3 -DDEBUG -W -Werror -std=c++17 -MMD -MD
else
CFLAGS = -W -Werror -std=c++11 -MMD -MD
CFLAGS = -W -Werror -std=c++17 -MMD -MD
endif
LDFLAGS=-pthread -lcurl
ifeq ($(DHT), true)
LDFLAGS += -lopendht
else
CFLAGS += -DNO_DHT
endif
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)

@ -1,37 +0,0 @@
// Copyright © 2015 Jean-Luc. All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "Notification.h"
CNotification::CNotification()
{
// init variables
m_iId = NOTIFICATION_NONE;
}
CNotification::CNotification(int iId)
{
m_iId = iId;
}
CNotification::CNotification(int iId, const CCallsign &Callsign)
{
m_iId = iId;
m_Callsign = Callsign;
}

@ -1,53 +0,0 @@
// Copyright © 2015 Jean-Luc. All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include "Callsign.h"
////////////////////////////////////////////////////////////////////////////////////////
// Id
#define NOTIFICATION_NONE 0
#define NOTIFICATION_CLIENTS 1
#define NOTIFICATION_USERS 2
#define NOTIFICATION_STREAM_OPEN 3
#define NOTIFICATION_STREAM_CLOSE 4
#define NOTIFICATION_PEERS 5
////////////////////////////////////////////////////////////////////////////////////////
// class
class CNotification
{
public:
// constructor
CNotification();
CNotification(int);
CNotification(int, const CCallsign &);
// get
int GetId(void) const { return m_iId; }
const CCallsign &GetCallsign(void) const { return m_Callsign; }
protected:
// data
int m_iId;
CCallsign m_Callsign;
};

@ -1,49 +0,0 @@
// Copyright © 2015 Jean-Luc. All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <queue>
#include <mutex>
#include "Notification.h"
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
// class
class CNotificationQueue
{
public:
// lock
void Lock() { m_Mutex.lock(); }
void Unlock() { m_Mutex.unlock(); }
// pass thru
CNotification front() { return queue.front(); }
void pop() { queue.pop(); }
void push(CNotification note) { queue.push(note); }
bool empty() const { return queue.empty(); }
protected:
// data
std::mutex m_Mutex;
std::queue<CNotification> queue;
};

@ -88,7 +88,7 @@ void CPacket::EncodeInterlinkPacket(const char *magic, CBuffer &buf) const
data[19] = m_uiYsfPacketFrameId;
}
// dstar contstructor
// dstar constructor
CPacket::CPacket(uint16_t sid, uint8_t dstarpid)
{
m_uiStreamId = sid;

@ -42,6 +42,7 @@ public:
const CCallsign &GetCallsign(void) const { return m_Callsign; }
const CIp &GetIp(void) const { return m_Ip; }
char *GetReflectorModules(void) { return m_ReflectorModules; }
std::time_t GetConnectTime(void) const { return m_ConnectTime; }
// set
@ -60,7 +61,7 @@ public:
int GetNbClients(void) const { return (int)m_Clients.size(); }
void ClearClients(void) { m_Clients.clear(); }
// pass-thru
// pass-through
std::list<std::shared_ptr<CClient>>::iterator begin() { return m_Clients.begin(); }
std::list<std::shared_ptr<CClient>>::iterator end() { return m_Clients.end(); }
std::list<std::shared_ptr<CClient>>::const_iterator cbegin() const { return m_Clients.cbegin(); }

@ -1,84 +0,0 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <fstream>
#include <string.h>
#include "PeerCallsignList.h"
bool CPeerCallsignList::LoadFromFile(const std::string &filename)
{
bool ok = false;
char sz[256];
// and load
std::ifstream file (filename);
if ( file.is_open() )
{
Lock();
// empty list
m_Callsigns.clear();
// fill with file content
while ( file.getline(sz, sizeof(sz)).good() )
{
// remove leading & trailing spaces
char *szt = TrimWhiteSpaces(sz);
// crack it
if ( (::strlen(szt) > 0) && (szt[0] != '#') )
{
// 1st token is callsign
if ( (szt = ::strtok(szt, " ,\t")) != nullptr )
{
CCallsign callsign(szt);
// 2nd token is ip
char *szip;
if ( (szip = ::strtok(nullptr, " ,\t")) != nullptr )
{
// 3rd token is modules list
if ( (szt = ::strtok(nullptr, " ,\t")) != nullptr )
{
// and load
m_Callsigns.push_back(CCallsignListItem(callsign, szip, szt));
}
}
}
}
}
// close file
file.close();
// keep file path
m_Filename = filename;
// update time
GetLastModTime(&m_LastModTime);
// and done
Unlock();
ok = true;
std::cout << "Gatekeeper loaded " << m_Callsigns.size() << " lines from " << filename << std::endl;
}
else
{
std::cout << "Gatekeeper cannot find " << filename << std::endl;
}
return ok;
}

@ -1,38 +0,0 @@
// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
// urfd -- The universal reflector
// Copyright © 2021 Thomas A. Early N7TAE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include "CallsignList.h"
////////////////////////////////////////////////////////////////////////////////////////
// class
class CPeerCallsignList : public CCallsignList
{
public:
// constructor
CPeerCallsignList() {}
// destructor
virtual ~CPeerCallsignList() {}
// file io
bool LoadFromFile(const std::string &filename);
};

@ -38,7 +38,7 @@ public:
void AddPeer(std::shared_ptr<CPeer>);
void RemovePeer(std::shared_ptr<CPeer>);
// pass-thru
// pass-through
std::list<std::shared_ptr<CPeer>>::iterator begin() { return m_Peers.begin(); }
std::list<std::shared_ptr<CPeer>>::iterator end() { return m_Peers.end(); }
std::list<std::shared_ptr<CPeer>>::const_iterator cbegin() const { return m_Peers.cbegin(); }

@ -32,7 +32,7 @@ public:
void Lock(void) { m_Mutex.lock(); }
void Unlock(void) { m_Mutex.unlock(); }
// pass-thru
// pass-through
std::list<std::unique_ptr<CProtocol>>::iterator begin() { return m_Protocols.begin(); }
std::list<std::unique_ptr<CProtocol>>::iterator end() { return m_Protocols.end(); }

@ -72,7 +72,7 @@ unsigned int CQR1676::getSyndrome1576(unsigned int pattern)
* Compute the syndrome corresponding to the given pattern, i.e., the
* remainder after dividing the pattern (when considering it as the vector
* representation of a polynomial) by the generator polynomial, GENPOL.
* In the program this pattern has several meanings: (1) pattern = infomation
* In the program this pattern has several meanings: (1) pattern = information
* bits, when constructing the encoding table; (2) pattern = error pattern,
* when constructing the decoding table; and (3) pattern = received vector, to
* obtain its syndrome in decoding.

@ -20,8 +20,13 @@
#include <string.h>
#include "Global.h"
////////////////////////////////////////////////////////////////////////////////////////
// destructor
CReflector::CReflector()
{
#ifndef NO_DHT
peers_put_count = clients_put_count = users_put_count = 0;
#endif
}
CReflector::~CReflector()
{
@ -47,10 +52,18 @@ CReflector::~CReflector()
bool CReflector::Start(void)
{
// get config stuff
m_Callsign = CCallsign(g_Configure.GetString(g_Keys.names.callsign).c_str(), false);
const auto cs(g_Configure.GetString(g_Keys.names.callsign));
m_Callsign.SetCallsign(cs, false);
m_Modules.assign(g_Configure.GetString(g_Keys.modules.modules));
std::string tcmods(g_Configure.GetString(g_Keys.modules.tcmodules));
#ifndef NO_DHT
// start the dht instance
refhash = dht::InfoHash::get(cs);
node.run(17171, dht::crypto::generateIdentity(cs), true);
node.bootstrap(g_Configure.GetString(g_Keys.names.bootstrap), "17171");
#endif
// let's go!
keep_running = true;
@ -107,13 +120,17 @@ bool CReflector::Start(void)
// start the reporting thread
try
{
m_XmlReportFuture = std::async(std::launch::async, &CReflector::XmlReportThread, this);
m_XmlReportFuture = std::async(std::launch::async, &CReflector::StateReportThread, this);
}
catch(const std::exception& e)
{
std::cerr << "Cannot start the dashboard data report thread: " << e.what() << '\n';
}
#ifndef NO_DHT
PutDHTConfig();
#endif
return false;
}
@ -145,6 +162,16 @@ void CReflector::Stop(void)
g_LDid.LookupClose();
g_LNid.LookupClose();
g_LYtr.LookupClose();
#ifndef NO_DHT
// kill the DHT
node.cancelPut(refhash, toUType(EUrfdValueID::Config));
node.cancelPut(refhash, toUType(EUrfdValueID::Peers));
node.cancelPut(refhash, toUType(EUrfdValueID::Clients));
node.cancelPut(refhash, toUType(EUrfdValueID::Users));
node.shutdown({}, true);
node.join();
#endif
}
////////////////////////////////////////////////////////////////////////////////////////
@ -155,7 +182,7 @@ bool CReflector::IsStreaming(char module)
return false;
}
// clients MUST have bee locked by the caller so we can freely access it within the fuction
// clients MUST have bee locked by the caller so we can freely access it within the function
std::shared_ptr<CPacketStream> CReflector::OpenStream(std::unique_ptr<CDvHeaderPacket> &DvHeader, std::shared_ptr<CClient>client)
{
// check sid is not zero
@ -209,7 +236,7 @@ std::shared_ptr<CPacketStream> CReflector::OpenStream(std::unique_ptr<CDvHeaderP
stream->Push(std::move(DvHeader));
// notify
OnStreamOpen(stream->GetUserCallsign());
//OnStreamOpen(stream->GetUserCallsign());
}
return stream;
@ -237,7 +264,7 @@ void CReflector::CloseStream(std::shared_ptr<CPacketStream> stream)
client->NotAMaster();
// notify
OnStreamClose(stream->GetUserCallsign());
//OnStreamClose(stream->GetUserCallsign());
std::cout << "Closing stream of module " << GetStreamModule(stream) << std::endl;
}
@ -296,10 +323,12 @@ void CReflector::RouterThread(const char ThisModule)
#define XML_UPDATE_PERIOD 10
void CReflector::XmlReportThread()
void CReflector::StateReportThread()
{
std::string xmlpath, jsonpath;
#ifndef NO_DHT
peers_changed = clients_changed = users_changed = true;
#endif
if (g_Configure.Contains(g_Keys.files.xml))
xmlpath.assign(g_Configure.GetString(g_Keys.files.xml));
if (g_Configure.Contains(g_Keys.files.json))
@ -343,58 +372,54 @@ void CReflector::XmlReportThread()
}
}
// and wait a bit
// and wait a bit and do something useful at the same time
for (int i=0; i< XML_UPDATE_PERIOD && keep_running; i++)
{
#ifndef NO_DHT
// update the dht data, if needed
if (peers_changed)
{
PutDHTPeers();
peers_changed = false;
}
if (clients_changed)
{
PutDHTClients();
clients_changed = false;
}
if (users_changed)
{
PutDHTUsers();
users_changed = false;
}
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
// notifications
void CReflector::OnPeersChanged(void)
{
CNotification notification(NOTIFICATION_PEERS);
m_Notifications.Lock();
m_Notifications.push(notification);
m_Notifications.Unlock();
#ifndef NO_DHT
peers_changed = true;
#endif
}
void CReflector::OnClientsChanged(void)
{
CNotification notification(NOTIFICATION_CLIENTS);
m_Notifications.Lock();
m_Notifications.push(notification);
m_Notifications.Unlock();
#ifndef NO_DHT
clients_changed = true;
#endif
}
void CReflector::OnUsersChanged(void)
{
CNotification notification(NOTIFICATION_USERS);
m_Notifications.Lock();
m_Notifications.push(notification);
m_Notifications.Unlock();
}
void CReflector::OnStreamOpen(const CCallsign &callsign)
{
CNotification notification(NOTIFICATION_STREAM_OPEN, callsign);
m_Notifications.Lock();
m_Notifications.push(notification);
m_Notifications.Unlock();
}
void CReflector::OnStreamClose(const CCallsign &callsign)
{
CNotification notification(NOTIFICATION_STREAM_CLOSE, callsign);
m_Notifications.Lock();
m_Notifications.push(notification);
m_Notifications.Unlock();
#ifndef NO_DHT
users_changed = true;
#endif
}
////////////////////////////////////////////////////////////////////////////////////////
@ -512,3 +537,202 @@ void CReflector::WriteXmlFile(std::ofstream &xmlFile)
ReleaseUsers();
xmlFile << "</" << cs << "heard users>" << std::endl;
}
#ifndef NO_DHT
// DHT put() and get()
void CReflector::PutDHTPeers()
{
const std::string cs(g_Configure.GetString(g_Keys.names.callsign));
// load it up
SUrfdPeers1 p;
time(&p.timestamp);
p.sequence = peers_put_count++;
auto peers = GetPeers();
for (auto pit=peers->cbegin(); pit!=peers->cend(); pit++)
{
p.list.emplace_back((*pit)->GetCallsign().GetCS(), (*pit)->GetReflectorModules(), (*pit)->GetConnectTime());
}
ReleasePeers();
auto nv = std::make_shared<dht::Value>(p);
nv->user_type.assign("urfd-peers-1");
nv->id = toUType(EUrfdValueID::Peers);
node.putSigned(
refhash,
nv,
#ifdef DEBUG
[](bool success){ std::cout << "PutDHTPeers() " << (success ? "successful" : "unsuccessful") << std::endl; },
#else
[](bool success){ if (! success) std::cout << "PutDHTPeers() unsuccessful" << std::endl; },
#endif
true // permanent!
);
}
void CReflector::PutDHTClients()
{
const std::string cs(g_Configure.GetString(g_Keys.names.callsign));
SUrfdClients1 c;
time(&c.timestamp);
c.sequence = clients_put_count++;
auto clients = GetClients();
for (auto cit=clients->cbegin(); cit!=clients->cend(); cit++)
{
c.list.emplace_back((*cit)->GetCallsign().GetCS(), std::string((*cit)->GetIp().GetAddress()), (*cit)->GetReflectorModule(), (*cit)->GetConnectTime(), (*cit)->GetLastHeardTime());
}
ReleaseClients();
auto nv = std::make_shared<dht::Value>(c);
nv->user_type.assign("urfd-clients-1");
nv->id = toUType(EUrfdValueID::Clients);
node.putSigned(
refhash,
nv,
#ifdef DEBUG
[](bool success){ std::cout << "PutDHTClients() " << (success ? "successful" : "unsuccessful") << std::endl; },
#else
[](bool success){ if (! success) std::cout << "PutDHTClients() unsuccessful" << std::endl; },
#endif
true // permanent!
);
}
void CReflector::PutDHTUsers()
{
const std::string cs(g_Configure.GetString(g_Keys.names.callsign));
SUrfdUsers1 u;
time(&u.timestamp);
u.sequence = users_put_count++;
auto users = GetUsers();
for (auto uit=users->cbegin(); uit!=users->cend(); uit++)
{
u.list.emplace_back((*uit).GetCallsign(), std::string((*uit).GetViaNode()), (*uit).GetOnModule(), (*uit).GetViaPeer(), (*uit).GetLastHeardTime());
}
ReleaseUsers();
auto nv = std::make_shared<dht::Value>(u);
nv->user_type.assign("urfd-users-1");
nv->id = toUType(EUrfdValueID::Users);
node.putSigned(
refhash,
nv,
#ifdef DEBUG
[](bool success){ std::cout << "PutDHTUsers() " << (success ? "successful" : "unsuccessful") << std::endl; },
#else
[](bool success){ if (! success) std::cout << "PutDHTUsers() unsuccessful" << std::endl; },
#endif
true // permanent!
);
}
void CReflector::PutDHTConfig()
{
const std::string cs(g_Configure.GetString(g_Keys.names.callsign));
SUrfdConfig1 cfg;
time(&cfg.timestamp);
cfg.cs.assign(cs);
cfg.ipv4.assign(g_Configure.GetString(g_Keys.ip.ipv4address));
cfg.ipv6.assign(g_Configure.GetString(g_Keys.ip.ipv6address));
cfg.mods.assign(g_Configure.GetString(g_Keys.modules.modules));
cfg.tcmods.assign(g_Configure.GetString(g_Keys.modules.tcmodules));
cfg.url.assign(g_Configure.GetString(g_Keys.names.url));
cfg.email.assign(g_Configure.GetString(g_Keys.names.email));
cfg.country.assign(g_Configure.GetString(g_Keys.names.country));
cfg.sponsor.assign(g_Configure.GetString(g_Keys.names.sponsor));
std::ostringstream ss;
ss << g_Version;
cfg.version.assign(ss.str());
cfg.almod[toUType(EUrfdAlMod::nxdn)] = g_Configure.GetString(g_Keys.nxdn.autolinkmod).at(0);
cfg.almod[toUType(EUrfdAlMod::p25)] = g_Configure.GetString(g_Keys.p25.autolinkmod).at(0);
cfg.almod[toUType(EUrfdAlMod::ysf)] = g_Configure.GetString(g_Keys.ysf.autolinkmod).at(0);
cfg.ysffreq[toUType(EUrfdTxRx::rx)] = g_Configure.GetUnsigned(g_Keys.ysf.defaultrxfreq);
cfg.ysffreq[toUType(EUrfdTxRx::tx)] = g_Configure.GetUnsigned(g_Keys.ysf.defaulttxfreq);
cfg.refid[toUType(EUrfdRefId::nxdn)] = g_Configure.GetUnsigned(g_Keys.nxdn.reflectorid);
cfg.refid[toUType(EUrfdRefId::p25)] = g_Configure.GetUnsigned(g_Keys.p25.reflectorid);
cfg.port[toUType(EUrfdPorts::dcs)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dcs.port);
cfg.port[toUType(EUrfdPorts::dextra)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dextra.port);
cfg.port[toUType(EUrfdPorts::dmrplus)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dmrplus.port);
cfg.port[toUType(EUrfdPorts::dplus)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.dplus.port);
cfg.port[toUType(EUrfdPorts::m17)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.m17.port);
cfg.port[toUType(EUrfdPorts::mmdvm)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.mmdvm.port);
cfg.port[toUType(EUrfdPorts::nxdn)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.nxdn.port);
cfg.port[toUType(EUrfdPorts::p25)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.p25.port);
cfg.port[toUType(EUrfdPorts::urf)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.urf.port);
cfg.port[toUType(EUrfdPorts::ysf)] = (uint16_t)g_Configure.GetUnsigned(g_Keys.ysf.port);
cfg.g3enabled = g_Configure.GetBoolean(g_Keys.g3.enable);
for (const auto m : cfg.mods)
cfg.description[m] = g_Configure.GetString(g_Keys.modules.descriptor[m-'A']);
auto nv = std::make_shared<dht::Value>(cfg);
nv->user_type.assign("urfd-config-1");
nv->id = toUType(EUrfdValueID::Config);
node.putSigned(
refhash,
nv,
#ifdef DEBUG
[](bool success){ std::cout << "PutDHTConfig() " << (success ? "successful" : "unsuccessful") << std::endl; },
#else
[](bool success){ if(! success) std::cout << "PutDHTConfig() unsuccessful" << std::endl; },
#endif
true
);
}
void CReflector::GetDHTConfig(const std::string &cs)
{
static SUrfdConfig1 cfg;
cfg.timestamp = 0; // every time this is called, zero the timestamp
std::cout << "Getting " << cs << " connection info..." << std::endl;
// we only want the configuration section of the reflector's document
dht::Where w;
w.id(toUType(EUrfdValueID::Config));
node.get(
dht::InfoHash::get(cs),
[](const std::shared_ptr<dht::Value> &v) {
if (0 == v->user_type.compare("urfd-config-1"))
{
auto rdat = dht::Value::unpack<SUrfdConfig1>(*v);
if (rdat.timestamp > cfg.timestamp)
{
// the time stamp is the newest so far, so put it in the static cfg struct
cfg = dht::Value::unpack<SUrfdConfig1>(*v);
}
}
else
{
std::cerr << "Get() returned unknown user_type: '" << v->user_type << "'" << std::endl;
}
return true; // check all the values returned
},
[](bool success) {
if (success)
{
if (cfg.timestamp)
{
// if the get() call was successful and there is a nonzero timestamp, then do the update
g_GateKeeper.GetInterlinkMap()->Update(cfg.cs, cfg.mods, cfg.ipv4, cfg.ipv6, cfg.port[toUType(EUrfdPorts::urf)], cfg.tcmods);
g_GateKeeper.ReleaseInterlinkMap();
}
else
{
std::cerr << "node.Get() was successful, but the timestamp was zero" << std::endl;
}
}
else
{
std::cout << "Get() was unsuccessful" << std::endl;
}
},
{}, // empty filter
w // just the configuration section
);
}
#endif

@ -25,7 +25,10 @@
#include "Peers.h"
#include "Protocols.h"
#include "PacketStream.h"
#include "NotificationQueue.h"
#ifndef NO_DHT
#include "urfd-dht-values.h"
#endif
////////////////////////////////////////////////////////////////////////////////////////
@ -42,6 +45,7 @@
class CReflector
{
public:
CReflector();
// destructor
~CReflector();
@ -75,13 +79,22 @@ public:
void OnPeersChanged(void);
void OnClientsChanged(void);
void OnUsersChanged(void);
void OnStreamOpen(const CCallsign &);
void OnStreamClose(const CCallsign &);
#ifndef NO_DHT
void GetDHTConfig(const std::string &cs);
#endif
protected:
#ifndef NO_DHT
// Publish DHT
void PutDHTConfig();
void PutDHTPeers();
void PutDHTClients();
void PutDHTUsers();
#endif
// threads
void RouterThread(const char);
void XmlReportThread(void);
void StateReportThread(void);
// streams
std::shared_ptr<CPacketStream> GetStream(char);
@ -92,7 +105,6 @@ protected:
void WriteXmlFile(std::ofstream &);
void JsonReport(nlohmann::json &report);
protected:
// identity
CCallsign m_Callsign;
std::string m_Modules, m_TCmodules;
@ -111,6 +123,11 @@ protected:
std::unordered_map<char, std::future<void>> m_RouterFuture;
std::future<void> m_XmlReportFuture;
// notifications
CNotificationQueue m_Notifications;
#ifndef NO_DHT
// Distributed Hash Table
dht::DhtRunner node;
dht::InfoHash refhash;
unsigned int peers_put_count, clients_put_count, users_put_count;
std::atomic<bool> peers_changed, clients_changed, users_changed;
#endif
};

@ -22,7 +22,7 @@
/************************************************************
* THIS IS IMPORTANT
* This template is primarly designed for std::unique_ptr!
* This template is primarily designed for std::unique_ptr!
* If you are going to use it for std::shared_ptr, then
* please consider that when you Push(), what you pushed
* from will be nullptr after the Push()!
@ -56,13 +56,13 @@ public:
}
}
// If the queue is empty, wait until an element is avaiable.
// If the queue is empty, wait until an element is available.
T PopWait(void)
{
std::unique_lock<std::mutex> lock(m);
while(q.empty())
{
// release lock as long as the wait and reaquire it afterwards.
// release lock as long as the wait and reacquire it afterwards.
c.wait(lock);
}
T val = std::move(q.front());

@ -40,7 +40,7 @@ CUdpSocket::~CUdpSocket()
// returns true on error
bool CUdpSocket::Open(const CIp &Ip)
{
// check for a vaild family
// check for a valid family
if (AF_UNSPEC == Ip.GetFamily())
return true;

@ -287,16 +287,16 @@ void CURFProtocol::HandlePeerLinks(void)
CBuffer buffer;
// get the list of peers
CPeerCallsignList *list = g_GateKeeper.GetPeerList();
auto ilmap = g_GateKeeper.GetInterlinkMap();
CPeers *peers = g_Reflector.GetPeers();
// check if all our connected peers are still listed by gatekeeper
// if not, disconnect
auto pit = peers->begin();
std::shared_ptr<CPeer>peer = nullptr;
while ( (peer = peers->FindNextPeer(EProtocol::urf, pit)) != nullptr )
while (nullptr != (peer = peers->FindNextPeer(EProtocol::urf, pit)))
{
if ( list->FindListItem(peer->GetCallsign()) == nullptr )
if (nullptr == ilmap->FindMapItem(peer->GetCallsign().GetBase()))
{
// send disconnect packet
EncodeDisconnectPacket(&buffer);
@ -307,29 +307,64 @@ void CURFProtocol::HandlePeerLinks(void)
}
}
// check if all ours peers listed by gatekeeper are connected
// check if all ours peers listed by interlink file are connected
// if not, connect or reconnect
for ( auto it=list->begin(); it!=list->end(); it++ )
for ( auto it=ilmap->begin(); it!=ilmap->end(); it++ )
{
const auto cs = it->first;
CCallsign callsign;
callsign.SetCallsign(cs, false);
if ((0 == cs.substr(0, 3).compare("URF")) && (nullptr==peers->FindPeer(callsign, EProtocol::urf)))
{
#ifndef NO_DHT
it->second.UpdateIP(g_Configure.GetString(g_Keys.ip.ipv6address).empty());
if (it->second.GetIp().IsSet())
{
if ( it->GetCallsign().HasSameCallsignWithWildcard(CCallsign("XRF*")) )
continue;
if ( it->GetCallsign().HasSameCallsignWithWildcard(CCallsign("BM*")) )
continue;
CCallsign cs = it->GetCallsign();
if (cs.HasSameCallsignWithWildcard(CCallsign("URF*")) && (nullptr==peers->FindPeer(cs, EProtocol::urf)))
bool ok = true;
// does everything match up?
for (const auto c : it->second.GetModules())
{
// resolve again peer's IP in case it's a dynamic IP
it->ResolveIp();
if (std::string::npos == g_Configure.GetString(g_Keys.modules.modules).find(c))
{ // is the local module not config'ed?
ok = false;
std::cerr << "This reflector has no module '" << c << "', so it can't interlink with " << it->first << std::endl;
}
else if (it->second.UsesDHT())
{
if (std::string::npos == it->second.GetCMods().find(c))
{ // the remote module not config'ed!
ok = false;
std::cerr << it->first << " has no module '" << c << "'" << std::endl;
}
else if ((std::string::npos == it->second.GetTCMods().find(c)) != (std::string::npos == g_Configure.GetString(g_Keys.modules.tcmodules).find(c)))
{ // are the transcoding states on both sides mismatched?
ok = false;
std::cerr << "The transcode states for module '" << c << "' don't match for this reflector and " << it->first << std::endl;
}
}
}
if (ok)
{
#endif
// send connect packet to re-initiate peer link
EncodeConnectPacket(&buffer, it->GetModules());
Send(buffer, it->GetIp(), m_Port);
std::cout << "Sending connect packet to URF peer " << cs << " @ " << it->GetIp() << " for modules " << it->GetModules() << std::endl;
EncodeConnectPacket(&buffer, it->second.GetModules().c_str());
Send(buffer, it->second.GetIp());
std::cout << "Sent connect packet to URF peer " << cs << " @ " << it->second.GetIp() << " for modules " << it->second.GetModules() << std::endl;
#ifndef NO_DHT
}
}
else // m_Ip is not set!
{
g_Reflector.GetDHTConfig(it->first);
}
#endif
}
}
// done
g_Reflector.ReleasePeers();
g_GateKeeper.ReleasePeerList();
g_GateKeeper.ReleaseInterlinkMap();
}
@ -415,10 +450,18 @@ bool CURFProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsi
callsign->CodeIn(Buffer.data()+4);
valid = callsign->IsValid();
*version = CVersion(Buffer.at(37), Buffer.at(38), Buffer.at(39));
if (valid)
{
memcpy(modules, Buffer.data()+10, 27);
for ( unsigned i = 0; i < strlen(modules); i++ )
{
valid = valid && (g_Reflector.IsValidModule (modules[i]));
auto moduleok = g_Reflector.IsValidModule(modules[i]);
if (! moduleok)
{
valid = false;
std::cout << "Requested module '" << modules[i] << "' is not confgured\n";
}
}
}
}
return valid;

@ -34,6 +34,13 @@ public:
// destructor
~CUser() {}
// get
const std::string GetCallsign(void) const { return m_My.GetCS(); }
const std::string GetViaNode(void) const { return m_Rpt1.GetCS(); }
char GetOnModule(void) const { return m_Rpt2.GetCSModule(); }
const std::string GetViaPeer(void) const { return m_Xlx.GetCS(); }
std::time_t GetLastHeardTime(void) const { return m_LastHeardTime; }
// operation
void HeardNow(void) { m_LastHeardTime = time(nullptr); }

@ -40,9 +40,11 @@ public:
int GetSize(void) const { return (int)m_Users.size(); }
void AddUser(const CUser &);
// pass-thru
// pass-through
std::list<CUser>::iterator begin() { return m_Users.begin(); }
std::list<CUser>::iterator end() { return m_Users.end(); }
std::list<CUser>::const_iterator cbegin() { return m_Users.cbegin(); }
std::list<CUser>::const_iterator cend() { return m_Users.cend(); }
// operation
void Hearing(const CCallsign &, const CCallsign &, const CCallsign &);

@ -85,5 +85,8 @@ bool CVersion::operator <(const CVersion &v) const
std::ostream &operator <<(std::ostream &os, const CVersion &v)
{
os << v.GetMajor() << '.' << v.GetMinor() << '.' << v.GetRevision();
#ifndef NO_DHT
os << "-dht";
#endif
return os;
};

@ -38,7 +38,7 @@ public:
// set
void Set(uint8_t, uint8_t, uint8_t);
// comparaison operators
// comparison operators
bool operator ==(const CVersion &v) const;
bool operator !=(const CVersion &v) const;
bool operator >=(const CVersion &v) const;

@ -0,0 +1,89 @@
// Copyright © 2023 Thomas A. Early, N7TAE
//
// ----------------------------------------------------------------------------
// This file is part of urfd.
//
// M17Refd is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// M17Refd is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// with this software. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#pragma once
#include <opendht.h>
/* HELPERS */
#ifndef TO_U_TYPE_DEF
#define TO_U_TYPE_DEF
template<typename E> constexpr auto toUType(E enumerator) noexcept
{
return static_cast<std::underlying_type_t<E>>(enumerator);
} // Item #10 in "Effective Modern C++", by Scott Meyers, O'REILLY
#endif
enum class EUrfdValueID : uint64_t { Config=1, Peers=2, Clients=3, Users=4 };
/* PEERS */
using UrfdPeerTuple = std::tuple<std::string, std::string, std::time_t>;
enum class EUrfdPeerFields { Callsign, Modules, ConnectTime };
struct SUrfdPeers1
{
std::time_t timestamp;
unsigned int sequence;
std::list<UrfdPeerTuple> list;
MSGPACK_DEFINE(timestamp, sequence, list)
};
/* CLIENTS */
using UrfdClientTuple = std::tuple<std::string, std::string, char, std::time_t, std::time_t>;
enum class EUrfdClientFields { Callsign, Ip, Module, ConnectTime, LastHeardTime };
struct SUrfdClients1
{
std::time_t timestamp;
unsigned int sequence;
std::list<UrfdClientTuple> list;
MSGPACK_DEFINE(timestamp, sequence, list)
};
/* USERS */
using UrfdUserTuple = std::tuple<std::string, std::string, char, std::string, std::time_t>;
enum class EUrfdUserFields { Callsign, ViaNode, OnModule, ViaPeer, LastHeardTime };
struct SUrfdUsers1
{
std::time_t timestamp;
unsigned int sequence;
std::list<UrfdUserTuple> list;
MSGPACK_DEFINE(timestamp, sequence, list)
};
/* CONFIGURATION */
// 'SIZE' has to be last for these scoped enums
enum class EUrfdPorts : unsigned { dcs, dextra, dmrplus, dplus, m17, mmdvm, nxdn, p25, urf, ysf, SIZE };
enum class EUrfdAlMod : unsigned { nxdn, p25, ysf, SIZE };
enum class EUrfdTxRx : unsigned { rx, tx, SIZE };
enum class EUrfdRefId : unsigned { nxdn, p25, SIZE };
struct SUrfdConfig1
{
std::time_t timestamp;
std::string cs, ipv4, ipv6, mods, tcmods, url, email, sponsor, country, version;
std::array<uint16_t, toUType(EUrfdPorts::SIZE)> port;
std::array<char, toUType(EUrfdAlMod::SIZE)> almod;
std::array<unsigned long, toUType(EUrfdTxRx::SIZE)> ysffreq;
std::array<unsigned, toUType(EUrfdRefId::SIZE)> refid;
std::unordered_map<char, std::string> description;
bool g3enabled;
MSGPACK_DEFINE(timestamp, cs, ipv4, ipv6, mods, tcmods, url, email, sponsor, country, version, almod, ysffreq, refid, g3enabled, port, description)
};
Loading…
Cancel
Save

Powered by TurnKey Linux.