Merge pull request #3 from n7tae/main

Remote Transcoding
pull/8/head
nostar 1 year ago committed by GitHub
commit cbb5a3362b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,10 +1,10 @@
# Universal, Multi-protocol Digital Voice Reflector
The URF Multiprotocol Gateway Reflector Server, ***urfd***, is part of the software system for a Digital Voice Network. The sources are published under GPL Licenses.
The URF Multi-protocol Gateway Reflector Server, ***urfd***, is part of the software system for a Digital Voice Network. The sources are published under GPL Licenses.
## Introduction
This will build a new kind of digital voice reflector. A *urfd* supports DStar protocols (DPlus, DCS, DExtra and G3) DMR protocols (MMDVMHost, DMR+ and NXDN), M17, YSF, P25 (using IMBE) and USRP (Allstar). A key part of this is the hybrid transcoder, [tcd](https://github.com/n7tae/tcd), which is in a seperate repository. You can't interlink urfd with xlxd. This reflector can be built without a transcoder, but clients will only hear other clients using the same codec. Please note that currently, urfd only supports the tcd transcoder when run locally. As a local device, urfd and tcd uses UNIX DGRAM sockets for interprocess communications. These kernel-base sockets are signifantly faster than conventional UDP/IP sockets. It should be noted that tcd supports DVSI-3003 nad DVSI-3000 devices, which it uses for AMBE vocoding.
This will build a new kind of digital voice reflector. A *urfd* supports DStar protocols (DPlus, DCS, DExtra and G3) DMR protocols (MMDVMHost, DMR+ and NXDN), M17, YSF, P25 (using IMBE) and USRP (Allstar). A key part of this is the hybrid transcoder, [tcd](https://github.com/n7tae/tcd), which is in a separate repository. You can't interlink urfd with xlxd. This reflector can be built without a transcoder, but clients will only hear other clients using the same codec. This version of urfd/tcd now supports ***remote transcoding***. Communication between urfd and tcd is now handled *via* TCP, with *urfd* acting as the server and *tcd* is the client. In this way, it shouldn't be necessary to open any ports on the tcd system. A two-way TCP channel will be opened for each transcoded module. It should be noted that tcd supports DVSI-3003 nad DVSI-3000 devices, which it uses for AMBE vocoding.
This build support *dual-stack* operation, so the server on which it's running, must have both an IPv4 and IPv6 routable address if you are going to configure a dual-stack reflector.
@ -13,18 +13,18 @@ There are many improvements previous multi-mode reflectors:
- Nearly all std::vector containers have been replaced with more appropriate containers.
- No classes are derived from any standard containers.
- For concurancy, *i.e.*, thread management, the standard thread (std::thread) library calls have been replaced with std::future.
- For concurrency, *i.e.*, thread management, the standard thread (std::thread) library calls have been replaced with std::future.
- Managed memory, std::unique_ptr and std::shared_ptr, is used replacing the need for calls to *new* and *delete*.
- Your reflector can be configured with up to 26 modules, *A* through *Z* and as few as one module. For other choices, the configure modules don't have to be contigious. For example, you could configure modules A, B, C and E.
- An integraded P25 Reflector with software imbe vocoder.
- Your reflector can be configured with up to 26 modules, *A* through *Z* and as few as one module. For other choices, the configure modules don't have to be contiguous. For example, you could configure modules A, B, C and E.
- An integrated P25 Reflector with software imbe vocoder.
- An integrated NXDN Reflector
- An inegrated USRP Reflector
- An integrated USRP Reflector
Only systemd-based operating systems are supported. Debian or Ubuntu is recommended. If you want to install this on a non-systemd based OS, you are on your own. Finally, this repository is designed so that you don't have to modify any file in the repository when you build your system. Any file you need to modify to properly configure your reflector will be a file you copy from you locally cloned repo. This makes it easier to update the source code when this repository is updated. Follow the instructions below to build your transcoding URF reflector.
## Usage
The packages which are described in this document are designed to install server software which is used for the D-Star network infrastructure. It requires a 24/7 internet connection which can support up to three transcoded modules and up to 23 more untranscoded modules to connect repeaters and hot-spot dongles!
The packages which are described in this document are designed to install server software which is used for the D-Star network infrastructure. It requires a 24/7 internet connection which can support up to three transcoded modules and up to 23 more untranscoded modules to connect repeaters and hot-spot dongles.
- The server can build a reflector that support IPv4, IPv6 or both (dual stack).
- The public IP addresses should have a DNS record which must be published in the common host files.
@ -33,7 +33,7 @@ The packages which are described in this document are designed to install server
Below are instructions to build a URF reflector. If you are planning on an URF reflector without a transcoder, you can help your users by naming modules with names that suggest which protocol is welcome. You name modules in the config.inc.php file mentioned below.
The transcoder is in a seperate repository, but you will build, install and monitor the transcoder and reflector from two different scripts, *rconfig* and *radmin* in this repository. You *should* look over the README.md file in the tcd repository to understand the transcoder.
For local transcoding reflectors only: The transcoder is in a separate repository, but you can install and monitor the transcoder and reflector from a script, *radmin* in this repository. You *should* look over the README.md file in the tcd repository to understand the transcoder.
### After a clean installation of Debian make sure to run update and upgrade
@ -42,18 +42,23 @@ sudo apt update
sudo apt upgrade
```
### Required packages (some of these will probably already be installed)
### Required packages (some of these may already be installed)
```bash
sudo apt install git
sudo apt install apache2 php5
sudo apt install build-essential
sudo apt install nlohmann-json3-dev
sudo apt install git apache2 php build-essential nlohmann-json3-dev libcurl4-gnutls-dev
```
### DVIN support (optional, but highly recommended)
On the latest systems. *e.g.*, Ubuntu 24, Debian 12, you can install the OpenDHT support without having to build it.
**DVIN**, the Digital Voice Information Network, is implemented using a distributed hash table provided by OpenDHT.
```bash
sudo apt install libopendht-dev
```
If the above command reports that this package is using something earlier that C++17, don't install it. You'll need to build it instead (described in the next section).
### Ham-DHT support (optional, but highly recommended)
**Ham-DHT**, a DHT network for hams, 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:
@ -72,8 +77,6 @@ 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
@ -93,12 +96,12 @@ This will create seven files:
1. The `urfd.mk` file contains compile-time options for *urfd*. If you change the `BINDIR`, you'll need to update how `urfd.service` starts *urfd*.
2. The `urfd.ini` file contains the run-time options for *urfd* and will be discussed below.
3. The `urfd.blacklist` file defines callsigns that are blocked from linking or transmitting.
4. The `urfd.whitelist` file defines callsigns that are allowed to link and transmit. Both of these files support the astrisk as a wild-card. The supplied blacklist and whitelist file are empty, which will allow any callsign to link and transmit, blocking no one. Both files support a limited wildcard feature.
4. The `urfd.whitelist` file defines callsigns that are allowed to link and transmit. Both of these files support the asterisk as a wild-card. The supplied blacklist and whitelist file are empty, which will allow any callsign to link and transmit, blocking no one. Both files support a limited wildcard feature.
5. The `urfd.interlink` file defines possible Brandmeister and URF linking.
6. The `urfd.terminal` file defines operations for Icom's Terminal and Access Point mode, sometimes called *G3*. This protocol requires significantly higher connection resources than any other mode, so it is possible to build a URF reflector without G3 support.
7. The `urfd.service` file is a systemd file that will start and stop *urfd*. Importantly, it contains the only reference to where the *urfd* ini file is located. Be sure to set a fully qualified path to your urfd.ini file on the `ExecStart` line.
You can acutally put the blacklist, whitelist, interlink, terminal and ini file anyplace and even rename them. Just make sure your ini file and service file have the proper, fully-qualified paths. The service file and the mk file need to remain in your `urfd/reflector` directory.
You can actually put the blacklist, whitelist, interlink, terminal and ini file anyplace and even rename them. Just make sure your ini file and service file have the proper, fully-qualified paths. The service file and the mk file need to remain in your `urfd/reflector` directory.
When you are done with the configuration files and ready to start the installation process, you can return to the main repository directory:
@ -115,9 +118,9 @@ After possibly editing `urfd.mk`, you can build your reflector: `make` . Besides
Use your favorite text editor to set your run-time configuration in your copy of `urfd.ini`.
There are only a few things that need to be specified. Most important are, the reflector callsign and the IP addresses for the IPv4 and IPv6 listen ports and a transcoder port, if there is a transcoder. Dual-stack operation is enabled by specifying both an IPv4 and IPv6 address. IPv4-only single stack can be specified by leaving the IPv6 address undefined.
There are only a few things that need to be specified. Most important are, the reflector callsign and the IP addresses for the IPv4 and IPv6 listen ports and a transcoder port, if there is a transcoder. If you don't have a transcoder, be sure to set the transcoding port to zero. If you have a local transcoder, set the binding address to `127.0.0.1` or `::1`. If your transcoder is remote, you will usually set the binding address to `0.0.0.0` or `::`. Dual-stack operation is enabled by specifying both an IPv4 and IPv6 address. IPv4-only single stack can be specified by leaving the IPv6 address undefined.
You can configure any modules, from **A** to **Z**. They don't have to be contigious. If your reflector is configured with a transcoder, you can specify which configured modules will be transcoded. Up to three modules can be transcoded if you have the necessary hardware.
You can configure any modules, from **A** to **Z**. They don't have to be contiguous. If your reflector is configured with a transcoder, you can specify which configured modules will be transcoded. Up to three modules can be transcoded if you have the necessary hardware.
Three protocols, BrandMeister, G3 and USRP should be disabled if you aren't going to use them.
@ -142,20 +145,20 @@ If you want to create listen-only clients, but you don't need a configured read/
### Helper apps
There are two, very useful helper applications, *inicheck* and *dbutil*. Both apps will show you a usage message if you execuate them without any arguments.
There are two, very useful helper applications, *inicheck* and *dbutil*. Both apps will show you a usage message if you execute them without any arguments.
The *inicheck* app will use the exact same code that urfd uses to validate your `urfd.ini` file. Do `./inicheck -q mrefd.ini` to check your infile for errors. If you see any messages containing `ERROR`, that means that *urfd* won't start. You'll have to fix the errors described in the message(s). If you only see messages containing `WARNING`, *urfd* will start, but it may not perform as expected. You will have to decide if the warning should be fixed. If you don't see any messages, it means that your ini file is syntactly correct.
The *inicheck* app will use the exact same code that urfd uses to validate your `urfd.ini` file. Do `./inicheck -q mrefd.ini` to check your infile for errors. If you see any messages containing `ERROR`, that means that *urfd* won't start. You'll have to fix the errors described in the message(s). If you only see messages containing `WARNING`, *urfd* will start, but it may not perform as expected. You will have to decide if the warning should be fixed. If you don't see any messages, it means that your ini file is syntactically correct.
The *dbutil* app can be used for serveral tasks relating to the three databases that *urfd* uses. The usage is: `./dbutil DATABASE SOURCE ACTION INIFILE`, where:
The *dbutil* app can be used for several tasks relating to the three databases that *urfd* uses. The usage is: `./dbutil DATABASE SOURCE ACTION INIFILE`, where:
- DATABASE is "dmr", "nxdn" or "ysf"
- SOURCE is "html" or "file"
- ACTION is "parse" or "errors"
- INIFLILE is the path to the infile that defines the location of the http and file sources for these three databases.
- INIFILE is the path to the infile that defines the location of the http and file sources for these three databases.
One at a time, *dbutil* can work with any of the three DATABASEs. It can read either the http or the file SOURCE. It can either show you the data entries that are syntactically correct or incorrect (ACTION).
### Installing your system
### Installing your system with a local transcoder
After you have written your configutation files, you can install your system:
After you have written your configuration files, you can install your system:
```bash
./radmin
@ -163,14 +166,13 @@ After you have written your configutation files, you can install your system:
You can use this interactive shell script to install and uninstall your system. This can also perform other tasks like restarting the reflector or transcoder process, or be used to view the reflector or transcoder log in real time.
### Stoping and starting the services manually
### Installing your system with a remote transcoder
```bash
sudo systemctl stop urfd # (or xrfd)
sudo systemctl stop tcd
```
You can start each component by replacing `stop` with `start`, or you can restart each by using `restart`.
In the `reflector` subdirectory, use:
- `make` to compile *urfd*.
- `sudo install` to install and start *urfd*.
- `systemctl` and `journalctl` can be used to manage *urfd* and view the log.
- `sudo make uninstall` will uninstall *urfd*.
### Copy dashboard to /var/www
@ -195,6 +197,7 @@ TCP port 80 (http) optional TCP port 443 (https)
UDP port 8880 (DMR+ DMO mode)
UDP port 10002 (BM connection)
UDP port 10017 (URF interlinking)
TCP port 10100 (Transcoder listening port)
UDP port 12345 - 12346 (G3 Icom Terminal presence and request port)
UDP port 17000 (M17 protocol)
UPD port 20001 (DPlus protocol)
@ -211,15 +214,10 @@ UDP port 62030 (MMDVM protocol)
## YSF Master Server
Pay attention, the URF Server acts as an YSF Master, which provides 26 wires-x rooms.
It has nothing to do with the regular YSFReflector network, hence you dont need to register your URF at ysfreflector.de !
## To-dos
I will eventually support a remote transcoder option, so that you can, for example, run *urfd* in a data center, and then run the transcoder somewhere you have physical access so you can plug in your AMBE vocoders. I don't recommend this as it will add unnessary and variable latency to your reflector.
A new dashboard is on the to-do list!
It has nothing to do with the regular YSFReflector network, hence you dont need to register your URF at ysfreflector.de!
## Copyright
- Copyright © 2016 Jean-Luc Deltombe LX3JL and Luc Engelmann LX1IQ
- Copyright © 2022 Doug McLain AD8DP and Thomas A. Early N7TAE
- Copyright © 2024 Thomas A. Early N7TAE

@ -3,7 +3,7 @@
# Do not use quotes, unless in a comment!
[Names]
Callsign = URF??? # where ? is A-Z or 0-9
Callsign = URF??? # where ? is A-Z or 0-9. NO EXCEPTIONS!
SysopEmail = me@somewhere.com
@ -33,12 +33,9 @@ IPv4Binding = 0.0.0.0
# define if you want to override what urfd finds using ipv6.icanhazip.com
# IPv6External = f:e:d:c:b:a:9:0
Transcoder = local # SORRY, but only local TC's are supported right now!
[Modules]
# Modules = ABCDEFGHIJKLMNOPQRSTUVWXYZ
Modules = ADMSZ
Transcoded = A # comment out if you don't have transcoding hardware
# Create Descriptions as needed...
DescriptionA = Transcoded
DescriptionD = DMR Chat
@ -46,6 +43,12 @@ DescriptionM = M17 Chat
DescriptionS = DStar Chat
DescriptionZ = Temp Meeting
[Transcoder]
Port = 10100 # TCP listening port for connection(s), set to 0 if there is no transcoder, then other two values will be ignored
BindingAddress = 127.0.0.1 # or ::1, the IPv4 or IPv6 "loop-back" address for a local transcoder
# For a connection to a remote transcoder, usually use the "any" address: 0.0.0.0 or ::
Modules = A # Transcoded modules one or three modules, depending on the hardware
# Protocols
[Brandmeister]
Enable = false # Set to true if you've configured BM connections in your urfd.interlink file.

@ -0,0 +1,6 @@
RewriteEngine On
RewriteBase /json/
RewriteRule ^index\\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /json/index.php [L]

@ -0,0 +1,61 @@
<?php
header("Access-Control-Allow-Origin: *");
header('Content-Type: application/json');
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pgs/functions.php")) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/functions.php");
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/pgs/config.inc.php")) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/config.inc.php");
if (!class_exists('ParseXML')) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/class.parsexml.php");
if (!class_exists('Node')) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/class.node.php");
if (!class_exists('xReflector')) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/class.reflector.php");
if (!class_exists('Station')) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/class.station.php");
if (!class_exists('Peer')) require_once($_SERVER['DOCUMENT_ROOT'] . "/pgs/class.peer.php");
$Reflector = new xReflector();
$Reflector->SetXMLFile($Service['XMLFile']);
$Reflector->SetPIDFile($Service['PIDFile']);
$Reflector->LoadXML();
$Reflector->SetFlagFile($_SERVER['DOCUMENT_ROOT'] . "/pgs/country.csv");
$Reflector->LoadFlags();
$Request = $_SERVER['REQUEST_URI'];
$ViewDir = '/views/';
switch ($Request) {
case '/json/links':
require __DIR__ . $ViewDir . 'links.php';
break;
case '/json/metadata':
require __DIR__ . $ViewDir . 'metadata.php';
break;
case '/json/modulesinuse':
require __DIR__ . $ViewDir . 'modulesinuse.php';
break;
case '/json/peers':
require __DIR__ . $ViewDir . 'peers.php';
break;
case '/json/reflector':
require __DIR__ . $ViewDir . 'reflector.php';
break;
case '/json/stations':
require __DIR__ . $ViewDir . 'stations.php';
break;
case '/json/status':
require __DIR__ . $ViewDir . 'status.php';
break;
default:
header('Content-Type: text/plain');
http_response_code(404);
echo('404 page not found');
}
?>

@ -0,0 +1,24 @@
<?php
/*** Add links to payload ***/
for ($i=0;$i<$Reflector->NodeCount();$i++) {
// craft payload array
$payload[$i] = array(
'callsign' => $Reflector->Nodes[$i]->GetCallSign() . ' ' . $Reflector->Nodes[$i]->GetSuffix(),
'ip' => $Reflector->Nodes[$i]->GetIP(),
'linkedmodule' => $Reflector->Nodes[$i]->GetLinkedModule(),
'protocol' => $Reflector->Nodes[$i]->GetProtocol(),
'connecttime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Nodes[$i]->GetConnectTime()),
'lastheardtime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Nodes[$i]->GetLastHeardTime())
);
}
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -0,0 +1,23 @@
<?php
$Url = @parse_url($CallingHome['MyDashBoardURL']);
$Net4 = @dns_get_record($Url['host'], DNS_A);
$Net6 = @dns_get_record($Url['host'], DNS_AAAA);
/*** add metadata to payload ***/
$payload = array(
'dashboard_version' => $PageOptions['DashboardVersion'],
'ipV4' => @$Net4[0]['ip'],
'ipV6' => @$Net6[0]['ipv6'],
'reflector_callsign' => str_replace("XLX", "URF", $Reflector->GetReflectorName()),
'reflector_version' => $Reflector->GetVersion(),
'sysop_email' => $PageOptions['ContactEmail']
);
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -0,0 +1,30 @@
<?php
$Modules = $Reflector->GetModules();
sort($Modules, SORT_STRING);
/*** Add modules to payload ***/
for ($i=0;$i<count($Modules);$i++) {
$payload[$i] = array(
'name' => $Modules[$i]
);
$Users = $Reflector->GetNodesInModulesByID($Modules[$i]);
for ($j=0;$j<count($Users);$j++) {
$payload[$i]['callsigns'][$j] = $Reflector->GetCallsignAndSuffixByID($Users[$j]);
}
}
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -0,0 +1,22 @@
<?php
/*** Add links to payload ***/
for ($i=0;$i<$Reflector->PeerCount();$i++) {
$payload[$i] = array(
'callsign' => $Reflector->Peers[$i]->GetCallSign(),
'ip' => $Reflector->Peers[$i]->GetIP(),
'linkedmodule' => $Reflector->Peers[$i]->GetLinkedModule(),
'connecttime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Peers[$i]->GetConnectTime()),
'lastheardtime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Peers[$i]->GetLastHeardTime())
);
}
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -0,0 +1,70 @@
<?php
$Status = (file_exists($Service['PIDFile'])) ? 'up' : 'down';
$payload = array(
'lastupdatechecktime' => date('Y-m-d\TH:i:sp', time()),
'status' => $Status,
'uptime' => $Reflector->GetServiceUptime()
);
/*** add data to payload ***/
$payload['data'] = array(
'filetime' => date('Y-m-d\TH:i:sp', filemtime($Service['XMLFile'])),
'callsign' => str_replace("XLX", "URF", $Reflector->GetReflectorName()),
'version' => $Reflector->GetVersion()
);
/*** Add peers to payload ***/
for ($i=0;$i<$Reflector->PeerCount();$i++) {
$payload['data']['peers'][] = array(
'callsign' => $Reflector->Peers[$i]->GetCallSign(),
'ip' => $Reflector->Peers[$i]->GetIP(),
'linkedmodule' => $Reflector->Peers[$i]->GetLinkedModule(),
'connecttime' => date('Y-m-d\TH:i:sp', $Reflector->Peers[$i]->GetConnectTime()),
'lastheardtime' => date('Y-m-d\TH:i:sp', $Reflector->Peers[$i]->GetLastHeardTime())
);
}
/*** Add nodes to payload ***/
for ($i=0;$i<$Reflector->NodeCount();$i++) {
// craft payload array
$payload['data']['nodes'][] = array(
'callsign' => $Reflector->Nodes[$i]->GetCallSign() . ' ' . $Reflector->Nodes[$i]->GetSuffix(),
'ip' => $Reflector->Nodes[$i]->GetIP(),
'linkedmodule' => $Reflector->Nodes[$i]->GetLinkedModule(),
'protocol' => $Reflector->Nodes[$i]->GetProtocol(),
'connecttime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Nodes[$i]->GetConnectTime()),
'lastheardtime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Nodes[$i]->GetLastHeardTime())
);
}
/*** Add stations to payload ***/
for ($i=0;$i<$Reflector->StationCount();$i++) {
// craft payload array
$payload['data']['stations'][] = array(
'callsign' => $Reflector->Stations[$i]->GetCallSign(),
'vianode' => $Reflector->Stations[$i]->GetVia(),
'onmodule' => $Reflector->Stations[$i]->GetModule(),
'viapeer' => $Reflector->Stations[$i]->GetPeer(),
'lastheardtime' => gmdate('Y-m-d\TH:i:sp', $Reflector->Stations[$i]->GetLastHeardTime())
);
}
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -0,0 +1,36 @@
<?php
/*** Add stations to payload ***/
for ($i=0;$i<$Reflector->StationCount();$i++) {
$tmp = preg_split('/\s+/', $Reflector->Stations[$i]->GetCallSign(), -1, PREG_SPLIT_NO_EMPTY);
$Callsign = $tmp[0];
$tmp = preg_split('/\s+/', $Reflector->Stations[$i]->GetVia(), -1, PREG_SPLIT_NO_EMPTY);
$CallsignSuffix = $tmp[1];
// craft payload array
$payload['stations'][$i] = array(
'callsign' => $Callsign,
'callsignsuffix' => $CallsignSuffix,
'vianode' => $Reflector->Stations[$i]->GetVia(),
'onmodule' => $Reflector->Stations[$i]->GetModule(),
'lastheard' => gmdate('Y-m-d\TH:i:sp', $Reflector->Stations[$i]->GetLastHeardTime())
);
list ($CountryCode, $Country) = $Reflector->GetFlag($Reflector->Stations[$i]->GetCallSign());
$payload['stations'][$i]['country'] = array (
'country' => $Country,
'countrycode' => $CountryCode
);
}
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -0,0 +1,18 @@
<?php
$ReflectorStatus = (file_exists($Service['PIDFile'])) ? 'up' : 'down';
$payload = array(
'lastupdate' => gmdate('U', time()),
'lasturfdupdate' => gmdate('U', filemtime($Service['XMLFile'])),
'reflectorstatus' => $ReflectorStatus,
'reflectoruptimeseconds' => $Reflector->GetServiceUptime()
);
// json encode payload array
$records = json_encode($payload);
echo $records;
?>

@ -1,7 +1,7 @@
<?php
class xReflector {
public $Nodes = null;
public $Stations = null;
public $Peers = null;
@ -27,7 +27,7 @@ class xReflector {
public $Interlinks = null;
private $InterlinkXML = null;
private $ReflectorXML = null;
public function __construct() {
$this->Nodes = array();
$this->Stations = array();
@ -35,61 +35,61 @@ class xReflector {
$this->Interlinks = array();
$this->Transferinterlink = false;
}
public function LoadXML() {
if ($this->XMLFile != null) {
$handle = fopen($this->XMLFile, 'r');
$this->XMLContent = fread($handle, filesize($this->XMLFile));
fclose($handle);
$this->ServiceName = substr($this->XMLContent, strpos($this->XMLContent, "<XLX")+4, 3);
if (!is_numeric($this->ServiceName)) {
if (preg_match('/[^a-zA-Z0-9]/', $this->ServiceName) == 1) {
$this->ServiceName = null;
return false;
}
$this->ReflectorName = "XLX".$this->ServiceName;
$LinkedPeersName = "XLX".$this->ServiceName." linked peers";
$LinkedNodesName = "XLX".$this->ServiceName." linked nodes";
$LinkedUsersName = "XLX".$this->ServiceName." heard users";
$XML = new ParseXML();
$AllNodesString = $XML->GetElement($this->XMLContent, $LinkedNodesName);
$tmpNodes = $XML->GetAllElements($AllNodesString, "NODE");
for ($i=0;$i<count($tmpNodes);$i++) {
$Node = new Node($XML->GetElement($tmpNodes[$i], 'Callsign'), $XML->GetElement($tmpNodes[$i], 'IP'), $XML->GetElement($tmpNodes[$i], 'LinkedModule'), $XML->GetElement($tmpNodes[$i], 'Protocol'), $XML->GetElement($tmpNodes[$i], 'ConnectTime'), $XML->GetElement($tmpNodes[$i], 'LastHeardTime'), CreateCode(16));
$this->AddNode($Node);
}
$AllStationsString = $XML->GetElement($this->XMLContent, $LinkedUsersName);
$tmpStations = $XML->GetAllElements($AllStationsString, "STATION");
for ($i=0;$i<count($tmpStations);$i++) {
$Station = new Station($XML->GetElement($tmpStations[$i], 'Callsign'), $XML->GetElement($tmpStations[$i], 'Via node'), $XML->GetElement($tmpStations[$i], 'Via peer'), $XML->GetElement($tmpStations[$i], 'LastHeardTime'), $XML->GetElement($tmpStations[$i], 'On module'));
$this->AddStation($Station, false);
}
$AllPeersString = $XML->GetElement($this->XMLContent, $LinkedPeersName);
$tmpPeers = $XML->GetAllElements($AllPeersString, "PEER");
for ($i=0;$i<count($tmpPeers);$i++) {
$Peer = new Peer($XML->GetElement($tmpPeers[$i], 'Callsign'), $XML->GetElement($tmpPeers[$i], 'IP'), $XML->GetElement($tmpPeers[$i], 'LinkedModule'), $XML->GetElement($tmpPeers[$i], 'Protocol'), $XML->GetElement($tmpPeers[$i], 'ConnectTime'), $XML->GetElement($tmpPeers[$i], 'LastHeardTime'));
$this->AddPeer($Peer, false);
}
$this->Version = $XML->GetElement($this->XMLContent, "Version");
$this->Version = $XML->GetElement($this->XMLContent, "Version");
}
}
public function GetVersion() {
return $this->Version;
}
public function GetReflectorName() {
return $this->ReflectorName;
}
public function SetXMLFile($XMLFile) {
if (file_exists($XMLFile) && (is_readable($XMLFile))) {
$this->XMLFile = $XMLFile;
@ -99,7 +99,7 @@ class xReflector {
$this->XMLContent = null;
}
}
public function SetPIDFile($ProcessIDFile) {
if (file_exists($ProcessIDFile)) {
$this->ProcessIDFile = $ProcessIDFile;
@ -110,11 +110,11 @@ class xReflector {
$this->ServiceUptime = null;
}
}
public function GetServiceUptime() {
return $this->ServiceUptime;
}
public function SetFlagFile($Flagfile) {
if (file_exists($Flagfile) && (is_readable($Flagfile))) {
$this->Flagfile = $Flagfile;
@ -122,7 +122,7 @@ class xReflector {
}
return false;
}
public function LoadFlags() {
if ($this->Flagfile != null) {
$this->Flagarray = array();
@ -133,18 +133,18 @@ class xReflector {
while(!feof($handle)) {
$row = fgets($handle,1024);
$tmp = explode(";", $row);
if (isset($tmp[0])) { $this->Flagarray[$i]['Country'] = $tmp[0]; } else { $this->Flagarray[$i]['Country'] = 'Undefined'; }
if (isset($tmp[1])) { $this->Flagarray[$i]['ISO'] = $tmp[1]; } else { $this->Flagarray[$i]['ISO'] = "Undefined"; }
//$this->Flagarray[$i]['DXCC'] = array();
if (isset($tmp[2])) {
if (isset($tmp[2])) {
$tmp2 = explode("-", $tmp[2]);
for ($j=0;$j<count($tmp2);$j++) {
//$this->Flagarray[$i]['DXCC'][] = $tmp2[$j];
$this->Flagarray_DXCC[ trim($tmp2[$j]) ] = $i;
}
}
$i++;
$i++;
}
fclose($handle);
}
@ -152,17 +152,17 @@ class xReflector {
}
return false;
}
public function AddNode($NodeObject) {
if (is_object($NodeObject)) {
$this->Nodes[] = $NodeObject;
}
}
public function NodeCount() {
return count($this->Nodes);
}
public function GetNode($ArrayIndex) {
if (isset($this->Nodes[$ArrayIndex])) {
return $this->Nodes[$ArrayIndex];
@ -175,11 +175,11 @@ class xReflector {
$this->Peers[] = $PeerObject;
}
}
public function PeerCount() {
return count($this->Peers);
}
public function GetPeer($ArrayIndex) {
if (isset($this->Peer[$ArrayIndex])) {
return $this->Peer[$ArrayIndex];
@ -189,34 +189,34 @@ class xReflector {
public function AddStation($StationObject, $AllowDouble = false) {
if (is_object($StationObject)) {
if ($AllowDouble) {
$this->Stations[] = $StationObject;
}
else {
$FoundStationInList = false;
$i = 0;
$tmp = explode(" ", $StationObject->GetCallsign());
$RealCallsign = trim($tmp[0]);
while (!$FoundStationInList && $i<$this->StationCount()) {
if ($this->Stations[$i]->GetCallsignOnly() == $RealCallsign) {
$FoundStationInList = true;
}
$i++;
}
if (!$FoundStationInList) {
if (strlen(trim($RealCallsign)) > 3) {
$this->Stations[] = $StationObject;
}
}
}
}
}
public function GetSuffixOfRepeater($Repeater, $LinkedModul, $StartWithIndex = 0) {
$suffix = "";
$found = false;
@ -232,7 +232,7 @@ class xReflector {
}
return $suffix;
}
public function GetCallsignAndSuffixByID($RandomId) {
$suffix = "";
$callsign = "";
@ -250,18 +250,18 @@ class xReflector {
}
return 'N/A';
}
public function StationCount() {
return count($this->Stations);
}
public function GetStation($ArrayIndex) {
if (isset($this->Stations[$ArrayIndex])) {
return $this->Stations[$ArrayIndex];
}
return false;
}
public function GetFlag($Callsign) {
$Image = "";
$Letters = 4;
@ -277,7 +277,7 @@ class xReflector {
}
return array(strtolower($Image), $Name);
}
public function GetModules() {
$out = array();
for ($i=0;$i<$this->NodeCount();$i++) {
@ -295,7 +295,7 @@ class xReflector {
}
return $out;
}
public function GetModuleOfNode($Node) {
die("FUNCTION DEPRECATED...");
$Node = trim(str_replace(" ", "-", $Node));
@ -305,38 +305,38 @@ class xReflector {
$i = 0;
$Module = "";
while (!$found && $i<$this->NodeCount()) {
if (strpos($Node, $this->Nodes[$i]->GetFullCallsign()) !== false) {
$Module = $this->Nodes[$i]->GetLinkedModule();
if (strpos($Node, $this->Nodes[$i]->GetFullCallsign()) !== false) {
$Module = $this->Nodes[$i]->GetLinkedModule();
$found = true;
}
$i++;
}
return $Module;
}
}
public function GetCallSignsInModules($Module) {
$out = array();
for ($i=0;$i<$this->NodeCount();$i++) {
if ($this->Nodes[$i]->GetLinkedModule() == $Module) {
$out[] = $this->Nodes[$i]->GetCallsign();
}
}
}
return $out;
}
public function GetNodesInModulesById($Module) {
$out = array();
for ($i=0;$i<$this->NodeCount();$i++) {
if ($this->Nodes[$i]->GetLinkedModule() == $Module) {
$out[] = $this->Nodes[$i]->GetRandomID();
}
}
}
return $out;
}
public function SetCallingHome($CallingHomeVariables, $Hash) {
if (!isset($CallingHomeVariables['Active'])) { $CallingHomeVariables['Active'] = false; }
if (!isset($CallingHomeVariables['MyDashBoardURL'])) { $CallingHomeVariables['MyDashBoardURL'] = ''; }
if (!isset($CallingHomeVariables['ServerURL'])) { $CallingHomeVariables['ServerURL'] = ''; }
@ -344,16 +344,16 @@ class xReflector {
if (!isset($CallingHomeVariables['Comment'])) { $CallingHomeVariables['Comment'] = ''; }
if (!isset($CallingHomeVariables['OverrideIPAddress'])) { $CallingHomeVariables['OverrideIPAddress'] = false; }
if (!isset($CallingHomeVariables['InterlinkFile'])) { $CallingHomeVariables['InterlinkFile'] = ''; }
if (!file_exists($CallingHomeVariables['InterlinkFile'])) {
$this->Interlinkfile = '';
if (!file_exists($CallingHomeVariables['InterlinkFile'])) {
$this->Interlinkfile = '';
$this->Transferinterlink = false;
}
else {
$this->Transferinterlink = true;
$this->Interlinkfile = $CallingHomeVariables['InterlinkFile'];
}
$this->CallingHomeActive = ($CallingHomeVariables['Active'] === true);
$this->CallingHomeHash = $Hash;
$this->CallingHomeDashboardURL = $CallingHomeVariables['MyDashBoardURL'];
@ -361,13 +361,13 @@ class xReflector {
$this->CallingHomeCountry = $CallingHomeVariables['Country'];
$this->CallingHomeComment = $CallingHomeVariables['Comment'];
$this->CallingHomeOverrideIP = $CallingHomeVariables['OverrideIPAddress'];
}
public function PushCallingHome() {
$CallingHome = @fopen($this->CallingHomeServerURL."?ReflectorName=".$this->ReflectorName."&ReflectorUptime=".$this->ServiceUptime."&ReflectorHash=".$this->CallingHomeHash."&DashboardURL=".$this->CallingHomeDashboardURL."&Country=".urlencode($this->CallingHomeCountry)."&Comment=".urlencode($this->CallingHomeComment)."&OverrideIP=".$this->CallingHomeOverrideIP, "r");
}
}
public function ReadInterlinkFile() {
if (file_exists($this->Interlinkfile) && (is_readable($this->Interlinkfile))) {
$this->Interlinks = array();
@ -379,7 +379,7 @@ class xReflector {
$this->Interlinks[] = new Interlink();
if (isset($Interlink[0])) { $this->Interlinks[count($this->Interlinks)-1]->SetName(trim($Interlink[0])); }
if (isset($Interlink[1])) { $this->Interlinks[count($this->Interlinks)-1]->SetAddress(trim($Interlink[1])); }
if (isset($Interlink[2])) {
if (isset($Interlink[2])) {
$Modules = str_split(trim($Interlink[2]), 1);
for ($j=0;$j<count($Modules);$j++) {
$this->Interlinks[count($this->Interlinks)-1]->AddModule($Modules[$j]);
@ -391,7 +391,7 @@ class xReflector {
}
return false;
}
public function PrepareInterlinkXML() {
$xml = '
<interlinks>';
@ -407,7 +407,7 @@ class xReflector {
</interlinks>';
$this->InterlinkXML = $xml;
}
public function PrepareReflectorXML() {
$this->ReflectorXML = '
<reflector>
@ -421,28 +421,28 @@ class xReflector {
<reflectorversion>'.$this->Version.'</reflectorversion>
</reflector>';
}
public function CallHome() {
$xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<query>CallingHome</query>'.$this->ReflectorXML.$this->InterlinkXML;
$p = @stream_context_create(array('http' => array('header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'method' => 'POST',
'content' => http_build_query(array('xml' => $xml)) )));
$result = @file_get_contents($this->CallingHomeServerURL, false, $p);
if ($result === false) {
die("CONNECTION FAILED!");
}
}
public function InterlinkCount() {
return count($this->Interlinks);
}
public function GetInterlink($Index) {
if (isset($this->Interlinks[$Index])) return $this->Interlinks[$Index];
return array();
}
public function IsInterlinked($Reflectorname) {
$i = -1;
$f = false;
@ -457,7 +457,7 @@ class xReflector {
}
return -1;
}
}
?>

@ -16,7 +16,7 @@ $PageOptions = array();
$PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address
$PageOptions['DashboardVersion'] = '2.4.1'; // Dashboard Version
$PageOptions['DashboardVersion'] = '2.5.0'; // Dashboard Version
$PageOptions['PageRefreshActive'] = true; // Activate automatic refresh
$PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds

@ -27,7 +27,7 @@
bool CBMProtocol::Initialize(const char *type, const EProtocol ptype, const uint16_t port, const bool has_ipv4, const bool has_ipv6)
{
m_HasTranscoder = g_Configure.IsString(g_Keys.modules.tcmodules);
m_HasTranscoder = (0 != g_Configure.GetUnsigned(g_Keys.tc.port));
if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
return false;

@ -20,6 +20,7 @@
#include <vector>
#include <fstream>
#include <cstdint>
////////////////////////////////////////////////////////////////////////////////////////

@ -63,8 +63,6 @@ void CClients::AddClient(std::shared_ptr<CClient> client)
std::cout << " on module " << client->GetReflectorModule();
}
std::cout << std::endl;
// notify
g_Reflector.OnClientsChanged();
}
void CClients::RemoveClient(std::shared_ptr<CClient> client)
@ -87,8 +85,6 @@ void CClients::RemoveClient(std::shared_ptr<CClient> client)
}
std::cout << std::endl;
m_Clients.erase(it);
// notify
g_Reflector.OnClientsChanged();
break;
}
}

@ -18,9 +18,12 @@
#include <string.h>
#include "Global.h"
#include "DVFramePacket.h"
#include "PacketStream.h"
#include "CodecStream.h"
#include "Reflector.h"
////////////////////////////////////////////////////////////////////////////////////////
// constructor
@ -42,7 +45,6 @@ CCodecStream::~CCodecStream()
m_Future.get();
}
// and close the socket
m_TCReader.Close();
}
void CCodecStream::ResetStats(uint16_t streamid, ECodecType type)
@ -80,12 +82,6 @@ void CCodecStream::ReportStats()
bool CCodecStream::InitCodecStream()
{
m_TCWriter.SetUp(REF2TC);
std::string name(TC2REF);
name.append(1, m_CSModule);
if (m_TCReader.Open(name.c_str()))
return true;
std::cout << "Initialized CodecStream receive socket " << name << std::endl;
keep_running = true;
try
{
@ -94,7 +90,6 @@ bool CCodecStream::InitCodecStream()
catch(const std::exception& e)
{
std::cerr << "Could not start Codec processing on module '" << m_CSModule << "': " << e.what() << std::endl;
m_TCReader.Close();
return true;
}
return false;
@ -114,27 +109,8 @@ void CCodecStream::Thread()
void CCodecStream::Task(void)
{
STCPacket pack;
// any packet from transcoder
if (m_TCReader.Receive(&pack, 5))
if (g_TCServer.Receive(m_CSModule, &pack, 8))
{
// update statistics
double rt = pack.rt_timer.time(); // the round-trip time
if (0 == m_RTCount)
{
m_RTMin = rt;
m_RTMax = rt;
}
else
{
if (rt < m_RTMin)
m_RTMin = rt;
else if (rt > m_RTMax)
m_RTMax = rt;
}
m_RTSum += rt;
m_RTCount++;
if ( m_LocalQueue.IsEmpty() )
{
std::cout << "Unexpected transcoded packet received from transcoder: Module='" << pack.module << "' StreamID=" << std::hex << std::showbase << ntohs(pack.streamid) << std::endl;
@ -143,40 +119,60 @@ void CCodecStream::Task(void)
{
// pop the original packet
auto Packet = m_LocalQueue.Pop();
auto Frame = (CDvFramePacket *)Packet.get();
// do things look okay?
if (pack.module != m_CSModule)
std::cerr << "CodecStream '" << m_CSModule << "' received a transcoded packet from module '" << pack.module << "'" << std::dec << std::noshowbase << std::endl;
if (pack.sequence != Frame->GetCodecPacket()->sequence)
std::cerr << "Sequence mismatch: this voice frame=" << Frame->GetCodecPacket()->sequence << " returned transcoder packet=" << pack.sequence << std::endl;
if (pack.streamid != Frame->GetCodecPacket()->streamid)
std::cerr << std::hex << std::showbase << "StreamID mismatch: this voice frame=" << ntohs(Frame->GetCodecPacket()->streamid) << " returned transcoder packet=" << ntohs(pack.streamid) << std::dec << std::noshowbase << std::endl;
// update content with transcoded data
Frame->SetCodecData(&pack);
// mark the DStar sync frames if the source isn't dstar
if (ECodecType::dstar!=Frame->GetCodecIn() && 0==Frame->GetPacketId()%21)
// make sure this is the correct packet
if ((pack.streamid == Packet->GetCodecPacket()->streamid) && (pack.sequence == Packet->GetCodecPacket()->sequence))
{
const uint8_t DStarSync[] = { 0x55, 0x2D, 0x16 };
Frame->SetDvData(DStarSync);
// update statistics
auto rt =Packet->m_rtTimer.time(); // the round-trip time
if (0 == m_RTCount)
{
m_RTMin = rt;
m_RTMax = rt;
}
else
{
if (rt < m_RTMin)
m_RTMin = rt;
else if (rt > m_RTMax)
m_RTMax = rt;
}
m_RTSum += rt;
m_RTCount++;
// update content with transcoded data
Packet->SetCodecData(&pack);
// mark the DStar sync frames if the source isn't dstar
if (ECodecType::dstar!=Packet->GetCodecIn() && 0==Packet->GetPacketId()%21)
{
const uint8_t DStarSync[] = { 0x55, 0x2D, 0x16 };
Packet->SetDvData(DStarSync);
}
// and push it back to client
m_PacketStream->ReturnPacket(std::move(Packet));
}
else
{
// Not the correct packet! It will be ignored
// Report it
if (pack.streamid != Packet->GetCodecPacket()->streamid)
std::cerr << std::hex << std::showbase << "StreamID mismatch: this voice frame=" << ntohs(Packet->GetCodecPacket()->streamid) << " returned transcoder packet=" << ntohs(pack.streamid) << std::dec << std::noshowbase << std::endl;
if (pack.sequence != Packet->GetCodecPacket()->sequence)
std::cerr << "Sequence mismatch: this voice frame=" << Packet->GetCodecPacket()->sequence << " returned transcoder packet=" << pack.sequence << std::endl;
}
// and push it back to client
m_PacketStream->ReturnPacket(std::move(Packet));
}
else
{
// Likewise, this packet will be ignored
std::cout << "Transcoder packet received but CodecStream[" << m_CSModule << "] is closed: Module='" << pack.module << "' StreamID=" << std::hex << std::showbase << ntohs(pack.streamid) << std::endl;
}
}
// anything in our queue
auto Packet = m_Queue.Pop();
while (Packet)
// anything in our queue, then get it to the transcoder!
while (! m_Queue.IsEmpty())
{
// we need a CDvFramePacket pointer to access Frame stuff
auto Frame = (CDvFramePacket *)Packet.get();
auto &Frame = m_Queue.Front();
if (m_IsOpen)
{
@ -185,13 +181,24 @@ void CCodecStream::Task(void)
Frame->SetTCParams(m_uiTotalPackets++);
// now send to transcoder
m_TCWriter.Send(Frame->GetCodecPacket());
int fd = g_TCServer.GetFD(Frame->GetCodecPacket()->module);
if (fd < 0)
{
// Crap! We've lost connection to the transcoder!
// we'll try to fix this on the next pass
return;
}
// push to our local queue where it can wait for the transcoder
m_LocalQueue.Push(std::move(Packet));
}
Frame->m_rtTimer.start(); // start the round-trip timer
if (g_TCServer.Send(Frame->GetCodecPacket()))
{
// ditto, we'll try to fix this on the next pass
return;
}
// the fd was good and then the send was successful, so...
// push the frame to our local queue where it can wait for the transcoder
// get the next packet, if there is one
Packet = m_Queue.Pop();
m_LocalQueue.Push(std::move(m_Queue.Pop()));
}
}
}

@ -22,7 +22,6 @@
#include <future>
#include "DVFramePacket.h"
#include "UnixDgramSocket.h"
#include "SafePacketQueue.h"
////////////////////////////////////////////////////////////////////////////////////////
@ -51,7 +50,7 @@ public:
void Task(void);
// pass-through
void Push(std::unique_ptr<CPacket> p) { m_Queue.Push(std::move(p)); }
void Push(std::unique_ptr<CDvFramePacket> p) { m_Queue.Push(std::move(p)); }
protected:
// identity
@ -64,15 +63,11 @@ protected:
uint8_t m_uiPid;
ECodecType m_eCodecIn;
// sockets
CUnixDgramReader m_TCReader;
CUnixDgramWriter m_TCWriter;
// associated packet stream
CPacketStream *m_PacketStream;
// queues
CSafePacketQueue<std::unique_ptr<CPacket>> m_LocalQueue, m_Queue;
CSafePacketQueue<std::unique_ptr<CDvFramePacket>> m_LocalQueue, m_Queue;
// thread
std::atomic<bool> keep_running;

@ -32,6 +32,7 @@
// ini file keywords
#define JAUTOLINKMODULE "AutoLinkModule"
#define JBINDINGADDRESS "BindingAddress"
#define JBLACKLISTPATH "BlacklistPath"
#define JBOOTSTRAP "Bootstrap"
#define JBRANDMEISTER "Brandmeister"
@ -79,7 +80,6 @@
#define JRXPORT "RxPort"
#define JSPONSOR "Sponsor"
#define JSYSOPEMAIL "SysopEmail"
#define JTRANSCODED "Transcoded"
#define JTRANSCODER "Transcoder"
#define JTXPORT "TxPort"
#define JURF "URF"
@ -131,6 +131,7 @@ bool CConfigure::ReadData(const std::string &path)
ESection section = ESection::none;
counter = 0;
SJsonKeys::DB *pdb;
unsigned tcport = 0;
//data.ysfalmodule = 0;
//data.DPlusPort = data.DCSPort = data.DExtraPort = data.BMPort = data.DMRPlusPort = 0;
@ -180,6 +181,8 @@ bool CConfigure::ReadData(const std::string &path)
section = ESection::names;
else if (0 == hname.compare(JIPADDRESSES))
section = ESection::ip;
else if (0 == hname.compare(JTRANSCODER))
section = ESection::tc;
else if (0 == hname.compare(JMODULES))
section = ESection::modules;
else if (0 == hname.compare(JDPLUS))
@ -282,13 +285,23 @@ bool CConfigure::ReadData(const std::string &path)
{
data[g_Keys.ip.ipv6address] = value;
}
else if (0 == key.compare(JTRANSCODER))
else
badParam(key);
break;
case ESection::tc:
if (0 == key.compare(JPORT))
data[g_Keys.tc.port] = getUnsigned(value, "Transcoder Port", 0, 40000, 10100);
else if (0 == key.compare(JBINDINGADDRESS))
data[g_Keys.tc.bind] = value;
else if (0 == key.compare(JMODULES))
{
if (value.compare("local"))
std::string m(value);
if (checkModules(m))
{
std::cout << "WARNING: Line #" << counter << ": malformed transcoder address, '" << value << "', resetting..." << std::endl;
std::cerr << "ERROR: line #" << counter << ": no letters found in transcoder Modules: '" << m << "'" << std::endl;
rval = true;
}
data[g_Keys.ip.transcoder] = "local";
data[g_Keys.tc.modules] = m;
}
else
badParam(key);
@ -304,16 +317,6 @@ bool CConfigure::ReadData(const std::string &path)
} else
data[g_Keys.modules.modules] = m;
}
else if (0 == key.compare(JTRANSCODED))
{
std::string m(value);
if (checkModules(m))
{
std::cerr << "ERROR: line #" << counter << ": no letters found in Transcoded: '" << m << "'" << std::endl;
rval = true;
} else
data[g_Keys.modules.tcmodules] = m;
}
else if (0 == key.compare(0, 11, "Description"))
{
if (12 == key.size() && isupper(key[11]))
@ -606,27 +609,6 @@ bool CConfigure::ReadData(const std::string &path)
if (isDefined(ErrorLevel::fatal, JMODULES, JMODULES, g_Keys.modules.modules, rval))
{
const auto mods(data[g_Keys.modules.modules].get<std::string>());
if (data.contains(g_Keys.modules.tcmodules))
{
const auto tcmods(data[g_Keys.modules.tcmodules].get<std::string>());
// how many transcoded modules
auto size = tcmods.size();
if (3 != size && 1 != size)
std::cout << "WARNING: [" << JMODULES << ']' << JTRANSCODED << " doesn't define one (or three) modules" << std::endl;
// make sure each transcoded module is configured
for (auto c : tcmods)
{
if (std::string::npos == mods.find(c))
{
std::cerr << "ERROR: transcoded module '" << c << "' not found in defined modules" << std::endl;
rval = true;
}
}
}
else
data[g_Keys.modules.tcmodules] = nullptr;
// finally, check the module descriptions
for (unsigned i=0; i<26; i++)
@ -652,6 +634,50 @@ bool CConfigure::ReadData(const std::string &path)
}
}
// Transcoder section
if (isDefined(ErrorLevel::fatal, JTRANSCODER, JPORT, g_Keys.tc.port, rval))
{
tcport = GetUnsigned(g_Keys.tc.port);
if (tcport)
{
if (isDefined(ErrorLevel::fatal, JTRANSCODER, JBINDINGADDRESS, g_Keys.tc.bind, rval))
{
const auto bind(data[g_Keys.tc.bind].get<std::string>());
if (!std::regex_match(bind, IPv4RegEx) && !std::regex_match(bind, IPv6RegEx))
{
std::cerr << "ERROR: Transcoder bind address [" << bind << "] is malformed" << std::endl;
rval = true;
}
}
if (isDefined(ErrorLevel::fatal, JTRANSCODER, JMODULES, g_Keys.tc.modules, rval))
{
const auto tcmods(data[g_Keys.tc.modules].get<std::string>());
// how many transcoded modules
auto size = tcmods.size();
if (3 != size && 1 != size)
std::cout << "WARNING: [" << JTRANSCODER << ']' << JMODULES << " doesn't define one (or three) modules" << std::endl;
// make sure each transcoded module is configured
const std::string mods(GetString(g_Keys.modules.modules));
for (auto c : tcmods)
{
if (std::string::npos == mods.find(c))
{
std::cerr << "ERROR: transcoded module '" << c << "' not found in defined modules" << std::endl;
rval = true;
}
}
}
}
else
{
// there is no transcoder
data[g_Keys.tc.modules] = "";
data[g_Keys.tc.bind] = "";
}
}
// "simple" protocols with only a Port
isDefined(ErrorLevel::fatal, JDCS, JPORT, g_Keys.dcs.port, rval);
isDefined(ErrorLevel::fatal, JDEXTRA, JPORT, g_Keys.dextra.port, rval);
@ -691,11 +717,11 @@ bool CConfigure::ReadData(const std::string &path)
{
if (GetBoolean(g_Keys.usrp.enable))
{
if (IsString(g_Keys.modules.tcmodules))
if (tcport)
{
if (isDefined(ErrorLevel::fatal, JUSRP, JMODULE, g_Keys.usrp.module, rval))
{
if (std::string::npos == GetString(g_Keys.modules.tcmodules).find(GetString(g_Keys.usrp.module).at(0)))
if (std::string::npos == GetString(g_Keys.tc.modules).find(GetString(g_Keys.usrp.module).at(0)))
{
std::cerr << "ERROR: [" << JUSRP << ']' << JMODULE << " is not a transcoded module" << std::endl;
rval = true;

@ -25,7 +25,7 @@
enum class ErrorLevel { fatal, mild };
enum class ERefreshType { file, http, both };
enum class ESection { none, names, ip, modules, urf, dplus, dextra, dcs, g3, dmrplus, mmdvm, nxdn, bm, ysf, p25, m17, usrp, dmrid, nxdnid, ysffreq, files };
enum class ESection { none, names, ip, modules, urf, dplus, dextra, dcs, g3, dmrplus, mmdvm, nxdn, bm, ysf, p25, m17, usrp, dmrid, nxdnid, ysffreq, files, tc };
#define IS_TRUE(a) ((a)=='t' || (a)=='T' || (a)=='1')

@ -21,16 +21,16 @@
#include "DVFramePacket.h"
// default constructor
CDvFramePacket::CDvFramePacket()
CDvFramePacket::CDvFramePacket() : CPacket()
{
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.dmr, 0, 9);
memset(m_uiDvSync, 0, 7);
memset(m_TCPack.m17, 0, 16);
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.usrp, 0, 320);
memset(m_Nonce, 0, 14);
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_TCPack.dmr, 0, sizeof(m_TCPack.dmr));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::none;
};
@ -38,14 +38,14 @@ CDvFramePacket::CDvFramePacket()
CDvFramePacket::CDvFramePacket(const SDStarFrame *dvframe, uint16_t sid, uint8_t pid)
: CPacket(sid, pid)
{
memcpy(m_TCPack.dstar, dvframe->AMBE, 9);
memcpy(m_uiDvData, dvframe->DVDATA, 3);
memset(m_TCPack.dmr, 0, 9);
memset(m_uiDvSync, 0, 7);
memset(m_TCPack.m17, 0, 16);
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.usrp, 0, 320);
memset(m_Nonce, 0, 14);
memcpy(m_TCPack.dstar, dvframe->AMBE, sizeof(m_TCPack.dstar));
memcpy(m_uiDvData, dvframe->DVDATA, sizeof(m_uiDvData));
memset(m_TCPack.dmr, 0, sizeof(m_TCPack.dmr));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::dstar;
}
@ -53,14 +53,14 @@ CDvFramePacket::CDvFramePacket(const SDStarFrame *dvframe, uint16_t sid, uint8_t
CDvFramePacket::CDvFramePacket(const uint8_t *ambe, const uint8_t *sync, uint16_t sid, uint8_t pid, uint8_t spid, bool islast)
: CPacket(sid, pid, spid, islast)
{
memcpy(m_TCPack.dmr, ambe, 9);
memcpy(m_uiDvSync, sync, 7);
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.m17, 0, 16);
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.usrp, 0, 320);
memset(m_Nonce, 0, 14);
memcpy(m_TCPack.dmr, ambe, sizeof(m_TCPack.dmr));
memcpy(m_uiDvSync, sync, sizeof(m_uiDvSync));
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::dmr;
}
@ -68,14 +68,14 @@ CDvFramePacket::CDvFramePacket(const uint8_t *ambe, const uint8_t *sync, uint16_
CDvFramePacket::CDvFramePacket(const uint8_t *ambe, uint16_t sid, uint8_t pid, uint8_t spid, uint8_t fid, CCallsign cs, bool islast)
: CPacket(sid, pid, spid, fid, islast)
{
memcpy(m_TCPack.dmr, ambe, 9);
memset(m_uiDvSync, 0, 7);
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.m17, 0, 16);
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.usrp, 0, 320);
memset(m_Nonce, 0, 14);
memcpy(m_TCPack.dmr, ambe, sizeof(m_TCPack.dmr));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::dmr;
uint8_t c[12];
cs.GetCallsign(c);
@ -87,10 +87,10 @@ CDvFramePacket::CDvFramePacket
(uint16_t sid, uint8_t dstarpid, const uint8_t *dstarambe, const uint8_t *dstardvdata, uint8_t dmrpid, uint8_t dprspid, const uint8_t *dmrambe, const uint8_t *dmrsync, ECodecType codecInType, bool islast)
: CPacket(sid, dstarpid, dmrpid, dprspid, 0xFF, 0xFF, 0xFF, codecInType, islast)
{
::memcpy(m_TCPack.dstar, dstarambe, 9);
::memcpy(m_uiDvData, dstardvdata, 3);
::memcpy(m_TCPack.dmr, dmrambe, 9);
::memcpy(m_uiDvSync, dmrsync, 7);
memcpy(m_TCPack.dstar, dstarambe, sizeof(m_TCPack.dstar));
memcpy(m_TCPack.dmr, dmrambe, sizeof(m_TCPack.dmr));
memcpy(m_uiDvData, dstardvdata, sizeof(m_uiDvData));
memcpy(m_uiDvSync, dmrsync, sizeof(m_uiDvSync));
m_TCPack.codec_in = codecInType;
}
@ -98,14 +98,14 @@ CDvFramePacket::CDvFramePacket
CDvFramePacket::CDvFramePacket(const CM17Packet &m17) : CPacket(m17)
{
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.dmr, 0, 9);
memset(m_uiDvSync, 0, 7);
memcpy(m_TCPack.m17, m17.GetPayload(), 16);
memcpy(m_Nonce, m17.GetNonce(), 14);
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.usrp, 0, 320);
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_TCPack.dmr, 0, sizeof(m_TCPack.dmr));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memcpy(m_TCPack.m17, m17.GetPayload(), sizeof(m_TCPack.m17));
memcpy(m_Nonce, m17.GetNonce(), sizeof(m_Nonce));
switch (0x6U & m17.GetFrameType())
{
case 0x4U:
@ -124,14 +124,14 @@ CDvFramePacket::CDvFramePacket(const CM17Packet &m17) : CPacket(m17)
CDvFramePacket::CDvFramePacket(const uint8_t *imbe, uint16_t streamid, bool islast)
: CPacket(streamid, false, islast)
{
memcpy(m_TCPack.p25, imbe, 11);
memset(m_TCPack.dmr, 0, 9);
memset(m_uiDvSync, 0, 7);
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.m17, 0, 16);
memset(m_TCPack.usrp, 0, 320);
memset(m_Nonce, 0, 14);
memcpy(m_TCPack.p25, imbe, sizeof(m_TCPack.p25));
memset(m_TCPack.dmr, 0, sizeof(m_TCPack.dmr));
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::p25;
}
@ -139,14 +139,14 @@ CDvFramePacket::CDvFramePacket(const uint8_t *imbe, uint16_t streamid, bool isla
CDvFramePacket::CDvFramePacket(const uint8_t *ambe, uint16_t sid, uint8_t pid, bool islast)
: CPacket(sid, pid, islast)
{
memcpy(m_TCPack.dmr, ambe, 9);
memset(m_uiDvSync, 0, 7);
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.m17, 0, 16);
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.usrp, 0, 320);
memset(m_Nonce, 0, 14);
memcpy(m_TCPack.dmr, ambe, sizeof(m_TCPack.dmr));
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_TCPack.usrp, 0, sizeof(m_TCPack.usrp));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::dmr;
}
@ -157,13 +157,13 @@ CDvFramePacket::CDvFramePacket(const int16_t *usrp, uint16_t streamid, bool isla
for(int i = 0; i < 160; ++i){
m_TCPack.usrp[i] = usrp[i];
}
memset(m_TCPack.p25, 0, 11);
memset(m_TCPack.dmr, 0, 9);
memset(m_uiDvSync, 0, 7);
memset(m_TCPack.dstar, 0, 9);
memset(m_uiDvData, 0, 3);
memset(m_TCPack.m17, 0, 16);
memset(m_Nonce, 0, 14);
memset(m_TCPack.dstar, 0, sizeof(m_TCPack.dstar));
memset(m_TCPack.dmr, 0, sizeof(m_TCPack.dmr));
memset(m_TCPack.m17, 0, sizeof(m_TCPack.m17));
memset(m_TCPack.p25, 0, sizeof(m_TCPack.p25));
memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
memset(m_uiDvData, 0, sizeof(m_uiDvData));
memset(m_Nonce, 0, sizeof(m_Nonce));
m_TCPack.codec_in = ECodecType::usrp;
}
@ -172,12 +172,6 @@ std::unique_ptr<CPacket> CDvFramePacket::Copy(void)
return std::unique_ptr<CPacket>(new CDvFramePacket(*this));
}
// Network
unsigned int CDvFramePacket::GetNetworkSize()
{
return CPacket::GetNetworkSize() + 4 + 3 + 7 + 14 + 9 + 9 + 16 + 11 + 320;
}
CDvFramePacket::CDvFramePacket(const CBuffer &buf) : CPacket(buf)
{
if (buf.size() >= GetNetworkSize())
@ -188,14 +182,14 @@ CDvFramePacket::CDvFramePacket(const CBuffer &buf) : CPacket(buf)
for (unsigned int i=0; i<4; i++)
seq = 0x100u * seq + data[off+i];
off += 4;
memcpy(m_uiDvData, data+off, 3); off += 3;
memcpy(m_uiDvSync, data+off, 7); off += 7;
memcpy(m_Nonce, data+off, 14); off += 14;
memcpy(m_TCPack.dstar, data+off, 9); off += 9;
memcpy(m_TCPack.dmr, data+off, 9); off += 9;
memcpy(m_TCPack.m17, data+off, 16); off += 16;
memcpy(m_TCPack.p25, data+off, 11); off += 11;
memcpy(m_TCPack.usrp, data+off, 320);
memcpy(m_uiDvData, data+off, sizeof(m_uiDvData)); off += sizeof(m_uiDvData);
memcpy(m_uiDvSync, data+off, sizeof(m_uiDvSync)); off += sizeof(m_uiDvSync);
memcpy(m_Nonce, data+off, sizeof(m_Nonce)); off += sizeof(m_Nonce);
memcpy(m_TCPack.dstar, data+off, sizeof(m_TCPack.dstar)); off += sizeof(m_TCPack.dstar);
memcpy(m_TCPack.dmr, data+off, sizeof(m_TCPack.dmr)); off += sizeof (m_TCPack.dmr);
memcpy(m_TCPack.m17, data+off, sizeof(m_TCPack.m17)); off += sizeof(m_TCPack.m17);
memcpy(m_TCPack.p25, data+off, sizeof(m_TCPack.p25)); off += sizeof(m_TCPack.p25);
memcpy(m_TCPack.usrp, data+off, sizeof(m_TCPack.usrp));
SetTCParams(seq);
}
else
@ -212,14 +206,14 @@ void CDvFramePacket::EncodeInterlinkPacket(CBuffer &buf) const
data[off++] = (m_TCPack.sequence >> 16) & 0xffu;
data[off++] = (m_TCPack.sequence >> 8) & 0xffu;
data[off++] = m_TCPack.sequence & 0xffu;
memcpy(data+off, m_uiDvData, 3); off += 3;
memcpy(data+off, m_uiDvSync, 7); off += 7;
memcpy(data+off, m_Nonce, 14); off += 14;
memcpy(data+off, m_TCPack.dstar, 9); off += 9;
memcpy(data+off, m_TCPack.dmr, 9); off += 9;
memcpy(data+off, m_TCPack.m17, 16); off += 16;
memcpy(data+off, m_TCPack.p25, 11); off += 11;
memcpy(data+off, m_TCPack.usrp, 320);
memcpy(data+off, m_uiDvData, sizeof(m_uiDvData)); off += sizeof(m_uiDvData);
memcpy(data+off, m_uiDvSync, sizeof(m_uiDvSync)); off += sizeof(m_uiDvSync);
memcpy(data+off, m_Nonce, sizeof(m_Nonce)); off += sizeof(m_Nonce);
memcpy(data+off, m_TCPack.dstar, sizeof(m_TCPack.dstar)); off += sizeof(m_TCPack.dstar);
memcpy(data+off, m_TCPack.dmr, sizeof(m_TCPack.dmr)); off += sizeof(m_TCPack.dmr);
memcpy(data+off, m_TCPack.m17, sizeof(m_TCPack.m17)); off += sizeof(m_TCPack.m17);
memcpy(data+off, m_TCPack.p25, sizeof(m_TCPack.p25)); off += sizeof(m_TCPack.p25);
memcpy(data+off, m_TCPack.usrp, sizeof(m_TCPack.usrp));
}
////////////////////////////////////////////////////////////////////////////////////////
@ -250,7 +244,7 @@ const uint8_t *CDvFramePacket::GetCodecData(ECodecType type) const
void CDvFramePacket::SetDvData(const uint8_t *DvData)
{
memcpy(m_uiDvData, DvData, 3);
memcpy(m_uiDvData, DvData, sizeof(m_uiDvData));
}
void CDvFramePacket::SetCodecData(const STCPacket *pack)
@ -264,5 +258,4 @@ void CDvFramePacket::SetTCParams(uint32_t seq)
m_TCPack.streamid = m_uiStreamId;
m_TCPack.is_last = m_bLastPacket;
m_TCPack.module = m_cModule;
m_TCPack.rt_timer.start();
}

@ -20,6 +20,7 @@
#include "Packet.h"
#include "Callsign.h"
#include "Timer.h"
////////////////////////////////////////////////////////////////////////////////////////
// defines
@ -59,7 +60,12 @@ public:
CDvFramePacket(const int16_t *usrp, uint16_t streamid, bool islast);
// URF Network
CDvFramePacket(const CBuffer &buf);
static unsigned int GetNetworkSize();
static constexpr unsigned GetNetworkSize() noexcept
{
return CPacket::GetNetworkSize() + sizeof(m_uiDvData) + sizeof(m_uiDvSync) + sizeof(m_Nonce) + sizeof(m_TCPack.dstar) + sizeof (m_TCPack.dmr) + sizeof(m_TCPack.m17) + sizeof(m_TCPack.p25) + sizeof(m_TCPack.usrp) + sizeof(m_TCPack.sequence);
}
void EncodeInterlinkPacket(CBuffer &buf) const;
// identity
@ -80,6 +86,9 @@ public:
void SetCodecData(const STCPacket *pack);
void SetTCParams(uint32_t seq);
// the round-trip timer
CTimer m_rtTimer;
protected:
// data (dstar)
uint8_t m_uiDvData[3];

@ -90,17 +90,6 @@ CDvHeaderPacket::CDvHeaderPacket(const struct dstar_header *buffer, uint16_t sid
m_uiFlag2 = buffer->Flag2;
m_uiFlag3 = buffer->Flag3;
m_csUR.SetCallsign(buffer->UR, CALLSIGN_LEN);
if((buffer->RPT1)[7] == 0x20){
char rptr1[8];
memcpy(rptr1, buffer->RPT1, 8);
rptr1[7] = DPLUS_DEFAULT_RPTR1_SUFFIX;
m_csRPT1.SetCallsign(rptr1, CALLSIGN_LEN);
}
else{
m_csRPT1.SetCallsign(buffer->RPT1, CALLSIGN_LEN);
}
m_csRPT1.SetCallsign(buffer->RPT1, CALLSIGN_LEN);
m_csRPT2.SetCallsign(buffer->RPT2, CALLSIGN_LEN);
m_csMY.SetCallsign(buffer->MY, CALLSIGN_LEN);

@ -21,6 +21,7 @@
#include "LookupDmr.h"
#include "LookupNxdn.h"
#include "LookupYsf.h"
#include "TCSocket.h"
#include "JsonKeys.h"
extern CReflector g_Reflector;
@ -31,3 +32,4 @@ extern CLookupDmr g_LDid;
extern CLookupNxdn g_LNid;
extern CLookupYsf g_LYtr;
extern SJsonKeys g_Keys;
extern CTCServer g_TCServer;

@ -42,11 +42,14 @@ struct SJsonKeys {
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" };
struct IP { const std::string ipv4bind, ipv4address, ipv6bind, ipv6address; }
ip { "ipv4bind", "IPv4Address", "ipv6bind", "IPv6Address" };
struct MODULES { const std::string modules, tcmodules, descriptor[26]; }
modules { "Modules", "TranscodedModules",
struct TC { const std::string port, bind, modules; }
tc { "tcport", "tcbind", "TranscodedModules" };
struct MODULES { const std::string modules, descriptor[26]; }
modules { "Modules",
"DescriptionA", "DescriptionB", "DescriptionC", "DescriptionD", "DescriptionE", "DescriptionF", "DescriptionG", "DescriptionH", "DescriptionI", "DescriptionJ", "DescriptionK", "DescriptionL", "DescriptionM", "DescriptionN", "DescriptionO", "DescriptionP", "DescriptionQ", "DescriptionR", "DescriptionS", "DescriptionT", "DescriptionU", "DescriptionV", "DescriptionW", "DescriptionX", "DescriptionY", "DescriptionZ" };
struct USRP { const std::string enable, ip, txport, rxport, module, callsign, filepath; }

@ -54,8 +54,8 @@ void CM17Protocol::Task(void)
std::unique_ptr<CDvFramePacket> Frame;
// handle incoming packets
#if DSTAR_IPV6==true
#if DSTAR_IPV4==true
#if M17_IPV6==true
#if M17_IPV4==true
if ( ReceiveDS(Buffer, Ip, 20) )
#else
if ( Receive6(Buffer, Ip, 20) )
@ -81,8 +81,6 @@ void CM17Protocol::Task(void)
// push the "first" packet
OnDvFramePacketIn(Frame, &Ip);
// leave a space between this pair of frames
std::this_thread::sleep_for(std::chrono::milliseconds(16));
// push the "second" packet
OnDvFramePacketIn(secondFrame, &Ip); // push two packet because we need a packet every 20 ms
}

@ -28,10 +28,11 @@ SJsonKeys g_Keys;
CReflector g_Reflector;
CGateKeeper g_GateKeeper;
CConfigure g_Configure;
CVersion g_Version(3,1,0); // The major byte should only change if the interlink packet changes!
CVersion g_Version(3,1,2); // The major byte should only change if the interlink packet changes!
CLookupDmr g_LDid;
CLookupNxdn g_LNid;
CLookupYsf g_LYtr;
CTCServer g_TCServer;
////////////////////////////////////////////////////////////////////////////////////////

@ -38,11 +38,6 @@ CPacket::CPacket()
};
// for the network
unsigned int CPacket::GetNetworkSize()
{
return 20u;
}
CPacket::CPacket(const CBuffer &buf)
{
if (buf.size() > 19)
@ -53,7 +48,7 @@ CPacket::CPacket(const CBuffer &buf)
m_bLastPacket = data[6] ? true : false;
m_cModule = data[7];
m_uiStreamId = data[8]*0x100u + data[9];
m_uiM17FrameNumber = data[10]*0x1000000u + data[11]*0x10000u + data[12]*0x100 + data[13];
m_uiM17FrameNumber = data[10]*0x1000000u + data[11]*0x10000u + data[12]*0x100u + data[13];
m_uiDstarPacketId = data[14];
m_uiDmrPacketId = data[15];
m_uiDmrPacketSubid = data[16];
@ -93,17 +88,17 @@ CPacket::CPacket(uint16_t sid, uint8_t dstarpid)
{
m_uiStreamId = sid;
m_uiDstarPacketId = dstarpid;
m_uiDmrPacketId = 0xFF;
m_uiDmrPacketSubid = 0xFF;
m_uiYsfPacketId = 0xFF;
m_uiYsfPacketSubId = 0xFF;
m_uiYsfPacketFrameId = 0xFF;
m_uiNXDNPacketId = 0xFF;
m_uiM17FrameNumber = 0xFFFFFFFFU;
m_uiDmrPacketId = 0xffu;
m_uiDmrPacketSubid = 0xffu;
m_uiYsfPacketId = 0xffu;
m_uiYsfPacketSubId = 0xffu;
m_uiYsfPacketFrameId = 0xffu;
m_uiNXDNPacketId = 0xffu;
m_uiM17FrameNumber = 0xffffffffu;
m_cModule = ' ';
m_eOrigin = EOrigin::local;
m_eCodecIn = ECodecType::dstar;
m_bLastPacket = (0x40U == (dstarpid & 0x40U));
m_bLastPacket = (0x40u == (dstarpid & 0x40u));
};
// dmr constructor
@ -112,12 +107,12 @@ CPacket::CPacket(uint16_t sid, uint8_t dmrpid, uint8_t dmrspid, bool lastpacket)
m_uiStreamId = sid;
m_uiDmrPacketId = dmrpid;
m_uiDmrPacketSubid = dmrspid;
m_uiDstarPacketId = 0xFF;
m_uiYsfPacketId = 0xFF;
m_uiYsfPacketSubId = 0xFF;
m_uiYsfPacketFrameId = 0xFF;
m_uiNXDNPacketId = 0xFF;
m_uiM17FrameNumber = 0xFFFFFFFFU;
m_uiDstarPacketId = 0xffu;
m_uiYsfPacketId = 0xffu;
m_uiYsfPacketSubId = 0xffu;
m_uiYsfPacketFrameId = 0xffu;
m_uiNXDNPacketId = 0xffu;
m_uiM17FrameNumber = 0xffffffffu;
m_cModule = ' ';
m_eOrigin = EOrigin::local;
m_eCodecIn = ECodecType::dmr;
@ -131,11 +126,11 @@ CPacket::CPacket(uint16_t sid, uint8_t ysfpid, uint8_t ysfsubpid, uint8_t ysffri
m_uiYsfPacketId = ysfpid;
m_uiYsfPacketSubId = ysfsubpid;
m_uiYsfPacketFrameId = ysffrid;
m_uiDstarPacketId = 0xFF;
m_uiDmrPacketId = 0xFF;
m_uiDmrPacketSubid = 0xFF;
m_uiNXDNPacketId = 0xFF;
m_uiM17FrameNumber = 0xFFFFFFFFU;
m_uiDstarPacketId = 0xffu;
m_uiDmrPacketId = 0xffu;
m_uiDmrPacketSubid = 0xffu;
m_uiNXDNPacketId = 0xffu;
m_uiM17FrameNumber = 0xffffffffu;
m_cModule = ' ';
m_eOrigin = EOrigin::local;
m_eCodecIn = ECodecType::dmr;
@ -147,13 +142,13 @@ CPacket::CPacket(uint16_t sid, uint8_t pid, bool lastpacket)
{
m_uiStreamId = sid;
m_uiNXDNPacketId = pid;
m_uiDmrPacketId = 0xFF;
m_uiDmrPacketSubid = 0xFF;
m_uiDstarPacketId = 0xFF;
m_uiYsfPacketId = 0xFF;
m_uiYsfPacketSubId = 0xFF;
m_uiYsfPacketFrameId = 0xFF;
m_uiM17FrameNumber = 0xFFFFFFFFU;
m_uiDmrPacketId = 0xffu;
m_uiDmrPacketSubid = 0xffu;
m_uiDstarPacketId = 0xffu;
m_uiYsfPacketId = 0xffu;
m_uiYsfPacketSubId = 0xffu;
m_uiYsfPacketFrameId = 0xffu;
m_uiM17FrameNumber = 0xffffffffu;
m_cModule = ' ';
m_eOrigin = EOrigin::local;
m_eCodecIn = ECodecType::dmr;
@ -164,14 +159,14 @@ CPacket::CPacket(uint16_t sid, uint8_t pid, bool lastpacket)
CPacket::CPacket(uint16_t sid, bool isusrp, bool lastpacket)
{
m_uiStreamId = sid;
m_uiDstarPacketId = 0xFF;
m_uiDmrPacketId = 0xFF;
m_uiDmrPacketSubid = 0xFF;
m_uiYsfPacketId = 0xFF;
m_uiYsfPacketSubId = 0xFF;
m_uiYsfPacketFrameId = 0xFF;
m_uiNXDNPacketId = 0xFF;
m_uiM17FrameNumber = 0xFFFFFFFFU;
m_uiDstarPacketId = 0xffu;
m_uiDmrPacketId = 0xffu;
m_uiDmrPacketSubid = 0xffu;
m_uiYsfPacketId = 0xffu;
m_uiYsfPacketSubId = 0xffu;
m_uiYsfPacketFrameId = 0xffu;
m_uiNXDNPacketId = 0xffu;
m_uiM17FrameNumber = 0xffffffffu;
m_cModule = ' ';
m_eOrigin = EOrigin::local;
isusrp ? m_eCodecIn = ECodecType::usrp : ECodecType::p25;
@ -188,8 +183,8 @@ CPacket::CPacket(uint16_t sid, uint8_t dstarpid, uint8_t dmrpid, uint8_t dmrsubp
m_uiYsfPacketId = ysfpid;
m_uiYsfPacketSubId = ysfsubpid;
m_uiYsfPacketFrameId = ysffrid;
m_uiM17FrameNumber = 0xFFFFFFFFU;
m_uiNXDNPacketId = 0xFF;
m_uiM17FrameNumber = 0xffffffffu;
m_uiNXDNPacketId = 0xffu;
m_cModule = ' ';
m_eOrigin = EOrigin::local;
m_eCodecIn = codecIn;
@ -200,15 +195,15 @@ CPacket::CPacket(uint16_t sid, uint8_t dstarpid, uint8_t dmrpid, uint8_t dmrsubp
CPacket::CPacket(const CM17Packet &m17) : CPacket()
{
m_uiStreamId = m17.GetStreamId();
m_uiDstarPacketId = 0xFF;
m_uiDmrPacketId = 0xFF;
m_uiDmrPacketSubid = 0xFF;
m_uiYsfPacketId = 0xFF;
m_uiYsfPacketSubId = 0xFF;
m_uiYsfPacketFrameId = 0xFF;
m_uiNXDNPacketId = 0xFF;
m_eCodecIn = (0x6U == (0x6U & m17.GetFrameType())) ? ECodecType::c2_1600 : ECodecType::c2_3200;
m_uiM17FrameNumber = 0xFFFFU & m17.GetFrameNumber();
m_uiDstarPacketId = 0xffu;
m_uiDmrPacketId = 0xffu;
m_uiDmrPacketSubid = 0xffu;
m_uiYsfPacketId = 0xffu;
m_uiYsfPacketSubId = 0xffu;
m_uiYsfPacketFrameId = 0xffu;
m_uiNXDNPacketId = 0xffu;
m_eCodecIn = (0x6u == (0x6u & m17.GetFrameType())) ? ECodecType::c2_1600 : ECodecType::c2_3200;
m_uiM17FrameNumber = 0xffffu & m17.GetFrameNumber();
m_bLastPacket = m17.IsLastPacket();
}
@ -223,31 +218,31 @@ void CPacket::UpdatePids(const uint32_t pid)
// derived from each other
// dstar pid needs update ?
if ( m_uiDstarPacketId == 0xFF )
if ( m_uiDstarPacketId == 0xffu )
{
m_uiDstarPacketId = (pid % 21);
m_uiDstarPacketId = (pid % 21u);
}
// dmr pids need update ?
if ( m_uiDmrPacketId == 0xFF )
if ( m_uiDmrPacketId == 0xffu )
{
m_uiDmrPacketId = ((pid / 3) % 6);
m_uiDmrPacketSubid = ((pid % 3) + 1);
m_uiDmrPacketId = ((pid / 3u) % 6u);
m_uiDmrPacketSubid = ((pid % 3u) + 1u);
}
// ysf pids need update ?
if ( m_uiYsfPacketId == 0xFF )
if ( m_uiYsfPacketId == 0xffu )
{
m_uiYsfPacketId = ((pid / 5) % 8);
m_uiYsfPacketSubId = pid % 5;
m_uiYsfPacketFrameId = ((pid / 5) & 0x7FU) << 1;
m_uiYsfPacketId = ((pid / 5u) % 8u);
m_uiYsfPacketSubId = pid % 5u;
m_uiYsfPacketFrameId = ((pid / 5u) & 0x7fu) << 1;
}
if ( m_uiNXDNPacketId == 0xFF )
if ( m_uiNXDNPacketId == 0xffu )
{
m_uiNXDNPacketId = pid % 4;
m_uiNXDNPacketId = pid % 4u;
}
// m17 needs update?
if (m_uiM17FrameNumber == 0xFFFFFFFFU)
if (m_uiM17FrameNumber == 0xffffffffu)
{
// frames are every 20 milliseconds, so the m17 data will come every 40 milliseconds
m_uiM17FrameNumber = (pid / 2) % 0x8000U;
m_uiM17FrameNumber = (pid / 2) % 0x8000u;
}
}

@ -72,7 +72,10 @@ public:
protected:
// network
void EncodeInterlinkPacket(const char *magic, CBuffer &Buffer) const;
static unsigned int GetNetworkSize();
static constexpr unsigned GetNetworkSize() noexcept
{
return 4u + sizeof(ECodecType) + sizeof(EOrigin) + sizeof(bool) + sizeof(char) + sizeof(uint16_t) + sizeof(uint32_t) + 7u * sizeof(uint8_t);
}
// data
// if you change something here, you'll need to update the CBuffer ctor and EncodeInterlinkPacket()!!!

@ -92,9 +92,11 @@ void CPacketStream::Push(std::unique_ptr<CPacket> Packet)
if ( m_CodecStream && Packet->IsDvFrame() && Packet->IsLocalOrigin())
{
// yes, push packet to trancoder queue
// trancoder will push it after transcoding
// first, recast to a CDvFramePacket
auto Frame = std::unique_ptr<CDvFramePacket>(static_cast<CDvFramePacket *>(Packet.release()));
// trancoder will push it to m_Queue after transcoding
// is completed
m_CodecStream->Push(std::move(Packet));
m_CodecStream->Push(std::move(Frame));
}
else
{

@ -64,9 +64,6 @@ void CPeers::AddPeer(std::shared_ptr<CPeer> peer)
clients->AddClient(*cit);
}
g_Reflector.ReleaseClients();
// notify
g_Reflector.OnPeersChanged();
}
void CPeers::RemovePeer(std::shared_ptr<CPeer> peer)
@ -92,8 +89,6 @@ void CPeers::RemovePeer(std::shared_ptr<CPeer> peer)
// remove it
std::cout << "Peer " << (*pit)->GetCallsign() << " at " << (*pit)->GetIp() << " removed" << std::endl;
pit = m_Peers.erase(pit);
// notify
g_Reflector.OnPeersChanged();
}
else
{

@ -21,19 +21,14 @@
#include "Global.h"
CReflector::CReflector()
{
#ifndef NO_DHT
peers_put_count = clients_put_count = users_put_count = 0;
#endif
}
CReflector::CReflector() {}
CReflector::~CReflector()
{
keep_running = false;
if ( m_XmlReportFuture.valid() )
if ( m_MaintenanceFuture.valid() )
{
m_XmlReportFuture.get();
m_MaintenanceFuture.get();
}
for (auto it=m_Modules.cbegin(); it!=m_Modules.cend(); it++)
@ -55,18 +50,26 @@ bool CReflector::Start(void)
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));
const auto tcmods(g_Configure.GetString(g_Keys.tc.modules));
const auto port = g_Configure.GetUnsigned(g_Keys.tc.port);
#ifndef NO_DHT
// start the dht instance
refhash = dht::InfoHash::get(cs);
node.run(17171, dht::crypto::generateIdentity(cs), true);
node.run(17171, dht::crypto::generateIdentity(cs), true, 59973);
node.bootstrap(g_Configure.GetString(g_Keys.names.bootstrap), "17171");
#endif
// let's go!
keep_running = true;
// init transcoder comms
if (port)
{
if (g_TCServer.Open(g_Configure.GetString(g_Keys.tc.bind), tcmods, port))
return true;
}
// init gate keeper. It can only return true!
g_GateKeeper.Init();
@ -93,10 +96,13 @@ bool CReflector::Start(void)
if (stream)
{
// if it's a transcoded module, then we need to initialize the codec stream
if (std::string::npos != tcmods.find(c))
if (port)
{
if (stream->InitCodecStream())
return true;
if (std::string::npos != tcmods.find(c))
{
if (stream->InitCodecStream())
return true;
}
}
m_Stream[c] = stream;
}
@ -120,7 +126,7 @@ bool CReflector::Start(void)
// start the reporting thread
try
{
m_XmlReportFuture = std::async(std::launch::async, &CReflector::StateReportThread, this);
m_MaintenanceFuture = std::async(std::launch::async, &CReflector::MaintenanceThread, this);
}
catch(const std::exception& e)
{
@ -139,10 +145,14 @@ void CReflector::Stop(void)
// stop & delete all threads
keep_running = false;
// stop transcoder comms
// if it was never opened, then there is nothing to close;
g_TCServer.Close();
// stop & delete report threads
if ( m_XmlReportFuture.valid() )
if ( m_MaintenanceFuture.valid() )
{
m_XmlReportFuture.get();
m_MaintenanceFuture.get();
}
// stop & delete all router thread
@ -318,21 +328,18 @@ void CReflector::RouterThread(const char ThisModule)
}
}
////////////////////////////////////////////////////////////////////////////////////////
// report threads
// Maintenance thread hands xml and/or json update,
// and also keeps the transcoder TCP port(s) connected
#define XML_UPDATE_PERIOD 10
void CReflector::StateReportThread()
void CReflector::MaintenanceThread()
{
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))
jsonpath.assign(g_Configure.GetString(g_Keys.files.json));
auto tcport = g_Configure.GetUnsigned(g_Keys.tc.port);
if (xmlpath.empty() && jsonpath.empty())
return; // nothing to do
@ -372,56 +379,23 @@ void CReflector::StateReportThread()
}
}
// and wait a bit and do something useful at the same time
for (int i=0; i< XML_UPDATE_PERIOD && keep_running; i++)
for (int i=0; i< XML_UPDATE_PERIOD*10 && 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)
if (tcport && g_TCServer.AnyAreClosed())
{
PutDHTUsers();
users_changed = false;
if (g_TCServer.Accept())
{
std::cerr << "Unrecoverable error, aborting..." << std::endl;
abort();
}
}
#endif
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
// notifications
void CReflector::OnPeersChanged(void)
{
#ifndef NO_DHT
peers_changed = true;
#endif
}
void CReflector::OnClientsChanged(void)
{
#ifndef NO_DHT
clients_changed = true;
#endif
}
void CReflector::OnUsersChanged(void)
{
#ifndef NO_DHT
users_changed = true;
#endif
}
////////////////////////////////////////////////////////////////////////////////////////
// modules & queues
@ -540,104 +514,17 @@ void CReflector::WriteXmlFile(std::ofstream &xmlFile)
#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.callsign.assign(cs);
cfg.ipv4addr.assign(g_Configure.GetString(g_Keys.ip.ipv4address));
cfg.ipv6addr.assign(g_Configure.GetString(g_Keys.ip.ipv6address));
cfg.modules.assign(g_Configure.GetString(g_Keys.modules.modules));
if (g_Configure.GetUnsigned(g_Keys.tc.port))
cfg.transcodedmods.assign(g_Configure.GetString(g_Keys.tc.modules));
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));
@ -663,11 +550,11 @@ void CReflector::PutDHTConfig()
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)
for (const auto m : cfg.modules)
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->user_type.assign(URFD_CONFIG_1);
nv->id = toUType(EUrfdValueID::Config);
node.putSigned(
@ -696,7 +583,7 @@ void CReflector::GetDHTConfig(const std::string &cs)
node.get(
dht::InfoHash::get(cs),
[](const std::shared_ptr<dht::Value> &v) {
if (0 == v->user_type.compare("urfd-config-1"))
if (0 == v->user_type.compare(URFD_CONFIG_1))
{
auto rdat = dht::Value::unpack<SUrfdConfig1>(*v);
if (rdat.timestamp > cfg.timestamp)
@ -717,7 +604,7 @@ void CReflector::GetDHTConfig(const std::string &cs)
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.GetInterlinkMap()->Update(cfg.callsign, cfg.modules, cfg.ipv4addr, cfg.ipv6addr, cfg.port[toUType(EUrfdPorts::urf)], cfg.transcodedmods);
g_GateKeeper.ReleaseInterlinkMap();
}
else

@ -27,7 +27,7 @@
#include "PacketStream.h"
#ifndef NO_DHT
#include "urfd-dht-values.h"
#include "dht-values.h"
#endif
@ -76,9 +76,6 @@ public:
// notifications
void OnPeersChanged(void);
void OnClientsChanged(void);
void OnUsersChanged(void);
#ifndef NO_DHT
void GetDHTConfig(const std::string &cs);
#endif
@ -87,14 +84,11 @@ protected:
#ifndef NO_DHT
// Publish DHT
void PutDHTConfig();
void PutDHTPeers();
void PutDHTClients();
void PutDHTUsers();
#endif
// threads
void RouterThread(const char);
void StateReportThread(void);
void MaintenanceThread(void);
// streams
std::shared_ptr<CPacketStream> GetStream(char);
@ -121,13 +115,11 @@ protected:
// threads
std::atomic<bool> keep_running;
std::unordered_map<char, std::future<void>> m_RouterFuture;
std::future<void> m_XmlReportFuture;
std::future<void> m_MaintenanceFuture;
#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
};

@ -16,6 +16,7 @@
#pragma once
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
@ -43,6 +44,18 @@ public:
c.notify_one();
}
// You will die if the queue is empty!
T &Front(void)
{
std::lock_guard<std::mutex> lock(m);
if (q.empty())
{
std::cerr << "ERROR: CSavePacketQueue::Front() called, but queue is EMPTY!" << std::endl;
abort();
}
return q.front();
}
T Pop(void)
{
std::lock_guard<std::mutex> lock(m);

@ -17,16 +17,9 @@
#include <cstdint>
#include "Timer.h"
// unix socket names
#define TC2REF "TC2URFMod"
#define REF2TC "URF2TC"
enum class ECodecType : std::uint8_t { none = 0, dstar = 1, dmr = 2, c2_1600 = 3, c2_3200 = 4, p25 = 5, usrp = 6 };
using STCPacket = struct tcpacket_tag {
CTimer rt_timer;
uint32_t sequence;
char module;
bool is_last;

@ -0,0 +1,498 @@
// urfd -- The universal reflector
// Copyright © 2024 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 <iostream>
#include <unistd.h>
#include <thread>
#include <chrono>
#include <csignal>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include "TCSocket.h"
void CTCSocket::Close()
{
for (auto &item : m_Pfd)
{
if (item.fd >= 0)
{
Close(item.fd);
}
}
m_Pfd.clear();
}
void CTCSocket::Close(char mod)
{
auto pos = m_Modules.find(mod);
if (std::string::npos == pos)
{
std::cerr << "Could not find module '" << mod << "'" << std::endl;
return;
}
if (m_Pfd[pos].fd < 0)
{
std::cerr << "Close(" << mod << ") is already closed" << std::endl;
return;
}
Close(m_Pfd[pos].fd);
m_Pfd[pos].fd = -1;
}
void CTCSocket::Close(int fd)
{
if (fd < 0)
{
return;
}
for (auto &p : m_Pfd)
{
if (fd == p.fd)
{
if (shutdown(p.fd, SHUT_RDWR))
{
perror("shutdown");
}
else
{
if (close(p.fd))
{
std::cerr << "Error while closing " << fd << ": ";
perror("close");
}
else
p.fd = -1;
}
return;
}
}
std::cerr << "Could not find a file descriptor with a value of " << fd << std::endl;
}
int CTCSocket::GetFD(char module) const
{
auto pos = m_Modules.find(module);
if (std::string::npos == pos)
return -1;
return m_Pfd[pos].fd;
}
char CTCSocket::GetMod(int fd) const
{
for (unsigned i=0; i<m_Pfd.size(); i++)
{
if (fd == m_Pfd[i].fd)
{
return m_Modules[i];
}
}
return '?';
}
bool CTCServer::AnyAreClosed() const
{
for (auto &fds : m_Pfd)
{
if (0 > fds.fd)
return true;
}
return false;
}
bool CTCSocket::Send(const STCPacket *packet)
{
const auto pos = m_Modules.find(packet->module);
if (pos == std::string::npos)
{
std::cerr << "Can't Send() this packet to unconfigured module '" << packet->module << "'" << std::endl;
return true;
}
unsigned count = 0;
auto data = (const unsigned char *)packet;
do {
auto n = send(m_Pfd[pos].fd, data+count, sizeof(STCPacket)-count, 0);
if (n <= 0)
{
if (0 == n)
{
std::cerr << "CTCSocket::Send: socket on module '" << packet->module << "' has been closed!" << std::endl;
}
else
{
perror("CTCSocket::Send");
}
Close(packet->module);
return true;
}
count += n;
} while (count < sizeof(STCPacket));
return false;
}
bool CTCSocket::receive(int fd, STCPacket *packet)
{
auto n = recv(fd, packet, sizeof(STCPacket), MSG_WAITALL);
if (n < 0)
{
perror("Receive recv");
Close(fd);
return true;
}
if (0 == n)
{
return true;
}
if (n != sizeof(STCPacket))
std::cout << "receive() only read " << n << " bytes of the transcoder packet from module '" << GetMod(fd) << "'" << std::endl;
return false;
}
// returns true if there is data to return
bool CTCServer::Receive(char module, STCPacket *packet, int ms)
{
bool rv = false;
const auto pos = m_Modules.find(module);
if (pos == std::string::npos)
{
std::cerr << "Can't receive on unconfigured module '" << module << "'" << std::endl;
return rv;
}
auto pfds = &m_Pfd[pos];
if (pfds->fd < 0)
{
return rv;
}
auto n = poll(pfds, 1, ms);
if (n < 0)
{
perror("Recieve poll");
Close(pfds->fd);
return rv;
}
if (0 == n)
return rv; // timeout
if (pfds->revents & POLLIN)
{
rv = receive(pfds->fd, packet);
}
// It's possible that even if we read the data, the socket can have an error after the read...
// So we'll check...
if (pfds->revents & POLLERR || pfds->revents & POLLHUP)
{
if (pfds->revents & POLLERR)
std::cerr << "POLLERR received on module '" << module << "', closing socket" << std::endl;
if (pfds->revents & POLLHUP)
std::cerr << "POLLHUP received on module '" << module << "', closing socket" << std::endl;
Close(pfds->fd);
}
if (pfds->revents & POLLNVAL)
{
std::cerr << "POLLNVAL received on module " << module << "'" << std::endl;
}
if (rv)
Close(pfds->fd);
return ! rv;
}
bool CTCServer::Open(const std::string &address, const std::string &modules, uint16_t port)
{
m_Modules.assign(modules);
m_Ip = CIp(address.c_str(), AF_UNSPEC, SOCK_STREAM, port);
m_Pfd.resize(m_Modules.size());
for (auto &pf : m_Pfd)
{
pf.fd = -1;
pf.events = POLLIN;
pf.revents = 0;
}
return Accept();
}
bool CTCServer::Accept()
{
auto fd = socket(m_Ip.GetFamily(), SOCK_STREAM, 0);
if (fd < 0)
{
perror("Open socket");
return true;
}
int yes = 1;
auto rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (rv < 0)
{
close(fd);
perror("Open setsockopt");
return true;
}
rv = bind(fd, m_Ip.GetCPointer(), m_Ip.GetSize());
if (rv < 0)
{
close(fd);
perror("Open bind");
return true;
}
rv = listen(fd, 3);
if (rv < 0)
{
perror("Open listen");
close(fd);
Close();
return true;
}
std::string wmod;
for (const char c : m_Modules)
{
if (GetFD(c) < 0)
wmod.append(1, c);
}
std::cout << "Waiting at " << m_Ip << " for transcoder connection";
if (wmod.size() > 1)
{
std::cout << "s for modules ";
}
else
{
std::cout << " for module ";
}
std::cout << wmod << "..." << std::endl;
while (AnyAreClosed())
{
if (acceptone(fd))
{
close(fd);
Close();
return true;
}
}
close(fd);
return false;
}
bool CTCServer::acceptone(int fd)
{
CIp their_addr; // connector's address information
socklen_t sin_size = sizeof(struct sockaddr_storage);
auto newfd = accept(fd, their_addr.GetPointer(), &sin_size);
if (newfd < 0)
{
perror("Accept accept");
return true;
}
char mod;
int rv = recv(newfd, &mod, 1, MSG_WAITALL); // block to get the identification byte
if (rv != 1)
{
if (rv < 0)
perror("Accept recv");
else
std::cerr << "recv got no identification byte!" << std::endl;
close(newfd);
return true;
}
const auto pos = m_Modules.find(mod);
if (std::string::npos == pos)
{
std::cerr << "New connection for module '" << mod << "', but it's not configured!" << std::endl;
std::cerr << "The transcoded modules need to be configured identically for both urfd and tcd." << std::endl;
close(newfd);
return true;
}
std::cout << "File descriptor " << newfd << " opened TCP port for module '" << mod << "' on " << their_addr << std::endl;
m_Pfd[pos].fd = newfd;
return false;
}
bool CTCClient::Open(const std::string &address, const std::string &modules, uint16_t port)
{
m_Address.assign(address);
m_Modules.assign(modules);
m_Port = port;
m_Pfd.resize(m_Modules.size());
for (auto &pf : m_Pfd)
{
pf.fd = -1;
pf.events = POLLIN;
}
std::cout << "Connecting to the TCP server..." << std::endl;
for (char c : modules)
{
if (Connect(c))
{
return true;
}
}
return false;
}
bool CTCClient::Connect(char module)
{
const auto pos = m_Modules.find(module);
if (pos == std::string::npos)
{
std::cerr << "CTCClient::Connect: could not find module '" << module << "' in configured modules!" << std::endl;
return true;
}
CIp ip(m_Address.c_str(), AF_UNSPEC, SOCK_STREAM, m_Port);
auto fd = socket(ip.GetFamily(), SOCK_STREAM, 0);
if (fd < 0)
{
std::cerr << "Could not open socket for module '" << module << "'" << std::endl;
perror("TC client socket");
return true;
}
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)))
{
std::cerr << "Moudule " << module << " error:";
perror("setsockopt");
close(fd);
return true;
}
unsigned count = 0;
while (connect(fd, ip.GetCPointer(), ip.GetSize()))
{
if (ECONNREFUSED == errno)
{
if (0 == ++count % 100) std::cout << "Connection refused! Restart the reflector." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
else
{
std::cerr << "Module " << module << " error: ";
perror("connect");
close(fd);
return true;
}
}
int sent = send(fd, &module, 1, 0); // send the identification byte
if (sent < 0)
{
std::cerr << "Error sending ID byte to module '" << module << "':" << std::endl;
perror("send");
close(fd);
return true;
}
else if (0 == sent)
{
std::cerr << "Could not set ID byte to module '" << module << "'" << std::endl;
close(fd);
return true;
}
std::cout << "File descriptor " << fd << " on " << ip << " opened for module '" << module << "'" << std::endl;
m_Pfd[pos].fd = fd;
return false;
}
void CTCClient::ReConnect()
{
for (char m : m_Modules)
{
if (0 > GetFD(m))
{
std::cout << "Reconnecting module " << m << "..." << std::endl;
if (Connect(m))
{
raise(SIGINT);
}
}
}
}
void CTCClient::Receive(std::queue<std::unique_ptr<STCPacket>> &queue, int ms)
{
for (auto &pfd : m_Pfd)
pfd.revents = 0;
auto rv = poll(m_Pfd.data(), m_Pfd.size(), ms);
if (rv < 0)
{
perror("Receive poll");
return;
}
if (0 == rv)
return;
for (auto &pfd : m_Pfd)
{
if (pfd.fd < 0)
continue;
if (pfd.revents & POLLIN)
{
auto p_tcpack = std::make_unique<STCPacket>();
if (receive(pfd.fd, p_tcpack.get()))
{
p_tcpack.reset();
Close(pfd.fd);
}
else
{
queue.push(std::move(p_tcpack));
}
}
if (pfd.revents & POLLERR || pfd.revents & POLLHUP)
{
std::cerr << "IO ERROR on Receive module " << GetMod(pfd.fd) << std::endl;
Close(pfd.fd);
}
if (pfd.revents & POLLNVAL)
{
std::cerr << "POLLNVAL received on fd " << pfd.fd << ", resetting to -1" << std::endl;
pfd.fd = -1;
}
}
}

@ -0,0 +1,82 @@
// urfd -- The universal reflector
// Copyright © 2024 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 <string>
#include <cstdint>
#include <mutex>
#include <vector>
#include <queue>
#include <memory>
#include <poll.h>
#include "IP.h"
#include "TCPacketDef.h"
class CTCSocket
{
public:
CTCSocket() {}
virtual ~CTCSocket() { Close(); }
virtual bool Open(const std::string &address, const std::string &modules, uint16_t port) = 0;
void Close(); // close all open sockets
void Close(char module); // close a specific module
void Close(int fd); // close a specific file descriptor
bool receive(int fd, STCPacket *packet);
// All bool functions, except Server Receive, return true if there was an error
bool Send(const STCPacket *packet);
int GetFD(char module) const; // can return -1!
char GetMod(int fd) const;
protected:
std::vector<struct pollfd> m_Pfd;
std::string m_Modules;
};
class CTCServer : public CTCSocket
{
public:
CTCServer() : CTCSocket() {}
~CTCServer() {}
bool Open(const std::string &address, const std::string &modules, uint16_t port);
// Returns true if there is data
bool Receive(char module, STCPacket *packet, int ms);
bool AnyAreClosed() const;
bool Accept();
private:
CIp m_Ip;
bool acceptone(int fd);
};
class CTCClient : public CTCSocket
{
public:
CTCClient() : CTCSocket(), m_Port(0) {}
~CTCClient() {}
bool Open(const std::string &address, const std::string &modules, uint16_t port);
void Receive(std::queue<std::unique_ptr<STCPacket>> &queue, int ms);
void ReConnect();
private:
std::string m_Address;
uint16_t m_Port;
bool Connect(char module);
};

@ -336,7 +336,7 @@ void CURFProtocol::HandlePeerLinks(void)
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)))
else if ((std::string::npos == it->second.GetTCMods().find(c)) != (std::string::npos == g_Configure.GetString(g_Keys.tc.modules).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;

@ -1,163 +0,0 @@
// 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 <iostream>
#include <unistd.h>
#include <string.h>
#include <cstring>
#include <thread>
#include <chrono>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "UnixDgramSocket.h"
CUnixDgramReader::CUnixDgramReader() : fd(-1) {}
CUnixDgramReader::~CUnixDgramReader()
{
Close();
}
bool CUnixDgramReader::Open(const char *path) // returns true on failure
{
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd < 0)
{
std::cerr << "socket() failed for " << path << ": " << strerror(errno) << std::endl;
return true;
}
//fcntl(fd, F_SETFL, O_NONBLOCK);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2);
int rval = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if (rval < 0)
{
std::cerr << "bind() failed for " << path << ": " << strerror(errno) << std::endl;
close(fd);
fd = -1;
return true;
}
return false;
}
bool CUnixDgramReader::Receive(STCPacket *pack, unsigned timeout) const
{
// socket valid ?
if ( 0 > fd )
return false;
// control socket
fd_set FdSet;
FD_ZERO(&FdSet);
FD_SET(fd, &FdSet);
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
auto rval = select(fd + 1, &FdSet, 0, 0, &tv);
if (rval <= 0) {
if (rval < 0) {
std::cerr << "select() error on transcoder socket: " << strerror(errno) << std::endl;
}
return false;
}
return Read(pack);
}
bool CUnixDgramReader::Read(STCPacket *pack) const
{
auto len = read(fd, pack, sizeof(STCPacket));
if (len != sizeof(STCPacket)) {
std::cerr << "Received transcoder packet is wrong size: " << len << " but should be " << sizeof(STCPacket) << std::endl;
return false;
}
return true;
}
void CUnixDgramReader::Close()
{
if (fd >= 0)
close(fd);
fd = -1;
}
int CUnixDgramReader::GetFD() const
{
return fd;
}
CUnixDgramWriter::CUnixDgramWriter() {}
CUnixDgramWriter::~CUnixDgramWriter() {}
void CUnixDgramWriter::SetUp(const char *path) // returns true on failure
{
// setup the socket address
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2);
}
bool CUnixDgramWriter::Send(const STCPacket *pack) const
{
auto len = Write(pack, sizeof(STCPacket));
if (len != sizeof(STCPacket))
return true;
return false;
}
ssize_t CUnixDgramWriter::Write(const void *buf, ssize_t size) const
{
// open the socket
int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd < 0)
{
std::cerr << "socket() failed for " << addr.sun_path+1 << ": " << strerror(errno) << std::endl;
return -1;
}
// connect to the receiver
int rval = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (rval < 0)
{
std::cerr << "connect() failed for " << addr.sun_path+1 << ": " << strerror(errno) << std::endl;
close(fd);
return -1;
}
auto written = write(fd, buf, size);
if (written != size) {
std::cerr << "write on " << addr.sun_path+1;
if (written < 0)
std::cerr << " returned error: " << strerror(errno) << std::endl;
else if (written == 0)
std::cerr << " returned zero" << std::endl;
else
std::cerr << " only wrote " << written << " bytes, should be " << size << std::endl;
}
close(fd);
return written;
}

@ -1,50 +0,0 @@
#pragma once
// 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 <stdlib.h>
#include <sys/un.h>
#include "TCPacketDef.h"
class CUnixDgramReader
{
public:
CUnixDgramReader();
~CUnixDgramReader();
bool Open(const char *path);
bool Read(STCPacket *pack) const;
bool Receive(STCPacket *pack, unsigned timeout) const;
void Close();
int GetFD() const;
private:
int fd;
};
class CUnixDgramWriter
{
public:
CUnixDgramWriter();
~CUnixDgramWriter();
void SetUp(const char *path);
bool Send(const STCPacket *pack) const;
private:
ssize_t Write(const void *buf, ssize_t size) const;
struct sockaddr_un addr;
};

@ -39,9 +39,6 @@ void CUsers::AddUser(const CUser &user)
{
m_Users.resize(m_Users.size()-1);
}
// notify
g_Reflector.OnUsersChanged();
}
////////////////////////////////////////////////////////////////////////////////////////

@ -0,0 +1,133 @@
/*
* Copyright (c) 2022-2024 by 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 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <opendht.h>
// comment out sections you don't need for your application
//#define USE_MREFD_VALUES
#define USE_URFD_VALUES
// a typesafe way to extract the numeric value from a enum class
// note that this is a constexpr and so can even be used in an
// array declaration or as a tuple index
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
#ifdef USE_MREFD_VALUES
// DHT::value::user_type for mrefd values
// These are the value release version strings in a
// preprocessor definition for your convience
// if your app needs a particular part, then it should handle all versions of that part
#define MREFD_CONFIG_1 "mrefd-config-1"
// dht::Value ids of the different parts of the mrefd document
// can be assigned any unsigned value except 0
// more parts can be added, but don't change the value of any existing part
// using toUType, you can set or query a user_type to determine the value part
// this can be done before unpacking the MSGPACK
enum class EMrefdValueID : uint64_t { Config=1, Peers=2, Clients=3, Users=4 };
///////////////// MREFD PART VALUES ///////////////
// the configuration part
struct SMrefdConfig1 // user_type is MREFD_CONFIG_1
{
std::time_t timestamp; // when this value was set
std::string callsign; // the callsign of the mrefd reflector
std::string ipv4addr; // the external IPv4 address
std::string ipv6addr; // the external IPv6 address
std::string modules; // all the configured modules, [A-Z]
std::string encryptedmods; // modules that will pass encrypted streams
std::string url; // the URL of the dashboard
std::string email; // the email of the responsible owner
std::string sponsor; // the organization or individual sponsoring the reflector
std::string country; // the 2-letter country code
std::string version; // the version of the reflector software
uint16_t port; // the connection listening UDP port, usually 17000
// the order in which MSGPACK serializes the data
MSGPACK_DEFINE(timestamp, callsign, ipv4addr, ipv6addr, modules, encryptedmods, url, email, sponsor, country, version, port)
};
#endif // USE_MREFD_VALUES
#ifdef USE_URFD_VALUES
// DHT::value::user_type for urfd values
// These are the value release version strings in a
// preprocessor definition for your convience
// if your app needs a particular part, then it should handle all versions of that part
#define URFD_CONFIG_1 "urfd-config-1"
#define URFD_CONFIG_2 "urfd-config-2"
// dht::Value::id of the different parts of the urfd document
// can be assigned any unsigned value except 0
// more parts can be added, but don't change the value of any existing part
// using toUType, you can set or query a user_type to determine the value part
// this can be done before unpacking the MSGPACK
enum class EUrfdValueID : uint64_t { Config=1, Peers=2, Clients=3, Users=4 };
// the following enum classes can be used to reference a particular value in a fixed array
// 'SIZE' has to be last value for these scoped enums as this is used to declare these arrays
//
// all the configurable ports in urfd (G3 and BM are not configurable)
enum class EUrfdPorts : unsigned { dcs, dextra, dmrplus, dplus, m17, mmdvm, nxdn, p25, urf, ysf, SIZE };
// autolink modules for these protocols
enum class EUrfdAlMod : unsigned { nxdn, p25, ysf, SIZE };
// default TX/RX values for ysf
enum class EUrfdTxRx : unsigned { rx, tx, SIZE };
// reflector ID values for these two modes
enum class EUrfdRefId : unsigned { nxdn, p25, SIZE };
struct SUrfdConfig1 // user_type is URFD_CONFIG_1
{
std::time_t timestamp;
std::string callsign, ipv4addr, ipv6addr, modules, transcodedmods, url, email, sponsor, country, version;
// transcodedmods are those modules that support full transcoding
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, callsign, ipv4addr, ipv6addr, modules, transcodedmods, url, email, sponsor, country, version, almod, ysffreq, refid, g3enabled, port, description)
};
enum class EUrfdPorts2 : unsigned { dcs, dextra, dmrplus, dplus, dsd, m17, mmdvm, nxdn, p25, urf, ysf, SIZE };
struct SUrfdConfig2
{
std::time_t timestamp;
std::string callsign, ipv4addr, ipv6addr, modules, transcodedmods, url, email, sponsor, country, version;
std::array<uint16_t, toUType(EUrfdPorts2::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, callsign, ipv4addr, ipv6addr, modules, transcodedmods, url, email, sponsor, country, version, almod, ysffreq, refid, g3enabled, port, description)
};
#endif // USE_URFD_VALUES

@ -1,89 +0,0 @@
// 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.