diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d040499
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+*.o
+*.d
+.vscode
+./*.blacklist
+./*.whitelist
+./*.interlink
+./*.terminal
+./*.service
+configure.mk
+configure.h
+configure.sql
+reflector.cfg
+wiresx/configure.php
+src/ulxd
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..60a9f18
--- /dev/null
+++ b/README.md
@@ -0,0 +1,159 @@
+# XLXD (or XRFD)
+
+The XLX Multiprotocol Gateway Reflector Server is part of the software system for a Digital Voice Network. The sources are published under GPL Licenses.
+
+## Introduction
+
+This will build **either** a new kind of XLX reflector **or** a tri-mode XRF reflector. 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. This XLX is different from the original because it can support out-going DExtra links, by adding a new DExtra Peer type *and* it has many changes designed to increase reliability and stability. The XRF reflector built from this source supports inbound DExtra, DPlus **and DCS connections**. Like XLX, XRF also supports out-going DExtra linking. Please note that for now, only one DExtra peer link per node is supported.
+
+This is an improved version of the multi-protocol Reflector. Nearly all std::vector containers have been replaced with std::list containers. This is a better choice for any collection where it is common to delete elements that are not at the end of the collection. Also in this package, no classes are derived from any standard containers. Because standard containers don't have a virtual destructor, derriving from them is ill-advised and while the original XLX server worked using such derivations, it represents a possible serious problem when considering future development. Also, the clean-up routines designed to be executed when shutting down were unreachable as designed and this has been fixed. Servers built on this code will shutdown gracefully in a few seconds. In original version, long sleep times in certain threads were preventing a polite systemd shutdown and this has been fixed. The C++ warning flag, -W has been turned on and a significant number of warning have been fixed. For thread management, the standard thread (std::thread) library calls have been replaced with std::future. Futures don't need static functions and this elimintates the need for passing *this* pointers to the thead. All heap memory is now managed with smart pointers, so all calls to *delete* have been removed.
+
+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. Also, by default, ambed and xlxd or xrfd are built without gdb support. 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 XLX reflector or tri-mode XRF 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 20 voice streams or more 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.
+
+## Installation
+
+Below are instructions for building either an XLX or XRF reflector. If you are planning on an XLX reflector without a transcoder, you can help your users by naming modules with names that attract D-Star or non-D-Star client. You name modules in the config.inc.php file mentioned below.
+
+### After a clean installation of Debian make sure to run update and upgrade
+
+```bash
+sudo apt update
+sudo apt upgrade
+```
+
+### Required packages (some of these will probably already be installed)
+
+```bash
+sudo apt install git
+sudo apt install apache2 php5
+sudo apt install build-essential
+sudo apt install g++
+# the following is only needed for XLX, not for XRF
+sudo apt install libmariadb-dev-compat
+# the following is needed if you plan on supporting local YSF frequency registration database
+sudo apt install php-mysql mariadb-server mariadb-client
+```
+
+### Download the repository and enter the directory
+
+```bash
+git clone https://github.com/n7tae/new-xlxd.git
+cd new-xlxd
+```
+
+### Create and edit your blacklist, whitelist and linking files
+
+If you are building an XLX reflector:
+
+```bash
+cp ../config/xlxd.blacklist .
+cp ../config/xlxd.whitelist .
+cp ../config/xlxd.interlink .
+cp ../config/xlxd.terminal .
+```
+
+If you are building an XRF reflector (please note the name changes, especially for the interlink file):
+
+```bash
+cp ../config/xlxd.blacklist xrfd.blacklist
+cp ../config/xlxd.whitelist xrfd.whitelist
+cp ../config/xlxd.interlink xrfd.interlink
+cp ../config/xlxd.terminal xrfd.terminal
+```
+
+If you are not going to support G3 linking, you don't need to copy the .terminal file. Use your favorite editor to modify each of these files. If you want a totally open network, the blacklist and whitelist files are ready to go. The blacklist determine which callsigns can't use the reflector. The whitelist determines which callsigns can use the reflector. The interlink file sets up the XLX<--->XLX inter-linking and/or out-going XRF peer linking.
+
+### Configuring your reflector
+
+Configuring, compiling and maintaining your reflector build is easy! Start the configuration script in the base directory of you cloned repo:
+
+```bash
+./rconfig
+```
+
+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 set to `none`. It's even possible to operate in an IPv6-only configuration by leaving the IPv4 address to the default `none`. You can override the ip addresses for any of the supported protocol and this is done in a sub-menu. This would allow you to install other Ham-related services that might use the same ports, like a Smart Group Server.
+
+Obviously the transcoder is only specified for an XLX reflector. If your reflector is configured with a transcoder, you can specify which configured modules will be transcoded. If you are building an XLX system with a transcoder, you can also specify which channels get transcoder support. There are also true/false flags to prevent G3 support and so that you can build executables that will support gdb debugging.
+
+You can support your own YSF frequency database. This is very useful for hot-spots that use YSF linking. These linked hot-spots can then use the *WiresX* command on their radios to be able to connect to any configured XLX module. Users can register their TX and RX frequency (typically the same for most hot-spot configurations) on http:<*xlx url*>/wiresx/login.php. Once their hot-spot is registered, XLX will return the correct frequency for their hot-spot when a *WiresX* command is sent to the reflector. You'll need to enable YSF auto-linking, specify a default module and define a database name, user and user password. When you write you XLX configuration, a database **configure.sql** script will be built to not only create the database and database user, but also the table for the hot-spot frequency data.
+
+Be sure to write out the configuration files and look over the up to seven different configration files that are created. The first file, reflector.cfg is the memory file for rconfig so that if you start that script again, it will remember how you left things. There are one or two `.h` files for the reflector and ambed and there are one or two `.mk` files for the reflector and ambed makefiles. You should **not** modify these files by hand unless you really know exactly how they work. The rconfig script will not start if it detects that an XLX or XRF server is already running. You can override this behavior in expert mode: `./rconfig expert`. If you do change the configuration after you have already compiled the code, it is safest if you clean the repo and then recompile.
+
+### Compling and installing your system
+
+After you have written your configutation files, you can build and install your system:
+
+```bash
+./radmin
+```
+
+Use this command to compile and install your system. It can also be used to uninstall your system. It will use the information in reflector.cfg to perform each task. This radmin menu can also perform other tasks like restarting the reflector or transcoder process. It can also be used to update the software, if the system is uninstalled.
+
+### Stoping and starting the services manually
+
+```bash
+sudo systemctl stop xlxd # (or xrfd)
+sudo systemctl stop ambed
+```
+
+You can start each component by replacing `stop` with `start`, or you can restart each by using `restart`.
+
+### Copy dashboard to /var/www
+
+There are two supplied, one for XRF systems and one for XLX systems.
+
+```bash
+sudo cp -r ~/xlxd/dashboard.xlx /var/www/db # or dashboard.xrf
+```
+
+Please note that your www root directory might be some place else. There is one file that needs configuration. Edit the copied files, not the ones from the repository:
+
+- **pgs/config.inc.php** - At a minimum set your email address, country and comment. **Do not** enable the calling home feature if you built an XRF reflector. This feature is for **XLX systems only**.
+
+If you have configured support of hot-spot frequency registation, recursively copy the **wiresx** directory where the index.php file is for your dashboard. Also from the build directory, create the database and database user and hot-spot frequency table:
+
+```bash
+sudo mysql < configure.sql
+```
+
+## Updating xlxd and ambed
+
+Updating can be performed entirely in the radmin script, but just in case there is a new version of the radmin script, you can start first with a simple `git pull`. If any .h or .cpp fiiles have updates, you can then start radmin and do a clean and compile and then uninstall and install: `cl, co, us, is`. Follow that with a `rl` to watch the reflector log, or an `rt` to watch the transcoder while it comes up.
+
+If rconfig was updated with the `git pull`, it might be wise to run it first to see if there have been any new options added to the code base. If so, be sure to write out the new configuration files before exiting rconfig. THen you can rebuild and reinstall your reflector.
+
+If you are change any configuration after your reflector has been compiled, be sure to do a clean/compile/uninstall/reinstall to sync your system to the new configuration.
+
+## Firewall settings
+
+XLX Server requires the following ports to be open and forwarded properly for in- and outgoing network traffic:
+
+- TCP port 80 (http) optional TCP port 443 (https)
+- UDP port 10002 (XLX interlink)
+- UDP port 42000 (YSF protocol)
+- UDP port 30001 (DExtra protocol)
+- UPD port 20001 (DPlus protocol)
+- UDP port 30051 (DCS protocol)
+- UDP port 8880 (DMR+ DMO mode)
+- UDP port 62030 (MMDVM protocol)
+- UDP port 10100 (AMBE controller port)
+- UDP port 10101 - 10199 (AMBE transcoding port) # only needed if your transcoder and reflector aren't running on the same domain.
+- UDP port 12345 - 12346 (Icom Terminal presence and request port)
+- UDP port 40000 (Icom Terminal dv port)
+
+## YSF Master Server
+
+Pay attention, the XLX Server acts as an YSF Master, which provides 26 wires-x rooms.
+It has nothing to do with the regular YSFReflector network, hence you don’t need to register your XLX at ysfreflector.de !
+
+## Copyright
+
+- Copyright © 2016 Jean-Luc Deltombe LX3JL and Luc Engelmann LX1IQ
+- Copyright © 2020 Thomas A. Early N7TAE
diff --git a/src/BMClient.cpp b/src/BMClient.cpp
new file mode 100644
index 0000000..843c2c4
--- /dev/null
+++ b/src/BMClient.cpp
@@ -0,0 +1,53 @@
+//
+// cbmclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 20/01/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include
+#include "Main.h"
+#include "BMClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CBmClient::CBmClient()
+{
+}
+
+CBmClient::CBmClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+}
+
+CBmClient::CBmClient(const CBmClient &client)
+ : CClient(client)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CBmClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < XLX_KEEPALIVE_TIMEOUT);
+}
diff --git a/src/BMClient.h b/src/BMClient.h
new file mode 100644
index 0000000..2c16f4c
--- /dev/null
+++ b/src/BMClient.h
@@ -0,0 +1,54 @@
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+
+// ulxd -- The ultimate Reflector
+// Copyright © 2021 Thomas A. Early N7TAE
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include "Client.h"
+#include "ULXClient.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CBmClient : public CClient
+{
+public:
+ // constructors
+ CBmClient();
+ CBmClient(const CCallsign &, const CIp &, char = ' ');
+ CBmClient(const CBmClient &);
+
+ // destructor
+ virtual ~CBmClient() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_XLX; }
+ int GetProtocolRevision(void) const { return XLX_PROTOCOL_REVISION_2; }
+ const char *GetProtocolName(void) const { return "XLX"; }
+ int GetCodec(void) const { return CODEC_AMBE2PLUS; }
+ bool IsPeer(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+
+ // reporting
+ void WriteXml(std::ofstream &) {}
+};
diff --git a/src/BMPeer.cpp b/src/BMPeer.cpp
new file mode 100644
index 0000000..1bd456c
--- /dev/null
+++ b/src/BMPeer.cpp
@@ -0,0 +1,67 @@
+//
+// cbmpeer.cpp
+// xlxd
+// Created by Jean-Luc Deltombe (LX3JL) on 10/12/2016.
+// Copyright © 2016 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "Reflector.h"
+#include "BMPeer.h"
+#include "BMClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+
+CBmPeer::CBmPeer()
+{
+}
+
+CBmPeer::CBmPeer(const CCallsign &callsign, const CIp &ip, const char *modules, const CVersion &version)
+ : CPeer(callsign, ip, modules, version)
+{
+ std::cout << "Adding BM peer" << std::endl;
+
+ // and construct all xlx clients
+ for ( unsigned i = 0; i < ::strlen(modules); i++ )
+ {
+ // create and append to vector
+ m_Clients.push_back(std::make_shared(callsign, ip, modules[i]));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CBmPeer::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < XLX_KEEPALIVE_TIMEOUT);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// revision helper
+
+int CBmPeer::GetProtocolRevision(const CVersion &version)
+{
+ return XLX_PROTOCOL_REVISION_2;
+}
diff --git a/src/BMPeer.h b/src/BMPeer.h
new file mode 100644
index 0000000..1170599
--- /dev/null
+++ b/src/BMPeer.h
@@ -0,0 +1,59 @@
+//
+// BMPeer.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 20/01/2017.
+// Copyright © 2016 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cbmpeer_h
+#define cbmpeer_h
+
+
+#include "Peer.h"
+#include "BMClient.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CBmPeer : public CPeer
+{
+public:
+ // constructors
+ CBmPeer();
+ CBmPeer(const CCallsign &, const CIp &, const char *, const CVersion &);
+ CBmPeer(const CBmPeer &) = delete;
+
+ // status
+ bool IsAlive(void) const;
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_XLX; }
+ const char *GetProtocolName(void) const { return "XLX"; }
+
+ // revision helper
+ static int GetProtocolRevision(const CVersion &);
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cbmpeer_h */
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
new file mode 100644
index 0000000..d5fef04
--- /dev/null
+++ b/src/Buffer.cpp
@@ -0,0 +1,267 @@
+//
+// cbuffer.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 02/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include
+#include "Buffer.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CBuffer::CBuffer(uint8_t *buffer, int len)
+{
+ m_data.resize(len);
+ ::memcpy(m_data.data(), buffer, len);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// set
+
+void CBuffer::Set(uint8_t *buffer, int len)
+{
+ m_data.resize(len);
+ ::memcpy(m_data.data(), buffer, len);
+}
+
+void CBuffer::Set(const char *sz)
+{
+ m_data.resize(::strlen(sz)+1);
+ ::strcpy((char *)m_data.data(), sz);
+}
+
+void CBuffer::Append(const uint8_t *buffer, int len)
+{
+ int n = (int)m_data.size();
+ m_data.resize(n+len);
+ ::memcpy(&(m_data.data()[n]), buffer, len);
+}
+
+void CBuffer::Append(uint8_t ui, int len)
+{
+ int n = (int)m_data.size();
+ m_data.resize(n+len);
+ ::memset(&(m_data.data()[n]), ui, len);
+}
+
+void CBuffer::Append(uint8_t ui)
+{
+ int n = (int)m_data.size();
+ m_data.resize(n+sizeof(uint8_t));
+ ::memcpy(&(m_data.data()[n]), &ui, sizeof(uint8_t));
+}
+
+void CBuffer::Append(uint16_t ui)
+{
+ int n = (int)m_data.size();
+ m_data.resize(n+sizeof(uint16_t));
+ ::memcpy(&(m_data.data()[n]), &ui, sizeof(uint16_t));
+}
+
+void CBuffer::Append(uint32_t ui)
+{
+ int n = (int)m_data.size();
+ m_data.resize(n+sizeof(uint32_t));
+ ::memcpy(&(m_data.data()[n]), &ui, sizeof(uint32_t));
+}
+
+void CBuffer::Append(const char *sz)
+{
+ Append((uint8_t *)sz, (int)strlen(sz));
+ Append((uint8_t)0x00);
+}
+
+void CBuffer::ReplaceAt(int i, uint8_t ui)
+{
+ if ( m_data.size() < (i+sizeof(uint8_t)) )
+ {
+ m_data.resize(i+sizeof(uint8_t));
+ }
+ *(uint8_t *)(&(m_data.data()[i])) = ui;
+}
+
+void CBuffer::ReplaceAt(int i, uint16_t ui)
+{
+ if ( m_data.size() < (i+sizeof(uint16_t)) )
+ {
+ m_data.resize(i+sizeof(uint16_t));
+ }
+ *(uint16_t *)(&(m_data.data()[i])) = ui;
+}
+
+void CBuffer::ReplaceAt(int i, uint32_t ui)
+{
+ if ( m_data.size() < (i+sizeof(uint32_t)) )
+ {
+ m_data.resize(i+sizeof(uint32_t));
+ }
+ *(uint32_t *)(&(m_data.data()[i])) = ui;
+}
+
+void CBuffer::ReplaceAt(int i, const uint8_t *ptr, int len)
+{
+ if ( m_data.size() < unsigned(i+len) )
+ {
+ m_data.resize(i+len);
+ }
+ ::memcpy(&(m_data.data()[i]), ptr, len);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+int CBuffer::Compare(uint8_t *buffer, int len) const
+{
+ int result = -1;
+ if ( m_data.size() >= unsigned(len) )
+ {
+ result = ::memcmp(m_data.data(), buffer, len);
+ }
+ return result;
+}
+
+int CBuffer::Compare(uint8_t *buffer, int off, int len) const
+{
+ int result = -1;
+ if ( m_data.size() >= unsigned(off+len) )
+ {
+ result = ::memcmp(&(m_data.data()[off]), buffer, len);
+ }
+ return result;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operator
+
+bool CBuffer::operator ==(const CBuffer &Buffer) const
+{
+ if ( m_data.size() == Buffer.m_data.size() )
+ {
+ return (::memcmp((const char *)m_data.data(), (const char *)Buffer.m_data.data(), m_data.size()) == 0);
+ }
+ return false;
+}
+
+bool CBuffer::operator ==(const char *sz) const
+{
+ if ( m_data.size() == ::strlen(sz) )
+ {
+ return (::memcmp((const char *)m_data.data(), sz, m_data.size()) == 0);
+ }
+ return false;
+}
+
+CBuffer::operator const char *() const
+{
+ return (const char *)m_data.data();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// debug
+
+void CBuffer::DebugDump(std::ofstream &debugout) const
+{
+ // dump an hex line
+ for ( unsigned int i = 0; i < m_data.size(); i++ )
+ {
+ char sz[16];
+ //sprintf(sz, "%02X", m_data.data()[i]);
+ sprintf(sz, "0x%02X", m_data.data()[i]);
+ debugout << sz;
+ if ( i == m_data.size()-1 )
+ {
+ debugout << std::endl;
+ }
+ else
+ {
+ debugout << ',';
+ }
+ }
+}
+
+void CBuffer::DebugDumpAscii(std::ofstream &debugout) const
+{
+ // dump an ascii line
+ for ( unsigned int i = 0; i < m_data.size(); i++ )
+ {
+ char c = m_data.data()[i];
+ if ( isascii(c) )
+ {
+ debugout << c;
+ }
+ else
+ {
+ debugout << '.';
+ }
+ if ( i == m_data.size()-1 )
+ {
+ debugout << std::endl;
+ }
+ }
+}
+
+void CBuffer::Dump(const std::string &title)
+{
+ std::cout << title << ":" << std::endl;
+
+ unsigned int offset = 0U;
+ unsigned int length = m_data.size();
+
+ while (length > 0U) {
+ std::string output;
+
+ unsigned int bytes = (length > 16U) ? 16U : length;
+
+ char temp[10U];
+ for (unsigned i = 0U; i < bytes; i++) {
+ ::sprintf(temp, "%02X ", m_data[offset + i]);
+ output += temp;
+ }
+
+ for (unsigned int i = bytes; i < 16U; i++)
+ output += " ";
+
+ output += " *";
+
+ for (unsigned i = 0U; i < bytes; i++) {
+ unsigned char c = m_data[offset + i];
+
+ if (::isprint(c))
+ output += c;
+ else
+ output += '.';
+ }
+
+ output += '*';
+
+ ::sprintf(temp, "%04X: ", offset);
+ std::cout << temp << output << std::endl;
+
+ offset += 16U;
+
+ if (length >= 16U)
+ length -= 16U;
+ else
+ length = 0U;
+ }
+}
diff --git a/src/Buffer.h b/src/Buffer.h
new file mode 100644
index 0000000..c9a6f5c
--- /dev/null
+++ b/src/Buffer.h
@@ -0,0 +1,85 @@
+//
+// Buffer.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 02/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cbuffer_h
+#define cbuffer_h
+
+#include
+#include
+#include "Main.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+class CBuffer
+{
+public:
+ CBuffer() {}
+ CBuffer(uint8_t *, int);
+
+ // destructor
+ virtual ~CBuffer() {};
+
+ // set
+ void Set(uint8_t *, int);
+ void Set(const char *);
+ void Append(const uint8_t *, int);
+ void Append(uint8_t, int);
+ void Append(uint8_t);
+ void Append(uint16_t);
+ void Append(uint32_t);
+ void Append(const char *);
+ void ReplaceAt(int, uint8_t);
+ void ReplaceAt(int, uint16_t);
+ void ReplaceAt(int, uint32_t);
+ void ReplaceAt(int, const uint8_t *, int);
+
+ // operation
+ int Compare(uint8_t *, int) const;
+ int Compare(uint8_t *, int, int) const;
+
+ // operator
+ bool operator ==(const CBuffer &) const;
+ bool operator ==(const char *) const;
+ operator const char *() const;
+
+ // debug
+ void DebugDump(std::ofstream &) const;
+ void DebugDumpAscii(std::ofstream &) const;
+ void Dump(const std::string &title);
+
+ // pass through
+ void clear() { m_data.clear(); }
+ std::vector::size_type size() const { return m_data.size(); }
+ uint8_t *data() { return m_data.data(); }
+ const uint8_t *data() const { return m_data.data(); }
+ void resize(std::vector::size_type len, uint8_t val = 0) { m_data.resize(len, val); }
+ uint8_t at(std::vector::size_type i) const { return m_data.at(i); }
+
+protected:
+ std::vector m_data;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cbuffer_h */
diff --git a/src/CBPTC19696.cpp b/src/CBPTC19696.cpp
new file mode 100644
index 0000000..e298340
--- /dev/null
+++ b/src/CBPTC19696.cpp
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2012 by Ian Wraith
+ * Copyright (C) 2015 by Jonathan Naylor G4KLX
+ *
+ * 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.
+ */
+
+#include "CBPT19696.h"
+
+#include "Hamming.h"
+#include "Utils.h"
+
+#include
+#include
+#include
+
+CBPTC19696::CBPTC19696()
+{
+}
+
+CBPTC19696::~CBPTC19696()
+{
+}
+
+// The main decode function
+void CBPTC19696::decode(const unsigned char* in, unsigned char* out)
+{
+ assert(in != nullptr);
+ assert(out != nullptr);
+
+ // Get the raw binary
+ decodeExtractBinary(in);
+
+ // Deinterleave
+ decodeDeInterleave();
+
+ // Error check
+ decodeErrorCheck();
+
+ // Extract Data
+ decodeExtractData(out);
+}
+
+// The main encode function
+void CBPTC19696::encode(const unsigned char* in, unsigned char* out)
+{
+ assert(in != nullptr);
+ assert(out != nullptr);
+
+ // Extract Data
+ encodeExtractData(in);
+
+ // Error check
+ encodeErrorCheck();
+
+ // Deinterleave
+ encodeInterleave();
+
+ // Get the raw binary
+ encodeExtractBinary(out);
+}
+
+void CBPTC19696::decodeExtractBinary(const unsigned char* in)
+{
+ // First block
+ CUtils::byteToBitsBE(in[0U], m_rawData + 0U);
+ CUtils::byteToBitsBE(in[1U], m_rawData + 8U);
+ CUtils::byteToBitsBE(in[2U], m_rawData + 16U);
+ CUtils::byteToBitsBE(in[3U], m_rawData + 24U);
+ CUtils::byteToBitsBE(in[4U], m_rawData + 32U);
+ CUtils::byteToBitsBE(in[5U], m_rawData + 40U);
+ CUtils::byteToBitsBE(in[6U], m_rawData + 48U);
+ CUtils::byteToBitsBE(in[7U], m_rawData + 56U);
+ CUtils::byteToBitsBE(in[8U], m_rawData + 64U);
+ CUtils::byteToBitsBE(in[9U], m_rawData + 72U);
+ CUtils::byteToBitsBE(in[10U], m_rawData + 80U);
+ CUtils::byteToBitsBE(in[11U], m_rawData + 88U);
+ CUtils::byteToBitsBE(in[12U], m_rawData + 96U);
+
+ // Handle the two bits
+ bool bits[8U];
+ CUtils::byteToBitsBE(in[20U], bits);
+ m_rawData[98U] = bits[6U];
+ m_rawData[99U] = bits[7U];
+
+ // Second block
+ CUtils::byteToBitsBE(in[21U], m_rawData + 100U);
+ CUtils::byteToBitsBE(in[22U], m_rawData + 108U);
+ CUtils::byteToBitsBE(in[23U], m_rawData + 116U);
+ CUtils::byteToBitsBE(in[24U], m_rawData + 124U);
+ CUtils::byteToBitsBE(in[25U], m_rawData + 132U);
+ CUtils::byteToBitsBE(in[26U], m_rawData + 140U);
+ CUtils::byteToBitsBE(in[27U], m_rawData + 148U);
+ CUtils::byteToBitsBE(in[28U], m_rawData + 156U);
+ CUtils::byteToBitsBE(in[29U], m_rawData + 164U);
+ CUtils::byteToBitsBE(in[30U], m_rawData + 172U);
+ CUtils::byteToBitsBE(in[31U], m_rawData + 180U);
+ CUtils::byteToBitsBE(in[32U], m_rawData + 188U);
+}
+
+// Deinterleave the raw data
+void CBPTC19696::decodeDeInterleave()
+{
+ for (unsigned int i = 0U; i < 196U; i++)
+ m_deInterData[i] = false;
+
+ // The first bit is R(3) which is not used so can be ignored
+ for (unsigned int a = 0U; a < 196U; a++)
+ {
+ // Calculate the interleave sequence
+ unsigned int interleaveSequence = (a * 181U) % 196U;
+ // Shuffle the data
+ m_deInterData[a] = m_rawData[interleaveSequence];
+ }
+}
+
+// Check each row with a Hamming (15,11,3) code and each column with a Hamming (13,9,3) code
+void CBPTC19696::decodeErrorCheck()
+{
+ bool fixing;
+ unsigned int count = 0U;
+ do
+ {
+ fixing = false;
+
+ // Run through each of the 15 columns
+ bool col[13U];
+ for (unsigned int c = 0U; c < 15U; c++)
+ {
+ unsigned int pos = c + 1U;
+ for (unsigned int a = 0U; a < 13U; a++)
+ {
+ col[a] = m_deInterData[pos];
+ pos = pos + 15U;
+ }
+
+ if (CHamming::decode1393(col))
+ {
+ unsigned int pos = c + 1U;
+ for (unsigned int a = 0U; a < 13U; a++)
+ {
+ m_deInterData[pos] = col[a];
+ pos = pos + 15U;
+ }
+
+ fixing = true;
+ }
+ }
+
+ // Run through each of the 9 rows containing data
+ for (unsigned int r = 0U; r < 9U; r++)
+ {
+ unsigned int pos = (r * 15U) + 1U;
+ if (CHamming::decode15113_2(m_deInterData + pos))
+ fixing = true;
+ }
+
+ count++;
+ }
+ while (fixing && count < 5U);
+}
+
+// Extract the 96 bits of payload
+void CBPTC19696::decodeExtractData(unsigned char* data) const
+{
+ bool bData[96U];
+ unsigned int pos = 0U;
+ for (unsigned int a = 4U; a <= 11U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 16U; a <= 26U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 31U; a <= 41U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 46U; a <= 56U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 61U; a <= 71U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 76U; a <= 86U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 91U; a <= 101U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 106U; a <= 116U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ for (unsigned int a = 121U; a <= 131U; a++, pos++)
+ bData[pos] = m_deInterData[a];
+
+ CUtils::bitsToByteBE(bData + 0U, data[0U]);
+ CUtils::bitsToByteBE(bData + 8U, data[1U]);
+ CUtils::bitsToByteBE(bData + 16U, data[2U]);
+ CUtils::bitsToByteBE(bData + 24U, data[3U]);
+ CUtils::bitsToByteBE(bData + 32U, data[4U]);
+ CUtils::bitsToByteBE(bData + 40U, data[5U]);
+ CUtils::bitsToByteBE(bData + 48U, data[6U]);
+ CUtils::bitsToByteBE(bData + 56U, data[7U]);
+ CUtils::bitsToByteBE(bData + 64U, data[8U]);
+ CUtils::bitsToByteBE(bData + 72U, data[9U]);
+ CUtils::bitsToByteBE(bData + 80U, data[10U]);
+ CUtils::bitsToByteBE(bData + 88U, data[11U]);
+}
+
+// Extract the 96 bits of payload
+void CBPTC19696::encodeExtractData(const unsigned char* in)
+{
+ bool bData[96U];
+ CUtils::byteToBitsBE(in[0U], bData + 0U);
+ CUtils::byteToBitsBE(in[1U], bData + 8U);
+ CUtils::byteToBitsBE(in[2U], bData + 16U);
+ CUtils::byteToBitsBE(in[3U], bData + 24U);
+ CUtils::byteToBitsBE(in[4U], bData + 32U);
+ CUtils::byteToBitsBE(in[5U], bData + 40U);
+ CUtils::byteToBitsBE(in[6U], bData + 48U);
+ CUtils::byteToBitsBE(in[7U], bData + 56U);
+ CUtils::byteToBitsBE(in[8U], bData + 64U);
+ CUtils::byteToBitsBE(in[9U], bData + 72U);
+ CUtils::byteToBitsBE(in[10U], bData + 80U);
+ CUtils::byteToBitsBE(in[11U], bData + 88U);
+
+ for (unsigned int i = 0U; i < 196U; i++)
+ m_deInterData[i] = false;
+
+ unsigned int pos = 0U;
+ for (unsigned int a = 4U; a <= 11U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 16U; a <= 26U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 31U; a <= 41U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 46U; a <= 56U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 61U; a <= 71U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 76U; a <= 86U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 91U; a <= 101U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 106U; a <= 116U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+
+ for (unsigned int a = 121U; a <= 131U; a++, pos++)
+ m_deInterData[a] = bData[pos];
+}
+
+// Check each row with a Hamming (15,11,3) code and each column with a Hamming (13,9,3) code
+void CBPTC19696::encodeErrorCheck()
+{
+
+ // Run through each of the 9 rows containing data
+ for (unsigned int r = 0U; r < 9U; r++)
+ {
+ unsigned int pos = (r * 15U) + 1U;
+ CHamming::encode15113_2(m_deInterData + pos);
+ }
+
+ // Run through each of the 15 columns
+ bool col[13U];
+ for (unsigned int c = 0U; c < 15U; c++)
+ {
+ unsigned int pos = c + 1U;
+ for (unsigned int a = 0U; a < 13U; a++)
+ {
+ col[a] = m_deInterData[pos];
+ pos = pos + 15U;
+ }
+
+ CHamming::encode1393(col);
+
+ pos = c + 1U;
+ for (unsigned int a = 0U; a < 13U; a++)
+ {
+ m_deInterData[pos] = col[a];
+ pos = pos + 15U;
+ }
+ }
+}
+
+// Interleave the raw data
+void CBPTC19696::encodeInterleave()
+{
+ for (unsigned int i = 0U; i < 196U; i++)
+ m_rawData[i] = false;
+
+ // The first bit is R(3) which is not used so can be ignored
+ for (unsigned int a = 0U; a < 196U; a++)
+ {
+ // Calculate the interleave sequence
+ unsigned int interleaveSequence = (a * 181U) % 196U;
+ // Unshuffle the data
+ m_rawData[interleaveSequence] = m_deInterData[a];
+ }
+}
+
+void CBPTC19696::encodeExtractBinary(unsigned char* data)
+{
+ // First block
+ CUtils::bitsToByteBE(m_rawData + 0U, data[0U]);
+ CUtils::bitsToByteBE(m_rawData + 8U, data[1U]);
+ CUtils::bitsToByteBE(m_rawData + 16U, data[2U]);
+ CUtils::bitsToByteBE(m_rawData + 24U, data[3U]);
+ CUtils::bitsToByteBE(m_rawData + 32U, data[4U]);
+ CUtils::bitsToByteBE(m_rawData + 40U, data[5U]);
+ CUtils::bitsToByteBE(m_rawData + 48U, data[6U]);
+ CUtils::bitsToByteBE(m_rawData + 56U, data[7U]);
+ CUtils::bitsToByteBE(m_rawData + 64U, data[8U]);
+ CUtils::bitsToByteBE(m_rawData + 72U, data[9U]);
+ CUtils::bitsToByteBE(m_rawData + 80U, data[10U]);
+ CUtils::bitsToByteBE(m_rawData + 88U, data[11U]);
+
+ // Handle the two bits
+ unsigned char byte;
+ CUtils::bitsToByteBE(m_rawData + 96U, byte);
+ data[12U] = (data[12U] & 0x3FU) | ((byte >> 0) & 0xC0U);
+ data[20U] = (data[20U] & 0xFCU) | ((byte >> 4) & 0x03U);
+
+ // Second block
+ CUtils::bitsToByteBE(m_rawData + 100U, data[21U]);
+ CUtils::bitsToByteBE(m_rawData + 108U, data[22U]);
+ CUtils::bitsToByteBE(m_rawData + 116U, data[23U]);
+ CUtils::bitsToByteBE(m_rawData + 124U, data[24U]);
+ CUtils::bitsToByteBE(m_rawData + 132U, data[25U]);
+ CUtils::bitsToByteBE(m_rawData + 140U, data[26U]);
+ CUtils::bitsToByteBE(m_rawData + 148U, data[27U]);
+ CUtils::bitsToByteBE(m_rawData + 156U, data[28U]);
+ CUtils::bitsToByteBE(m_rawData + 164U, data[29U]);
+ CUtils::bitsToByteBE(m_rawData + 172U, data[30U]);
+ CUtils::bitsToByteBE(m_rawData + 180U, data[31U]);
+ CUtils::bitsToByteBE(m_rawData + 188U, data[32U]);
+}
diff --git a/src/CBPTC19696.h b/src/CBPTC19696.h
new file mode 100644
index 0000000..cbf584c
--- /dev/null
+++ b/src/CBPTC19696.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 by Jonathan Naylor G4KLX
+ *
+ * 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.
+ */
+
+#if !defined(BPTC19696_H)
+#define BPTC19696_H
+
+class CBPTC19696
+{
+public:
+ CBPTC19696();
+ ~CBPTC19696();
+
+ void decode(const unsigned char* in, unsigned char* out);
+
+ void encode(const unsigned char* in, unsigned char* out);
+
+private:
+ bool m_rawData[196];
+ bool m_deInterData[196];
+
+ void decodeExtractBinary(const unsigned char* in);
+ void decodeErrorCheck();
+ void decodeDeInterleave();
+ void decodeExtractData(unsigned char* data) const;
+
+ void encodeExtractData(const unsigned char* in);
+ void encodeInterleave();
+ void encodeErrorCheck();
+ void encodeExtractBinary(unsigned char* data);
+};
+
+#endif
diff --git a/src/CNotificationQueue.h b/src/CNotificationQueue.h
new file mode 100644
index 0000000..3d50154
--- /dev/null
+++ b/src/CNotificationQueue.h
@@ -0,0 +1,68 @@
+//
+// NotificationQueue.h
+// xlxd
+//
+// Created by Jean-Luc on 05/12/2015.
+// Copyright © 2015 Jean-Luc. All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+
+
+#ifndef cnotificationqueue_h
+#define cnotificationqueue_h
+
+#include
+#include
+
+#include "Notification.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CNotificationQueue
+{
+public:
+ // constructor
+ CNotificationQueue() {}
+
+ // destructor
+ ~CNotificationQueue() {}
+
+ // lock
+ void Lock() { m_Mutex.lock(); }
+ void Unlock() { m_Mutex.unlock(); }
+
+ // pass thru
+ CNotification front() { return queue.front(); }
+ void pop() { queue.pop(); }
+ void push(CNotification note) { queue.push(note); }
+ bool empty() const { return queue.empty(); }
+
+protected:
+ // data
+ std::mutex m_Mutex;
+ std::queue queue;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cnotificationqueue_h */
diff --git a/src/CRC.cpp b/src/CRC.cpp
new file mode 100644
index 0000000..93b20d1
--- /dev/null
+++ b/src/CRC.cpp
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
+ *
+ * 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.
+ */
+
+#include "CRC.h"
+
+#include "Utils.h"
+
+#include
+#include
+#include
+#include
+
+const uint8_t CRC8_TABLE[] =
+{
+ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31,
+ 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
+ 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9,
+ 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+ 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1,
+ 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
+ 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE,
+ 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+ 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16,
+ 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
+ 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80,
+ 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+ 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8,
+ 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
+ 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10,
+ 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+ 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F,
+ 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
+ 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7,
+ 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+ 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF,
+ 0xFA, 0xFD, 0xF4, 0xF3, 0x01
+};
+
+const uint16_t CCITT16_TABLE1[] =
+{
+ 0x0000U, 0x1189U, 0x2312U, 0x329bU, 0x4624U, 0x57adU, 0x6536U, 0x74bfU,
+ 0x8c48U, 0x9dc1U, 0xaf5aU, 0xbed3U, 0xca6cU, 0xdbe5U, 0xe97eU, 0xf8f7U,
+ 0x1081U, 0x0108U, 0x3393U, 0x221aU, 0x56a5U, 0x472cU, 0x75b7U, 0x643eU,
+ 0x9cc9U, 0x8d40U, 0xbfdbU, 0xae52U, 0xdaedU, 0xcb64U, 0xf9ffU, 0xe876U,
+ 0x2102U, 0x308bU, 0x0210U, 0x1399U, 0x6726U, 0x76afU, 0x4434U, 0x55bdU,
+ 0xad4aU, 0xbcc3U, 0x8e58U, 0x9fd1U, 0xeb6eU, 0xfae7U, 0xc87cU, 0xd9f5U,
+ 0x3183U, 0x200aU, 0x1291U, 0x0318U, 0x77a7U, 0x662eU, 0x54b5U, 0x453cU,
+ 0xbdcbU, 0xac42U, 0x9ed9U, 0x8f50U, 0xfbefU, 0xea66U, 0xd8fdU, 0xc974U,
+ 0x4204U, 0x538dU, 0x6116U, 0x709fU, 0x0420U, 0x15a9U, 0x2732U, 0x36bbU,
+ 0xce4cU, 0xdfc5U, 0xed5eU, 0xfcd7U, 0x8868U, 0x99e1U, 0xab7aU, 0xbaf3U,
+ 0x5285U, 0x430cU, 0x7197U, 0x601eU, 0x14a1U, 0x0528U, 0x37b3U, 0x263aU,
+ 0xdecdU, 0xcf44U, 0xfddfU, 0xec56U, 0x98e9U, 0x8960U, 0xbbfbU, 0xaa72U,
+ 0x6306U, 0x728fU, 0x4014U, 0x519dU, 0x2522U, 0x34abU, 0x0630U, 0x17b9U,
+ 0xef4eU, 0xfec7U, 0xcc5cU, 0xddd5U, 0xa96aU, 0xb8e3U, 0x8a78U, 0x9bf1U,
+ 0x7387U, 0x620eU, 0x5095U, 0x411cU, 0x35a3U, 0x242aU, 0x16b1U, 0x0738U,
+ 0xffcfU, 0xee46U, 0xdcddU, 0xcd54U, 0xb9ebU, 0xa862U, 0x9af9U, 0x8b70U,
+ 0x8408U, 0x9581U, 0xa71aU, 0xb693U, 0xc22cU, 0xd3a5U, 0xe13eU, 0xf0b7U,
+ 0x0840U, 0x19c9U, 0x2b52U, 0x3adbU, 0x4e64U, 0x5fedU, 0x6d76U, 0x7cffU,
+ 0x9489U, 0x8500U, 0xb79bU, 0xa612U, 0xd2adU, 0xc324U, 0xf1bfU, 0xe036U,
+ 0x18c1U, 0x0948U, 0x3bd3U, 0x2a5aU, 0x5ee5U, 0x4f6cU, 0x7df7U, 0x6c7eU,
+ 0xa50aU, 0xb483U, 0x8618U, 0x9791U, 0xe32eU, 0xf2a7U, 0xc03cU, 0xd1b5U,
+ 0x2942U, 0x38cbU, 0x0a50U, 0x1bd9U, 0x6f66U, 0x7eefU, 0x4c74U, 0x5dfdU,
+ 0xb58bU, 0xa402U, 0x9699U, 0x8710U, 0xf3afU, 0xe226U, 0xd0bdU, 0xc134U,
+ 0x39c3U, 0x284aU, 0x1ad1U, 0x0b58U, 0x7fe7U, 0x6e6eU, 0x5cf5U, 0x4d7cU,
+ 0xc60cU, 0xd785U, 0xe51eU, 0xf497U, 0x8028U, 0x91a1U, 0xa33aU, 0xb2b3U,
+ 0x4a44U, 0x5bcdU, 0x6956U, 0x78dfU, 0x0c60U, 0x1de9U, 0x2f72U, 0x3efbU,
+ 0xd68dU, 0xc704U, 0xf59fU, 0xe416U, 0x90a9U, 0x8120U, 0xb3bbU, 0xa232U,
+ 0x5ac5U, 0x4b4cU, 0x79d7U, 0x685eU, 0x1ce1U, 0x0d68U, 0x3ff3U, 0x2e7aU,
+ 0xe70eU, 0xf687U, 0xc41cU, 0xd595U, 0xa12aU, 0xb0a3U, 0x8238U, 0x93b1U,
+ 0x6b46U, 0x7acfU, 0x4854U, 0x59ddU, 0x2d62U, 0x3cebU, 0x0e70U, 0x1ff9U,
+ 0xf78fU, 0xe606U, 0xd49dU, 0xc514U, 0xb1abU, 0xa022U, 0x92b9U, 0x8330U,
+ 0x7bc7U, 0x6a4eU, 0x58d5U, 0x495cU, 0x3de3U, 0x2c6aU, 0x1ef1U, 0x0f78U
+};
+
+const uint16_t CCITT16_TABLE2[] =
+{
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+};
+
+
+bool CCRC::checkFiveBit(bool* in, unsigned int tcrc)
+{
+ assert(in != nullptr);
+
+ unsigned int crc;
+ encodeFiveBit(in, crc);
+
+ return crc == tcrc;
+}
+
+void CCRC::encodeFiveBit(const bool* in, unsigned int& tcrc)
+{
+ assert(in != nullptr);
+
+ unsigned short total = 0U;
+ for (unsigned int i = 0U; i < 72U; i += 8U)
+ {
+ unsigned char c;
+ CUtils::bitsToByteBE(in + i, c);
+ total += c;
+ }
+
+ total %= 31U;
+
+ tcrc = total;
+}
+
+void CCRC::addCCITT162(unsigned char *in, unsigned int length)
+{
+ assert(in != nullptr);
+ assert(length > 2U);
+
+ union
+ {
+ uint16_t crc16;
+ uint8_t crc8[2U];
+ };
+
+ crc16 = 0U;
+
+ for (unsigned i = 0U; i < (length - 2U); i++)
+ crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]];
+
+ crc16 = ~crc16;
+
+ in[length - 1U] = crc8[0U];
+ in[length - 2U] = crc8[1U];
+}
+
+bool CCRC::checkCCITT162(const unsigned char *in, unsigned int length)
+{
+ assert(in != nullptr);
+ assert(length > 2U);
+
+ union
+ {
+ uint16_t crc16;
+ uint8_t crc8[2U];
+ };
+
+ crc16 = 0U;
+
+ for (unsigned i = 0U; i < (length - 2U); i++)
+ crc16 = (uint16_t(crc8[0U]) << 8) ^ CCITT16_TABLE2[crc8[1U] ^ in[i]];
+
+ crc16 = ~crc16;
+
+ return crc8[0U] == in[length - 1U] && crc8[1U] == in[length - 2U];
+}
+
+void CCRC::addCCITT161(unsigned char *in, unsigned int length)
+{
+ assert(in != nullptr);
+ assert(length > 2U);
+
+ union
+ {
+ uint16_t crc16;
+ uint8_t crc8[2U];
+ };
+
+ crc16 = 0xFFFFU;
+
+ for (unsigned int i = 0U; i < (length - 2U); i++)
+ crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]];
+
+ crc16 = ~crc16;
+
+ in[length - 2U] = crc8[0U];
+ in[length - 1U] = crc8[1U];
+}
+
+bool CCRC::checkCCITT161(const unsigned char *in, unsigned int length)
+{
+ assert(in != nullptr);
+ assert(length > 2U);
+
+ union
+ {
+ uint16_t crc16;
+ uint8_t crc8[2U];
+ };
+
+ crc16 = 0xFFFFU;
+
+ for (unsigned int i = 0U; i < (length - 2U); i++)
+ crc16 = uint16_t(crc8[1U]) ^ CCITT16_TABLE1[crc8[0U] ^ in[i]];
+
+ crc16 = ~crc16;
+
+ return crc8[0U] == in[length - 2U] && crc8[1U] == in[length - 1U];
+}
+
+unsigned char CCRC::crc8(const unsigned char *in, unsigned int length)
+{
+ assert(in != nullptr);
+
+ uint8_t crc = 0U;
+
+ for (unsigned int i = 0U; i < length; i++)
+ crc = CRC8_TABLE[crc ^ in[i]];
+
+ return crc;
+}
+
+unsigned char CCRC::addCRC(const unsigned char* in, unsigned int length)
+{
+ assert(in != nullptr);
+
+ unsigned char crc = 0U;
+
+ for (unsigned int i = 0U; i < length; i++)
+ crc += in[i];
+
+ return crc;
+}
diff --git a/src/CRC.h b/src/CRC.h
new file mode 100644
index 0000000..90bca98
--- /dev/null
+++ b/src/CRC.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
+ *
+ * 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.
+ */
+
+#if !defined(CRC_H)
+#define CRC_H
+
+class CCRC
+{
+public:
+ static bool checkFiveBit(bool* in, unsigned int tcrc);
+ static void encodeFiveBit(const bool* in, unsigned int& tcrc);
+
+ static void addCCITT161(unsigned char* in, unsigned int length);
+ static void addCCITT162(unsigned char* in, unsigned int length);
+
+ static bool checkCCITT161(const unsigned char* in, unsigned int length);
+ static bool checkCCITT162(const unsigned char* in, unsigned int length);
+
+ static unsigned char crc8(const unsigned char* in, unsigned int length);
+
+ static unsigned char addCRC(const unsigned char* in, unsigned int length);
+};
+
+#endif
diff --git a/src/Callsign.cpp b/src/Callsign.cpp
new file mode 100644
index 0000000..92e2466
--- /dev/null
+++ b/src/Callsign.cpp
@@ -0,0 +1,370 @@
+//
+// ccallsign.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include
+#include "cdmriddirfile.h"
+#include "DMRIdDirHttp.h"
+#include "Callsign.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CCallsign::CCallsign()
+{
+ // blank all
+ ::memset(m_Callsign, ' ', sizeof(m_Callsign));
+ ::memset(m_Suffix, ' ', sizeof(m_Suffix));
+ m_Module = ' ';
+#ifndef NO_XLX
+ m_uiDmrid = 0;
+#endif
+}
+
+CCallsign::CCallsign(const char *sz, uint32 dmrid)
+{
+ // blank all
+ ::memset(m_Callsign, ' ', sizeof(m_Callsign));
+ ::memset(m_Suffix, ' ', sizeof(m_Suffix));
+ m_Module = ' ';
+#ifndef NO_XLX
+ m_uiDmrid = dmrid;
+#endif
+
+ // and populate
+ if ( ::strlen(sz) > 0 )
+ {
+ // callsign valid
+ ::memcpy(m_Callsign, sz, MIN(strlen(sz), sizeof(m_Callsign)-1));
+ if ( strlen(sz) >= sizeof(m_Callsign) )
+ {
+ m_Module = sz[sizeof(m_Callsign)-1];
+ }
+#ifndef NO_XLX
+ // dmrid ok ?
+ if ( m_uiDmrid == 0 )
+ {
+ g_DmridDir.Lock();
+ {
+ m_uiDmrid = g_DmridDir.FindDmrid(*this);
+ }
+ g_DmridDir.Unlock();
+ }
+ }
+ else if ( m_uiDmrid != 0 )
+ {
+ g_DmridDir.Lock();
+ {
+ const CCallsign *callsign = g_DmridDir.FindCallsign(m_uiDmrid);
+ if ( callsign != nullptr )
+ {
+ ::memcpy(m_Callsign, callsign->m_Callsign, sizeof(m_Callsign));
+ }
+ }
+ g_DmridDir.Unlock();
+#endif
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CCallsign::IsValid(void) const
+{
+ bool valid = true;
+ int i;
+
+ // callsign
+ // first 3 chars are letter or number but cannot be all number
+ int iNum = 0;
+ for ( i = 0; i < 3; i++ )
+ {
+ valid &= IsLetter(m_Callsign[i]) || IsNumber(m_Callsign[i]);
+ if ( IsNumber(m_Callsign[i]) )
+ {
+ iNum++;
+ }
+ }
+ valid &= (iNum < 3);
+ // all remaining char are letter, number or space
+ for ( ; i < CALLSIGN_LEN; i++)
+ {
+ valid &= IsLetter(m_Callsign[i]) || IsNumber(m_Callsign[i]) || IsSpace(m_Callsign[i]);
+ }
+
+ // prefix
+ // all chars are number, uppercase or space
+ for ( i = 0; i < CALLSUFFIX_LEN; i++ )
+ {
+ valid &= IsLetter(m_Suffix[i]) || IsNumber(m_Suffix[i]) || IsSpace(m_Suffix[i]);
+ }
+
+ // module
+ // is an letter or space
+ valid &= IsLetter(m_Module) || IsSpace(m_Module);
+
+ // dmrid is not tested, as it can be nullptr
+ // if station does is not dmr registered
+
+ // done
+ return valid;
+}
+
+bool CCallsign::HasSuffix(void) const
+{
+ bool has = false;
+ for ( int i = 0; i < CALLSUFFIX_LEN; i++ )
+ {
+ has |= (m_Suffix[i] != ' ');
+ }
+ return has;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// set
+
+void CCallsign::SetCallsign(const char *sz, bool UpdateDmrid)
+{
+ // set callsign
+ ::memset(m_Callsign, ' ', sizeof(m_Callsign));
+ m_Module = ' ';
+ ::memcpy(m_Callsign, sz, MIN(strlen(sz), sizeof(m_Callsign)-1));
+ if ( strlen(sz) >= sizeof(m_Callsign) )
+ {
+ m_Module = sz[sizeof(m_Callsign)-1];
+ }
+#ifndef NO_XLX
+ // and update dmrid
+ if ( UpdateDmrid )
+ {
+ g_DmridDir.Lock();
+ {
+ m_uiDmrid = g_DmridDir.FindDmrid(*this);
+ }
+ g_DmridDir.Unlock();
+ }
+#endif
+}
+
+void CCallsign::SetCallsign(const uint8 *buffer, int len, bool UpdateDmrid)
+{
+ // set callsign
+ ::memset(m_Callsign, ' ', sizeof(m_Callsign));
+ m_Module = ' ';
+ ::memcpy(m_Callsign, buffer, MIN(len, (int)sizeof(m_Callsign)-1));
+ for ( unsigned i = 0; i < sizeof(m_Callsign); i++ )
+ {
+ if ( m_Callsign[i] == 0 )
+ {
+ m_Callsign[i] = ' ';
+ }
+ }
+ if ( (len >= (int)sizeof(m_Callsign)) && ((char)buffer[sizeof(m_Callsign)-1] != 0) )
+ {
+ m_Module = (char)buffer[sizeof(m_Callsign)-1];
+ }
+#ifndef NO_XLX
+ if ( UpdateDmrid )
+ {
+ g_DmridDir.Lock();
+ {
+ m_uiDmrid = g_DmridDir.FindDmrid(*this);
+ }
+ g_DmridDir.Unlock();
+ }
+#endif
+}
+
+#ifndef NO_XLX
+void CCallsign::SetDmrid(uint32 dmrid, bool UpdateCallsign)
+{
+ m_uiDmrid = dmrid;
+ if ( UpdateCallsign )
+ {
+ g_DmridDir.Lock();
+ {
+ const CCallsign *callsign = g_DmridDir.FindCallsign(dmrid);
+ if ( callsign != nullptr )
+ {
+ ::memcpy(m_Callsign, callsign->m_Callsign, sizeof(m_Callsign));
+ }
+ }
+ g_DmridDir.Unlock();
+ }
+}
+
+void CCallsign::SetDmrid(const uint8 *buffer, bool UpdateCallsign)
+{
+ char sz[9];
+ ::memcpy(sz, buffer, 8);
+ sz[8] = 0;
+ SetDmrid((uint32)::strtol(sz, nullptr, 16), UpdateCallsign);
+}
+#endif
+
+void CCallsign::SetModule(char c)
+{
+ m_Module = c;
+}
+
+
+void CCallsign::SetSuffix(const char *sz)
+{
+ ::memset(m_Suffix, ' ', sizeof(m_Suffix));
+ ::memcpy(m_Suffix, sz, MIN(strlen(sz), sizeof(m_Suffix)));
+}
+
+void CCallsign::SetSuffix(const uint8 *buffer, int len)
+{
+ len = MIN(len, (int)sizeof(m_Suffix));
+ ::memset(m_Suffix, ' ', sizeof(m_Suffix));
+ ::memcpy(m_Suffix, buffer, len);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// modify
+
+void CCallsign::PatchCallsign(int off, const uint8 *patch, int len)
+{
+ if ( off < CALLSIGN_LEN )
+ {
+ ::memcpy(m_Callsign, patch, MIN(len, (int)sizeof(m_Callsign) - off));
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// get
+
+void CCallsign::GetCallsign(uint8 *buffer) const
+{
+ ::memcpy(buffer, m_Callsign, sizeof(m_Callsign));
+ if ( HasModule() )
+ {
+ buffer[sizeof(m_Callsign)-1] = m_Module;
+ }
+}
+
+void CCallsign::GetCallsignString(char *sz) const
+{
+ unsigned i;
+ for ( i = 0; (i < sizeof(m_Callsign)) && (m_Callsign[i] != ' '); i++ )
+ {
+ sz[i] = m_Callsign[i];
+ }
+ sz[i] = 0;
+}
+
+void CCallsign::GetSuffix(uint8 *buffer) const
+{
+ ::memcpy(buffer, m_Suffix, sizeof(m_Suffix));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// compare
+
+bool CCallsign::HasSameCallsign(const CCallsign &Callsign) const
+{
+ return (::memcmp(m_Callsign, Callsign.m_Callsign, sizeof(m_Callsign)) == 0);
+}
+
+bool CCallsign::HasSameCallsignWithWildcard(const CCallsign &callsign) const
+{
+ bool same = true;
+ bool done = false;
+
+ for ( unsigned i = 0; (i < sizeof(m_Callsign)) && same && !done; i++ )
+ {
+ if ( !(done = ((m_Callsign[i] == '*') || (callsign[i] == '*'))) )
+ {
+ same &= (m_Callsign[i] == callsign[i]);
+ }
+ }
+ return same;
+}
+
+bool CCallsign::HasLowerCallsign(const CCallsign &Callsign) const
+{
+ return (::memcmp(m_Callsign, Callsign.m_Callsign, sizeof(m_Callsign)) < 0);
+}
+
+bool CCallsign::HasSameModule(const CCallsign &Callsign) const
+{
+ return (m_Module == Callsign.m_Module);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operators
+
+bool CCallsign::operator ==(const CCallsign &callsign) const
+{
+ return ((::memcmp(callsign.m_Callsign, m_Callsign, sizeof(m_Callsign)) == 0) && (m_Module == callsign.m_Module)
+ && (::memcmp(callsign.m_Suffix, m_Suffix, sizeof(m_Suffix)) == 0)
+#ifndef NO_XLX
+ && (m_uiDmrid == callsign.m_uiDmrid)
+#endif
+ );
+}
+
+CCallsign::operator const char *() const
+{
+ // empty
+ ::memset(m_sz, 0, sizeof(m_sz));
+ // callsign
+ ::memcpy(m_sz, m_Callsign, sizeof(m_Callsign));
+ // module
+ if ( HasModule() )
+ {
+ m_sz[sizeof(m_Callsign)] = m_Module;
+ }
+ // suffix
+ if ( HasSuffix() )
+ {
+ ::strcat(m_sz, " / ");
+ ::strncat(m_sz, m_Suffix, sizeof(m_Suffix));
+ }
+
+ // done
+ return m_sz;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// helper
+
+bool CCallsign::IsNumber(char c) const
+{
+ return ((c >= '0') && (c <= '9'));
+}
+
+bool CCallsign::IsLetter(char c) const
+{
+ return ((c >= 'A') && (c <= 'Z'));
+}
+
+bool CCallsign::IsSpace(char c) const
+{
+ return (c == ' ');
+}
diff --git a/src/Callsign.h b/src/Callsign.h
new file mode 100644
index 0000000..bb858ee
--- /dev/null
+++ b/src/Callsign.h
@@ -0,0 +1,101 @@
+//
+// Callsign.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef ccallsign_h
+#define ccallsign_h
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+#define CALLSIGN_LEN 8
+#define CALLSUFFIX_LEN 4
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CCallsign
+{
+public:
+ // contructors
+ CCallsign();
+ CCallsign(const char *, uint32 = 0);
+
+ // status
+ bool IsValid(void) const;
+ bool HasSuffix(void) const;
+ bool HasModule(void) const { return m_Module != ' '; }
+
+ // set
+ void SetCallsign(const char *, bool = true);
+ void SetCallsign(const uint8 *, int, bool = true);
+#ifndef NO_XLX
+ void SetDmrid(uint32, bool = true);
+ void SetDmrid(const uint8 *, bool = true);
+#endif
+ void SetModule(char);
+ void SetSuffix(const char *);
+ void SetSuffix(const uint8 *, int);
+
+ // modify
+ void PatchCallsign(int, const uint8 *, int);
+
+ // get
+ void GetCallsign(uint8 *) const;
+ void GetCallsignString(char *) const;
+#ifndef NO_XLX
+ uint32 GetDmrid(void) const { return m_uiDmrid; }
+#endif
+ void GetSuffix(uint8 *) const;
+ char GetModule(void) const { return m_Module; }
+
+ // compare
+ bool HasSameCallsign(const CCallsign &) const;
+ bool HasSameCallsignWithWildcard(const CCallsign &) const;
+ bool HasLowerCallsign(const CCallsign &) const;
+ bool HasSameModule(const CCallsign &) const;
+
+ // operators
+ bool operator ==(const CCallsign &) const;
+ operator const char *() const;
+
+protected:
+ // helper
+ bool IsNumber(char) const;
+ bool IsLetter(char) const;
+ bool IsSpace(char) const;
+
+protected:
+ // data
+ char m_Callsign[CALLSIGN_LEN];
+ char m_Suffix[CALLSUFFIX_LEN];
+ char m_Module;
+ mutable char m_sz[CALLSIGN_LEN+CALLSUFFIX_LEN+5];
+#ifndef NO_XLX
+ uint32 m_uiDmrid;
+#endif
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* ccallsign_h */
diff --git a/src/CallsignList.cpp b/src/CallsignList.cpp
new file mode 100644
index 0000000..c94a4c4
--- /dev/null
+++ b/src/CallsignList.cpp
@@ -0,0 +1,230 @@
+//
+// ccallsignlist.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 30/12/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include
+#include
+#include
+#include "Main.h"
+#include "CallsignList.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CCallsignList::CCallsignList()
+{
+ m_Filename = nullptr;
+ ::memset(&m_LastModTime, 0, sizeof(time_t));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// file io
+
+bool CCallsignList::LoadFromFile(const char *filename)
+{
+ bool ok = false;
+ char sz[256];
+ char szStar[2] = "*";
+
+ // and load
+ std::ifstream file (filename);
+ if ( file.is_open() )
+ {
+ Lock();
+
+ // empty list
+ m_Callsigns.clear();
+ // fill with file content
+ while ( file.getline(sz, sizeof(sz)).good() )
+ {
+ // remove leading & trailing spaces
+ char *szt = TrimWhiteSpaces(sz);
+
+ // crack it
+ if ( (::strlen(szt) > 0) && (szt[0] != '#') )
+ {
+ // 1st token is callsign
+ if ( (szt = ::strtok(szt, " ,\t")) != nullptr )
+ {
+ CCallsign callsign(szt);
+ // 2nd token is modules list
+ szt = ::strtok(nullptr, " ,\t");
+ // if token absent, use wildcard
+ if ( szt == nullptr )
+ {
+ szt = szStar;
+ }
+ // and add to list
+ m_Callsigns.push_back(CCallsignListItem(callsign, CIp(), szt));
+ }
+ }
+ }
+ // close file
+ file.close();
+
+ // keep file path
+ m_Filename = filename;
+
+ // update time
+ GetLastModTime(&m_LastModTime);
+
+ // and done
+ Unlock();
+ ok = true;
+ std::cout << "Gatekeeper loaded " << m_Callsigns.size() << " lines from " << filename << std::endl;
+ }
+ else
+ {
+ std::cout << "Gatekeeper cannot find " << filename << std::endl;
+ }
+
+ return ok;
+}
+
+bool CCallsignList::ReloadFromFile(void)
+{
+ bool ok = false;
+
+ if ( m_Filename != nullptr )
+ {
+ ok = LoadFromFile(m_Filename);
+ }
+ return ok;
+}
+
+bool CCallsignList::NeedReload(void)
+{
+ bool needReload = false;
+
+ time_t time;
+ if ( GetLastModTime(&time) )
+ {
+ needReload = time != m_LastModTime;
+ }
+ return needReload;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// compare
+
+bool CCallsignList::IsCallsignListedWithWildcard(const CCallsign &callsign) const
+{
+ for ( const auto &item : m_Callsigns )
+ {
+ if (item.HasSameCallsignWithWildcard(callsign))
+ return true;
+ }
+
+ return false;
+}
+
+bool CCallsignList::IsCallsignListedWithWildcard(const CCallsign &callsign, char module) const
+{
+ for ( const auto &item : m_Callsigns )
+ {
+ if (item.HasSameCallsignWithWildcard(callsign) && ((module == ' ') || item.HasModuleListed(module)) )
+ return true;
+ }
+
+ return false;
+}
+
+bool CCallsignList::IsCallsignListed(const CCallsign &callsign, char module) const
+{
+ for ( const auto &item : m_Callsigns )
+ {
+ if (item.HasSameCallsign(callsign) && item.HasModuleListed(module))
+ return true;
+ }
+
+ return false;
+}
+
+bool CCallsignList::IsCallsignListed(const CCallsign &callsign, char *modules) const
+{
+ for ( const auto &item : m_Callsigns )
+ {
+ if (item.HasSameCallsign(callsign) && item.CheckListedModules(modules))
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// find
+
+CCallsignListItem *CCallsignList::FindListItem(const CCallsign &Callsign)
+{
+ for ( auto &item : m_Callsigns )
+ {
+ if ( item.GetCallsign().HasSameCallsign(Callsign) )
+ {
+ return &item;
+ }
+ }
+
+ return nullptr;
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// helpers
+
+char *CCallsignList::TrimWhiteSpaces(char *str)
+{
+ char *end;
+
+ // Trim leading space & tabs
+ while((*str == ' ') || (*str == '\t')) str++;
+
+ // All spaces?
+ if(*str == 0)
+ return str;
+
+ // Trim trailing space, tab or lf
+ end = str + ::strlen(str) - 1;
+ while((end > str) && ((*end == ' ') || (*end == '\t') || (*end == '\r'))) end--;
+
+ // Write new null terminator
+ *(end+1) = 0;
+
+ return str;
+}
+
+bool CCallsignList::GetLastModTime(time_t *time)
+{
+ bool ok = false;
+
+ if ( m_Filename != nullptr )
+ {
+ struct stat fileStat;
+ if( ::stat(m_Filename, &fileStat) != -1 )
+ {
+ *time = fileStat.st_mtime;
+ ok = true;
+ }
+ }
+ return ok;
+}
diff --git a/src/CallsignList.h b/src/CallsignList.h
new file mode 100644
index 0000000..9d98654
--- /dev/null
+++ b/src/CallsignList.h
@@ -0,0 +1,81 @@
+//
+// CallsignList.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 30/12/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+
+#ifndef ccallsignlist_h
+#define ccallsignlist_h
+
+#include "Main.h"
+#include "CallsignListItem.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CCallsignList
+{
+public:
+ // constructor
+ CCallsignList();
+
+ // destructor
+ virtual ~CCallsignList() {}
+
+ // locks
+ void Lock(void) { m_Mutex.lock(); }
+ void Unlock(void) { m_Mutex.unlock(); }
+
+ // file io
+ virtual bool LoadFromFile(const char *);
+ bool ReloadFromFile(void);
+ bool NeedReload(void);
+
+ // compare
+ bool IsCallsignListedWithWildcard(const CCallsign &) const;
+ bool IsCallsignListedWithWildcard(const CCallsign &, char) const;
+ bool IsCallsignListed(const CCallsign &, char) const;
+ bool IsCallsignListed(const CCallsign &, char*) const;
+
+ // pass-thru
+ bool empty() const { return m_Callsigns.empty(); }
+ std::list::iterator begin() { return m_Callsigns.begin(); }
+ std::list::iterator end() { return m_Callsigns.end(); }
+
+ // find
+ CCallsignListItem *FindListItem(const CCallsign &);
+
+protected:
+ bool GetLastModTime(time_t *);
+ char *TrimWhiteSpaces(char *);
+
+ // data
+ std::mutex m_Mutex;
+ const char * m_Filename;
+ time_t m_LastModTime;
+ std::list m_Callsigns;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* ccallsignlist_h */
diff --git a/src/CallsignListItem.cpp b/src/CallsignListItem.cpp
new file mode 100644
index 0000000..3ec0697
--- /dev/null
+++ b/src/CallsignListItem.cpp
@@ -0,0 +1,146 @@
+//
+// ccallsignlistitem.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/01/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include
+#include "Main.h"
+#include "CallsignListItem.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CCallsignListItem::CCallsignListItem()
+{
+ ::memset(m_Modules, 0, sizeof(m_Modules));
+ ::memset(m_szUrl, 0, sizeof(m_szUrl));
+}
+
+CCallsignListItem::CCallsignListItem(const CCallsign &callsign, const CIp &ip, const char *modules)
+{
+ m_Callsign = callsign;
+ ::memset(m_szUrl, 0, sizeof(m_szUrl));
+ m_Ip = ip;
+ if ( modules != nullptr )
+ {
+ ::memset(m_Modules, 0, sizeof(m_Modules));
+ if ( modules[0] == '*' )
+ {
+ for ( char i = 0; i < NB_OF_MODULES; i++ )
+ {
+ m_Modules[i] = 'A' + i;
+ }
+ }
+ else
+ {
+ int n = MIN(::strlen(modules), sizeof(m_Modules)-1);
+ int j = 0;
+ for ( int i = 0; i < n; i++ )
+ {
+ if ( (modules[i] - 'A') < NB_OF_MODULES )
+ {
+ m_Modules[j++] = modules[i];
+ }
+ }
+ }
+ }
+}
+
+CCallsignListItem::CCallsignListItem(const CCallsign &callsign, const char *url, const char *modules)
+{
+ m_Callsign = callsign;
+ ::strncpy(m_szUrl, url, URL_MAXLEN);
+ m_Ip = CIp(m_szUrl);
+ if ( modules != nullptr )
+ {
+ ::memset(m_Modules, 0, sizeof(m_Modules));
+ if ( modules[0] == '*' )
+ {
+ for ( char i = 0; i < NB_OF_MODULES; i++ )
+ {
+ m_Modules[i] = 'A' + i;
+ }
+ }
+ else
+ {
+ int n = MIN(::strlen(modules), sizeof(m_Modules)-1);
+ int j = 0;
+ for ( int i = 0; i < n; i++ )
+ {
+ if ( (modules[i] - 'A') < NB_OF_MODULES )
+ {
+ m_Modules[j++] = modules[i];
+ }
+ }
+ }
+ }
+}
+
+CCallsignListItem::CCallsignListItem(const CCallsignListItem &item)
+{
+ m_Callsign = item.m_Callsign;
+ ::memcpy(m_szUrl, item.m_szUrl, sizeof(m_szUrl));
+ m_Ip = item.m_Ip;
+ ::memcpy(m_Modules, item.m_Modules, sizeof(m_Modules));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// compare
+
+bool CCallsignListItem::HasSameCallsign(const CCallsign &callsign) const
+{
+ return m_Callsign.HasSameCallsign(callsign);
+}
+
+bool CCallsignListItem::HasSameCallsignWithWildcard(const CCallsign &callsign) const
+{
+ return m_Callsign.HasSameCallsignWithWildcard(callsign);
+}
+
+bool CCallsignListItem::HasModuleListed(char module) const
+{
+ return (::strchr(m_Modules, (int)module) != nullptr);
+}
+
+bool CCallsignListItem::CheckListedModules(char *Modules) const
+{
+ bool listed = false;
+
+ if ( Modules != nullptr )
+ {
+ // build a list of common modules
+ char list[27];
+ list[0] = 0;
+ //
+ for ( unsigned i = 0; i < ::strlen(Modules); i++ )
+ {
+ if ( HasModuleListed(Modules[i]) )
+ {
+ ::strncat(list, &(Modules[i]), 1);
+ listed = true;
+ }
+ }
+ ::strcpy(Modules, list);
+ }
+ return listed;
+}
diff --git a/src/CallsignListItem.h b/src/CallsignListItem.h
new file mode 100644
index 0000000..cc33155
--- /dev/null
+++ b/src/CallsignListItem.h
@@ -0,0 +1,77 @@
+//
+// CallsignListItem.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/01/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef ccallsignlistitem_h
+#define ccallsignlistitem_h
+
+#include "Main.h"
+#include "Callsign.h"
+#include "IP.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+#define URL_MAXLEN 256
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CCallsignListItem
+{
+public:
+ // constructor
+ CCallsignListItem();
+ CCallsignListItem(const CCallsign &, const CIp &, const char *);
+ CCallsignListItem(const CCallsign &, const char *, const char *);
+ CCallsignListItem(const CCallsignListItem &);
+
+ // destructor
+ virtual ~CCallsignListItem() {}
+
+ // compare
+ bool HasSameCallsign(const CCallsign &) const;
+ bool HasSameCallsignWithWildcard(const CCallsign &) const;
+ bool HasModuleListed(char) const;
+ bool CheckListedModules(char*) const;
+
+ // get
+ const CCallsign &GetCallsign(void) const { return m_Callsign; }
+ const CIp &GetIp(void) const { return m_Ip; }
+ const char *GetModules(void) { return m_Modules; }
+
+ // update
+ void ResolveIp(void) { m_Ip = CIp(m_szUrl); }
+
+protected:
+ // data
+ CCallsign m_Callsign;
+ char m_szUrl[URL_MAXLEN+1];
+ CIp m_Ip;
+ char m_Modules[27];
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+#endif /* ccallsignlistitem_h */
diff --git a/src/Client.cpp b/src/Client.cpp
new file mode 100644
index 0000000..0ffbdb2
--- /dev/null
+++ b/src/Client.cpp
@@ -0,0 +1,122 @@
+//
+// cclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "Client.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CClient::CClient()
+{
+ m_ReflectorModule = ' ';
+ m_ModuleMastered = ' ';
+ m_LastKeepaliveTime.Now();
+ m_ConnectTime = std::time(nullptr);
+ m_LastHeardTime = std::time(nullptr);
+}
+
+CClient::CClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+{
+ m_ReflectorModule = reflectorModule;
+ m_Callsign = callsign;
+ m_Ip = ip;
+ m_ModuleMastered = ' ';
+ m_LastKeepaliveTime.Now();
+ m_ConnectTime = std::time(nullptr);
+ m_LastHeardTime = std::time(nullptr);
+}
+
+CClient::CClient(const CClient &client)
+{
+ m_Callsign = client.m_Callsign;
+ m_Ip = client.m_Ip;
+ m_ReflectorModule = client.m_ReflectorModule;
+ m_ModuleMastered = client.m_ModuleMastered;
+ m_LastKeepaliveTime = client.m_LastKeepaliveTime;
+ m_ConnectTime = client.m_ConnectTime;
+ m_LastHeardTime = client.m_LastHeardTime;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+void CClient::Alive(void)
+{
+ m_LastKeepaliveTime.Now();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operators
+
+bool CClient::operator ==(const CClient &client) const
+{
+ return ((client.m_Callsign == m_Callsign) &&
+ (client.m_Ip == m_Ip) &&
+ (client.m_ReflectorModule == m_ReflectorModule));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// reporting
+
+void CClient::WriteXml(std::ofstream &xmlFile)
+{
+ xmlFile << "" << std::endl;
+ xmlFile << "\t" << m_Callsign << "" << std::endl;
+ xmlFile << "\t" << m_Ip.GetAddress() << "" << std::endl;
+ xmlFile << "\t" << m_ReflectorModule << "" << std::endl;
+ xmlFile << "\t" << GetProtocolName() << "" << std::endl;
+ char mbstr[100];
+ if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_ConnectTime)))
+ {
+ xmlFile << "\t" << mbstr << "" << std::endl;
+ }
+ if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime)))
+ {
+ xmlFile << "\t" << mbstr << "" << std::endl;
+ }
+ xmlFile << "" << std::endl;
+}
+
+void CClient::GetJsonObject(char *Buffer)
+{
+ char sz[512];
+ char mbstr[100];
+ char cs[16];
+
+ if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime)))
+ {
+ m_Callsign.GetCallsignString(cs);
+
+ ::sprintf(sz, "{\"callsign\":\"%s\",\"module\":\"%c\",\"linkedto\":\"%c\",\"time\":\"%s\"}",
+ cs,
+ m_Callsign.GetModule(),
+ m_ReflectorModule,
+ mbstr);
+ ::strcat(Buffer, sz);
+ }
+}
diff --git a/src/Client.h b/src/Client.h
new file mode 100644
index 0000000..27eaf90
--- /dev/null
+++ b/src/Client.h
@@ -0,0 +1,103 @@
+//
+// Client.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cclient_h
+#define cclient_h
+
+#include "Timer.h"
+#include "IP.h"
+#include "Callsign.h"
+#include "Buffer.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+//
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CClient
+{
+public:
+ // constructors
+ CClient();
+ CClient(const CCallsign &, const CIp &, char = ' ');
+ CClient(const CClient &);
+
+ // destructor
+ virtual ~CClient() {};
+
+ // operators
+ bool operator ==(const CClient &) const;
+
+ // get
+ const CCallsign &GetCallsign(void) const { return m_Callsign; }
+ const CIp &GetIp(void) const { return m_Ip; }
+ bool HasModule(void) const { return m_Callsign.HasModule(); }
+ char GetModule(void) const { return m_Callsign.GetModule(); }
+ bool HasReflectorModule(void) const { return m_ReflectorModule != ' '; }
+ char GetReflectorModule(void) const { return m_ReflectorModule; }
+
+ // set
+ void SetModule(char c) { m_Callsign.SetModule(c); }
+ void SetReflectorModule(char c) { m_ReflectorModule = c; }
+
+ // identity
+ virtual int GetProtocol(void) const { return PROTOCOL_NONE; }
+ virtual int GetProtocolRevision(void) const { return 0; }
+ virtual int GetCodec(void) const { return CODEC_NONE; }
+ virtual const char *GetProtocolName(void) const { return "none"; }
+ virtual bool IsNode(void) const { return false; }
+ virtual bool IsPeer(void) const { return false; }
+ virtual bool IsDextraDongle(void) const { return false; }
+ virtual void SetDextraDongle(void) { }
+
+ // status
+ virtual void Alive(void);
+ virtual bool IsAlive(void) const { return false; }
+ virtual bool IsAMaster(void) const { return (m_ModuleMastered != ' '); }
+ virtual void SetMasterOfModule(char c) { m_ModuleMastered = c; }
+ virtual void NotAMaster(void) { m_ModuleMastered = ' '; }
+ virtual void Heard(void) { m_LastHeardTime = std::time(nullptr); }
+
+ // reporting
+ virtual void WriteXml(std::ofstream &);
+ virtual void GetJsonObject(char *);
+
+protected:
+ // data
+ CCallsign m_Callsign;
+ CIp m_Ip;
+
+ // linked to
+ char m_ReflectorModule;
+
+ // status
+ char m_ModuleMastered;
+ CTimePoint m_LastKeepaliveTime;
+ std::time_t m_ConnectTime;
+ std::time_t m_LastHeardTime;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cclient_h */
diff --git a/src/Clients.cpp b/src/Clients.cpp
new file mode 100644
index 0000000..5bdba73
--- /dev/null
+++ b/src/Clients.cpp
@@ -0,0 +1,247 @@
+//
+// cclients.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "Reflector.h"
+#include "Clients.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+
+CClients::CClients()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// destructors
+
+CClients::~CClients()
+{
+ m_Mutex.lock();
+ m_Clients.clear();
+ m_Mutex.unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// manage Clients
+
+void CClients::AddClient(std::shared_ptr client)
+{
+ // first check if client already exists
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if (*client == *(*it))
+ // if found, just do nothing
+ // so *client keep pointing on a valid object
+ // on function return
+ {
+ // delete new one
+ return;
+ }
+ }
+
+ // and append
+ m_Clients.push_back(client);
+ std::cout << "New client " << client->GetCallsign() << " at " << client->GetIp() << " added with protocol " << client->GetProtocolName();
+ if ( client->GetReflectorModule() != ' ' )
+ {
+ std::cout << " on module " << client->GetReflectorModule();
+ }
+ std::cout << std::endl;
+ // notify
+ g_Reflector.OnClientsChanged();
+}
+
+void CClients::RemoveClient(std::shared_ptr client)
+{
+ // look for the client
+ bool found = false;
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ // compare objetc pointers
+ if ( *it == client )
+ {
+ // found it !
+ if ( !(*it)->IsAMaster() )
+ {
+ // remove it
+ std::cout << "Client " << (*it)->GetCallsign() << " at " << (*it)->GetIp() << " removed with protocol " << (*it)->GetProtocolName();
+ if ( (*it)->GetReflectorModule() != ' ' )
+ {
+ std::cout << " on module " << (*it)->GetReflectorModule();
+ }
+ std::cout << std::endl;
+ m_Clients.erase(it);
+ // notify
+ g_Reflector.OnClientsChanged();
+ break;
+ }
+ }
+ }
+}
+
+bool CClients::IsClient(std::shared_ptr client) const
+{
+ for ( auto it=cbegin(); it!=cend(); it++ )
+ {
+ if (*it == client)
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// find Clients
+
+std::shared_ptr CClients::FindClient(const CIp &Ip)
+{
+ // find client
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if ( (*it)->GetIp() == Ip )
+ {
+ return *it;
+ }
+ }
+
+ // done
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindClient(const CIp &Ip, int Protocol)
+{
+ // find client
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if ( ((*it)->GetIp() == Ip) && ((*it)->GetProtocol() == Protocol))
+ {
+ return *it;
+ }
+ }
+
+ // done
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindClient(const CIp &Ip, int Protocol, char ReflectorModule)
+{
+ // find client
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if ( ((*it)->GetIp() == Ip) && ((*it)->GetReflectorModule() == ReflectorModule) && ((*it)->GetProtocol() == Protocol) )
+ {
+ return *it;
+ }
+ }
+
+ // done
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindClient(const CCallsign &Callsign, const CIp &Ip, int Protocol)
+{
+ // find client
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if ( (*it)->GetCallsign().HasSameCallsign(Callsign) && ((*it)->GetIp() == Ip) && ((*it)->GetProtocol() == Protocol) )
+ {
+ return *it;
+ }
+ }
+
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindClient(const CCallsign &Callsign, char module, const CIp &Ip, int Protocol)
+{
+ // find client
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if ( (*it)->GetCallsign().HasSameCallsign(Callsign) && ((*it)->GetModule() == module) && ((*it)->GetIp() == Ip) && ((*it)->GetProtocol() == Protocol) )
+ {
+ return *it;
+ }
+ }
+
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindClient(const CCallsign &Callsign, int Protocol)
+{
+ // find client
+ for ( auto it=begin(); it!=end(); it++ )
+ {
+ if ( ((*it)->GetProtocol() == Protocol) && (*it)->GetCallsign().HasSameCallsign(Callsign) )
+ {
+ return *it;
+ }
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// iterate on clients
+
+std::shared_ptr CClients::FindNextClient(int Protocol, std::list>::iterator &it)
+{
+ while ( it != end() )
+ {
+ if ( (*it)->GetProtocol() == Protocol )
+ {
+ return *it++;
+ }
+ it++;
+ }
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindNextClient(const CIp &Ip, int Protocol, std::list>::iterator &it)
+{
+ while ( it != end() )
+ {
+ if ( ((*it)->GetProtocol() == Protocol) && ((*it)->GetIp() == Ip) )
+ {
+ return *it++;
+ }
+ it++;
+ }
+ return nullptr;
+}
+
+std::shared_ptr CClients::FindNextClient(const CCallsign &Callsign, const CIp &Ip, int Protocol, std::list>::iterator &it)
+{
+ while ( it != end() )
+ {
+ if ( ((*it)->GetProtocol() == Protocol) && ((*it)->GetIp() == Ip) && (*it)->GetCallsign().HasSameCallsign(Callsign) )
+ {
+ return *it++;
+ }
+ it++;
+ }
+ return nullptr;
+}
diff --git a/src/Clients.h b/src/Clients.h
new file mode 100644
index 0000000..1b9f219
--- /dev/null
+++ b/src/Clients.h
@@ -0,0 +1,85 @@
+//
+// Clients.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cclients_h
+#define cclients_h
+
+#include "Client.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CClients
+{
+public:
+ // constructors
+ CClients();
+
+ // destructors
+ virtual ~CClients();
+
+ // locks
+ void Lock(void) { m_Mutex.lock(); }
+ void Unlock(void) { m_Mutex.unlock(); }
+
+ // manage Clients
+ int GetSize(void) const { return (int)m_Clients.size(); }
+ void AddClient(std::shared_ptr);
+ void RemoveClient(std::shared_ptr);
+ bool IsClient(std::shared_ptr) const;
+
+ // pass-thru
+ std::list>::iterator begin() { return m_Clients.begin(); }
+ std::list>::iterator end() { return m_Clients.end(); }
+ std::list>::const_iterator cbegin() const { return m_Clients.cbegin(); }
+ std::list>::const_iterator cend() const { return m_Clients.cend(); }
+
+ // find clients
+ std::shared_ptr FindClient(const CIp &);
+ std::shared_ptr FindClient(const CIp &, int);
+ std::shared_ptr FindClient(const CIp &, int, char);
+ std::shared_ptr FindClient(const CCallsign &, const CIp &, int);
+ std::shared_ptr FindClient(const CCallsign &, char, const CIp &, int);
+ std::shared_ptr FindClient(const CCallsign &, int);
+
+ // iterate on clients
+ std::shared_ptr FindNextClient(int, std::list>::iterator &);
+ std::shared_ptr FindNextClient(const CIp &, int, std::list>::iterator &);
+ std::shared_ptr FindNextClient(const CCallsign &, const CIp &, int, std::list>::iterator &);
+
+protected:
+ // data
+ std::mutex m_Mutex;
+ std::list> m_Clients;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cclients_h */
diff --git a/src/CodecStream.cpp b/src/CodecStream.cpp
new file mode 100644
index 0000000..df48f3f
--- /dev/null
+++ b/src/CodecStream.cpp
@@ -0,0 +1,266 @@
+//
+// ccodecstream.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 13/04/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "CodecStream.h"
+#include "DVFramePacket.h"
+#include "Reflector.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CCodecStream::CCodecStream(CPacketStream *PacketStream, uint16 uiId, uint8 uiCodecIn, uint8 uiCodecOut)
+{
+ keep_running = true;
+ m_uiStreamId = uiId;
+ m_uiPid = 0;
+ m_uiCodecIn = uiCodecIn;
+ m_uiCodecOut = uiCodecOut;
+ m_bConnected = false;
+ m_fPingMin = -1;
+ m_fPingMax = -1;
+ m_fPingSum = 0;
+ m_fPingCount = 0;
+ m_uiTotalPackets = 0;
+ m_uiTimeoutPackets = 0;
+ m_PacketStream = PacketStream;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// destructor
+
+CCodecStream::~CCodecStream()
+{
+ // close socket
+ m_Socket.Close();
+
+ // kill threads
+ keep_running = false;
+ if ( m_Future.valid() )
+ {
+ m_Future.get();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// initialization
+
+bool CCodecStream::Init(uint16 uiPort)
+{
+ m_bConnected = keep_running = false; // prepare for the worst
+
+ // create the send to address
+ m_uiPort = uiPort;
+ auto s = g_Reflector.GetTranscoderIp();
+ m_Ip.Initialize(strchr(s, ':') ? AF_INET6 : AF_INET, m_uiPort, s);
+
+ if (0 == strncasecmp(s, "none", 4))
+ {
+ return false; // the user has disabled the transcoder
+ }
+
+ // create socket address, family based on transcoder listen address
+#ifdef LISTEN_IPV4
+#ifdef LISTEN_IPV6
+ const auto paddr = (AF_INET == m_Ip.GetFamily()) ? g_Reflector.m_Address.GetV4Address(PROTOCOL_ANY) : g_Reflector.m_Address.GetV6Address(PROTOCOL_ANY);
+#else
+ const auto paddr = g_Reflector.m_Address.GetV4Address(PROTOCOL_ANY);
+#endif
+#else
+ const auto paddr = g_Reflector.m_Address.GetV6Address(PROTOCOL_ANY);
+#endif
+ CIp ip(m_Ip.GetFamily(), m_uiPort, paddr.c_str());
+
+ // create our socket
+ if (ip.IsSet())
+ {
+ if (! m_Socket.Open(ip))
+ {
+ std::cerr << "Error opening socket on IP address " << m_Ip << std::endl;
+ return false;
+ }
+ }
+ else
+ {
+ std::cerr << "Could not initialize Codec Stream on " << paddr << std::endl;
+ return false;
+ }
+
+ keep_running = m_bConnected = true;
+ m_Future = std::async(std::launch::async, &CCodecStream::Thread, this);
+
+ return true;
+}
+
+void CCodecStream::Close(void)
+{
+ // close socket
+ keep_running = m_bConnected = false;
+ m_Socket.Close();
+
+ // kill threads
+ if ( m_Future.valid() )
+ {
+ m_Future.get();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// get
+
+bool CCodecStream::IsEmpty(void) const
+{
+ return (m_LocalQueue.empty() && m_PacketStream->empty());
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// thread
+
+void CCodecStream::Thread()
+{
+ while (keep_running)
+ {
+ Task();
+ }
+}
+
+void CCodecStream::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ uint8 Ambe[AMBE_SIZE];
+ uint8 DStarSync[] = { 0x55,0x2D,0x16 };
+
+ // any packet from transcoder
+ if ( m_Socket.Receive(Buffer, Ip, 5) )
+ {
+ // crack
+ if ( IsValidAmbePacket(Buffer, Ambe) )
+ {
+ // tickle
+ m_TimeoutTimer.Now();
+
+ // update statistics
+ double ping = m_StatsTimer.DurationSinceNow();
+ if ( m_fPingMin == -1 )
+ {
+ m_fPingMin = ping;
+ m_fPingMax = ping;
+
+ }
+ else
+ {
+ m_fPingMin = MIN(m_fPingMin, ping);
+ m_fPingMax = MAX(m_fPingMax, ping);
+
+ }
+ m_fPingSum += ping;
+ m_fPingCount += 1;
+
+ // pop the original packet
+ if ( !m_LocalQueue.empty() )
+ {
+ auto Packet = m_LocalQueue.front();
+ auto Frame = (CDvFramePacket *)Packet.get();
+ m_LocalQueue.pop();
+ // todo: check the PID
+ // update content with transcoded ambe
+ Frame->SetAmbe(m_uiCodecOut, Ambe);
+ // tag syncs in DvData
+ if ( (m_uiCodecOut == CODEC_AMBEPLUS) && (Frame->GetPacketId() % 21) == 0 )
+ {
+ Frame->SetDvData(DStarSync);
+ }
+ // and push it back to client
+ m_PacketStream->Lock();
+ m_PacketStream->push(Packet);
+ m_PacketStream->Unlock();
+ }
+ else
+ {
+ std::cout << "Unexpected transcoded packet received from ambed" << std::endl;
+ }
+ }
+ }
+
+ // anything in our queue
+ while ( !empty() )
+ {
+ // yes, pop it from queue
+ auto Packet = front();
+ auto Frame = (CDvFramePacket *)Packet.get();
+ pop();
+
+ // yes, send to ambed
+ // this assume that thread pushing the Packet
+ // have verified that the CodecStream is connected
+ // and that the packet needs transcoding
+ m_StatsTimer.Now();
+ m_uiTotalPackets++;
+ EncodeAmbePacket(&Buffer, Frame->GetAmbe(m_uiCodecIn));
+ m_Socket.Send(Buffer, m_Ip, m_uiPort);
+
+ // and push to our local queue
+ m_LocalQueue.push(Packet);
+ }
+
+ // handle timeout
+ if ( !m_LocalQueue.empty() && (m_TimeoutTimer.DurationSinceNow() >= (TRANSCODER_AMBEPACKET_TIMEOUT/1000.0f)) )
+ {
+ //std::cout << "ambed packet timeout" << std::endl;
+ m_uiTimeoutPackets++;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+/// packet decoding helpers
+
+bool CCodecStream::IsValidAmbePacket(const CBuffer &Buffer, uint8 *Ambe)
+{
+ bool valid = false;
+
+ if ( (Buffer.size() == 11) && (Buffer.data()[0] == m_uiCodecOut) )
+ {
+ ::memcpy(Ambe, &(Buffer.data()[2]), 9);
+ valid = true;
+ }
+ return valid;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+/// packet encoding helpers
+
+void CCodecStream::EncodeAmbePacket(CBuffer *Buffer, const uint8 *Ambe)
+{
+ Buffer->clear();
+ Buffer->Append(m_uiCodecIn);
+ Buffer->Append(m_uiPid);
+ Buffer->Append((uint8 *)Ambe, 9);
+}
diff --git a/src/CodecStream.h b/src/CodecStream.h
new file mode 100644
index 0000000..3d08609
--- /dev/null
+++ b/src/CodecStream.h
@@ -0,0 +1,116 @@
+//
+// CodecStream.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 13/04/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef ccodecstream_h
+#define ccodecstream_h
+
+#include "Semaphore.h"
+#include "UDPSocket.h"
+#include "PacketQueue.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+// frame sizes
+#define AMBE_SIZE 9
+#define AMBEPLUS_SIZE 9
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CPacketStream;
+
+class CCodecStream : public CPacketQueue
+{
+public:
+ // constructor
+ CCodecStream(CPacketStream *, uint16, uint8, uint8);
+
+ // destructor
+ virtual ~CCodecStream();
+
+ // initialization
+ bool Init(uint16);
+ void Close(void);
+
+ // get
+ bool IsConnected(void) const { return m_bConnected; }
+ uint16 GetStreamId(void) const { return m_uiStreamId; }
+ double GetPingMin(void) const { return m_fPingMin; }
+ double GetPingMax(void) const { return m_fPingMax; }
+ double GetPingAve(void) const { return (m_fPingCount != 0) ? m_fPingSum/m_fPingCount : 0; }
+ uint32 GetTotalPackets(void) const { return m_uiTotalPackets; }
+ uint32 GetTimeoutPackets(void) const { return m_uiTimeoutPackets; }
+ bool IsEmpty(void) const;
+
+ // task
+ void Thread(void);
+ void Task(void);
+
+
+protected:
+ // packet decoding helpers
+ bool IsValidAmbePacket(const CBuffer &, uint8 *);
+
+ // packet encoding helpers
+ void EncodeAmbePacket(CBuffer *, const uint8 *);
+
+
+protected:
+ // data
+ uint16 m_uiStreamId;
+ uint16 m_uiPort;
+ uint8 m_uiPid;
+ uint8 m_uiCodecIn;
+ uint8 m_uiCodecOut;
+
+ // socket
+ CIp m_Ip;
+ CUdpSocket m_Socket;
+ bool m_bConnected;
+
+ // associated packet stream
+ CPacketStream *m_PacketStream;
+ CPacketQueue m_LocalQueue;
+
+ // thread
+ std::atomic keep_running;
+ std::future m_Future;
+ CTimePoint m_TimeoutTimer;
+ CTimePoint m_StatsTimer;
+
+ // statistics
+ double m_fPingMin;
+ double m_fPingMax;
+ double m_fPingSum;
+ double m_fPingCount;
+ uint32 m_uiTotalPackets;
+ uint32 m_uiTimeoutPackets;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* ccodecstream_h */
diff --git a/src/DCSClient.cpp b/src/DCSClient.cpp
new file mode 100644
index 0000000..2075384
--- /dev/null
+++ b/src/DCSClient.cpp
@@ -0,0 +1,52 @@
+//
+// cdcsclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 07/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "DCSClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CDcsClient::CDcsClient()
+{
+}
+
+CDcsClient::CDcsClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+}
+
+CDcsClient::CDcsClient(const CDcsClient &client)
+ : CClient(client)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CDcsClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < DCS_KEEPALIVE_TIMEOUT);
+}
diff --git a/src/DCSClient.h b/src/DCSClient.h
new file mode 100644
index 0000000..bf58065
--- /dev/null
+++ b/src/DCSClient.h
@@ -0,0 +1,60 @@
+//
+// DCSClient.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 07/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdcsclient_h
+#define cdcsclient_h
+
+#include "Client.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDcsClient : public CClient
+{
+public:
+ // constructors
+ CDcsClient();
+ CDcsClient(const CCallsign &, const CIp &, char = ' ');
+ CDcsClient(const CDcsClient &);
+
+ // destructor
+ virtual ~CDcsClient() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_DCS; }
+ const char *GetProtocolName(void) const { return "DCS"; }
+ int GetCodec(void) const { return CODEC_AMBEPLUS; }
+ bool IsNode(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+#endif /* cdcsclient_h */
diff --git a/src/DCSProtocol.cpp b/src/DCSProtocol.cpp
new file mode 100644
index 0000000..c09476d
--- /dev/null
+++ b/src/DCSProtocol.cpp
@@ -0,0 +1,513 @@
+//
+// cdcsprotocol.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 07/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "DCSClient.h"
+#include "cdcsprotocol.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CDcsProtocol::Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6)
+{
+ // base class
+ if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
+ return false;
+
+ // update time
+ m_LastKeepaliveTime.Now();
+
+ // done
+ return true;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// task
+
+void CDcsProtocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+ char ToLinkModule;
+ std::unique_ptr Header;
+ std::unique_ptr Frame;
+
+ // handle incoming packets
+#if DSTAR_IPV6==true
+#if DSTAR_IPV4==true
+ if ( ReceiveDS(Buffer, Ip, 20) )
+#else
+ if ( Receive6(Buffer, Ip, 20) )
+#endif
+#else
+ if ( Receive4(Buffer, Ip, 20) )
+#endif
+ {
+ // crack the packet
+ if ( IsValidDvPacket(Buffer, Header, Frame) )
+ {
+ // callsign muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, PROTOCOL_DCS, Header->GetRpt2Module()) )
+ {
+ OnDvHeaderPacketIn(Header, Ip);
+
+ if ( !Frame->IsLastPacket() )
+ {
+ //std::cout << "DCS DV frame" << std::endl;
+ OnDvFramePacketIn(Frame, &Ip);
+ }
+ else
+ {
+ //std::cout << "DCS DV last frame" << std::endl;
+ OnDvLastFramePacketIn((std::unique_ptr &)Frame, &Ip);
+ }
+ }
+ }
+ else if ( IsValidConnectPacket(Buffer, &Callsign, &ToLinkModule) )
+ {
+ std::cout << "DCS connect packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << std::endl;
+
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, PROTOCOL_DCS) && g_Reflector.IsValidModule(ToLinkModule) )
+ {
+ // valid module ?
+ if ( g_Reflector.IsValidModule(ToLinkModule) )
+ {
+ // acknowledge the request
+ EncodeConnectAckPacket(Callsign, ToLinkModule, &Buffer);
+ Send(Buffer, Ip);
+
+ // create the client and append
+ g_Reflector.GetClients()->AddClient(std::make_shared(Callsign, Ip, ToLinkModule));
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ std::cout << "DCS node " << Callsign << " connect attempt on non-existing module" << std::endl;
+
+ // deny the request
+ EncodeConnectNackPacket(Callsign, ToLinkModule, &Buffer);
+ Send(Buffer, Ip);
+ }
+ }
+ else
+ {
+ // deny the request
+ EncodeConnectNackPacket(Callsign, ToLinkModule, &Buffer);
+ Send(Buffer, Ip);
+ }
+
+ }
+ else if ( IsValidDisconnectPacket(Buffer, &Callsign) )
+ {
+ std::cout << "DCS disconnect packet from " << Callsign << " at " << Ip << std::endl;
+
+ // find client
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Ip, PROTOCOL_DCS);
+ if ( client != nullptr )
+ {
+ // remove it
+ clients->RemoveClient(client);
+ // and acknowledge the disconnect
+ EncodeConnectNackPacket(Callsign, ' ', &Buffer);
+ Send(Buffer, Ip);
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else if ( IsValidKeepAlivePacket(Buffer, &Callsign) )
+ {
+ //std::cout << "DCS keepalive packet from " << Callsign << " at " << Ip << std::endl;
+
+ // find all clients with that callsign & ip and keep them alive
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(Callsign, Ip, PROTOCOL_DCS, it)) != nullptr )
+ {
+ client->Alive();
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else if ( IsIgnorePacket(Buffer) )
+ {
+ // valid but ignore packet
+ //std::cout << "DCS ignored packet from " << Ip << std::endl;
+ }
+ else
+ {
+ // invalid packet
+ std::string title("Unknown DCS packet from ");
+ title += Ip.GetAddress();
+ Buffer.Dump(title);
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+ // keep client alive
+ if ( m_LastKeepaliveTime.DurationSinceNow() > DCS_KEEPALIVE_PERIOD )
+ {
+ //
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.Now();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CDcsProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip)
+{
+ // find the stream
+ CPacketStream *stream = GetStream(Header->GetStreamId());
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ // no stream open yet, open a new one
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+
+ // find this client
+ std::shared_ptrclient = g_Reflector.GetClients()->FindClient(Ip, PROTOCOL_DCS);
+ if ( client )
+ {
+ // get client callsign
+ rpt1 = client->GetCallsign();
+ // and try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams.push_back(stream);
+ }
+ }
+ // release
+ g_Reflector.ReleaseClients();
+
+ // update last heard
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CDcsProtocol::HandleQueue(void)
+{
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // get the packet
+ auto packet = m_Queue.front();
+ m_Queue.pop();
+
+ // get our sender's id
+ int iModId = g_Reflector.GetModuleIndex(packet->GetModuleId());
+
+ // check if it's header and update cache
+ if ( packet->IsDvHeader() )
+ {
+ // this relies on queue feeder setting valid module id
+ m_StreamsCache[iModId].m_dvHeader = CDvHeaderPacket((const CDvHeaderPacket &)*packet);
+ m_StreamsCache[iModId].m_iSeqCounter = 0;
+ }
+ else
+ {
+ // encode it
+ CBuffer buffer;
+ if ( packet->IsLastPacket() )
+ {
+ EncodeDvLastPacket(
+ m_StreamsCache[iModId].m_dvHeader,
+ (const CDvFramePacket &)*packet,
+ m_StreamsCache[iModId].m_iSeqCounter++,
+ &buffer);
+ }
+ else if ( packet->IsDvFrame() )
+ {
+ EncodeDvPacket(
+ m_StreamsCache[iModId].m_dvHeader,
+ (const CDvFramePacket &)*packet,
+ m_StreamsCache[iModId].m_iSeqCounter++,
+ &buffer);
+ }
+
+ // send it
+ if ( buffer.size() > 0 )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DCS, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == packet->GetModuleId()) )
+ {
+ // no, send the packet
+ Send(buffer, client->GetIp());
+
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+ }
+ m_Queue.Unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CDcsProtocol::HandleKeepalives(void)
+{
+ // DCS protocol sends and monitors keepalives packets
+ // event if the client is currently streaming
+ // so, send keepalives to all
+ CBuffer keepalive1;
+ EncodeKeepAlivePacket(&keepalive1);
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DCS, it)) != nullptr )
+ {
+ // encode client's specific keepalive packet
+ CBuffer keepalive2;
+ EncodeKeepAlivePacket(&keepalive2, client);
+
+ // send keepalive
+ Send(keepalive1, client->GetIp());
+ Send(keepalive2, client->GetIp());
+
+ // is this client busy ?
+ if ( client->IsAMaster() )
+ {
+ // yes, just tickle it
+ client->Alive();
+ }
+ // check it's still with us
+ else if ( !client->IsAlive() )
+ {
+ // no, disconnect
+ CBuffer disconnect;
+ EncodeDisconnectPacket(&disconnect, client);
+ Send(disconnect, client->GetIp());
+
+ // remove it
+ std::cout << "DCS client " << client->GetCallsign() << " keepalive timeout" << std::endl;
+ clients->RemoveClient(client);
+ }
+
+ }
+ g_Reflector.ReleaseClients();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet decoding helpers
+
+bool CDcsProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsign, char *reflectormodule)
+{
+ bool valid = false;
+ if ( Buffer.size() == 519 )
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ callsign->SetModule(Buffer.data()[8]);
+ *reflectormodule = Buffer.data()[9];
+ valid = (callsign->IsValid() && IsLetter(*reflectormodule));
+ }
+ return valid;
+}
+
+bool CDcsProtocol::IsValidDisconnectPacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ bool valid = false;
+ if ((Buffer.size() == 11) && (Buffer.data()[9] == ' '))
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ callsign->SetModule(Buffer.data()[8]);
+ valid = callsign->IsValid();
+ }
+ else if ((Buffer.size() == 19) && (Buffer.data()[9] == ' ') && (Buffer.data()[10] == 0x00))
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ callsign->SetModule(Buffer.data()[8]);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDcsProtocol::IsValidKeepAlivePacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ bool valid = false;
+ if ( (Buffer.size() == 17) || (Buffer.size() == 15) || (Buffer.size() == 22) )
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDcsProtocol::IsValidDvPacket(const CBuffer &Buffer, std::unique_ptr &header, std::unique_ptr &frame)
+{
+ uint8 tag[] = { '0','0','0','1' };
+
+ if ( (Buffer.size() >= 100) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ // get the header
+ header = std::unique_ptr(new CDvHeaderPacket((struct dstar_header *)&(Buffer.data()[4]), *((uint16 *)&(Buffer.data()[43])), 0x80));
+
+ // get the frame
+ if ( Buffer.data()[45] & 0x40U )
+ {
+ // it's the last frame
+ frame = std::unique_ptr(new CDvLastFramePacket((struct dstar_dvframe *)&(Buffer.data()[46]), *((uint16 *)&(Buffer.data()[43])), Buffer.data()[45]));
+ }
+ else
+ {
+ // it's a regular DV frame
+ frame = std::unique_ptr(new CDvFramePacket((struct dstar_dvframe *)&(Buffer.data()[46]), *((uint16 *)&(Buffer.data()[43])), Buffer.data()[45]));
+ }
+
+ // check validity of packets
+ if ( header && header->IsValid() && frame && frame->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CDcsProtocol::IsIgnorePacket(const CBuffer &Buffer)
+{
+ uint8 tag[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, };
+
+ if ( Buffer.size() == 15 && Buffer.Compare(tag, sizeof(tag)) == 0 )
+ return true;
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet encoding helpers
+
+void CDcsProtocol::EncodeKeepAlivePacket(CBuffer *Buffer)
+{
+ Buffer->Set(GetReflectorCallsign());
+}
+
+void CDcsProtocol::EncodeKeepAlivePacket(CBuffer *Buffer, std::shared_ptrClient)
+{
+ uint8 tag[] = { 0x0A,0x00,0x20,0x20 };
+
+ Buffer->Set((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN-1);
+ Buffer->Append((uint8)Client->GetReflectorModule());
+ Buffer->Append((uint8)' ');
+ Buffer->Append((uint8 *)(const char *)Client->GetCallsign(), CALLSIGN_LEN-1);
+ Buffer->Append((uint8)Client->GetModule());
+ Buffer->Append((uint8)Client->GetModule());
+ Buffer->Append(tag, sizeof(tag));
+}
+
+void CDcsProtocol::EncodeConnectAckPacket(const CCallsign &Callsign, char ReflectorModule, CBuffer *Buffer)
+{
+ uint8 tag[] = { 'A','C','K',0x00 };
+ uint8 cs[CALLSIGN_LEN];
+
+ Callsign.GetCallsign(cs);
+ Buffer->Set(cs, CALLSIGN_LEN-1);
+ Buffer->Append((uint8)' ');
+ Buffer->Append((uint8)Callsign.GetModule());
+ Buffer->Append((uint8)ReflectorModule);
+ Buffer->Append(tag, sizeof(tag));
+}
+
+void CDcsProtocol::EncodeConnectNackPacket(const CCallsign &Callsign, char ReflectorModule, CBuffer *Buffer)
+{
+ uint8 tag[] = { 'N','A','K',0x00 };
+ uint8 cs[CALLSIGN_LEN];
+
+ Callsign.GetCallsign(cs);
+ Buffer->Set(cs, CALLSIGN_LEN-1);
+ Buffer->Append((uint8)' ');
+ Buffer->Append((uint8)Callsign.GetModule());
+ Buffer->Append((uint8)ReflectorModule);
+ Buffer->Append(tag, sizeof(tag));
+}
+
+void CDcsProtocol::EncodeDisconnectPacket(CBuffer *Buffer, std::shared_ptrClient)
+{
+ Buffer->Set((uint8 *)(const char *)Client->GetCallsign(), CALLSIGN_LEN-1);
+ Buffer->Append((uint8)' ');
+ Buffer->Append((uint8)Client->GetModule());
+ Buffer->Append((uint8)0x00);
+ Buffer->Append((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN-1);
+ Buffer->Append((uint8)' ');
+ Buffer->Append((uint8)0x00);
+}
+
+void CDcsProtocol::EncodeDvPacket(const CDvHeaderPacket &Header, const CDvFramePacket &DvFrame, uint32 iSeq, CBuffer *Buffer) const
+{
+ uint8 tag[] = { '0','0','0','1' };
+ struct dstar_header DstarHeader;
+
+ Header.ConvertToDstarStruct(&DstarHeader);
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append((uint8 *)&DstarHeader, sizeof(struct dstar_header) - sizeof(uint16));
+ Buffer->Append(DvFrame.GetStreamId());
+ Buffer->Append((uint8)(DvFrame.GetPacketId() % 21));
+ Buffer->Append((uint8 *)DvFrame.GetAmbe(), AMBE_SIZE);
+ Buffer->Append((uint8 *)DvFrame.GetDvData(), DVDATA_SIZE);
+ Buffer->Append((uint8)((iSeq >> 0) & 0xFF));
+ Buffer->Append((uint8)((iSeq >> 8) & 0xFF));
+ Buffer->Append((uint8)((iSeq >> 16) & 0xFF));
+ Buffer->Append((uint8)0x01);
+ Buffer->Append((uint8)0x00, 38);
+}
+
+void CDcsProtocol::EncodeDvLastPacket(const CDvHeaderPacket &Header, const CDvFramePacket &DvFrame, uint32 iSeq, CBuffer *Buffer) const
+{
+ EncodeDvPacket(Header, DvFrame, iSeq, Buffer);
+ (Buffer->data())[45] |= 0x40;
+}
diff --git a/src/DCSProtocol.h b/src/DCSProtocol.h
new file mode 100644
index 0000000..d802bcc
--- /dev/null
+++ b/src/DCSProtocol.h
@@ -0,0 +1,94 @@
+//
+// cdcsprotocol.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 07/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdcsprotocol_h
+#define cdcsprotocol_h
+
+#include "Timer.h"
+#include "DCSProtocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+#include "DVLastFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDcsStreamCacheItem
+{
+public:
+ CDcsStreamCacheItem() { m_iSeqCounter = 0; }
+ ~CDcsStreamCacheItem() {}
+
+ CDvHeaderPacket m_dvHeader;
+ uint32 m_iSeqCounter;
+};
+
+class CDcsProtocol : public CProtocol
+{
+public:
+ // initialization
+ bool Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6);
+
+ // task
+ void Task(void);
+
+protected:
+ // queue helper
+ void HandleQueue(void);
+
+ // keepalive helpers
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &);
+
+ // packet decoding helpers
+ bool IsValidConnectPacket(const CBuffer &, CCallsign *, char *);
+ bool IsValidDisconnectPacket(const CBuffer &, CCallsign *);
+ bool IsValidKeepAlivePacket(const CBuffer &, CCallsign *);
+ bool IsValidDvPacket(const CBuffer &, std::unique_ptr &, std::unique_ptr &);
+ bool IsIgnorePacket(const CBuffer &);
+
+ // packet encoding helpers
+ void EncodeKeepAlivePacket(CBuffer *);
+ void EncodeKeepAlivePacket(CBuffer *, std::shared_ptr);
+ void EncodeConnectAckPacket(const CCallsign &, char, CBuffer *);
+ void EncodeConnectNackPacket(const CCallsign &, char, CBuffer *);
+ void EncodeDisconnectPacket(CBuffer *, std::shared_ptr);
+ void EncodeDvPacket(const CDvHeaderPacket &, const CDvFramePacket &, uint32, CBuffer *) const;
+ void EncodeDvLastPacket(const CDvHeaderPacket &, const CDvFramePacket &, uint32, CBuffer *) const;
+
+protected:
+ // for keep alive
+ CTimePoint m_LastKeepaliveTime;
+
+ // for queue header caches
+ std::array m_StreamsCache;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdcsprotocol_h */
diff --git a/src/DExtraClient.cpp b/src/DExtraClient.cpp
new file mode 100644
index 0000000..20fa003
--- /dev/null
+++ b/src/DExtraClient.cpp
@@ -0,0 +1,55 @@
+//
+// cdextraclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "DExtraClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CDextraClient::CDextraClient()
+{
+ m_ProtRev = 0;
+}
+
+CDextraClient::CDextraClient(const CCallsign &callsign, const CIp &ip, char reflectorModule, int protRev)
+ : CClient(callsign, ip, reflectorModule)
+{
+ m_ProtRev = protRev;
+}
+
+CDextraClient::CDextraClient(const CDextraClient &client)
+ : CClient(client)
+{
+ m_ProtRev = client.m_ProtRev;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CDextraClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < DEXTRA_KEEPALIVE_TIMEOUT);
+}
diff --git a/src/DExtraClient.h b/src/DExtraClient.h
new file mode 100644
index 0000000..13ff2f0
--- /dev/null
+++ b/src/DExtraClient.h
@@ -0,0 +1,64 @@
+//
+// DExtraClient.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdextraclient_h
+#define cdextraclient_h
+
+#include "Client.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDextraClient : public CClient
+{
+public:
+ // constructors
+ CDextraClient();
+ CDextraClient(const CCallsign &, const CIp &, char = ' ', int = 0);
+ CDextraClient(const CDextraClient &);
+
+ // destructor
+ virtual ~CDextraClient() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_DEXTRA; }
+ int GetProtocolRevision(void) const { return m_ProtRev; }
+ const char *GetProtocolName(void) const { return "DExtra"; }
+ int GetCodec(void) const { return CODEC_AMBEPLUS; }
+ bool IsNode(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+
+protected:
+ // data
+ int m_ProtRev;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdextraclient_h */
diff --git a/src/DExtraPeer.cpp b/src/DExtraPeer.cpp
new file mode 100644
index 0000000..ffb547d
--- /dev/null
+++ b/src/DExtraPeer.cpp
@@ -0,0 +1,73 @@
+//
+// cdextrapeer.cpp
+// xlxd
+//
+// Created by Antony Chazapis (SV9OAN) on 25/2/2018.
+// Copyright © 2016 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "Reflector.h"
+#include "DExtraPeer.h"
+#include "DExtraClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+
+CDextraPeer::CDextraPeer()
+{
+}
+
+CDextraPeer::CDextraPeer(const CCallsign &callsign, const CIp &ip, const char *modules, const CVersion &version)
+ : CPeer(callsign, ip, modules, version)
+{
+ std::cout << "Adding DExtra peer" << std::endl;
+
+ // and construct the DExtra clients
+ for ( unsigned i = 0; i < ::strlen(modules); i++ )
+ {
+ // create and append to vector
+ m_Clients.push_back(std::make_shared(callsign, ip, modules[i], version.GetMajor()));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CDextraPeer::IsAlive(void) const
+{
+ for ( auto it=cbegin(); it!=cend(); it++ )
+ {
+ if (! (*it)->IsAlive())
+ return false;
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// revision helper
+
+int CDextraPeer::GetProtocolRevision(const CVersion &version)
+{
+ return version.GetMajor();
+}
diff --git a/src/DExtraPeer.h b/src/DExtraPeer.h
new file mode 100644
index 0000000..be1b571
--- /dev/null
+++ b/src/DExtraPeer.h
@@ -0,0 +1,58 @@
+//
+// DExtraPeer.h
+// xlxd
+//
+// Created by Antony Chazapis (SV9OAN) on 25/2/2018.
+// Copyright © 2016 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdextrapeer_h
+#define cdextrapeer_h
+
+#include "Peer.h"
+#include "DExtraClient.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDextraPeer : public CPeer
+{
+public:
+ // constructors
+ CDextraPeer();
+ CDextraPeer(const CCallsign &, const CIp &, const char *, const CVersion &);
+ CDextraPeer(const CDextraPeer &) = delete;
+
+ // status
+ bool IsAlive(void) const;
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_DEXTRA; }
+ const char *GetProtocolName(void) const { return "DExtra"; }
+
+ // revision helper
+ static int GetProtocolRevision(const CVersion &);
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdextrapeer_h */
diff --git a/src/DExtraProtocol.cpp b/src/DExtraProtocol.cpp
new file mode 100644
index 0000000..48c4f12
--- /dev/null
+++ b/src/DExtraProtocol.cpp
@@ -0,0 +1,641 @@
+//
+// cdextraprotocol.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "DExtraPeer.h"
+#include "DExtraClient.h"
+#include "DExtraProtocol.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CDextraProtocol::Initialize(const char *type, int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6)
+{
+ // base class
+ if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
+ return false;
+
+ // update time
+ m_LastKeepaliveTime.Now();
+ m_LastPeersLinkTime.Now();
+
+ // done
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// task
+
+void CDextraProtocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+ char ToLinkModule;
+ int ProtRev;
+ std::unique_ptr Header;
+ std::unique_ptr Frame;
+ std::unique_ptr LastFrame;
+
+ // any incoming packet ?
+#if DSTAR_IPV6==true
+#if DSTAR_IPV4==true
+ if ( ReceiveDS(Buffer, Ip, 20) )
+#else
+ if ( Receive6(Buffer, Ip, 20) )
+#endif
+#else
+ if ( Receive4(Buffer, Ip, 20) )
+#endif
+ {
+ // crack the packet
+ if ( IsValidDvFramePacket(Buffer, Frame) )
+ {
+ OnDvFramePacketIn(Frame, &Ip);
+ }
+ else if ( IsValidDvHeaderPacket(Buffer, Header) )
+ {
+ // callsign muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, PROTOCOL_DEXTRA, Header->GetRpt2Module()) )
+ {
+ OnDvHeaderPacketIn(Header, Ip);
+ }
+ }
+ else if ( IsValidDvLastFramePacket(Buffer, LastFrame) )
+ {
+ OnDvLastFramePacketIn(LastFrame, &Ip);
+ }
+ else if ( IsValidConnectPacket(Buffer, &Callsign, &ToLinkModule, &ProtRev) )
+ {
+ std::cout << "DExtra connect packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << " rev " << ProtRev << std::endl;
+
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, PROTOCOL_DEXTRA) )
+ {
+ // valid module ?
+ if ( g_Reflector.IsValidModule(ToLinkModule) )
+ {
+ // is this an ack for a link request?
+ CPeerCallsignList *list = g_GateKeeper.GetPeerList();
+ CCallsignListItem *item = list->FindListItem(Callsign);
+ if ( item != nullptr && Callsign.GetModule() == item->GetModules()[1] && ToLinkModule == item->GetModules()[0] )
+ {
+ std::cout << "DExtra ack packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << std::endl;
+
+ // already connected ?
+ CPeers *peers = g_Reflector.GetPeers();
+ if ( peers->FindPeer(Callsign, Ip, PROTOCOL_DEXTRA) == nullptr )
+ {
+ // create the new peer
+ // this also create one client per module
+ // append the peer to reflector peer list
+ // this also add all new clients to reflector client list
+ peers->AddPeer(std::make_shared(Callsign, Ip, std::string(1, ToLinkModule).c_str(), CVersion(2, 0, 0)));
+ }
+ g_Reflector.ReleasePeers();
+ }
+ else
+ {
+ // acknowledge the request
+ EncodeConnectAckPacket(&Buffer, ProtRev);
+ Send(Buffer, Ip);
+
+ // create the client and append
+ g_Reflector.GetClients()->AddClient(std::make_shared(Callsign, Ip, ToLinkModule, ProtRev));
+ g_Reflector.ReleaseClients();
+ }
+ g_GateKeeper.ReleasePeerList();
+ }
+ else
+ {
+ std::cout << "DExtra node " << Callsign << " connect attempt on non-existing module" << std::endl;
+
+ // deny the request
+ EncodeConnectNackPacket(&Buffer);
+ Send(Buffer, Ip);
+ }
+ }
+ else
+ {
+ // deny the request
+ EncodeConnectNackPacket(&Buffer);
+ Send(Buffer, Ip);
+ }
+ }
+ else if ( IsValidDisconnectPacket(Buffer, &Callsign) )
+ {
+ std::cout << "DExtra disconnect packet from " << Callsign << " at " << Ip << std::endl;
+
+ // find client & remove it
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Ip, PROTOCOL_DEXTRA);
+ if ( client != nullptr )
+ {
+ // ack disconnect packet
+ if ( client->GetProtocolRevision() == 1 )
+ {
+ EncodeDisconnectedPacket(&Buffer);
+ Send(Buffer, Ip);
+ }
+ else if ( client->GetProtocolRevision() == 2 )
+ {
+ Send(Buffer, Ip);
+ }
+ // and remove it
+ clients->RemoveClient(client);
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else if ( IsValidKeepAlivePacket(Buffer, &Callsign) )
+ {
+ //std::cout << "DExtra keepalive packet from " << Callsign << " at " << Ip << std::endl;
+
+ // find all clients with that callsign & ip and keep them alive
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(Callsign, Ip, PROTOCOL_DEXTRA, it)) != nullptr )
+ {
+ client->Alive();
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ std::string title("Unknown DExtra packet from ");
+ title += Ip.GetAddress();
+ Buffer.Dump(title);
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+ // keep alive
+ if ( m_LastKeepaliveTime.DurationSinceNow() > DEXTRA_KEEPALIVE_PERIOD )
+ {
+ // handle keep alives
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.Now();
+ }
+
+ // peer connections
+ if ( m_LastPeersLinkTime.DurationSinceNow() > DEXTRA_RECONNECT_PERIOD )
+ {
+ // handle remote peers connections
+ HandlePeerLinks();
+
+ // update time
+ m_LastPeersLinkTime.Now();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CDextraProtocol::HandleQueue(void)
+{
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // get the packet
+ auto packet = m_Queue.front();
+ m_Queue.pop();
+
+ // encode it
+ CBuffer buffer;
+ if ( EncodeDvPacket(*packet, &buffer) )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DEXTRA, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == packet->GetModuleId()) )
+ {
+ // no, send the packet
+ int n = packet->IsDvHeader() ? 5 : 1;
+ for ( int i = 0; i < n; i++ )
+ {
+ Send(buffer, client->GetIp());
+ }
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+ m_Queue.Unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CDextraProtocol::HandleKeepalives(void)
+{
+ // DExtra protocol sends and monitors keepalives packets
+ // event if the client is currently streaming
+ // so, send keepalives to all
+ CBuffer keepalive;
+ EncodeKeepAlivePacket(&keepalive);
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DEXTRA, it)) != nullptr )
+ {
+ // send keepalive
+ Send(keepalive, client->GetIp());
+
+ // client busy ?
+ if ( client->IsAMaster() )
+ {
+ // yes, just tickle it
+ client->Alive();
+ }
+ // otherwise check if still with us
+ else if ( !client->IsAlive() )
+ {
+ CPeers *peers = g_Reflector.GetPeers();
+ std::shared_ptrpeer = peers->FindPeer(client->GetCallsign(), client->GetIp(), PROTOCOL_DEXTRA);
+ if ( peer != nullptr && peer->GetReflectorModules()[0] == client->GetReflectorModule() )
+ {
+ // no, but this is a peer client, so it will be handled below
+ }
+ else
+ {
+ // no, disconnect
+ CBuffer disconnect;
+ EncodeDisconnectPacket(&disconnect, client->GetReflectorModule());
+ Send(disconnect, client->GetIp());
+
+ // remove it
+ std::cout << "DExtra client " << client->GetCallsign() << " keepalive timeout" << std::endl;
+ clients->RemoveClient(client);
+ }
+ g_Reflector.ReleasePeers();
+ }
+
+ }
+ g_Reflector.ReleaseClients();
+
+ // iterate on peers
+ CPeers *peers = g_Reflector.GetPeers();
+ auto pit = peers->begin();
+ std::shared_ptrpeer = nullptr;
+ while ( (peer = peers->FindNextPeer(PROTOCOL_DEXTRA, pit)) != nullptr )
+ {
+ // keepalives are sent between clients
+
+ // some client busy or still with us ?
+ if ( !peer->IsAMaster() && !peer->IsAlive() )
+ {
+ // no, disconnect all clients
+ CBuffer disconnect;
+ EncodeDisconnectPacket(&disconnect, peer->GetReflectorModules()[0]);
+ CClients *clients = g_Reflector.GetClients();
+ for ( auto cit=peer->cbegin(); cit!=peer->cend(); cit++ )
+ {
+ Send(disconnect, (*cit)->GetIp());
+ }
+ g_Reflector.ReleaseClients();
+
+ // remove it
+ std::cout << "DExtra peer " << peer->GetCallsign() << " keepalive timeout" << std::endl;
+ peers->RemovePeer(peer);
+ }
+ }
+ g_Reflector.ReleasePeers();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Peers helpers
+
+void CDextraProtocol::HandlePeerLinks(void)
+{
+ CBuffer buffer;
+
+ // get the list of peers
+ CPeerCallsignList *list = g_GateKeeper.GetPeerList();
+ CPeers *peers = g_Reflector.GetPeers();
+
+ // check if all our connected peers are still listed by gatekeeper
+ // if not, disconnect
+ auto pit = peers->begin();
+ std::shared_ptrpeer = nullptr;
+ while ( (peer = peers->FindNextPeer(PROTOCOL_DEXTRA, pit)) != nullptr )
+ {
+ if ( list->FindListItem(peer->GetCallsign()) == nullptr )
+ {
+ // send disconnect packet
+ EncodeDisconnectPacket(&buffer, peer->GetReflectorModules()[0]);
+ Send(buffer, peer->GetIp());
+ std::cout << "Sending disconnect packet to XRF peer " << peer->GetCallsign() << " at " << peer->GetIp() << std::endl;
+ // remove client
+ peers->RemovePeer(peer);
+ }
+ }
+
+ // check if all ours peers listed by gatekeeper are connected
+ // if not, connect or reconnect
+ for ( auto it=list->begin(); it!=list->end(); it++ )
+ {
+ if ( !(*it).GetCallsign().HasSameCallsignWithWildcard(CCallsign("XRF*")) )
+ continue;
+ if ( strlen((*it).GetModules()) != 2 )
+ continue;
+ if ( peers->FindPeer((*it).GetCallsign(), PROTOCOL_DEXTRA) == nullptr )
+ {
+ // resolve again peer's IP in case it's a dynamic IP
+ (*it).ResolveIp();
+ // send connect packet to re-initiate peer link
+ EncodeConnectPacket(&buffer, (*it).GetModules());
+ Send(buffer, (*it).GetIp(), DEXTRA_PORT);
+ std::cout << "Sending connect packet to XRF peer " << (*it).GetCallsign() << " @ " << (*it).GetIp() << " for module " << (*it).GetModules()[1] << " (module " << (*it).GetModules()[0] << ")" << std::endl;
+ }
+ }
+
+ // done
+ g_Reflector.ReleasePeers();
+ g_GateKeeper.ReleasePeerList();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CDextraProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip)
+{
+ // find the stream
+ CPacketStream *stream = GetStream(Header->GetStreamId());
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ // no stream open yet, open a new one
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+
+ // find this client
+ std::shared_ptrclient = g_Reflector.GetClients()->FindClient(Ip, PROTOCOL_DEXTRA);
+ if ( client )
+ {
+ // get client callsign
+ rpt1 = client->GetCallsign();
+ // apply protocol revision details
+ if ( client->GetProtocolRevision() == 2 )
+ {
+ // update Header RPT2 module letter with
+ // the module the client is linked to
+ auto m = client->GetReflectorModule();
+ Header->SetRpt2Module(m);
+ rpt2.SetModule(m);
+ }
+ // and try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams.push_back(stream);
+ }
+ }
+ // release
+ g_Reflector.ReleaseClients();
+
+ // update last heard
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet decoding helpers
+
+bool CDextraProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsign, char *reflectormodule, int *revision)
+{
+ bool valid = false;
+ if ((Buffer.size() == 11) && (Buffer.data()[9] != ' '))
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ callsign->SetModule(Buffer.data()[8]);
+ *reflectormodule = Buffer.data()[9];
+ *revision = (Buffer.data()[10] == 11) ? 1 : 0;
+ valid = (callsign->IsValid() && IsLetter(*reflectormodule));
+ // detect revision
+ if ( (Buffer.data()[10] == 11) )
+ {
+ *revision = 1;
+ }
+ else if ( callsign->HasSameCallsignWithWildcard(CCallsign("XRF*")) )
+ {
+ *revision = 2;
+ }
+ else
+ {
+ *revision = 0;
+ }
+ }
+ return valid;
+}
+
+bool CDextraProtocol::IsValidDisconnectPacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ bool valid = false;
+ if ((Buffer.size() == 11) && (Buffer.data()[9] == ' '))
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ callsign->SetModule(Buffer.data()[8]);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDextraProtocol::IsValidKeepAlivePacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ bool valid = false;
+ if (Buffer.size() == 9)
+ {
+ callsign->SetCallsign(Buffer.data(), 8);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDextraProtocol::IsValidDvHeaderPacket(const CBuffer &Buffer, std::unique_ptr &header)
+{
+ if ( 56==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 4) && 0x10U==Buffer.data()[4] && 0x20U==Buffer.data()[8] )
+ {
+ // create packet
+ header = std::unique_ptr(new CDvHeaderPacket((struct dstar_header *)&(Buffer.data()[15]), *((uint16 *)&(Buffer.data()[12])), 0x80));
+ // check validity of packet
+ if ( header && header->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CDextraProtocol::IsValidDvFramePacket(const CBuffer &Buffer, std::unique_ptr &dvframe)
+{
+ if ( 27==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 4) && 0x20U==Buffer.data()[4] && 0x20U==Buffer.data()[8] && 0U==(Buffer.data()[14] & 0x40U) )
+ {
+ // create packet
+ dvframe = std::unique_ptr(new CDvFramePacket((struct dstar_dvframe *)&(Buffer.data()[15]), *((uint16 *)&(Buffer.data()[12])), Buffer.data()[14]));
+ // check validity of packet
+ if ( dvframe && dvframe->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CDextraProtocol::IsValidDvLastFramePacket(const CBuffer &Buffer, std::unique_ptr &dvframe)
+{
+ if ( 27==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 4) && 0x20U==Buffer.data()[4] && 0x20U==Buffer.data()[8] && (Buffer.data()[14] & 0x40) )
+ {
+ // create packet
+ dvframe = std::unique_ptr(new CDvLastFramePacket((struct dstar_dvframe *)&(Buffer.data()[15]), *((uint16 *)&(Buffer.data()[12])), Buffer.data()[14]));
+ // check validity of packet
+ if ( dvframe && dvframe->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet encoding helpers
+
+void CDextraProtocol::EncodeKeepAlivePacket(CBuffer *Buffer)
+{
+ Buffer->Set(GetReflectorCallsign());
+}
+
+void CDextraProtocol::EncodeConnectPacket(CBuffer *Buffer, const char *Modules)
+{
+ uint8 lm = (uint8)Modules[0];
+ uint8 rm = (uint8)Modules[1];
+ Buffer->Set((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN);
+ Buffer->Append(lm);
+ Buffer->Append(rm);
+ Buffer->Append((uint8)0);
+}
+
+void CDextraProtocol::EncodeConnectAckPacket(CBuffer *Buffer, int ProtRev)
+{
+ // is it for a XRF or repeater
+ if ( ProtRev == 2 )
+ {
+ // XRFxxx
+ uint8 rm = (Buffer->data())[8];
+ uint8 lm = (Buffer->data())[9];
+ Buffer->clear();
+ Buffer->Set((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN);
+ Buffer->Append(lm);
+ Buffer->Append(rm);
+ Buffer->Append((uint8)0);
+ }
+ else
+ {
+ // regular repeater
+ uint8 tag[] = { 'A','C','K',0 };
+ Buffer->resize(Buffer->size()-1);
+ Buffer->Append(tag, sizeof(tag));
+ }
+}
+
+void CDextraProtocol::EncodeConnectNackPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 'N','A','K',0 };
+ Buffer->resize(Buffer->size()-1);
+ Buffer->Append(tag, sizeof(tag));
+}
+
+void CDextraProtocol::EncodeDisconnectPacket(CBuffer *Buffer, char Module)
+{
+ uint8 tag[] = { ' ',0 };
+ Buffer->Set((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN);
+ Buffer->Append((uint8)Module);
+ Buffer->Append(tag, sizeof(tag));
+}
+
+void CDextraProtocol::EncodeDisconnectedPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 'D','I','S','C','O','N','N','E','C','T','E','D' };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+bool CDextraProtocol::EncodeDvHeaderPacket(const CDvHeaderPacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','S','V','T',0x10,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+ struct dstar_header DstarHeader;
+
+ Packet.ConvertToDstarStruct(&DstarHeader);
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)0x80);
+ Buffer->Append((uint8 *)&DstarHeader, sizeof(struct dstar_header));
+
+ return true;
+}
+
+bool CDextraProtocol::EncodeDvFramePacket(const CDvFramePacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','S','V','T',0x20,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)(Packet.GetPacketId() % 21));
+ Buffer->Append((uint8 *)Packet.GetAmbe(), AMBE_SIZE);
+ Buffer->Append((uint8 *)Packet.GetDvData(), DVDATA_SIZE);
+
+ return true;
+
+}
+
+bool CDextraProtocol::EncodeDvLastFramePacket(const CDvLastFramePacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag1[] = { 'D','S','V','T',0x20,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+ uint8 tag2[] = { 0x55,0xC8,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x25,0x1A,0xC6 };
+
+ Buffer->Set(tag1, sizeof(tag1));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)((Packet.GetPacketId() % 21) | 0x40));
+ Buffer->Append(tag2, sizeof(tag2));
+
+ return true;
+}
diff --git a/src/DExtraProtocol.h b/src/DExtraProtocol.h
new file mode 100644
index 0000000..2984348
--- /dev/null
+++ b/src/DExtraProtocol.h
@@ -0,0 +1,104 @@
+//
+// DExtraProtocol.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdextraprotocol_h
+#define cdextraprotocol_h
+
+#include "Timer.h"
+#include "DCSProtocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+#include "DVLastFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+// note on protocol revisions:
+//
+// rev 0:
+// this is standard protocol implementation
+//
+// rev 1:
+// this is specific UP4DAR umplementation
+// the protocol is detected using byte(10) of connect packet (value is 11)
+// the protocol require a specific non-standard disconnect acqknowleding packet
+//
+// rev 2:
+// this is specific to KI4KLF dxrfd reflector
+// the protocol is detected by looking at "XRF" in connect packet callsign
+// the protocol require a specific connect ack packet
+// the protocol also implement a workaround for detecting stream's module
+// as dxrfd soes not set DV header RPT2 properly.
+// the protocol assumes that a dxrfd can only be linked to one module at a time
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDextraProtocol : public CProtocol
+{
+public:
+ // initialization
+ bool Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6);
+
+ // task
+ void Task(void);
+
+protected:
+ // queue helper
+ void HandleQueue(void);
+
+ // keepalive helpers
+ void HandlePeerLinks(void);
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &);
+
+ // packet decoding helpers
+ bool IsValidConnectPacket( const CBuffer &, CCallsign *, char *, int *);
+ bool IsValidDisconnectPacket( const CBuffer &, CCallsign *);
+ bool IsValidKeepAlivePacket( const CBuffer &, CCallsign *);
+ bool IsValidDvHeaderPacket( const CBuffer &, std::unique_ptr &);
+ bool IsValidDvFramePacket( const CBuffer &, std::unique_ptr &);
+ bool IsValidDvLastFramePacket(const CBuffer &, std::unique_ptr &);
+
+ // packet encoding helpers
+ void EncodeKeepAlivePacket(CBuffer *);
+ void EncodeConnectPacket(CBuffer *, const char *);
+ void EncodeConnectAckPacket(CBuffer *, int);
+ void EncodeConnectNackPacket(CBuffer *);
+ void EncodeDisconnectPacket(CBuffer *, char);
+ void EncodeDisconnectedPacket(CBuffer *);
+ bool EncodeDvHeaderPacket(const CDvHeaderPacket &, CBuffer *) const;
+ bool EncodeDvFramePacket(const CDvFramePacket &, CBuffer *) const;
+ bool EncodeDvLastFramePacket(const CDvLastFramePacket &, CBuffer *) const;
+
+protected:
+ // time
+ CTimePoint m_LastKeepaliveTime;
+ CTimePoint m_LastPeersLinkTime;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdextraprotocol_h */
diff --git a/src/DMRIdDir.cpp b/src/DMRIdDir.cpp
new file mode 100644
index 0000000..ffe0ffc
--- /dev/null
+++ b/src/DMRIdDir.cpp
@@ -0,0 +1,151 @@
+//
+// cdmriddir.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 08/10/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include
+#include "Main.h"
+#include "Reflector.h"
+#include "DMRIdDir.h"
+#include "cdmriddirfile.h"
+#include "DMRIdDirHttp.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor & destructor
+
+CDmridDir::CDmridDir()
+{
+ keep_running = true;
+}
+
+CDmridDir::~CDmridDir()
+{
+ Close();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// init & close
+
+bool CDmridDir::Init(void)
+{
+ // load content
+ Reload();
+
+ // reset run flag
+ keep_running = true;
+
+ // start thread;
+ m_Future = std::async(std::launch::async, &CDmridDir::Thread, this);
+
+ return true;
+}
+
+void CDmridDir::Close(void)
+{
+ keep_running = false;
+ if ( m_Future.valid() )
+ {
+ m_Future.get();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// thread
+
+void CDmridDir::Thread()
+{
+ while (keep_running)
+ {
+ // Wait DMRIDDB_REFRESH_RATE minutes
+ for (int i=0; i<30*DMRIDDB_REFRESH_RATE && keep_running; i++)
+ CTimePoint::TaskSleepFor(2000);
+
+ // have lists files changed ?
+ if ( NeedReload() )
+ {
+ Reload();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Reload
+
+bool CDmridDir::Reload(void)
+{
+ CBuffer buffer;
+ bool ok = false;
+
+ if ( LoadContent(&buffer) )
+ {
+ Lock();
+ {
+ ok = RefreshContent(buffer);
+ }
+ Unlock();
+ }
+ return ok;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// find
+
+const CCallsign *CDmridDir::FindCallsign(uint32 dmrid)
+{
+ auto found = m_CallsignMap.find(dmrid);
+ if ( found != m_CallsignMap.end() )
+ {
+ return &(found->second);
+ }
+ return nullptr;
+}
+
+uint32 CDmridDir::FindDmrid(const CCallsign &callsign)
+{
+ auto found = m_DmridMap.find(callsign);
+ if ( found != m_DmridMap.end() )
+ {
+ return (found->second);
+ }
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// syntax helpers
+
+bool CDmridDir::IsValidDmrid(const char *sz)
+{
+ bool ok = false;
+ size_t n = ::strlen(sz);
+ if ( (n > 0) && (n <= 8) )
+ {
+ ok = true;
+ for ( size_t i = 0; (i < n) && ok; i++ )
+ {
+ ok &= ::isdigit(sz[i]);
+ }
+ }
+ return ok;
+}
diff --git a/src/DMRIdDir.h b/src/DMRIdDir.h
new file mode 100644
index 0000000..d526697
--- /dev/null
+++ b/src/DMRIdDir.h
@@ -0,0 +1,95 @@
+//
+// DMRIdDir.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 08/10/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdmriddir_h
+#define cdmriddir_h
+
+#include
+#include
+#include
+#include
+#include "Buffer.h"
+#include "Callsign.h"
+
+// compare function for std::map::find
+
+struct CDmridDirCallsignCompare
+{
+ bool operator() (const CCallsign &cs1, const CCallsign &cs2) const
+ { return cs1.HasLowerCallsign(cs2);}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+class CDmridDir
+{
+public:
+ // constructor
+ CDmridDir();
+
+ // destructor
+ ~CDmridDir();
+
+ // init & close
+ virtual bool Init(void);
+ virtual void Close(void);
+
+ // locks
+ void Lock(void) { m_Mutex.lock(); }
+ void Unlock(void) { m_Mutex.unlock(); }
+
+ // refresh
+ virtual bool LoadContent(CBuffer *) { return false; }
+ virtual bool RefreshContent(const CBuffer &) { return false; }
+
+ // find
+ const CCallsign *FindCallsign(uint32);
+ uint32 FindDmrid(const CCallsign &);
+
+protected:
+ // thread
+ void Thread();
+
+ // reload helpers
+ bool Reload(void);
+ virtual bool NeedReload(void) { return false; }
+ bool IsValidDmrid(const char *);
+
+protected:
+ // data
+ std::map m_CallsignMap;
+ std::map m_DmridMap;
+
+ // Lock()
+ std::mutex m_Mutex;
+
+ // thread
+ std::atomic keep_running;
+ std::future m_Future;
+
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdmriddir_h */
diff --git a/src/DMRIdDirHttp.cpp b/src/DMRIdDirHttp.cpp
new file mode 100644
index 0000000..bf5beb0
--- /dev/null
+++ b/src/DMRIdDirHttp.cpp
@@ -0,0 +1,182 @@
+//
+// cdmriddirhttp.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 29/12/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include
+#include "Main.h"
+#include "Reflector.h"
+#include "DMRIdDirHttp.h"
+
+#if (DMRIDDB_USE_RLX_SERVER == 1)
+CDmridDirHttp g_DmridDir;
+#endif
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// refresh
+
+bool CDmridDirHttp::LoadContent(CBuffer *buffer)
+{
+ // get file from xlxapi server
+ return HttpGet("xlxapi.rlx.lu", "api/exportdmr.php", 80, buffer);
+}
+
+bool CDmridDirHttp::RefreshContent(const CBuffer &buffer)
+{
+ bool ok = false;
+
+ // clear directory
+ m_CallsignMap.clear();
+ m_DmridMap.clear();
+
+ // scan file
+ if ( buffer.size() > 0 )
+ {
+ char *ptr1 = (char *)buffer.data();
+ char *ptr2;
+ // get next line
+ while ( (ptr2 = ::strchr(ptr1, '\n')) != nullptr )
+ {
+ *ptr2 = 0;
+ // get items
+ char *dmrid;
+ char *callsign;
+ if ( ((dmrid = ::strtok(ptr1, ";")) != nullptr) && IsValidDmrid(dmrid) )
+ {
+ if ( ((callsign = ::strtok(nullptr, ";")) != nullptr) )
+ {
+ // new entry
+ uint32 ui = atoi(dmrid);
+ CCallsign cs(callsign, ui);
+ if ( cs.IsValid() )
+ {
+ m_CallsignMap.insert(std::pair(ui, cs));
+ m_DmridMap.insert(std::pair(cs,ui));
+ }
+ }
+ }
+ // next line
+ ptr1 = ptr2+1;
+ }
+ // done
+ ok = true;
+ }
+
+ // report
+ std::cout << "Read " << m_DmridMap.size() << " DMR ids from xlxapi.rlx.lu database " << std::endl;
+
+ // done
+ return ok;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// httpd helpers
+
+#define DMRID_HTTPGET_SIZEMAX (256)
+
+bool CDmridDirHttp::HttpGet(const char *hostname, const char *filename, int port, CBuffer *buffer)
+{
+ bool ok = false;
+ int sock_id;
+
+ // open socket
+ if ( (sock_id = ::socket(AF_INET, SOCK_STREAM, 0)) >= 0 )
+ {
+ // get hostname address
+ struct sockaddr_in servaddr;
+ struct hostent *hp;
+ ::memset(&servaddr,0,sizeof(servaddr));
+ if( (hp = gethostbyname(hostname)) != nullptr )
+ {
+ // dns resolved
+ ::memcpy((char *)&servaddr.sin_addr.s_addr, (char *)hp->h_addr, hp->h_length);
+ servaddr.sin_port = htons(port);
+ servaddr.sin_family = AF_INET;
+
+ // connect
+ if ( ::connect(sock_id, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0)
+ {
+ // send the GET request
+ char request[DMRID_HTTPGET_SIZEMAX];
+ ::sprintf(request, "GET /%s HTTP/1.0\r\nFrom: %s\r\nUser-Agent: xlxd\r\n\r\n",
+ filename, (const char *)g_Reflector.GetCallsign());
+ ::write(sock_id, request, strlen(request));
+
+ // config receive timeouts
+ fd_set read_set;
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ FD_ZERO(&read_set);
+ FD_SET(sock_id, &read_set);
+
+ // get the reply back
+ buffer->clear();
+ bool done = false;
+ do
+ {
+ char buf[1440];
+ ssize_t len = 0;
+ select(sock_id+1, &read_set, nullptr, nullptr, &timeout);
+ //if ( (ret > 0) || ((ret < 0) && (errno == EINPROGRESS)) )
+ //if ( ret >= 0 )
+ //{
+ usleep(5000);
+ len = read(sock_id, buf, 1440);
+ if ( len > 0 )
+ {
+ buffer->Append((uint8 *)buf, (int)len);
+ ok = true;
+ }
+ //}
+ done = (len <= 0);
+
+ }
+ while (!done);
+ buffer->Append((uint8)0);
+
+ // and disconnect
+ close(sock_id);
+ }
+ else
+ {
+ std::cout << "Cannot establish connection with host " << hostname << std::endl;
+ }
+ }
+ else
+ {
+ std::cout << "Host " << hostname << " not found" << std::endl;
+ }
+
+ }
+ else
+ {
+ std::cout << "Failed to open wget socket" << std::endl;
+ }
+
+ // done
+ return ok;
+}
diff --git a/src/DMRIdDirHttp.h b/src/DMRIdDirHttp.h
new file mode 100644
index 0000000..60056d1
--- /dev/null
+++ b/src/DMRIdDirHttp.h
@@ -0,0 +1,52 @@
+//
+// DMRIdDirHttp.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 29/12/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdmriddirhttp_h
+#define cdmriddirhttp_h
+
+#include "DMRIdDir.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+class CDmridDirHttp : public CDmridDir
+{
+public:
+ // constructor
+ CDmridDirHttp() {}
+
+ // destructor
+ ~CDmridDirHttp() {}
+
+ // refresh
+ bool LoadContent(CBuffer *);
+ bool RefreshContent(const CBuffer &);
+
+protected:
+ // reload helpers
+ bool NeedReload(void) { return true; }
+ bool HttpGet(const char *, const char *, int, CBuffer *);
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdmriddirhttp_h */
diff --git a/src/DMRMMDVMClient.cpp b/src/DMRMMDVMClient.cpp
new file mode 100644
index 0000000..24132e6
--- /dev/null
+++ b/src/DMRMMDVMClient.cpp
@@ -0,0 +1,52 @@
+//
+// cdmrmmdvmclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 04/03/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "DMMMDVMClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CDmrmmdvmClient::CDmrmmdvmClient()
+{
+}
+
+CDmrmmdvmClient::CDmrmmdvmClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+}
+
+CDmrmmdvmClient::CDmrmmdvmClient(const CDmrmmdvmClient &client)
+ : CClient(client)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CDmrmmdvmClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < DMRMMDVM_KEEPALIVE_TIMEOUT);
+}
diff --git a/src/DMRMMDVMClient.h b/src/DMRMMDVMClient.h
new file mode 100644
index 0000000..f61b16d
--- /dev/null
+++ b/src/DMRMMDVMClient.h
@@ -0,0 +1,60 @@
+//
+// DMMMDVMClient.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 04/03/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdmrmmdvmclient_h
+#define cdmrmmdvmclient_h
+
+#include "Client.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDmrmmdvmClient : public CClient
+{
+public:
+ // constructors
+ CDmrmmdvmClient();
+ CDmrmmdvmClient(const CCallsign &, const CIp &, char = ' ');
+ CDmrmmdvmClient(const CDmrmmdvmClient &);
+
+ // destructor
+ virtual ~CDmrmmdvmClient() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_DMRMMDVM; }
+ const char *GetProtocolName(void) const { return "DMRMmdvm"; }
+ int GetCodec(void) const { return CODEC_AMBE2PLUS; }
+ bool IsNode(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+#endif /* cdmrmmdvmclient_h */
diff --git a/src/DMRMMDVMProtocol.cpp b/src/DMRMMDVMProtocol.cpp
new file mode 100644
index 0000000..0abdcc4
--- /dev/null
+++ b/src/DMRMMDVMProtocol.cpp
@@ -0,0 +1,1160 @@
+//
+// cdmrmmdvmprotocol.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 104/03/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "DMMMDVMClient.h"
+#include "DMMMDVMProtocol.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+#include "CBPT19696.h"
+#include "RS129.h"
+#include "Golay2087.h"
+#include "QR1676.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+#define CMD_NONE 0
+#define CMD_LINK 1
+#define CMD_UNLINK 2
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constants
+
+static uint8 g_DmrSyncBSVoice[] = { 0x07,0x55,0xFD,0x7D,0xF7,0x5F,0x70 };
+static uint8 g_DmrSyncBSData[] = { 0x0D,0xFF,0x57,0xD7,0x5D,0xF5,0xD0 };
+static uint8 g_DmrSyncMSVoice[] = { 0x07,0xF7,0xD5,0xDD,0x57,0xDF,0xD0 };
+static uint8 g_DmrSyncMSData[] = { 0x0D,0x5D,0x7F,0x77,0xFD,0x75,0x70 };
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CDmrmmdvmProtocol::Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6)
+{
+ // base class
+ if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
+ return false;
+
+ // update time
+ m_LastKeepaliveTime.Now();
+
+ // random number generator
+ time_t t;
+ ::srand((unsigned) time(&t));
+ m_uiAuthSeed = (uint32)rand();
+
+ // done
+ return true;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// task
+
+void CDmrmmdvmProtocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+ int iRssi;
+ uint8 Cmd;
+ uint8 CallType;
+ std::unique_ptr Header;
+ std::unique_ptr LastFrame;
+ std::array, 3> Frames;
+
+ // handle incoming packets
+#if DMR_IPV6==true
+#if DMR_IPV4==true
+ if ( ReceiveDS(Buffer, Ip, 20) )
+#else
+ if ( Receive6(Buffer, Ip, 20) )
+#endif
+#else
+ if ( Receive4(Buffer, Ip, 20) )
+#endif
+ {
+ //Buffer.DebugDump(g_Reflector.m_DebugFile);
+ // crack the packet
+ if ( IsValidDvFramePacket(Buffer, Frames) )
+ {
+ for ( int i = 0; i < 3; i++ )
+ {
+ OnDvFramePacketIn(Frames.at(i), &Ip);
+ }
+ }
+ else if ( IsValidDvHeaderPacket(Buffer, Header, &Cmd, &CallType) )
+ {
+ // callsign muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, PROTOCOL_DMRMMDVM) )
+ {
+ // handle it
+ OnDvHeaderPacketIn(Header, Ip, Cmd, CallType);
+ }
+ }
+ else if ( IsValidDvLastFramePacket(Buffer, LastFrame) )
+ {
+ OnDvLastFramePacketIn(LastFrame, &Ip);
+ }
+ else if ( IsValidConnectPacket(Buffer, &Callsign, Ip) )
+ {
+ std::cout << "DMRmmdvm connect packet from " << Callsign << " at " << Ip << std::endl;
+
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, PROTOCOL_DMRMMDVM) )
+ {
+ // acknowledge the request
+ EncodeConnectAckPacket(&Buffer, Callsign, m_uiAuthSeed);
+ Send(Buffer, Ip);
+ }
+ else
+ {
+ // deny the request
+ EncodeNackPacket(&Buffer, Callsign);
+ Send(Buffer, Ip);
+ }
+
+ }
+ else if ( IsValidAuthenticationPacket(Buffer, &Callsign, Ip) )
+ {
+ std::cout << "DMRmmdvm authentication packet from " << Callsign << " at " << Ip << std::endl;
+
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, PROTOCOL_DMRMMDVM) )
+ {
+ // acknowledge the request
+ EncodeAckPacket(&Buffer, Callsign);
+ Send(Buffer, Ip);
+
+ // add client if needed
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Callsign, Ip, PROTOCOL_DMRMMDVM);
+ // client already connected ?
+ if ( client == nullptr )
+ {
+ std::cout << "DMRmmdvm login from " << Callsign << " at " << Ip << std::endl;
+
+ // create the client and append
+ clients->AddClient(std::make_shared(Callsign, Ip));
+ }
+ else
+ {
+ client->Alive();
+ }
+ // and done
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ // deny the request
+ EncodeNackPacket(&Buffer, Callsign);
+ Send(Buffer, Ip);
+ }
+
+ }
+ else if ( IsValidDisconnectPacket(Buffer, &Callsign) )
+ {
+ std::cout << "DMRmmdvm disconnect packet from " << Callsign << " at " << Ip << std::endl;
+
+ // find client & remove it
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Ip, PROTOCOL_DMRMMDVM);
+ if ( client != nullptr )
+ {
+ clients->RemoveClient(client);
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else if ( IsValidConfigPacket(Buffer, &Callsign, Ip) )
+ {
+ std::cout << "DMRmmdvm configuration packet from " << Callsign << " at " << Ip << std::endl;
+
+ // acknowledge the request
+ EncodeAckPacket(&Buffer, Callsign);
+ Send(Buffer, Ip);
+ }
+ else if ( IsValidKeepAlivePacket(Buffer, &Callsign) )
+ {
+ //std::cout << "DMRmmdvm keepalive packet from " << Callsign << " at " << Ip << std::endl;
+
+ // find all clients with that callsign & ip and keep them alive
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(Callsign, Ip, PROTOCOL_DMRMMDVM, it)) != nullptr )
+ {
+ // acknowledge
+ EncodeKeepAlivePacket(&Buffer, client);
+ Send(Buffer, Ip);
+
+ // and mark as alive
+ client->Alive();
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else if ( IsValidRssiPacket(Buffer, &Callsign, &iRssi) )
+ {
+ // std::cout << "DMRmmdvm RSSI packet from " << Callsign << " at " << Ip << std::endl
+
+ // ignore...
+ }
+ else if ( IsValidOptionPacket(Buffer, &Callsign) )
+ {
+ std::cout << "DMRmmdvm options packet from " << Callsign << " at " << Ip << std::endl;
+
+ // acknowledge the request
+ EncodeAckPacket(&Buffer, Callsign);
+ Send(Buffer, Ip);
+ }
+ else if ( Buffer.size() != 55 )
+ {
+ std::string title("Unknown DMRMMDVM packet from ");
+ title += Ip.GetAddress();
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+
+ // keep client alive
+ if ( m_LastKeepaliveTime.DurationSinceNow() > DMRMMDVM_KEEPALIVE_PERIOD )
+ {
+ //
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.Now();
+ }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CDmrmmdvmProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip, uint8 cmd, uint8 CallType)
+{
+ bool lastheard = false;
+
+ // find the stream
+ CPacketStream *stream = GetStream(Header->GetStreamId());
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+ // no stream open yet, open a new one
+ // firstfind this client
+ std::shared_ptrclient = g_Reflector.GetClients()->FindClient(Ip, PROTOCOL_DMRMMDVM);
+ if ( client )
+ {
+ // process cmd if any
+ if ( !client->HasReflectorModule() )
+ {
+ // not linked yet
+ if ( cmd == CMD_LINK )
+ {
+ if ( g_Reflector.IsValidModule(rpt2.GetModule()) )
+ {
+ std::cout << "DMRmmdvm client " << client->GetCallsign() << " linking on module " << rpt2.GetModule() << std::endl;
+ // link
+ client->SetReflectorModule(rpt2.GetModule());
+ }
+ else
+ {
+ std::cout << "DMRMMDVM node " << rpt1 << " link attempt on non-existing module" << std::endl;
+ }
+ }
+ }
+ else
+ {
+ // already linked
+ if ( cmd == CMD_UNLINK )
+ {
+ std::cout << "DMRmmdvm client " << client->GetCallsign() << " unlinking" << std::endl;
+ // unlink
+ client->SetReflectorModule(' ');
+ }
+ else
+ {
+ // replace rpt2 module with currently linked module
+ auto m = client->GetReflectorModule();
+ Header->SetRpt2Module(m);
+ rpt2.SetModule(m);
+ }
+ }
+
+ // and now, re-check module is valid && that it's not a private call
+ if ( g_Reflector.IsValidModule(rpt2.GetModule()) && (CallType == DMR_GROUP_CALL) )
+ {
+ // yes, try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams.push_back(stream);
+ lastheard = true;
+ }
+ }
+ }
+ else
+ {
+ lastheard = true;
+ }
+
+ // release
+ g_Reflector.ReleaseClients();
+
+ // update last heard
+ if ( lastheard )
+ {
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CDmrmmdvmProtocol::HandleQueue(void)
+{
+
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // get the packet
+ auto packet = m_Queue.front();
+ m_Queue.pop();
+
+ // get our sender's id
+ int iModId = g_Reflector.GetModuleIndex(packet->GetModuleId());
+
+ // encode
+ CBuffer buffer;
+
+ // check if it's header
+ if ( packet->IsDvHeader() )
+ {
+ // update local stream cache
+ // this relies on queue feeder setting valid module id
+ m_StreamsCache[iModId].m_dvHeader = CDvHeaderPacket((const CDvHeaderPacket &)*packet);
+ m_StreamsCache[iModId].m_uiSeqId = 0;
+
+ // encode it
+ EncodeDvHeaderPacket((CDvHeaderPacket &)*packet, m_StreamsCache[iModId].m_uiSeqId, &buffer);
+ m_StreamsCache[iModId].m_uiSeqId = 1;
+ }
+ // check if it's a last frame
+ else if ( packet->IsLastPacket() )
+ {
+ // encode it
+ EncodeDvLastPacket(m_StreamsCache[iModId].m_dvHeader, m_StreamsCache[iModId].m_uiSeqId, &buffer);
+ m_StreamsCache[iModId].m_uiSeqId = (m_StreamsCache[iModId].m_uiSeqId + 1) & 0xFF;
+ }
+ // otherwise, just a regular DV frame
+ else
+ {
+ // update local stream cache or send triplet when needed
+ switch ( packet->GetDmrPacketSubid() )
+ {
+ case 1:
+ m_StreamsCache[iModId].m_dvFrame0 = CDvFramePacket((const CDvFramePacket &)*packet);
+ break;
+ case 2:
+ m_StreamsCache[iModId].m_dvFrame1 = CDvFramePacket((const CDvFramePacket &)*packet);
+ break;
+ case 3:
+ EncodeDvPacket(
+ m_StreamsCache[iModId].m_dvHeader,
+ m_StreamsCache[iModId].m_dvFrame0,
+ m_StreamsCache[iModId].m_dvFrame1,
+ (const CDvFramePacket &)*packet,
+ m_StreamsCache[iModId].m_uiSeqId,
+ &buffer);
+ m_StreamsCache[iModId].m_uiSeqId = (m_StreamsCache[iModId].m_uiSeqId + 1) & 0xFF;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // send it
+ if ( buffer.size() > 0 )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DMRMMDVM, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == packet->GetModuleId()) )
+ {
+ // no, send the packet
+ Send(buffer, client->GetIp());
+
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+ m_Queue.Unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CDmrmmdvmProtocol::HandleKeepalives(void)
+{
+ // DMRhomebrew protocol keepalive request is client tasks
+ // here, just check that all clients are still alive
+ // and disconnect them if not
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DMRMMDVM, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( client->IsAMaster() )
+ {
+ // yes, just tickle it
+ client->Alive();
+ }
+ // check it's still with us
+ else if ( !client->IsAlive() )
+ {
+ // no, disconnect
+ CBuffer disconnect;
+ Send(disconnect, client->GetIp());
+
+ // remove it
+ std::cout << "DMRmmdvm client " << client->GetCallsign() << " keepalive timeout" << std::endl;
+ clients->RemoveClient(client);
+ }
+
+ }
+ g_Reflector.ReleaseClients();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet decoding helpers
+
+bool CDmrmmdvmProtocol::IsValidKeepAlivePacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ uint8 tag[] = { 'R','P','T','P','I','N','G' };
+
+ bool valid = false;
+ if ( (Buffer.size() == 11) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[10],Buffer.data()[9]),MAKEWORD(Buffer.data()[8],Buffer.data()[7]));
+ callsign->SetDmrid(uiRptrId, true);
+ callsign->SetModule(MMDVM_MODULE_ID);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsign, const CIp &Ip)
+{
+ uint8 tag[] = { 'R','P','T','L' };
+
+ bool valid = false;
+ if ( (Buffer.size() == 8) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],Buffer.data()[4]));
+ callsign->SetDmrid(uiRptrId, true);
+ callsign->SetModule(MMDVM_MODULE_ID);
+ valid = callsign->IsValid();
+ if ( !valid)
+ {
+ std::cout << "Invalid callsign in DMRmmdvm RPTL packet from IP: " << Ip << " CS:" << *callsign << " DMRID:" << callsign->GetDmrid() << std::endl;
+ }
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidAuthenticationPacket(const CBuffer &Buffer, CCallsign *callsign, const CIp &Ip)
+{
+ uint8 tag[] = { 'R','P','T','K' };
+
+ bool valid = false;
+ if ( (Buffer.size() == 40) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],Buffer.data()[4]));
+ callsign->SetDmrid(uiRptrId, true);
+ callsign->SetModule(MMDVM_MODULE_ID);
+ valid = callsign->IsValid();
+ if ( !valid)
+ {
+ std::cout << "Invalid callsign in DMRmmdvm RPTK packet from IP: " << Ip << " CS:" << *callsign << " DMRID:" << callsign->GetDmrid() << std::endl;
+ }
+
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidDisconnectPacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ uint8 tag[] = { 'R','P','T','C','L' };
+
+ bool valid = false;
+ if ( (Buffer.size() == 13) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],Buffer.data()[4]));
+ callsign->SetDmrid(uiRptrId, true);
+ callsign->SetModule(MMDVM_MODULE_ID);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidConfigPacket(const CBuffer &Buffer, CCallsign *callsign, const CIp &Ip)
+{
+ uint8 tag[] = { 'R','P','T','C' };
+
+ bool valid = false;
+ if ( (Buffer.size() == 302) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],Buffer.data()[4]));
+ callsign->SetDmrid(uiRptrId, true);
+ callsign->SetModule(MMDVM_MODULE_ID);
+ valid = callsign->IsValid();
+ if ( !valid)
+ {
+ std::cout << "Invalid callsign in DMRmmdvm RPTC packet from IP: " << Ip << " CS:" << *callsign << " DMRID:" << callsign->GetDmrid() << std::endl;
+ }
+
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidOptionPacket(const CBuffer &Buffer, CCallsign *callsign)
+{
+ uint8 tag[] = { 'R','P','T','O' };
+
+ bool valid = false;
+ if ( (Buffer.size() >= 8) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],Buffer.data()[4]));
+ callsign->SetDmrid(uiRptrId, true);
+ callsign->SetModule(MMDVM_MODULE_ID);
+ valid = callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidRssiPacket(const CBuffer &Buffer, CCallsign *callsign, int *rssi)
+{
+ uint8 tag[] = { 'R','P','T','I','N','T','R' };
+
+ bool valid = false;
+ if ( (Buffer.size() == 17) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ // ignore rest of it, as not used
+ // dmrid is asci hex on 8 bytes
+ // rssi is ascii :x-xx
+ valid = true;
+ }
+ return valid;
+}
+
+bool CDmrmmdvmProtocol::IsValidDvHeaderPacket(const CBuffer &Buffer, std::unique_ptr &header, uint8 *cmd, uint8 *CallType)
+{
+ uint8 tag[] = { 'D','M','R','D' };
+
+ *cmd = CMD_NONE;
+
+ if ( (Buffer.size() == 55) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ // frame details
+ uint8 uiFrameType = (Buffer.data()[15] & 0x30) >> 4;
+ uint8 uiSlot = (Buffer.data()[15] & 0x80) ? DMR_SLOT2 : DMR_SLOT1;
+ uint8 uiCallType = (Buffer.data()[15] & 0x40) ? DMR_PRIVATE_CALL : DMR_GROUP_CALL;
+ uint8 uiSlotType = Buffer.data()[15] & 0x0F;
+ //std::cout << (int)uiSlot << std::endl;
+ if ( (uiFrameType == DMRMMDVM_FRAMETYPE_DATASYNC) &&
+ (uiSlot == DMRMMDVM_REFLECTOR_SLOT) &&
+ (uiSlotType == MMDVM_SLOTTYPE_HEADER) )
+ {
+ // extract sync
+ uint8 dmrsync[7];
+ dmrsync[0] = Buffer.data()[33] & 0x0F;
+ ::memcpy(&dmrsync[1], &Buffer.data()[34], 5);
+ dmrsync[6] = Buffer.data()[39] & 0xF0;
+ // and check
+ if ( (::memcmp(dmrsync, g_DmrSyncMSData, sizeof(dmrsync)) == 0) ||
+ (::memcmp(dmrsync, g_DmrSyncBSData, sizeof(dmrsync)) == 0))
+ {
+ // get payload
+ //CBPTC19696 bptc;
+ //uint8 lcdata[12];
+ //bptc.decode(&(Buffer.data()[20]), lcdata);
+
+ // crack DMR header
+ //uint8 uiSeqId = Buffer.data()[4];
+ uint32 uiSrcId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],0));
+ uint32 uiDstId = MAKEDWORD(MAKEWORD(Buffer.data()[10],Buffer.data()[9]),MAKEWORD(Buffer.data()[8],0));
+ uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[14],Buffer.data()[13]),MAKEWORD(Buffer.data()[12],Buffer.data()[11]));
+ //uint8 uiVoiceSeq = (Buffer.data()[15] & 0x0F);
+ uint32 uiStreamId = *(uint32 *)(&Buffer.data()[16]);
+
+ // call type
+ *CallType = uiCallType;
+
+ // link/unlink command ?
+ if ( uiDstId == 4000 )
+ {
+ *cmd = CMD_UNLINK;
+ }
+ else if ( DmrDstIdToModule(uiDstId) != ' ' )
+ {
+ *cmd = CMD_LINK;
+ }
+ else
+ {
+ *cmd = CMD_NONE;
+ }
+
+ // build DVHeader
+ CCallsign csMY = CCallsign("", uiSrcId);
+ CCallsign rpt1 = CCallsign("", uiRptrId);
+ rpt1.SetModule(MMDVM_MODULE_ID);
+ CCallsign rpt2 = m_ReflectorCallsign;
+ rpt2.SetModule(DmrDstIdToModule(uiDstId));
+
+ // and packet
+ header = std::unique_ptr(new CDvHeaderPacket(uiSrcId, CCallsign("CQCQCQ"), rpt1, rpt2, uiStreamId, 0, 0));
+ if ( header && header->IsValid() )
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool CDmrmmdvmProtocol::IsValidDvFramePacket(const CBuffer &Buffer, std::array, 3> &frames)
+{
+ uint8 tag[] = { 'D','M','R','D' };
+
+ if ( (Buffer.size() == 55) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ // frame details
+ uint8 uiFrameType = (Buffer.data()[15] & 0x30) >> 4;
+ uint8 uiSlot = (Buffer.data()[15] & 0x80) ? DMR_SLOT2 : DMR_SLOT1;
+ uint8 uiCallType = (Buffer.data()[15] & 0x40) ? DMR_PRIVATE_CALL : DMR_GROUP_CALL;
+ if ( ((uiFrameType == DMRMMDVM_FRAMETYPE_VOICE) || (uiFrameType == DMRMMDVM_FRAMETYPE_VOICESYNC)) &&
+ (uiSlot == DMRMMDVM_REFLECTOR_SLOT) && (uiCallType == DMR_GROUP_CALL) )
+ {
+ // crack DMR header
+ //uint8 uiSeqId = Buffer.data()[4];
+ //uint32 uiSrcId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],0));
+ //uint32 uiDstId = MAKEDWORD(MAKEWORD(Buffer.data()[10],Buffer.data()[9]),MAKEWORD(Buffer.data()[8],0));
+ //uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[14],Buffer.data()[13]),MAKEWORD(Buffer.data()[12],Buffer.data()[11]));
+ uint8 uiVoiceSeq = (Buffer.data()[15] & 0x0F);
+ uint32 uiStreamId = *(uint32 *)(&Buffer.data()[16]);
+
+ // crack payload
+ uint8 dmrframe[33];
+ uint8 dmr3ambe[27];
+ uint8 dmrambe[9];
+ uint8 dmrsync[7];
+ // get the 33 bytes ambe
+ memcpy(dmrframe, &(Buffer.data()[20]), 33);
+ // extract the 3 ambe frames
+ memcpy(dmr3ambe, dmrframe, 14);
+ dmr3ambe[13] &= 0xF0;
+ dmr3ambe[13] |= (dmrframe[19] & 0x0F);
+ memcpy(&dmr3ambe[14], &dmrframe[20], 13);
+ // extract sync
+ dmrsync[0] = dmrframe[13] & 0x0F;
+ ::memcpy(&dmrsync[1], &dmrframe[14], 5);
+ dmrsync[6] = dmrframe[19] & 0xF0;
+
+ // debug
+ //CBuffer dump;
+ //dump.Set(dmrsync, 6);
+ //dump.DebugDump(g_Reflector.m_DebugFile);
+
+ // and create 3 dv frames
+ // frame1
+ memcpy(dmrambe, &dmr3ambe[0], 9);
+ frames[0] = std::unique_ptr(new CDvFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 1));
+
+ // frame2
+ memcpy(dmrambe, &dmr3ambe[9], 9);
+ frames[1] = std::unique_ptr(new CDvFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 2));
+
+ // frame3
+ memcpy(dmrambe, &dmr3ambe[18], 9);
+ frames[2] = std::unique_ptr(new CDvFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 3));
+
+ // check
+ if (frames[0] && frames[1] && frames[2])
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CDmrmmdvmProtocol::IsValidDvLastFramePacket(const CBuffer &Buffer, std::unique_ptr &frame)
+{
+ uint8 tag[] = { 'D','M','R','D' };
+
+ if ( (Buffer.size() == 55) && (Buffer.Compare(tag, sizeof(tag)) == 0) )
+ {
+ // frame details
+ uint8 uiFrameType = (Buffer.data()[15] & 0x30) >> 4;
+ uint8 uiSlot = (Buffer.data()[15] & 0x80) ? DMR_SLOT2 : DMR_SLOT1;
+ //uint8 uiCallType = (Buffer.data()[15] & 0x40) ? DMR_PRIVATE_CALL : DMR_GROUP_CALL;
+ uint8 uiSlotType = Buffer.data()[15] & 0x0F;
+ //std::cout << (int)uiSlot << std::endl;
+ if ( (uiFrameType == DMRMMDVM_FRAMETYPE_DATASYNC) &&
+ (uiSlot == DMRMMDVM_REFLECTOR_SLOT) &&
+ (uiSlotType == MMDVM_SLOTTYPE_TERMINATOR) )
+ {
+ // extract sync
+ uint8 dmrsync[7];
+ dmrsync[0] = Buffer.data()[33] & 0x0F;
+ ::memcpy(&dmrsync[1], &Buffer.data()[34], 5);
+ dmrsync[6] = Buffer.data()[39] & 0xF0;
+ // and check
+ if ( (::memcmp(dmrsync, g_DmrSyncMSData, sizeof(dmrsync)) == 0) ||
+ (::memcmp(dmrsync, g_DmrSyncBSData, sizeof(dmrsync)) == 0))
+ {
+ // get payload
+ //CBPTC19696 bptc;
+ //uint8 lcdata[12];
+ //bptc.decode(&(Buffer.data()[20]), lcdata);
+
+ // crack DMR header
+ //uint8 uiSeqId = Buffer.data()[4];
+ //uint32 uiSrcId = MAKEDWORD(MAKEWORD(Buffer.data()[7],Buffer.data()[6]),MAKEWORD(Buffer.data()[5],0));
+ //uint32 uiDstId = MAKEDWORD(MAKEWORD(Buffer.data()[10],Buffer.data()[9]),MAKEWORD(Buffer.data()[8],0));
+ //uint32 uiRptrId = MAKEDWORD(MAKEWORD(Buffer.data()[14],Buffer.data()[13]),MAKEWORD(Buffer.data()[12],Buffer.data()[11]));
+ //uint8 uiVoiceSeq = (Buffer.data()[15] & 0x0F);
+ uint32 uiStreamId = *(uint32 *)(&Buffer.data()[16]);
+
+ // dummy ambe
+ uint8 ambe[9];
+ ::memset(ambe, 0, sizeof(ambe));
+
+
+ // and packet
+ frame = std::unique_ptr(new CDvLastFramePacket(ambe, dmrsync, uiStreamId, 0, 0));
+ if (frame)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet encoding helpers
+
+void CDmrmmdvmProtocol::EncodeKeepAlivePacket(CBuffer *Buffer, std::shared_ptrClient)
+{
+ uint8 tag[] = { 'M','S','T','P','O','N','G' };
+
+ Buffer->Set(tag, sizeof(tag));
+ uint32 uiDmrId = Client->GetCallsign().GetDmrid();
+ Buffer->Append((uint8 *)&uiDmrId, 4);
+}
+
+void CDmrmmdvmProtocol::EncodeAckPacket(CBuffer *Buffer, const CCallsign &Callsign)
+{
+ uint8 tag[] = { 'R','P','T','A','C','K' };
+
+ Buffer->Set(tag, sizeof(tag));
+}
+
+void CDmrmmdvmProtocol::EncodeConnectAckPacket(CBuffer *Buffer, const CCallsign &Callsign, uint32 AuthSeed)
+{
+ uint8 tag[] = { 'R','P','T','A','C','K' };
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(AuthSeed);
+}
+
+void CDmrmmdvmProtocol::EncodeNackPacket(CBuffer *Buffer, const CCallsign &Callsign)
+{
+ uint8 tag[] = { 'M','S','T','N','A','K' };
+
+ Buffer->Set(tag, sizeof(tag));
+}
+
+void CDmrmmdvmProtocol::EncodeClosePacket(CBuffer *Buffer, std::shared_ptrClient)
+{
+ uint8 tag[] = { 'M','S','T','C','L' };
+
+ Buffer->Set(tag, sizeof(tag));
+}
+
+
+bool CDmrmmdvmProtocol::EncodeDvHeaderPacket(const CDvHeaderPacket &Packet, uint8 seqid, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','M','R','D' };
+
+ Buffer->Set(tag, sizeof(tag));
+
+ // DMR header
+ // uiSeqId
+ Buffer->Append((uint8)seqid);
+ // uiSrcId
+ uint32 uiSrcId = Packet.GetMyCallsign().GetDmrid();
+ AppendDmrIdToBuffer(Buffer, uiSrcId);
+ // uiDstId = TG9
+ uint32 uiDstId = 9; // ModuleToDmrDestId(Packet.GetRpt2Module());
+ AppendDmrIdToBuffer(Buffer, uiDstId);
+ // uiRptrId
+ uint32 uiRptrId = Packet.GetRpt1Callsign().GetDmrid();
+ AppendDmrRptrIdToBuffer(Buffer, uiRptrId);
+ // uiBitField
+ uint8 uiBitField =
+ (DMRMMDVM_FRAMETYPE_DATASYNC << 4) |
+ ((DMRMMDVM_REFLECTOR_SLOT == DMR_SLOT2) ? 0x80 : 0x00) |
+ MMDVM_SLOTTYPE_HEADER;
+ Buffer->Append((uint8)uiBitField);
+ // uiStreamId
+ uint32 uiStreamId = Packet.GetStreamId();
+ Buffer->Append((uint32)uiStreamId);
+
+ // Payload
+ AppendVoiceLCToBuffer(Buffer, uiSrcId);
+
+ // BER
+ Buffer->Append((uint8)0);
+
+ // RSSI
+ Buffer->Append((uint8)0);
+
+ // done
+ return true;
+}
+
+void CDmrmmdvmProtocol::EncodeDvPacket(
+ const CDvHeaderPacket &Header,
+ const CDvFramePacket &DvFrame0, const CDvFramePacket &DvFrame1, const CDvFramePacket &DvFrame2,
+ uint8 seqid, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','M','R','D' };
+ Buffer->Set(tag, sizeof(tag));
+
+ // DMR header
+ // uiSeqId
+ Buffer->Append((uint8)seqid);
+ // uiSrcId
+ uint32 uiSrcId = Header.GetMyCallsign().GetDmrid();
+ AppendDmrIdToBuffer(Buffer, uiSrcId);
+ // uiDstId = TG9
+ uint32 uiDstId = 9; // ModuleToDmrDestId(Header.GetRpt2Module());
+ AppendDmrIdToBuffer(Buffer, uiDstId);
+ // uiRptrId
+ uint32 uiRptrId = Header.GetRpt1Callsign().GetDmrid();
+ AppendDmrRptrIdToBuffer(Buffer, uiRptrId);
+ // uiBitField
+ uint8 uiBitField =
+ ((DMRMMDVM_REFLECTOR_SLOT == DMR_SLOT2) ? 0x80 : 0x00);
+ if ( DvFrame0.GetDmrPacketId() == 0 )
+ {
+ uiBitField |= (DMRMMDVM_FRAMETYPE_VOICESYNC << 4);
+ }
+ else
+ {
+ uiBitField |= (DMRMMDVM_FRAMETYPE_VOICE << 4);
+ }
+ uiBitField |= (DvFrame0.GetDmrPacketId() & 0x0F);
+ Buffer->Append((uint8)uiBitField);
+
+ // uiStreamId
+ uint32 uiStreamId = Header.GetStreamId();
+ Buffer->Append((uint32)uiStreamId);
+
+ // Payload
+ // frame0
+ Buffer->ReplaceAt(20, DvFrame0.GetAmbePlus(), 9);
+ // 1/2 frame1
+ Buffer->ReplaceAt(29, DvFrame1.GetAmbePlus(), 5);
+ Buffer->ReplaceAt(33, (uint8)(Buffer->at(33) & 0xF0));
+ // 1/2 frame1
+ Buffer->ReplaceAt(39, DvFrame1.GetAmbePlus()+4, 5);
+ Buffer->ReplaceAt(39, (uint8)(Buffer->at(39) & 0x0F));
+ // frame2
+ Buffer->ReplaceAt(44, DvFrame2.GetAmbePlus(), 9);
+
+ // sync or embedded signaling
+ ReplaceEMBInBuffer(Buffer, DvFrame0.GetDmrPacketId());
+
+ // debug
+ //CBuffer dump;
+ //dump.Set(&(Buffer->data()[33]), 7);
+ //dump.DebugDump(g_Reflector.m_DebugFile);
+
+ // BER
+ Buffer->Append((uint8)0);
+
+ // RSSI
+ Buffer->Append((uint8)0);
+}
+
+
+void CDmrmmdvmProtocol::EncodeDvLastPacket(const CDvHeaderPacket &Packet, uint8 seqid, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','M','R','D' };
+
+ Buffer->Set(tag, sizeof(tag));
+
+ // DMR header
+ // uiSeqId
+ Buffer->Append((uint8)seqid);
+ // uiSrcId
+ uint32 uiSrcId = Packet.GetMyCallsign().GetDmrid();
+ AppendDmrIdToBuffer(Buffer, uiSrcId);
+ // uiDstId
+ uint32 uiDstId = 9; //ModuleToDmrDestId(Packet.GetRpt2Module());
+ AppendDmrIdToBuffer(Buffer, uiDstId);
+ // uiRptrId
+ uint32 uiRptrId = Packet.GetRpt1Callsign().GetDmrid();
+ AppendDmrRptrIdToBuffer(Buffer, uiRptrId);
+ // uiBitField
+ uint8 uiBitField =
+ (DMRMMDVM_FRAMETYPE_DATASYNC << 4) |
+ ((DMRMMDVM_REFLECTOR_SLOT == DMR_SLOT2) ? 0x80 : 0x00) |
+ MMDVM_SLOTTYPE_TERMINATOR;
+ Buffer->Append((uint8)uiBitField);
+ // uiStreamId
+ uint32 uiStreamId = Packet.GetStreamId();
+ Buffer->Append((uint32)uiStreamId);
+
+ // Payload
+ AppendTerminatorLCToBuffer(Buffer, uiSrcId);
+
+ // BER
+ Buffer->Append((uint8)0);
+
+ // RSSI
+ Buffer->Append((uint8)0);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DestId to Module helper
+
+char CDmrmmdvmProtocol::DmrDstIdToModule(uint32 tg) const
+{
+ // is it a 4xxx ?
+ if ( (tg >= 4001) && (tg <= (4000 + NB_OF_MODULES)) )
+ {
+ return ((char)(tg - 4001) + 'A');
+ }
+ return ' ';
+}
+
+uint32 CDmrmmdvmProtocol::ModuleToDmrDestId(char m) const
+{
+ return (uint32)(m - 'A')+4001;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Buffer & LC helpers
+
+void CDmrmmdvmProtocol::AppendVoiceLCToBuffer(CBuffer *buffer, uint32 uiSrcId) const
+{
+ uint8 payload[33];
+
+ // fill payload
+ CBPTC19696 bptc;
+ ::memset(payload, 0, sizeof(payload));
+ // LC data
+ uint8 lc[12];
+ {
+ ::memset(lc, 0, sizeof(lc));
+ // uiDstId = TG9
+ lc[5] = 9;
+ // uiSrcId
+ lc[6] = (uint8)LOBYTE(HIWORD(uiSrcId));
+ lc[7] = (uint8)HIBYTE(LOWORD(uiSrcId));
+ lc[8] = (uint8)LOBYTE(LOWORD(uiSrcId));
+ // parity
+ uint8 parity[4];
+ CRS129::encode(lc, 9, parity);
+ lc[9] = parity[2] ^ DMR_VOICE_LC_HEADER_CRC_MASK;
+ lc[10] = parity[1] ^ DMR_VOICE_LC_HEADER_CRC_MASK;
+ lc[11] = parity[0] ^ DMR_VOICE_LC_HEADER_CRC_MASK;
+ }
+ // sync
+ ::memcpy(payload+13, g_DmrSyncBSData, sizeof(g_DmrSyncBSData));
+ // slot type
+ {
+ // slot type
+ uint8 slottype[3];
+ ::memset(slottype, 0, sizeof(slottype));
+ slottype[0] = (DMRMMDVM_REFLECTOR_COLOUR << 4) & 0xF0;
+ slottype[0] |= (DMR_DT_VOICE_LC_HEADER << 0) & 0x0FU;
+ CGolay2087::encode(slottype);
+ payload[12U] = (payload[12U] & 0xC0U) | ((slottype[0U] >> 2) & 0x3FU);
+ payload[13U] = (payload[13U] & 0x0FU) | ((slottype[0U] << 6) & 0xC0U) | ((slottype[1U] >> 2) & 0x30U);
+ payload[19U] = (payload[19U] & 0xF0U) | ((slottype[1U] >> 2) & 0x0FU);
+ payload[20U] = (payload[20U] & 0x03U) | ((slottype[1U] << 6) & 0xC0U) | ((slottype[2U] >> 2) & 0x3CU);
+
+ }
+ // and encode
+ bptc.encode(lc, payload);
+
+ // and append
+ buffer->Append(payload, sizeof(payload));
+}
+
+void CDmrmmdvmProtocol::AppendTerminatorLCToBuffer(CBuffer *buffer, uint32 uiSrcId) const
+{
+ uint8 payload[33];
+
+ // fill payload
+ CBPTC19696 bptc;
+ ::memset(payload, 0, sizeof(payload));
+ // LC data
+ uint8 lc[12];
+ {
+ ::memset(lc, 0, sizeof(lc));
+ // uiDstId = TG9
+ lc[5] = 9;
+ // uiSrcId
+ lc[6] = (uint8)LOBYTE(HIWORD(uiSrcId));
+ lc[7] = (uint8)HIBYTE(LOWORD(uiSrcId));
+ lc[8] = (uint8)LOBYTE(LOWORD(uiSrcId));
+ // parity
+ uint8 parity[4];
+ CRS129::encode(lc, 9, parity);
+ lc[9] = parity[2] ^ DMR_TERMINATOR_WITH_LC_CRC_MASK;
+ lc[10] = parity[1] ^ DMR_TERMINATOR_WITH_LC_CRC_MASK;
+ lc[11] = parity[0] ^ DMR_TERMINATOR_WITH_LC_CRC_MASK;
+ }
+ // sync
+ ::memcpy(payload+13, g_DmrSyncBSData, sizeof(g_DmrSyncBSData));
+ // slot type
+ {
+ // slot type
+ uint8 slottype[3];
+ ::memset(slottype, 0, sizeof(slottype));
+ slottype[0] = (DMRMMDVM_REFLECTOR_COLOUR << 4) & 0xF0;
+ slottype[0] |= (DMR_DT_TERMINATOR_WITH_LC << 0) & 0x0FU;
+ CGolay2087::encode(slottype);
+ payload[12U] = (payload[12U] & 0xC0U) | ((slottype[0U] >> 2) & 0x3FU);
+ payload[13U] = (payload[13U] & 0x0FU) | ((slottype[0U] << 6) & 0xC0U) | ((slottype[1U] >> 2) & 0x30U);
+ payload[19U] = (payload[19U] & 0xF0U) | ((slottype[1U] >> 2) & 0x0FU);
+ payload[20U] = (payload[20U] & 0x03U) | ((slottype[1U] << 6) & 0xC0U) | ((slottype[2U] >> 2) & 0x3CU);
+ }
+ // and encode
+ bptc.encode(lc, payload);
+
+ // and append
+ buffer->Append(payload, sizeof(payload));
+}
+
+void CDmrmmdvmProtocol::ReplaceEMBInBuffer(CBuffer *buffer, uint8 uiDmrPacketId) const
+{
+ // voice packet A ?
+ if ( uiDmrPacketId == 0 )
+ {
+ // sync
+ buffer->ReplaceAt(33, (uint8)(buffer->at(33) | (g_DmrSyncBSVoice[0] & 0x0F)));
+ buffer->ReplaceAt(34, g_DmrSyncBSVoice+1, 5);
+ buffer->ReplaceAt(39, (uint8)(buffer->at(39) | (g_DmrSyncBSVoice[6] & 0xF0)));
+ }
+ // voice packet B,C,D,E ?
+ else if ( (uiDmrPacketId >= 1) && (uiDmrPacketId <= 4 ) )
+ {
+ // EMB LC
+ uint8 emb[2];
+ emb[0] = (DMRMMDVM_REFLECTOR_COLOUR << 4) & 0xF0;
+ //emb[0] |= PI ? 0x08U : 0x00;
+ //emb[0] |= (LCSS << 1) & 0x06;
+ emb[1] = 0x00;
+ // encode
+ CQR1676::encode(emb);
+ // and append
+ buffer->ReplaceAt(33, (uint8)((buffer->at(33) & 0xF0) | ((emb[0U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(34, (uint8)((buffer->at(34) & 0x0F) | ((emb[0U] << 4) & 0xF0)));
+ buffer->ReplaceAt(34, (uint8)(buffer->at(34) & 0xF0));
+ buffer->ReplaceAt(35, (uint8)0);
+ buffer->ReplaceAt(36, (uint8)0);
+ buffer->ReplaceAt(37, (uint8)0);
+ buffer->ReplaceAt(38, (uint8)(buffer->at(38) & 0x0F));
+ buffer->ReplaceAt(38, (uint8)((buffer->at(38) & 0xF0) | ((emb[1U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(39, (uint8)((buffer->at(39) & 0x0F) | ((emb[1U] << 4) & 0xF0)));
+ }
+ // voice packet F
+ else
+ {
+ // nullptr
+ uint8 emb[2];
+ emb[0] = (DMRMMDVM_REFLECTOR_COLOUR << 4) & 0xF0;
+ //emb[0] |= PI ? 0x08U : 0x00;
+ //emb[0] |= (LCSS << 1) & 0x06;
+ emb[1] = 0x00;
+ // encode
+ CQR1676::encode(emb);
+ // and append
+ buffer->ReplaceAt(33, (uint8)((buffer->at(33) & 0xF0) | ((emb[0U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(34, (uint8)((buffer->at(34) & 0x0F) | ((emb[0U] << 4) & 0xF0)));
+ buffer->ReplaceAt(34, (uint8)(buffer->at(34) & 0xF0));
+ buffer->ReplaceAt(35, (uint8)0);
+ buffer->ReplaceAt(36, (uint8)0);
+ buffer->ReplaceAt(37, (uint8)0);
+ buffer->ReplaceAt(38, (uint8)(buffer->at(38) & 0x0F));
+ buffer->ReplaceAt(38, (uint8)((buffer->at(38) & 0xF0) | ((emb[1U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(39, (uint8)((buffer->at(39) & 0x0F) | ((emb[1U] << 4) & 0xF0)));
+ }
+}
+
+void CDmrmmdvmProtocol::AppendDmrIdToBuffer(CBuffer *buffer, uint32 id) const
+{
+ buffer->Append((uint8)LOBYTE(HIWORD(id)));
+ buffer->Append((uint8)HIBYTE(LOWORD(id)));
+ buffer->Append((uint8)LOBYTE(LOWORD(id)));
+}
+
+void CDmrmmdvmProtocol::AppendDmrRptrIdToBuffer(CBuffer *buffer, uint32 id) const
+{
+ buffer->Append((uint8)HIBYTE(HIWORD(id)));
+ buffer->Append((uint8)LOBYTE(HIWORD(id)));
+ buffer->Append((uint8)HIBYTE(LOWORD(id)));
+ buffer->Append((uint8)LOBYTE(LOWORD(id)));
+}
diff --git a/src/DMRMMDVMProtocol.h b/src/DMRMMDVMProtocol.h
new file mode 100644
index 0000000..3ddbd4e
--- /dev/null
+++ b/src/DMRMMDVMProtocol.h
@@ -0,0 +1,136 @@
+//
+// DMMMDVMProtocol.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 04/03/2017.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdmrmmdvmprotocol_h
+#define cdmrmmdvmprotocol_h
+
+#include "Timer.h"
+#include "DCSProtocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+#include "DVLastFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+// frame type
+#define DMRMMDVM_FRAMETYPE_VOICE 0
+#define DMRMMDVM_FRAMETYPE_VOICESYNC 1
+#define DMRMMDVM_FRAMETYPE_DATASYNC 2
+
+// slot type
+#define MMDVM_SLOTTYPE_HEADER 1
+#define MMDVM_SLOTTYPE_TERMINATOR 2
+
+// DMRMMDVM Module ID
+#define MMDVM_MODULE_ID 'B'
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDmrmmdvmStreamCacheItem
+{
+public:
+ CDmrmmdvmStreamCacheItem() {}
+ ~CDmrmmdvmStreamCacheItem() {}
+
+ CDvHeaderPacket m_dvHeader;
+ CDvFramePacket m_dvFrame0;
+ CDvFramePacket m_dvFrame1;
+
+ uint8 m_uiSeqId;
+};
+
+
+class CDmrmmdvmProtocol : public CProtocol
+{
+public:
+ // initialization
+ bool Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6);
+
+ // task
+ void Task(void);
+
+protected:
+ // queue helper
+ void HandleQueue(void);
+
+ // keepalive helpers
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &, uint8, uint8);
+
+ // packet decoding helpers
+ bool IsValidConnectPacket(const CBuffer &, CCallsign *, const CIp &);
+ bool IsValidAuthenticationPacket(const CBuffer &, CCallsign *, const CIp &);
+ bool IsValidDisconnectPacket(const CBuffer &, CCallsign *);
+ bool IsValidConfigPacket(const CBuffer &, CCallsign *, const CIp &);
+ bool IsValidOptionPacket(const CBuffer &, CCallsign *);
+ bool IsValidKeepAlivePacket(const CBuffer &, CCallsign *);
+ bool IsValidRssiPacket(const CBuffer &, CCallsign *, int *);
+ bool IsValidDvHeaderPacket(const CBuffer &, std::unique_ptr &, uint8 *, uint8 *);
+ bool IsValidDvFramePacket(const CBuffer &, std::array, 3> &);
+ bool IsValidDvLastFramePacket(const CBuffer &, std::unique_ptr &);
+
+ // packet encoding helpers
+ void EncodeKeepAlivePacket(CBuffer *, std::shared_ptr);
+ void EncodeAckPacket(CBuffer *, const CCallsign &);
+ void EncodeConnectAckPacket(CBuffer *, const CCallsign &, uint32);
+ void EncodeNackPacket(CBuffer *, const CCallsign &);
+ void EncodeClosePacket(CBuffer *, std::shared_ptr);
+ bool EncodeDvHeaderPacket(const CDvHeaderPacket &, uint8, CBuffer *) const;
+ void EncodeDvPacket(const CDvHeaderPacket &, const CDvFramePacket &, const CDvFramePacket &, const CDvFramePacket &, uint8, CBuffer *) const;
+ void EncodeDvLastPacket(const CDvHeaderPacket &, uint8, CBuffer *) const;
+
+ // dmr DstId to Module helper
+ char DmrDstIdToModule(uint32) const;
+ uint32 ModuleToDmrDestId(char) const;
+
+ // Buffer & LC helpers
+ void AppendVoiceLCToBuffer(CBuffer *, uint32) const;
+ void AppendTerminatorLCToBuffer(CBuffer *, uint32) const;
+ void ReplaceEMBInBuffer(CBuffer *, uint8) const;
+ void AppendDmrIdToBuffer(CBuffer *, uint32) const;
+ void AppendDmrRptrIdToBuffer(CBuffer *, uint32) const;
+
+
+protected:
+ // for keep alive
+ CTimePoint m_LastKeepaliveTime;
+
+ // for stream id
+ uint16 m_uiStreamId;
+
+ // for queue header caches
+ std::array m_StreamsCache;
+
+ // for authentication
+ uint32 m_uiAuthSeed;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+#endif /* cdmrmmdvmprotocol_h */
diff --git a/src/DMRPlusClient.cpp b/src/DMRPlusClient.cpp
new file mode 100644
index 0000000..9c791b2
--- /dev/null
+++ b/src/DMRPlusClient.cpp
@@ -0,0 +1,52 @@
+//
+// cdmrplusclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL on 10/01/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "cdmrplusclient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CDmrplusClient::CDmrplusClient()
+{
+}
+
+CDmrplusClient::CDmrplusClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+}
+
+CDmrplusClient::CDmrplusClient(const CDmrplusClient &client)
+ : CClient(client)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CDmrplusClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < DMRPLUS_KEEPALIVE_TIMEOUT);
+}
diff --git a/src/DMRPlusClient.h b/src/DMRPlusClient.h
new file mode 100644
index 0000000..c032140
--- /dev/null
+++ b/src/DMRPlusClient.h
@@ -0,0 +1,59 @@
+//
+// cdmrplusclient.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 10/01/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdmrplusclient_h
+#define cdmrplusclient_h
+
+#include "Client.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDmrplusClient : public CClient
+{
+public:
+ // constructors
+ CDmrplusClient();
+ CDmrplusClient(const CCallsign &, const CIp &, char = ' ');
+ CDmrplusClient(const CDmrplusClient &);
+
+ // destructor
+ virtual ~CDmrplusClient() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_DMRPLUS; }
+ const char *GetProtocolName(void) const { return "DMRplus"; }
+ int GetCodec(void) const { return CODEC_AMBE2PLUS; }
+ bool IsNode(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdmrplusclient_h */
diff --git a/src/DMRPlusProtocol.cpp b/src/DMRPlusProtocol.cpp
new file mode 100644
index 0000000..f2e2910
--- /dev/null
+++ b/src/DMRPlusProtocol.cpp
@@ -0,0 +1,788 @@
+//
+// cdmrplusprotocol.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 10/01/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "cdmrplusclient.h"
+#include "DMRPlusProtocol.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+#include "DMRIdDir.h"
+#include "CBPT19696.h"
+#include "RS129.h"
+#include "Golay2087.h"
+#include "QR1676.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constants
+
+static uint8 g_DmrSyncBSVoice[] = { 0x07,0x55,0xFD,0x7D,0xF7,0x5F,0x70 };
+static uint8 g_DmrSyncBSData[] = { 0x0D,0xFF,0x57,0xD7,0x5D,0xF5,0xD0 };
+static uint8 g_DmrSyncMSVoice[] = { 0x07,0xF7,0xD5,0xDD,0x57,0xDF,0xD0 };
+static uint8 g_DmrSyncMSData[] = { 0x0D,0x5D,0x7F,0x77,0xFD,0x75,0x70 };
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CDmrplusProtocol::Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6)
+{
+ // base class
+ if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
+ return false;
+
+ // update time
+ m_LastKeepaliveTime.Now();
+
+ // random number generator
+ time_t t;
+ ::srand((unsigned) time(&t));
+
+ // done
+ return true;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// task
+
+void CDmrplusProtocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+ char ToLinkModule;
+ std::unique_ptr Header;
+ std::array, 3> Frames;
+
+ // handle incoming packets
+#if DMR_IPV6==true
+#if DMR_IPV4==true
+ if ( ReceiveDS(Buffer, Ip, 20) )
+#else
+ if ( Receive6(Buffer, Ip, 20) )
+#endif
+#else
+ if ( Receive4(Buffer, Ip, 20) )
+#endif
+ {
+ // crack the packet
+ if ( IsValidDvFramePacket(Ip, Buffer, Frames) )
+ {
+ for ( int i = 0; i < 3; i++ )
+ {
+ OnDvFramePacketIn(Frames[i], &Ip);
+ }
+ }
+ else if ( IsValidDvHeaderPacket(Ip, Buffer, Header) )
+ {
+ // callsign muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, PROTOCOL_DMRPLUS) )
+ {
+ // handle it
+ OnDvHeaderPacketIn(Header, Ip);
+ }
+ }
+ else if ( IsValidConnectPacket(Buffer, &Callsign, &ToLinkModule, Ip) )
+ {
+ //std::cout << "DMRplus keepalive/connect packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << std::endl;
+
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, PROTOCOL_DMRPLUS) )
+ {
+ // acknowledge the request
+ EncodeConnectAckPacket(&Buffer);
+ Send(Buffer, Ip);
+
+ // add client if needed
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Callsign, Ip, PROTOCOL_DMRPLUS);
+ // client already connected ?
+ if ( client == nullptr )
+ {
+ std::cout << "DMRplus connect packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << std::endl;
+
+ // create the client and append
+ clients->AddClient(std::make_shared(Callsign, Ip, ToLinkModule));
+ }
+ else
+ {
+ client->Alive();
+ }
+ // and done
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ // deny the request
+ EncodeConnectNackPacket(&Buffer);
+ Send(Buffer, Ip);
+ }
+
+ }
+ else if ( IsValidDisconnectPacket(Buffer, &Callsign, &ToLinkModule) )
+ {
+ std::cout << "DMRplus disconnect packet for module " << ToLinkModule << " from " << Callsign << " at " << Ip << std::endl;
+
+ // find client & remove it
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Ip, PROTOCOL_DMRPLUS);
+ if ( client != nullptr )
+ {
+ clients->RemoveClient(client);
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ std::string title("Unknown DMR+ packet from ");
+ title += Ip.GetAddress();
+ Buffer.Dump(title);
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+
+ // keep client alive
+ if ( m_LastKeepaliveTime.DurationSinceNow() > DMRPLUS_KEEPALIVE_PERIOD )
+ {
+ //
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.Now();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CDmrplusProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip)
+{
+ // find the stream
+ CPacketStream *stream = GetStream(Header->GetStreamId());
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+
+ // no stream open yet, open a new one
+ // find this client
+ std::shared_ptrclient = g_Reflector.GetClients()->FindClient(Ip, PROTOCOL_DMRPLUS);
+ if ( client )
+ {
+ // and try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams.push_back(stream);
+ }
+ }
+ // release
+ g_Reflector.ReleaseClients();
+ // update last heard
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CDmrplusProtocol::HandleQueue(void)
+{
+
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // get the packet
+ auto packet = m_Queue.front();
+ m_Queue.pop();
+
+ // get our sender's id
+ int iModId = g_Reflector.GetModuleIndex(packet->GetModuleId());
+
+ // encode
+ CBuffer buffer;
+
+ // check if it's header
+ if ( packet->IsDvHeader() )
+ {
+ // update local stream cache
+ // this relies on queue feeder setting valid module id
+ m_StreamsCache[iModId].m_dvHeader = CDvHeaderPacket((const CDvHeaderPacket &)*packet);
+ m_StreamsCache[iModId].m_uiSeqId = 4;
+
+ // encode it
+ EncodeDvHeaderPacket((const CDvHeaderPacket &)*packet, &buffer);
+ }
+ else
+ {
+ // update local stream cache or send triplet when needed
+ switch ( packet->GetDmrPacketSubid() )
+ {
+ case 1:
+ m_StreamsCache[iModId].m_dvFrame0 = CDvFramePacket((const CDvFramePacket &)*packet);
+ break;
+ case 2:
+ m_StreamsCache[iModId].m_dvFrame1 = CDvFramePacket((const CDvFramePacket &)*packet);
+ break;
+ case 3:
+ EncodeDvPacket(
+ m_StreamsCache[iModId].m_dvHeader,
+ m_StreamsCache[iModId].m_dvFrame0,
+ m_StreamsCache[iModId].m_dvFrame1,
+ (const CDvFramePacket &)*packet,
+ m_StreamsCache[iModId].m_uiSeqId,
+ &buffer);
+ m_StreamsCache[iModId].m_uiSeqId = GetNextSeqId(m_StreamsCache[iModId].m_uiSeqId);
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ // send it
+ if ( buffer.size() > 0 )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DMRPLUS, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == packet->GetModuleId()) )
+ {
+ // no, send the packet
+ Send(buffer, client->GetIp());
+ }
+ }
+ g_Reflector.ReleaseClients();
+
+ // debug
+ //buffer.DebugDump(g_Reflector.m_DebugFile);
+ }
+ }
+ m_Queue.Unlock();
+}
+
+void CDmrplusProtocol::SendBufferToClients(const CBuffer &buffer, uint8 module)
+{
+ if ( buffer.size() > 0 )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DMRPLUS, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == module) )
+ {
+ // no, send the packet
+ Send(buffer, client->GetIp());
+ }
+ }
+ g_Reflector.ReleaseClients();
+
+ // debug
+ //buffer.DebugDump(g_Reflector.m_DebugFile);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CDmrplusProtocol::HandleKeepalives(void)
+{
+ // DMRplus protocol keepalive request is client tasks
+ // here, just check that all clients are still alive
+ // and disconnect them if not
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DMRPLUS, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( client->IsAMaster() )
+ {
+ // yes, just tickle it
+ client->Alive();
+ }
+ // check it's still with us
+ else if ( !client->IsAlive() )
+ {
+ // no, disconnect
+ //CBuffer disconnect;
+ //EncodeDisconnectPacket(&disconnect, client);
+ //Send(disconnect, client->GetIp());
+
+ // remove it
+ std::cout << "DMRplus client " << client->GetCallsign() << " keepalive timeout" << std::endl;
+ clients->RemoveClient(client);
+ }
+
+ }
+ g_Reflector.ReleaseClients();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet decoding helpers
+
+bool CDmrplusProtocol::IsValidConnectPacket(const CBuffer &Buffer, CCallsign *callsign, char *reflectormodule, const CIp &Ip)
+{
+ bool valid = false;
+ if ( Buffer.size() == 31 )
+ {
+ char sz[9];
+ ::memcpy(sz, Buffer.data(), 8);
+ sz[8] = 0;
+ uint32 dmrid = atoi(sz);
+ callsign->SetDmrid(dmrid, true);
+ callsign->SetModule(DMRPLUS_MODULE_ID);
+ ::memcpy(sz, &Buffer.data()[8], 4);
+ sz[4] = 0;
+ *reflectormodule = DmrDstIdToModule(atoi(sz));
+ valid = (callsign->IsValid() && (std::isupper(*reflectormodule) || (*reflectormodule == ' ')) );
+ if ( !valid)
+ {
+ std::cout << "Invalid callsign in DMR+ connect packet from IP: " << Ip << " CS:" << *callsign << " DMRID:" << callsign->GetDmrid() << " ReflectorModule:" << *reflectormodule << std::endl;
+ }
+ }
+ return valid;
+}
+
+bool CDmrplusProtocol::IsValidDisconnectPacket(const CBuffer &Buffer, CCallsign *callsign, char *reflectormodule)
+{
+ bool valid = false;
+ if ( Buffer.size() == 32 )
+ {
+ char sz[9];
+ ::memcpy(sz, Buffer.data(), 8);
+ sz[8] = 0;
+ uint32 dmrid = atoi(sz);
+ callsign->SetDmrid(dmrid, true);
+ callsign->SetModule(DMRPLUS_MODULE_ID);
+ *reflectormodule = Buffer.data()[11] - '0' + 'A';
+ valid = (callsign->IsValid() && std::isupper(*reflectormodule));
+ }
+ return valid;
+}
+
+bool CDmrplusProtocol::IsValidDvHeaderPacket(const CIp &Ip, const CBuffer &Buffer, std::unique_ptr &Header)
+{
+ uint8 uiPacketType = Buffer.data()[8];
+ if ( (Buffer.size() == 72) && ( uiPacketType == 2 ) )
+ {
+ // frame details
+ uint8 uiSlot = (Buffer.data()[16] == 0x22) ? DMR_SLOT2 : DMR_SLOT1;
+ uint8 uiCallType = (Buffer.data()[62] == 1) ? DMR_GROUP_CALL : DMR_PRIVATE_CALL;
+ uint8 uiColourCode = Buffer.data()[20] & 0x0F;
+ if ( (uiSlot == DMRPLUS_REFLECTOR_SLOT) && (uiCallType == DMR_GROUP_CALL) && (uiColourCode == DMRPLUS_REFLECTOR_COLOUR) )
+ {
+ // more frames details
+ //uint8 uiSeqId = Buffer.data()[4];
+ //uint8 uiVoiceSeq = (Buffer.data()[18] & 0x0F) - 7; // aka slot type
+ uint32 uiDstId = *(uint32 *)(&Buffer.data()[64]) & 0x00FFFFFF;
+ uint32 uiSrcId = *(uint32 *)(&Buffer.data()[68]) & 0x00FFFFFF;
+
+ // build DVHeader
+ CCallsign csMY = CCallsign("", uiSrcId);
+ CCallsign rpt1 = CCallsign("", uiSrcId);
+ rpt1.SetModule(DMRPLUS_MODULE_ID);
+ CCallsign rpt2 = m_ReflectorCallsign;
+ rpt2.SetModule(DmrDstIdToModule(uiDstId));
+ uint32 uiStreamId = IpToStreamId(Ip);
+
+ // and packet
+ Header = std::unique_ptr(new CDvHeaderPacket(uiSrcId, CCallsign("CQCQCQ"), rpt1, rpt2, uiStreamId, 0, 0));
+ if (Header && Header->IsValid())
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CDmrplusProtocol::IsValidDvFramePacket(const CIp &Ip, const CBuffer &Buffer, std::array, 3> &frames)
+{
+ uint8 uiPacketType = Buffer.data()[8];
+ if ( (Buffer.size() == 72) && ((uiPacketType == 1) || (uiPacketType == 3)) )
+ {
+ // frame details
+ uint8 uiSlot = (Buffer.data()[16] == 0x22) ? DMR_SLOT2 : DMR_SLOT1;
+ uint8 uiCallType = (Buffer.data()[62] == 1) ? DMR_GROUP_CALL : DMR_PRIVATE_CALL;
+ uint8 uiColourCode = Buffer.data()[20] & 0x0F;
+ if ( (uiSlot == DMRPLUS_REFLECTOR_SLOT) && (uiCallType == DMR_GROUP_CALL) && (uiColourCode == DMRPLUS_REFLECTOR_COLOUR) )
+ {
+ // more frames details
+ //uint8 uiSeqId = Buffer.data()[4];
+ uint8 uiVoiceSeq = (Buffer.data()[18] & 0x0F) - 7; // aka slot type
+ //uint32 uiDstId = *(uint32 *)(&Buffer.data()[64]) & 0x00FFFFFF;
+ //uint32 uiSrcId = *(uint32 *)(&Buffer.data()[68]) & 0x00FFFFFF;
+
+ // crack payload
+ uint8 dmrframe[33];
+ uint8 dmr3ambe[27];
+ uint8 dmrambe[9];
+ uint8 dmrsync[7];
+ // get the 33 bytes ambe
+ memcpy(dmrframe, &(Buffer.data()[26]), 33);
+ // handle endianess
+ SwapEndianess(dmrframe, sizeof(dmrframe));
+ // extract the 3 ambe frames
+ memcpy(dmr3ambe, dmrframe, 14);
+ dmr3ambe[13] &= 0xF0;
+ dmr3ambe[13] |= (dmrframe[19] & 0x0F);
+ memcpy(&dmr3ambe[14], &dmrframe[20], 13);
+ // extract sync
+ dmrsync[0] = dmrframe[13] & 0x0F;
+ ::memcpy(&dmrsync[1], &dmrframe[14], 5);
+ dmrsync[6] = dmrframe[19] & 0xF0;
+
+ // and create 3 dv frames
+ uint32 uiStreamId = IpToStreamId(Ip);
+ // frame1
+ memcpy(dmrambe, &dmr3ambe[0], 9);
+ frames[0] = std::unique_ptr(new CDvFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 1));
+
+ // frame2
+ memcpy(dmrambe, &dmr3ambe[9], 9);
+ frames[1] = std::unique_ptr(new CDvFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 2));
+
+ // frame3
+ memcpy(dmrambe, &dmr3ambe[18], 9);
+ if ( uiPacketType == 3 )
+ {
+ frames[2] = std::unique_ptr(new CDvLastFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 3));
+ }
+ else
+ {
+ frames[2] = std::unique_ptr(new CDvFramePacket(dmrambe, dmrsync, uiStreamId, uiVoiceSeq, 3));
+ }
+
+ // check
+ if (frames[0] && frames[1] && frames[2])
+ return true;
+ }
+ }
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet encoding helpers
+
+void CDmrplusProtocol::EncodeConnectAckPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 'A','C','K',' ','O','K',0x0A,0x00 };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+void CDmrplusProtocol::EncodeConnectNackPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 'N','A','K',' ','O','K',0x0A,0x00 };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+bool CDmrplusProtocol::EncodeDvHeaderPacket(const CDvHeaderPacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,
+ 0x00,0x05,0x01,0x02,0x00,0x00,0x00
+ } ;
+ Buffer->Set(tag, sizeof(tag));
+
+ // uiSeqId
+ //Buffer->ReplaceAt(4, 2);
+ // uiPktType
+ //Buffer->ReplaceAt(8, 2);
+ // uiSlot
+ Buffer->Append((uint16)((DMRPLUS_REFLECTOR_SLOT == DMR_SLOT1) ? 0x1111 : 0x2222));
+ // uiSlotType
+ Buffer->Append((uint16)0xEEEE);
+ // uiColourCode
+ uint8 uiColourCode = DMRPLUS_REFLECTOR_COLOUR | (DMRPLUS_REFLECTOR_COLOUR << 4);
+ Buffer->Append((uint8)uiColourCode);
+ Buffer->Append((uint8)uiColourCode);
+ // uiFrameType
+ Buffer->Append((uint16)0x1111);
+ // reserved
+ Buffer->Append((uint16)0x0000);
+ // payload
+ uint32 uiSrcId = Packet.GetMyCallsign().GetDmrid() & 0x00FFFFFF;
+ uint32 uiDstId = ModuleToDmrDestId(Packet.GetRpt2Module()) & 0x00FFFFFF;
+ Buffer->Append((uint8)0x00, 34);
+ Buffer->ReplaceAt(36, HIBYTE(HIWORD(uiSrcId)));
+ Buffer->ReplaceAt(38, LOBYTE(HIWORD(uiSrcId)));
+ Buffer->ReplaceAt(40, HIBYTE(LOWORD(uiSrcId)));
+ Buffer->ReplaceAt(42, LOBYTE(LOWORD(uiSrcId)));
+
+ // reserved
+ Buffer->Append((uint16)0x0000);
+ // uiCallType
+ Buffer->Append((uint8)0x01);
+ // reserved
+ Buffer->Append((uint8)0x00);
+ // uiDstId
+ Buffer->Append(uiDstId);
+ // uiSrcId
+ Buffer->Append(uiSrcId);
+
+ // done
+ return true;
+}
+
+void CDmrplusProtocol::EncodeDvPacket
+(const CDvHeaderPacket &Header,
+ const CDvFramePacket &DvFrame0, const CDvFramePacket &DvFrame1, const CDvFramePacket &DvFrame2,
+ uint8 seqid, CBuffer *Buffer) const
+{
+
+ uint8 tag[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
+ 0x00,0x05,0x01,0x02,0x00,0x00,0x00
+ } ;
+ Buffer->Set(tag, sizeof(tag));
+
+ // uiSeqId
+ Buffer->ReplaceAt(4, seqid);
+ // uiPktType
+ //Buffer->ReplaceAt(8, 1);
+ // uiSlot
+ Buffer->Append((uint16)((DMRPLUS_REFLECTOR_SLOT == DMR_SLOT1) ? 0x1111 : 0x2222));
+ // uiVoiceSeq
+ uint8 uiVoiceSeq = (DvFrame0.GetDmrPacketId() + 7) | ((DvFrame0.GetDmrPacketId() + 7) << 4);
+ Buffer->Append((uint8)uiVoiceSeq);
+ Buffer->Append((uint8)uiVoiceSeq);
+ // uiColourCode
+ uint8 uiColourCode = DMRPLUS_REFLECTOR_COLOUR | (DMRPLUS_REFLECTOR_COLOUR << 4);
+ Buffer->Append((uint8)uiColourCode);
+ Buffer->Append((uint8)uiColourCode);
+ // uiFrameType
+ Buffer->Append((uint16)0x1111);
+ // reserved
+ Buffer->Append((uint16)0x0000);
+
+ // payload
+ uint32 uiSrcId = Header.GetMyCallsign().GetDmrid() & 0x00FFFFFF;
+ uint32 uiDstId = ModuleToDmrDestId(Header.GetRpt2Module()) & 0x00FFFFFF;
+ // frame0
+ Buffer->ReplaceAt(26, DvFrame0.GetAmbePlus(), 9);
+ // 1/2 frame1
+ Buffer->ReplaceAt(35, DvFrame1.GetAmbePlus(), 5);
+ Buffer->ReplaceAt(39, (uint8)(Buffer->at(39) & 0xF0));
+ // 1/2 frame1
+ Buffer->ReplaceAt(45, DvFrame1.GetAmbePlus()+4, 5);
+ Buffer->ReplaceAt(45, (uint8)(Buffer->at(45) & 0x0F));
+ // frame2
+ Buffer->ReplaceAt(50, DvFrame2.GetAmbePlus(), 9);
+
+ // sync or embedded signaling
+ ReplaceEMBInBuffer(Buffer, DvFrame0.GetDmrPacketId());
+
+ // reserved
+ Buffer->Append((uint16)0x0000);
+ Buffer->Append((uint8)0x00);
+ // uiCallType
+ Buffer->Append((uint8)0x01);
+ // reserved
+ Buffer->Append((uint8)0x00);
+ // uiDstId
+ Buffer->Append(uiDstId);
+ // uiSrcId
+ Buffer->Append(uiSrcId);
+
+ // handle indianess
+ SwapEndianess(&(Buffer->data()[26]), 34);
+}
+
+void CDmrplusProtocol::SwapEndianess(uint8 *buffer, int len) const
+{
+ for ( int i = 0; i < len; i += 2 )
+ {
+ uint8 t = buffer[i];
+ buffer[i] = buffer[i+1];
+ buffer[i+1] = t;
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SeqId helper
+
+uint8 CDmrplusProtocol::GetNextSeqId(uint8 uiSeqId) const
+{
+ return (uiSeqId + 1) & 0xFF;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DestId to Module helper
+
+char CDmrplusProtocol::DmrDstIdToModule(uint32 tg) const
+{
+ // is it a 4xxx ?
+ if ( (tg >= 4001) && (tg <= (4000 + NB_OF_MODULES)) )
+ {
+ return ((char)(tg - 4001) + 'A');
+ }
+ return ' ';
+}
+
+uint32 CDmrplusProtocol::ModuleToDmrDestId(char m) const
+{
+ return (uint32)(m - 'A')+4001;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Buffer & LC helpers
+
+void CDmrplusProtocol::AppendVoiceLCToBuffer(CBuffer *buffer, uint32 uiSrcId) const
+{
+ uint8 payload[34];
+
+ // fill payload
+ CBPTC19696 bptc;
+ ::memset(payload, 0, sizeof(payload));
+ // LC data
+ uint8 lc[12];
+ {
+ ::memset(lc, 0, sizeof(lc));
+ // uiDstId = TG9
+ lc[5] = 9;
+ // uiSrcId
+ lc[6] = (uint8)LOBYTE(HIWORD(uiSrcId));
+ lc[7] = (uint8)HIBYTE(LOWORD(uiSrcId));
+ lc[8] = (uint8)LOBYTE(LOWORD(uiSrcId));
+ // parity
+ uint8 parity[4];
+ CRS129::encode(lc, 9, parity);
+ lc[9] = parity[2] ^ DMR_VOICE_LC_HEADER_CRC_MASK;
+ lc[10] = parity[1] ^ DMR_VOICE_LC_HEADER_CRC_MASK;
+ lc[11] = parity[0] ^ DMR_VOICE_LC_HEADER_CRC_MASK;
+ }
+ // sync
+ ::memcpy(payload+13, g_DmrSyncBSData, sizeof(g_DmrSyncBSData));
+ // slot type
+ {
+ // slot type
+ uint8 slottype[3];
+ ::memset(slottype, 0, sizeof(slottype));
+ slottype[0] = (DMRPLUS_REFLECTOR_COLOUR << 4) & 0xF0;
+ slottype[0] |= (DMR_DT_VOICE_LC_HEADER << 0) & 0x0FU;
+ CGolay2087::encode(slottype);
+ payload[12U] = (payload[12U] & 0xC0U) | ((slottype[0U] >> 2) & 0x3FU);
+ payload[13U] = (payload[13U] & 0x0FU) | ((slottype[0U] << 6) & 0xC0U) | ((slottype[1U] >> 2) & 0x30U);
+ payload[19U] = (payload[19U] & 0xF0U) | ((slottype[1U] >> 2) & 0x0FU);
+ payload[20U] = (payload[20U] & 0x03U) | ((slottype[1U] << 6) & 0xC0U) | ((slottype[2U] >> 2) & 0x3CU);
+
+ }
+ // and encode
+ bptc.encode(lc, payload);
+
+ // and append
+ buffer->Append(payload, sizeof(payload));
+}
+
+void CDmrplusProtocol::ReplaceEMBInBuffer(CBuffer *buffer, uint8 uiDmrPacketId) const
+{
+ // voice packet A ?
+ if ( uiDmrPacketId == 0 )
+ {
+ // sync
+ buffer->ReplaceAt(39, (uint8)(buffer->at(39) | (g_DmrSyncBSVoice[0] & 0x0F)));
+ buffer->ReplaceAt(40, g_DmrSyncBSVoice+1, 5);
+ buffer->ReplaceAt(45, (uint8)(buffer->at(45) | (g_DmrSyncBSVoice[6] & 0xF0)));
+ }
+ // voice packet B,C,D,E ?
+ else if ( (uiDmrPacketId >= 1) && (uiDmrPacketId <= 4 ) )
+ {
+ // EMB LC
+ uint8 emb[2];
+ emb[0] = (DMRMMDVM_REFLECTOR_COLOUR << 4) & 0xF0;
+ //emb[0] |= PI ? 0x08U : 0x00;
+ //emb[0] |= (LCSS << 1) & 0x06;
+ emb[1] = 0x00;
+ // encode
+ CQR1676::encode(emb);
+ // and append
+ buffer->ReplaceAt(39, (uint8)((buffer->at(39) & 0xF0) | ((emb[0U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(40, (uint8)((buffer->at(40) & 0x0F) | ((emb[0U] << 4) & 0xF0)));
+ buffer->ReplaceAt(40, (uint8)(buffer->at(40) & 0xF0));
+ buffer->ReplaceAt(41, (uint8)0);
+ buffer->ReplaceAt(42, (uint8)0);
+ buffer->ReplaceAt(43, (uint8)0);
+ buffer->ReplaceAt(44, (uint8)(buffer->at(44) & 0x0F));
+ buffer->ReplaceAt(44, (uint8)((buffer->at(44) & 0xF0) | ((emb[1U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(45, (uint8)((buffer->at(45) & 0x0F) | ((emb[1U] << 4) & 0xF0)));
+ }
+ // voice packet F
+ else
+ {
+ // nullptr
+ uint8 emb[2];
+ emb[0] = (DMRMMDVM_REFLECTOR_COLOUR << 4) & 0xF0;
+ //emb[0] |= PI ? 0x08U : 0x00;
+ //emb[0] |= (LCSS << 1) & 0x06;
+ emb[1] = 0x00;
+ // encode
+ CQR1676::encode(emb);
+ // and append
+ buffer->ReplaceAt(39, (uint8)((buffer->at(39) & 0xF0) | ((emb[0U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(40, (uint8)((buffer->at(40) & 0x0F) | ((emb[0U] << 4) & 0xF0)));
+ buffer->ReplaceAt(40, (uint8)(buffer->at(40) & 0xF0));
+ buffer->ReplaceAt(41, (uint8)0);
+ buffer->ReplaceAt(42, (uint8)0);
+ buffer->ReplaceAt(43, (uint8)0);
+ buffer->ReplaceAt(44, (uint8)(buffer->at(44) & 0x0F));
+ buffer->ReplaceAt(44, (uint8)((buffer->at(44) & 0xF0) | ((emb[1U] >> 4) & 0x0F)));
+ buffer->ReplaceAt(45, (uint8)((buffer->at(45) & 0x0F) | ((emb[1U] << 4) & 0xF0)));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// uiStreamId helpers
+
+
+// uiStreamId helpers
+uint32 CDmrplusProtocol::IpToStreamId(const CIp &ip) const
+{
+ return ip.GetAddr() ^ (uint32)(MAKEDWORD(ip.GetPort(), ip.GetPort()));
+}
diff --git a/src/DMRPlusProtocol.h b/src/DMRPlusProtocol.h
new file mode 100644
index 0000000..c0b7c15
--- /dev/null
+++ b/src/DMRPlusProtocol.h
@@ -0,0 +1,116 @@
+//
+// DMRPlusProtocol.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 10/01/2016.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdmrplusprotocol_h
+#define cdmrplusprotocol_h
+
+#include "Timer.h"
+#include "DCSProtocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+#include "DVLastFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+// DMR Plus Module ID
+#define DMRPLUS_MODULE_ID 'B'
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDmrplusStreamCacheItem
+{
+public:
+ CDmrplusStreamCacheItem() { m_uiSeqId = 0x77; }
+
+ CDvHeaderPacket m_dvHeader;
+ CDvFramePacket m_dvFrame0;
+ CDvFramePacket m_dvFrame1;
+
+ uint8 m_uiSeqId;
+};
+
+
+class CDmrplusProtocol : public CProtocol
+{
+public:
+ // initialization
+ bool Initialize(const char *type, const int pytpe, const uint16 port, const bool has_ipv4, const bool has_ipv6);
+
+ // task
+ void Task(void);
+
+protected:
+ // queue helper
+ void HandleQueue(void);
+ void SendBufferToClients(const CBuffer &, uint8);
+
+ // keepalive helpers
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &);
+
+ // packet decoding helpers
+ bool IsValidConnectPacket(const CBuffer &, CCallsign *, char *, const CIp &);
+ bool IsValidDisconnectPacket(const CBuffer &, CCallsign *, char *);
+ bool IsValidDvHeaderPacket(const CIp &, const CBuffer &, std::unique_ptr &);
+ bool IsValidDvFramePacket(const CIp &, const CBuffer &, std::array, 3> &);
+
+ // packet encoding helpers
+ void EncodeConnectAckPacket(CBuffer *);
+ void EncodeConnectNackPacket(CBuffer *);
+ bool EncodeDvHeaderPacket(const CDvHeaderPacket &, CBuffer *) const;
+ void EncodeDvPacket(const CDvHeaderPacket &, const CDvFramePacket &, const CDvFramePacket &, const CDvFramePacket &, uint8, CBuffer *) const;
+ void SwapEndianess(uint8 *, int) const;
+
+ // dmr SeqId helper
+ uint8 GetNextSeqId(uint8) const;
+
+ // dmr DstId to Module helper
+ char DmrDstIdToModule(uint32) const;
+ uint32 ModuleToDmrDestId(char) const;
+
+ // uiStreamId helpers
+ uint32 IpToStreamId(const CIp &) const;
+
+ // Buffer & LC helpers
+ void AppendVoiceLCToBuffer(CBuffer *, uint32) const;
+ void AppendTerminatorLCToBuffer(CBuffer *, uint32) const;
+ void ReplaceEMBInBuffer(CBuffer *, uint8) const;
+
+
+protected:
+ // for keep alive
+ CTimePoint m_LastKeepaliveTime;
+
+ // for queue header caches
+ std::array m_StreamsCache;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+
+#endif /* cdmrplusprotocol_h */
diff --git a/src/DPlusClient.cpp b/src/DPlusClient.cpp
new file mode 100644
index 0000000..87cbf96
--- /dev/null
+++ b/src/DPlusClient.cpp
@@ -0,0 +1,61 @@
+//
+// cdplusclient.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "DPlusClient.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CDplusClient::CDplusClient()
+{
+ m_bDextraDongle = false;
+}
+
+CDplusClient::CDplusClient(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+ m_bDextraDongle = false;
+}
+
+CDplusClient::CDplusClient(const CDplusClient &client)
+ : CClient(client)
+{
+ m_bDextraDongle = client.m_bDextraDongle;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CDplusClient::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < DPLUS_KEEPALIVE_TIMEOUT);
+}
+
+void CDplusClient::SetMasterOfModule(char c)
+{
+ CClient::SetMasterOfModule(c);
+ SetReflectorModule(c);
+}
diff --git a/src/DPlusClient.h b/src/DPlusClient.h
new file mode 100644
index 0000000..fefc468
--- /dev/null
+++ b/src/DPlusClient.h
@@ -0,0 +1,66 @@
+//
+// DPlusClient.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 31/10/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdplusclient_h
+#define cdplusclient_h
+
+#include "cclient.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDplusClient : public CClient
+{
+public:
+ // constructors
+ CDplusClient();
+ CDplusClient(const CCallsign &, const CIp &, char = ' ');
+ CDplusClient(const CDplusClient &);
+
+ // destructor
+ virtual ~CDplusClient() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_DPLUS; }
+ const char *GetProtocolName(void) const { return "DPlus"; }
+ int GetCodec(void) const { return CODEC_AMBEPLUS; }
+ bool IsNode(void) const { return true; }
+ bool IsDextraDongle(void) const { return m_bDextraDongle; }
+ void SetDextraDongle(void) { m_bDextraDongle = true; }
+
+ // status
+ bool IsAlive(void) const;
+ void SetMasterOfModule(char);
+
+protected:
+ // data
+ bool m_bDextraDongle;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdplusclient_h */
diff --git a/src/DPlusProtocol.cpp b/src/DPlusProtocol.cpp
new file mode 100644
index 0000000..6a15dce
--- /dev/null
+++ b/src/DPlusProtocol.cpp
@@ -0,0 +1,532 @@
+//
+// cdplusprotocol.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include "DPlusClient.h"
+#include "DPlusProtocol.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CDplusProtocol::Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6)
+{
+ // base class
+ if (! CProtocol::Initialize(type, ptype, port, has_ipv4, has_ipv6))
+ return false;
+
+ // update time
+ m_LastKeepaliveTime.Now();
+
+ // done
+ return true;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// task
+
+void CDplusProtocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+ std::unique_ptr Header;
+ std::unique_ptr Frame;
+ std::unique_ptr LastFrame;
+
+ // handle incoming packets
+#if DSTAR_IPV6==true
+#if DSTAR_IPV4==true
+ if ( ReceiveDS(Buffer, Ip, 20) )
+#else
+ if ( Receive6(Buffer, Ip, 20) )
+#endif
+#else
+ if ( Receive4(Buffer, Ip, 20) )
+#endif
+ {
+ // crack the packet
+ if ( IsValidDvFramePacket(Buffer, Frame) )
+ {
+ OnDvFramePacketIn(Frame, &Ip);
+ }
+ else if ( IsValidDvHeaderPacket(Buffer, Header) )
+ {
+ // is muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, PROTOCOL_DPLUS, Header->GetRpt2Module()) )
+ {
+ // handle it
+ OnDvHeaderPacketIn(Header, Ip);
+ }
+ }
+ else if ( IsValidDvLastFramePacket(Buffer, LastFrame) )
+ {
+ OnDvLastFramePacketIn(LastFrame, &Ip);
+ }
+ else if ( IsValidConnectPacket(Buffer) )
+ {
+ std::cout << "DPlus connect request packet from " << Ip << std::endl;
+
+ // acknowledge the request
+ Send(Buffer, Ip);
+ }
+ else if ( IsValidLoginPacket(Buffer, &Callsign) )
+ {
+ std::cout << "DPlus login packet from " << Callsign << " at " << Ip << std::endl;
+
+ // callsign authorized?
+ if ( g_GateKeeper.MayLink(Callsign, Ip, PROTOCOL_DPLUS) )
+ {
+ // acknowledge the request
+ EncodeLoginAckPacket(&Buffer);
+ Send(Buffer, Ip);
+
+ // create the client and append
+ g_Reflector.GetClients()->AddClient(std::make_shared(Callsign, Ip));
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ // deny the request
+ EncodeLoginNackPacket(&Buffer);
+ Send(Buffer, Ip);
+ }
+
+ }
+ else if ( IsValidDisconnectPacket(Buffer) )
+ {
+ std::cout << "DPlus disconnect packet from " << Ip << std::endl;
+
+ // find client
+ CClients *clients = g_Reflector.GetClients();
+ std::shared_ptrclient = clients->FindClient(Ip, PROTOCOL_DPLUS);
+ if ( client != nullptr )
+ {
+ // remove it
+ clients->RemoveClient(client);
+ // and acknowledge the disconnect
+ EncodeDisconnectPacket(&Buffer);
+ Send(Buffer, Ip);
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else if ( IsValidKeepAlivePacket(Buffer) )
+ {
+ //std::cout << "DPlus keepalive packet from " << Ip << std::endl;
+
+ // find all clients with that callsign & ip and keep them alive
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(Ip, PROTOCOL_DPLUS, it)) != nullptr )
+ {
+ client->Alive();
+ }
+ g_Reflector.ReleaseClients();
+ }
+ else
+ {
+ std::string title("Unknown DPlus packet from ");
+ title += Ip.GetAddress();
+ Buffer.Dump(title);
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+ // keep client alive
+ if ( m_LastKeepaliveTime.DurationSinceNow() > DPLUS_KEEPALIVE_PERIOD )
+ {
+ //
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.Now();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CDplusProtocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip)
+{
+ // find the stream
+ CPacketStream *stream = GetStream(Header->GetStreamId());
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ // no stream open yet, open a new one
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+
+ // first, check module is valid
+ if ( g_Reflector.IsValidModule(rpt2.GetModule()) )
+ {
+ // find this client
+ std::shared_ptrclient = g_Reflector.GetClients()->FindClient(Ip, PROTOCOL_DPLUS);
+ if ( client )
+ {
+ // now we know if it's a dextra dongle or a genuine dplus node
+ if ( Header->GetRpt2Callsign().HasSameCallsignWithWildcard(CCallsign("XRF*")) )
+ {
+ client->SetDextraDongle();
+ }
+ // now we know its module, let's update it
+ if ( !client->HasModule() )
+ {
+ client->SetModule(rpt1.GetModule());
+ }
+ // get client callsign
+ rpt1 = client->GetCallsign();
+ // and try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams.push_back(stream);
+ }
+ }
+ // release
+ g_Reflector.ReleaseClients();
+
+ // update last heard
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+ else
+ {
+ std::cout << "DPlus node " << rpt1 << " link attempt on non-existing module" << std::endl;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CDplusProtocol::HandleQueue(void)
+{
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // get the packet
+ auto packet = m_Queue.front();
+ m_Queue.pop();
+
+ // get our sender's id
+ int iModId = g_Reflector.GetModuleIndex(packet->GetModuleId());
+
+ // check if it's header and update cache
+ if ( packet->IsDvHeader() )
+ {
+ // this relies on queue feeder setting valid module id
+ m_StreamsCache[iModId].m_dvHeader = CDvHeaderPacket((const CDvHeaderPacket &)*packet);
+ m_StreamsCache[iModId].m_iSeqCounter = 0;
+ }
+
+ // encode it
+ CBuffer buffer;
+ if ( EncodeDvPacket(*packet, &buffer) )
+ {
+ // and push it to all our clients who are not streaming in
+ // note that for dplus protocol, all stream of all modules are push to all clients
+ // it's client who decide which stream he's interrrested in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DPLUS, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() )
+ {
+ // check if client is a dextra dongle
+ // then replace RPT2 with XRF instead of REF
+ // if the client type is not yet known, send bothheaders
+ if ( packet->IsDvHeader() )
+ {
+ // sending header in Dplus is client specific
+ SendDvHeader((CDvHeaderPacket *)packet.get(), (CDplusClient *)client.get());
+ }
+ else if ( packet->IsDvFrame() )
+ {
+ // and send the DV frame
+ Send(buffer, client->GetIp());
+
+ // is it time to insert a DVheader copy ?
+ if ( (m_StreamsCache[iModId].m_iSeqCounter++ % 21) == 20 )
+ {
+ // yes, clone it
+ CDvHeaderPacket packet2(m_StreamsCache[iModId].m_dvHeader);
+ // and send it
+ SendDvHeader(&packet2, (CDplusClient *)client.get());
+ }
+ }
+ else
+ {
+ // otherwise, send the original packet
+ Send(buffer, client->GetIp());
+ }
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+ m_Queue.Unlock();
+}
+
+void CDplusProtocol::SendDvHeader(CDvHeaderPacket *packet, CDplusClient *client)
+{
+ // encode it
+ CBuffer buffer;
+ if ( EncodeDvPacket(*packet, &buffer) )
+ {
+ if ( (client->IsDextraDongle() || !client->HasModule()) )
+ {
+ // clone the packet and patch it
+ CDvHeaderPacket packet2(*((CDvHeaderPacket *)packet));
+ CCallsign rpt2 = packet2.GetRpt2Callsign();
+ rpt2.PatchCallsign(0, (const uint8 *)"XRF", 3);
+ packet2.SetRpt2Callsign(rpt2);
+
+ // encode it
+ CBuffer buffer2;
+ if ( EncodeDvPacket(packet2, &buffer2) )
+ {
+ // and send it
+ Send(buffer2, client->GetIp());
+ }
+
+ // client type known ?
+ if ( !client->HasModule() )
+ {
+ // no, send also the genuine packet
+ Send(buffer, client->GetIp());
+ }
+ }
+ else
+ {
+ // otherwise, send the original packet
+ Send(buffer, client->GetIp());
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CDplusProtocol::HandleKeepalives(void)
+{
+ // send keepalives
+ CBuffer keepalive;
+ EncodeKeepAlivePacket(&keepalive);
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_DPLUS, it)) != nullptr )
+ {
+ // send keepalive
+ //std::cout << "Sending DPlus packet @ " << client->GetIp() << std::endl;
+ Send(keepalive, client->GetIp());
+
+ // is this client busy ?
+ if ( client->IsAMaster() )
+ {
+ // yes, just tickle it
+ client->Alive();
+ }
+ // check it's still with us
+ else if ( !client->IsAlive() )
+ {
+ // no, disconnect
+ CBuffer disconnect;
+ EncodeDisconnectPacket(&disconnect);
+ Send(disconnect, client->GetIp());
+
+ // and remove it
+ std::cout << "DPlus client " << client->GetCallsign() << " keepalive timeout" << std::endl;
+ clients->RemoveClient(client);
+ }
+ }
+ g_Reflector.ReleaseClients();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet decoding helpers
+
+bool CDplusProtocol::IsValidConnectPacket(const CBuffer &Buffer)
+{
+ uint8 tag[] = { 0x05,0x00,0x18,0x00,0x01 };
+ return (Buffer == CBuffer(tag, sizeof(tag)));
+}
+
+bool CDplusProtocol::IsValidLoginPacket(const CBuffer &Buffer, CCallsign *Callsign)
+{
+ uint8 Tag[] = { 0x1C,0xC0,0x04,0x00 };
+ bool valid = false;
+
+ if ( (Buffer.size() == 28) &&(::memcmp(Buffer.data(), Tag, sizeof(Tag)) == 0) )
+ {
+ Callsign->SetCallsign(&(Buffer.data()[4]), 8);
+ valid = Callsign->IsValid();
+ }
+ return valid;
+}
+
+bool CDplusProtocol::IsValidDisconnectPacket(const CBuffer &Buffer)
+{
+ uint8 tag[] = { 0x05,0x00,0x18,0x00,0x00 };
+ return (Buffer == CBuffer(tag, sizeof(tag)));
+}
+
+bool CDplusProtocol::IsValidKeepAlivePacket(const CBuffer &Buffer)
+{
+ uint8 tag[] = { 0x03,0x60,0x00 };
+ return (Buffer == CBuffer(tag, sizeof(tag)));
+}
+
+bool CDplusProtocol::IsValidDvHeaderPacket(const CBuffer &Buffer, std::unique_ptr &header)
+{
+ if ( 58==Buffer.size() && 0x3AU==Buffer.data()[0] && 0x80U==Buffer.data()[1] && 0==Buffer.Compare((uint8 *)"DSVT", 2, 4) && 0x10U==Buffer.data()[6] && 0x20U==Buffer.data()[10] )
+ {
+ // create packet
+ header = std::unique_ptr(new CDvHeaderPacket((struct dstar_header *)&(Buffer.data()[17]), *((uint16 *)&(Buffer.data()[14])), 0x80));
+ // check validity of packet
+ if ( header && header->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CDplusProtocol::IsValidDvFramePacket(const CBuffer &Buffer, std::unique_ptr &dvframe)
+{
+ if ( 29==Buffer.size() && 0x1DU==Buffer.data()[0] && 0x80U==Buffer.data()[1] && 0==Buffer.Compare((uint8 *)"DSVT", 2, 4) && 0x20U==Buffer.data()[6] && 0x20U==Buffer.data()[10] )
+ {
+ // create packet
+ dvframe = std::unique_ptr(new CDvFramePacket((struct dstar_dvframe *)&(Buffer.data()[17]), *((uint16 *)&(Buffer.data()[14])), Buffer.data()[16]));
+ // check validity of packet
+ if ( dvframe && dvframe->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CDplusProtocol::IsValidDvLastFramePacket(const CBuffer &Buffer, std::unique_ptr &dvframe)
+{
+ if ( 32==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 2, 4) && 0x20U==Buffer.data()[0] && 0x80U==Buffer.data()[1] && 0x20U==Buffer.data()[6] && 0x20U==Buffer.data()[10] )
+ {
+ // create packet
+ dvframe = std::unique_ptr(new CDvLastFramePacket((struct dstar_dvframe *)&(Buffer.data()[17]), *((uint16 *)&(Buffer.data()[14])), Buffer.data()[16]));
+ // check validity of packet
+ if ( dvframe && dvframe->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet encoding helpers
+
+void CDplusProtocol::EncodeKeepAlivePacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 0x03,0x60,0x00 };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+void CDplusProtocol::EncodeLoginAckPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 0x08,0xC0,0x04,0x00,'O','K','R','W' };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+void CDplusProtocol::EncodeLoginNackPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 0x08,0xC0,0x04,0x00,'B','U','S','Y' };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+void CDplusProtocol::EncodeDisconnectPacket(CBuffer *Buffer)
+{
+ uint8 tag[] = { 0x05,0x00,0x18,0x00,0x00 };
+ Buffer->Set(tag, sizeof(tag));
+}
+
+
+bool CDplusProtocol::EncodeDvHeaderPacket(const CDvHeaderPacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 0x3A,0x80,0x44,0x53,0x56,0x54,0x10,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+ struct dstar_header DstarHeader;
+
+ Packet.ConvertToDstarStruct(&DstarHeader);
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)0x80);
+ Buffer->Append((uint8 *)&DstarHeader, sizeof(struct dstar_header));
+
+ return true;
+}
+
+bool CDplusProtocol::EncodeDvFramePacket(const CDvFramePacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 0x1D,0x80,0x44,0x53,0x56,0x54,0x20,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)(Packet.GetPacketId() % 21));
+ Buffer->Append((uint8 *)Packet.GetAmbe(), AMBE_SIZE);
+ Buffer->Append((uint8 *)Packet.GetDvData(), DVDATA_SIZE);
+
+ return true;
+
+}
+
+bool CDplusProtocol::EncodeDvLastFramePacket(const CDvLastFramePacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag1[] = { 0x20,0x80,0x44,0x53,0x56,0x54,0x20,0x00,0x81,0x00,0x20,0x00,0x01,0x02 };
+ uint8 tag2[] = { 0x55,0xC8,0x7A,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x25,0x1A,0xC6 };
+
+ Buffer->Set(tag1, sizeof(tag1));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)((Packet.GetPacketId() % 21) | 0x40));
+ Buffer->Append(tag2, sizeof(tag2));
+
+ return true;
+}
diff --git a/src/DPlusProtocol.h b/src/DPlusProtocol.h
new file mode 100644
index 0000000..631f06b
--- /dev/null
+++ b/src/DPlusProtocol.h
@@ -0,0 +1,99 @@
+//
+// DPlusProtocol.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdplusprotocol_h
+#define cdplusprotocol_h
+
+#include "Timer.h"
+#include "Protocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+#include "DVLastFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDplusClient;
+
+class CDPlusStreamCacheItem
+{
+public:
+ CDPlusStreamCacheItem() { m_iSeqCounter = 0; }
+
+ CDvHeaderPacket m_dvHeader;
+ uint8 m_iSeqCounter;
+};
+
+class CDplusProtocol : public CProtocol
+{
+public:
+ // initialization
+ bool Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6);
+
+ // task
+ void Task(void);
+
+protected:
+ // queue helper
+ void HandleQueue(void);
+ void SendDvHeader(CDvHeaderPacket *, CDplusClient *);
+
+ // keepalive helpers
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &);
+
+ // packet decoding helpers
+ bool IsValidConnectPacket(const CBuffer &);
+ bool IsValidLoginPacket(const CBuffer &, CCallsign *);
+ bool IsValidDisconnectPacket(const CBuffer &);
+ bool IsValidKeepAlivePacket(const CBuffer &);
+ bool IsValidDvHeaderPacket(const CBuffer &, std::unique_ptr &);
+ bool IsValidDvFramePacket(const CBuffer &, std::unique_ptr &);
+ bool IsValidDvLastFramePacket(const CBuffer &, std::unique_ptr &);
+
+ // packet encoding helpers
+ void EncodeKeepAlivePacket(CBuffer *);
+ void EncodeLoginAckPacket(CBuffer *);
+ void EncodeLoginNackPacket(CBuffer *);
+ void EncodeDisconnectPacket(CBuffer *);
+ bool EncodeDvHeaderPacket(const CDvHeaderPacket &, CBuffer *) const;
+ bool EncodeDvFramePacket(const CDvFramePacket &, CBuffer *) const;
+ bool EncodeDvLastFramePacket(const CDvLastFramePacket &, CBuffer *) const;
+
+
+protected:
+ // for keep alive
+ CTimePoint m_LastKeepaliveTime;
+
+ // for queue header caches
+ std::array m_StreamsCache;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdplusprotocol_h */
diff --git a/src/DVFramePacket.cpp b/src/DVFramePacket.cpp
new file mode 100644
index 0000000..94cdd44
--- /dev/null
+++ b/src/DVFramePacket.cpp
@@ -0,0 +1,157 @@
+//
+// cdvframepacket.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+
+#include "Main.h"
+#include
+#include "DVFramePacket.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CDvFramePacket::CDvFramePacket()
+{
+ ::memset(m_uiAmbe, 0, sizeof(m_uiAmbe));
+ ::memset(m_uiDvData, 0, sizeof(m_uiDvData));
+#ifndef NO_XLX
+ ::memset(m_uiAmbePlus, 0, sizeof(m_uiAmbePlus));
+ ::memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
+#endif
+};
+
+// dstar constructor
+
+CDvFramePacket::CDvFramePacket(const struct dstar_dvframe *dvframe, uint16 sid, uint8 pid)
+ : CPacket(sid, pid)
+{
+ ::memcpy(m_uiAmbe, dvframe->AMBE, sizeof(m_uiAmbe));
+ ::memcpy(m_uiDvData, dvframe->DVDATA, sizeof(m_uiDvData));
+#ifndef NO_XLX
+ ::memset(m_uiAmbePlus, 0, sizeof(m_uiAmbePlus));
+ ::memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
+#endif
+}
+
+#ifndef NO_XLX
+// dmr constructor
+
+CDvFramePacket::CDvFramePacket(const uint8 *ambe, const uint8 *sync, uint16 sid, uint8 pid, uint8 spid)
+ : CPacket(sid, pid, spid)
+{
+ ::memcpy(m_uiAmbePlus, ambe, sizeof(m_uiAmbePlus));
+ ::memcpy(m_uiDvSync, sync, sizeof(m_uiDvSync));
+ ::memset(m_uiAmbe, 0, sizeof(m_uiAmbe));
+ ::memset(m_uiDvData, 0, sizeof(m_uiDvData));
+}
+
+// ysf constructor
+
+CDvFramePacket::CDvFramePacket(const uint8 *ambe, uint16 sid, uint8 pid, uint8 spid, uint8 fid)
+ : CPacket(sid, pid, spid, fid)
+{
+ ::memcpy(m_uiAmbePlus, ambe, sizeof(m_uiAmbePlus));
+ ::memset(m_uiDvSync, 0, sizeof(m_uiDvSync));
+ ::memset(m_uiAmbe, 0, sizeof(m_uiAmbe));
+ ::memset(m_uiDvData, 0, sizeof(m_uiDvData));
+}
+
+// xlx constructor
+
+CDvFramePacket::CDvFramePacket
+(uint16 sid,
+ uint8 dstarpid, const uint8 *dstarambe, const uint8 *dstardvdata,
+ uint8 dmrpid, uint8 dprspid, const uint8 *dmrambe, const uint8 *dmrsync)
+ : CPacket(sid, dstarpid, dmrpid, dprspid, 0xFF, 0xFF, 0xFF)
+{
+ ::memcpy(m_uiAmbe, dstarambe, sizeof(m_uiAmbe));
+ ::memcpy(m_uiDvData, dstardvdata, sizeof(m_uiDvData));
+ ::memcpy(m_uiAmbePlus, dmrambe, sizeof(m_uiAmbePlus));
+ ::memcpy(m_uiDvSync, dmrsync, sizeof(m_uiDvSync));
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////
+// virtual duplication
+
+std::unique_ptr CDvFramePacket::Duplicate(void) const
+{
+ return std::unique_ptr(new CDvFramePacket(*this));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// get
+
+const uint8 *CDvFramePacket::GetAmbe(uint8 uiCodec) const
+{
+ switch (uiCodec)
+ {
+ case CODEC_AMBEPLUS:
+ return m_uiAmbe;
+#ifndef NO_XLX
+ case CODEC_AMBE2PLUS:
+ return m_uiAmbePlus;
+#endif
+ default:
+ return nullptr;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// set
+
+void CDvFramePacket::SetDvData(uint8 *DvData)
+{
+ ::memcpy(m_uiDvData, DvData, sizeof(m_uiDvData));
+}
+
+void CDvFramePacket::SetAmbe(uint8 uiCodec, uint8 *Ambe)
+{
+ switch (uiCodec)
+ {
+ case CODEC_AMBEPLUS:
+ ::memcpy(m_uiAmbe, Ambe, sizeof(m_uiAmbe));
+ break;
+#ifndef NO_XLX
+ case CODEC_AMBE2PLUS:
+ ::memcpy(m_uiAmbePlus, Ambe, sizeof(m_uiAmbe));
+ break;
+#endif
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operators
+
+bool CDvFramePacket::operator ==(const CDvFramePacket &DvFrame) const
+{
+ return ( (::memcmp(m_uiAmbe, DvFrame.m_uiAmbe, sizeof(m_uiAmbe)) == 0)
+ && (::memcmp(m_uiDvData, DvFrame.m_uiDvData, sizeof(m_uiDvData)) == 0)
+#ifndef NO_XLX
+ && (::memcmp(m_uiAmbePlus, DvFrame.m_uiAmbePlus, sizeof(m_uiAmbePlus)) == 0)
+ && (::memcmp(m_uiDvSync, DvFrame.m_uiDvSync, sizeof(m_uiDvSync)) == 0)
+#endif
+ );
+}
diff --git a/src/DVFramePacket.h b/src/DVFramePacket.h
new file mode 100644
index 0000000..52a0762
--- /dev/null
+++ b/src/DVFramePacket.h
@@ -0,0 +1,108 @@
+//
+// DVFramePacket.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdvframepacket_h
+#define cdvframepacket_h
+
+#include "Packet.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// defines
+
+#define AMBE_SIZE 9
+#define DVDATA_SIZE 3
+
+#define AMBEPLUS_SIZE 9
+#define DVSYNC_SIZE 7
+
+// typedef & structures
+
+struct __attribute__ ((__packed__))dstar_dvframe
+{
+ uint8 AMBE[AMBE_SIZE];
+ uint8 DVDATA[DVDATA_SIZE];
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDvFramePacket : public CPacket
+{
+ //friend class CCodecStream;
+public:
+ // constructor
+ CDvFramePacket();
+ CDvFramePacket(const struct dstar_dvframe *, uint16, uint8);
+#ifndef NO_XLX
+ CDvFramePacket(const uint8 *, const uint8 *, uint16, uint8, uint8);
+ CDvFramePacket(const uint8 *, uint16, uint8, uint8, uint8);
+ CDvFramePacket(uint16, uint8, const uint8 *, const uint8 *, uint8, uint8, const uint8 *, const uint8 *);
+#endif
+
+ // virtual duplication
+ std::unique_ptr Duplicate(void) const;
+
+ // identity
+ bool IsDvFrame(void) const { return true; }
+#ifndef NO_XLX
+ bool HasTranscodableAmbe(void) const { return true; }
+#endif
+
+ // get
+ const uint8 *GetAmbe(uint8) const;
+ const uint8 *GetAmbe(void) const { return m_uiAmbe; }
+#ifndef NO_XLX
+ const uint8 *GetAmbePlus(void) const { return m_uiAmbePlus; }
+ const uint8 *GetDvSync(void) const { return m_uiDvSync; }
+#endif
+ const uint8 *GetDvData(void) const { return m_uiDvData; }
+
+ // set
+ void SetDvData(uint8 *);
+ void SetAmbe(uint8, uint8 *);
+
+ // operators
+ bool operator ==(const CDvFramePacket &) const;
+
+protected:
+ // get
+ uint8 *GetAmbeData(void) { return m_uiAmbe; }
+#ifndef NO_XLX
+ uint8 *GetAmbePlusData(void) { return m_uiAmbePlus; }
+#endif
+
+protected:
+ // data (dstar)
+ uint8 m_uiAmbe[AMBE_SIZE];
+ uint8 m_uiDvData[DVDATA_SIZE];
+#ifndef NO_XLX
+ // data (dmr)
+ uint8 m_uiAmbePlus[AMBEPLUS_SIZE];
+ uint8 m_uiDvSync[DVSYNC_SIZE];
+#endif
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdvframepacket_h */
diff --git a/src/DVHeaderPacket.cpp b/src/DVHeaderPacket.cpp
new file mode 100644
index 0000000..7ceb682
--- /dev/null
+++ b/src/DVHeaderPacket.cpp
@@ -0,0 +1,159 @@
+//
+// cdvheaderpacket.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include
+#include "DMRIdDir.h"
+#include "DVHeaderPacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CDvHeaderPacket::CDvHeaderPacket()
+{
+ m_uiFlag1 = 0;
+ m_uiFlag2 = 0;
+ m_uiFlag3 = 0;
+ m_uiCrc = 0;
+}
+
+// dstar constructor
+
+CDvHeaderPacket::CDvHeaderPacket(const struct dstar_header *buffer, uint16 sid, uint8 pid)
+ : CPacket(sid, pid)
+{
+ m_uiFlag1 = buffer->Flag1;
+ m_uiFlag2 = buffer->Flag2;
+ m_uiFlag3 = buffer->Flag3;
+ m_csUR.SetCallsign(buffer->UR, CALLSIGN_LEN);
+ m_csRPT1.SetCallsign(buffer->RPT1, CALLSIGN_LEN);
+ m_csRPT2.SetCallsign(buffer->RPT2, CALLSIGN_LEN);
+ m_csMY.SetCallsign(buffer->MY, CALLSIGN_LEN);
+ m_csMY.SetSuffix(buffer->SUFFIX, CALLSUFFIX_LEN);
+ m_uiCrc = buffer->Crc;
+}
+
+#ifndef NO_XLX
+// dmr constructor
+
+CDvHeaderPacket::CDvHeaderPacket(uint32 my, const CCallsign &ur, const CCallsign &rpt1, const CCallsign &rpt2, uint16 sid, uint8 pid, uint8 spid)
+ : CPacket(sid, pid, spid)
+{
+ m_uiFlag1 = 0;
+ m_uiFlag2 = 0;
+ m_uiFlag3 = 0;
+ m_uiCrc = 0;
+ m_csUR = ur;
+ m_csRPT1 = rpt1;
+ m_csRPT2 = rpt2;
+ m_csMY = CCallsign("", my);
+}
+
+// YSF constructor
+
+CDvHeaderPacket::CDvHeaderPacket(const CCallsign &my, const CCallsign &ur, const CCallsign &rpt1, const CCallsign &rpt2, uint16 sid, uint8 pid)
+ : CPacket(sid, pid, 0, 0)
+{
+ m_uiFlag1 = 0;
+ m_uiFlag2 = 0;
+ m_uiFlag3 = 0;
+ m_uiCrc = 0;
+ m_csUR = ur;
+ m_csRPT1 = rpt1;
+ m_csRPT2 = rpt2;
+ m_csMY = my;
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////
+// virtual duplication
+
+std::unique_ptr CDvHeaderPacket::Duplicate(void) const
+{
+ return std::unique_ptr(new CDvHeaderPacket(*this));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// conversion
+
+void CDvHeaderPacket::ConvertToDstarStruct(struct dstar_header *buffer) const
+{
+ ::memset(buffer, 0, sizeof(struct dstar_header));
+ buffer->Flag1 = m_uiFlag1;
+ buffer->Flag2 = m_uiFlag2;
+ buffer->Flag3 = m_uiFlag3;
+ m_csUR.GetCallsign(buffer->UR);
+ m_csRPT1.GetCallsign(buffer->RPT1);
+ m_csRPT2.GetCallsign(buffer->RPT2);
+ m_csMY.GetCallsign(buffer->MY);
+ m_csMY.GetSuffix(buffer->SUFFIX);
+ buffer->Crc = m_uiCrc;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// get valid
+
+bool CDvHeaderPacket::IsValid(void) const
+{
+ bool valid = CPacket::IsValid();
+
+ valid &= m_csRPT1.IsValid();
+ valid &= m_csRPT2.IsValid();
+ valid &= m_csMY.IsValid();
+
+ return valid;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operators
+
+bool CDvHeaderPacket::operator ==(const CDvHeaderPacket &Header) const
+{
+ return ( (m_uiFlag1 == Header.m_uiFlag1) &&
+ (m_uiFlag2 == Header.m_uiFlag2) &&
+ (m_uiFlag3 == Header.m_uiFlag3) &&
+ (m_csUR == Header.m_csUR) &&
+ (m_csRPT1 == Header.m_csRPT1) &&
+ (m_csRPT2 == Header.m_csRPT2) &&
+ (m_csMY == Header.m_csMY) );
+}
+
+#ifdef IMPLEMENT_CDVHEADERPACKET_CONST_CHAR_OPERATOR
+CDvHeaderPacket::operator const char *() const
+{
+ char *sz = (char *)(const char *)m_sz;
+
+ std::sprintf(sz, "%02X %02X %02X\n%s\n%s\n%s\n%s",
+ m_uiFlag1, m_uiFlag2, m_uiFlag3,
+ (const char *)m_csUR,
+ (const char *)m_csRPT1,
+ (const char *)m_csRPT2,
+ (const char *)m_csMY);
+
+ return m_sz;
+}
+#endif
diff --git a/src/DVHeaderPacket.h b/src/DVHeaderPacket.h
new file mode 100644
index 0000000..9c493c6
--- /dev/null
+++ b/src/DVHeaderPacket.h
@@ -0,0 +1,121 @@
+//
+// DVHeaderPacket.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 01/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdvheaderpacket_h
+#define cdvheaderpacket_h
+
+#include "Callsign.h"
+#include "Packet.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// implementation details
+
+//#define IMPLEMENT_CDVHEADERPACKET_CONST_CHAR_OPERATOR
+
+////////////////////////////////////////////////////////////////////////////////////////
+// typedef & structures
+
+struct __attribute__ ((__packed__))dstar_header
+{
+ // flags
+ uint8 Flag1;
+ uint8 Flag2;
+ uint8 Flag3;
+ // callsigns
+ uint8 RPT2[CALLSIGN_LEN];
+ uint8 RPT1[CALLSIGN_LEN];
+ uint8 UR[CALLSIGN_LEN];
+ uint8 MY[CALLSIGN_LEN];
+ uint8 SUFFIX[CALLSUFFIX_LEN];
+ // crc
+ uint16 Crc;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDvHeaderPacket : public CPacket
+{
+public:
+ // constructor
+ CDvHeaderPacket();
+ CDvHeaderPacket(const struct dstar_header *, uint16, uint8);
+#ifndef NO_XLX
+ CDvHeaderPacket(uint32, const CCallsign &, const CCallsign &, const CCallsign &, uint16, uint8, uint8);
+ CDvHeaderPacket(const CCallsign &, const CCallsign &, const CCallsign &, const CCallsign &, uint16, uint8);
+#endif
+
+ // virtual duplication
+ std::unique_ptr Duplicate(void) const;
+
+ // identity
+ bool IsDvHeader(void) const { return true; }
+
+ // conversion
+ void ConvertToDstarStruct(struct dstar_header *) const;
+
+ // get valid
+ bool IsValid(void) const;
+
+ // get callsigns
+ const CCallsign &GetUrCallsign(void) const { return m_csUR; }
+ const CCallsign &GetRpt1Callsign(void) const { return m_csRPT1; }
+ const CCallsign &GetRpt2Callsign(void) const { return m_csRPT2; }
+ const CCallsign &GetMyCallsign(void) const { return m_csMY; }
+
+ // get modules
+ char GetUrModule(void) const { return m_csUR.GetModule(); }
+ char GetRpt1Module(void) const { return m_csRPT1.GetModule(); }
+ char GetRpt2Module(void) const { return m_csRPT2.GetModule(); }
+ char GetMyModule(void) const { return m_csMY.GetModule(); }
+
+ // set callsigns
+ void SetRpt2Callsign(const CCallsign &cs) { m_csRPT2 = cs; }
+ void SetRpt2Module(char c) { m_csRPT2.SetModule(c); }
+
+ // operators
+ bool operator ==(const CDvHeaderPacket &) const;
+#ifdef IMPLEMENT_CDVHEADERPACKET_CONST_CHAR_OPERATOR
+ operator const char *() const;
+#endif
+
+protected:
+ // data
+ uint8 m_uiFlag1;
+ uint8 m_uiFlag2;
+ uint8 m_uiFlag3;
+ CCallsign m_csUR;
+ CCallsign m_csRPT1;
+ CCallsign m_csRPT2;
+ CCallsign m_csMY;
+ uint16 m_uiCrc;
+#ifdef IMPLEMENT_CDVHEADERPACKET_CONST_CHAR_OPERATOR
+ // buffer
+ char m_sz[256];
+#endif
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdvheaderpacket_h */
diff --git a/src/DVLastFramePacket.cpp b/src/DVLastFramePacket.cpp
new file mode 100644
index 0000000..323dc7b
--- /dev/null
+++ b/src/DVLastFramePacket.cpp
@@ -0,0 +1,83 @@
+//
+// cdvlastframepacket.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 03/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "DVLastFramePacket.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CDvLastFramePacket::CDvLastFramePacket()
+{
+}
+
+// dstar constructor
+
+CDvLastFramePacket::CDvLastFramePacket(const struct dstar_dvframe *DvFrame, uint16 sid, uint8 pid)
+ : CDvFramePacket(DvFrame, sid, pid)
+{
+}
+
+#ifndef NO_XLX
+// dmr constructor
+
+CDvLastFramePacket::CDvLastFramePacket(const uint8 *ambe, const uint8 *sync, uint16 sid, uint8 pid, uint8 spid)
+ : CDvFramePacket(ambe, sync, sid, pid, spid)
+{
+}
+
+// dstar + dmr constructor
+
+CDvLastFramePacket::CDvLastFramePacket
+(uint16 sid,
+ uint8 dstarpid, const uint8 *dstarambe, const uint8 *dstardvdata,
+ uint8 dmrpid, uint8 dprspid, const uint8 *dmrambe, const uint8 *dmrsync)
+ : CDvFramePacket(sid, dstarpid, dstarambe, dstardvdata, dmrpid, dprspid, dmrambe, dmrsync)
+{
+}
+
+// ysf constructor
+
+CDvLastFramePacket::CDvLastFramePacket(const uint8 *ambe, uint16 sid, uint8 pid, uint8 spid, uint8 fid)
+ : CDvFramePacket(ambe, sid, pid, spid, fid)
+{
+}
+#endif
+
+
+// copy constructor
+
+CDvLastFramePacket::CDvLastFramePacket(const CDvLastFramePacket &DvFrame)
+ : CDvFramePacket(DvFrame)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// virtual duplication
+
+std::unique_ptr CDvLastFramePacket::Duplicate(void) const
+{
+ return std::unique_ptr(new CDvLastFramePacket(*this));
+}
diff --git a/src/DVLastFramePacket.h b/src/DVLastFramePacket.h
new file mode 100644
index 0000000..b3ebe85
--- /dev/null
+++ b/src/DVLastFramePacket.h
@@ -0,0 +1,61 @@
+//
+// DVLastFramePacket.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 03/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cdvlastframepacket_h
+#define cdvlastframepacket_h
+
+
+#include "DVFramePacket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// defines
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CDvLastFramePacket : public CDvFramePacket
+{
+public:
+ // constructor
+ CDvLastFramePacket();
+ CDvLastFramePacket(const struct dstar_dvframe *, uint16, uint8);
+#ifndef NO_XLX
+ CDvLastFramePacket(const uint8 *, const uint8 *, uint16, uint8, uint8);
+ CDvLastFramePacket(const uint8 *, uint16, uint8, uint8, uint8);
+ CDvLastFramePacket(uint16, uint8, const uint8 *, const uint8 *, uint8, uint8, const uint8 *, const uint8 *);
+#endif
+ CDvLastFramePacket(const CDvLastFramePacket &);
+
+ // virtual duplication
+ std::unique_ptr Duplicate(void) const;
+
+ // identity
+ bool IsLastPacket(void) const { return true; }
+ bool HasTranscodableAmbe(void) const { return false; }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cdvlastframepacket_h */
diff --git a/src/G3Client.cpp b/src/G3Client.cpp
new file mode 100644
index 0000000..e7b00f3
--- /dev/null
+++ b/src/G3Client.cpp
@@ -0,0 +1,53 @@
+//
+// cg3client.cpp
+// xlxd
+//
+// Created by Marius Petrescu (YO2LOJ) on 03/06/2019.
+// Copyright © 2019 Marius Petrescu (YO2LOJ). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "G3Client.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructors
+
+CG3Client::CG3Client()
+{
+}
+
+CG3Client::CG3Client(const CCallsign &callsign, const CIp &ip, char reflectorModule)
+ : CClient(callsign, ip, reflectorModule)
+{
+
+}
+
+CG3Client::CG3Client(const CG3Client &client)
+ : CClient(client)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// status
+
+bool CG3Client::IsAlive(void) const
+{
+ return (m_LastKeepaliveTime.DurationSinceNow() < G3_KEEPALIVE_TIMEOUT);
+}
diff --git a/src/G3Client.h b/src/G3Client.h
new file mode 100644
index 0000000..9f08d50
--- /dev/null
+++ b/src/G3Client.h
@@ -0,0 +1,62 @@
+//
+// G3Client.h
+// xlxd
+//
+// Created by Marius Petrescu (YO2LOJ) on 03/06/2019.
+// Copyright © 2019 Marius Petrescu (YO2LOJ). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cg3client_h
+#define cg3client_h
+
+#include "Client.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// define
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CG3Client : public CClient
+{
+public:
+ // constructors
+ CG3Client();
+ CG3Client(const CCallsign &, const CIp &, char = ' ');
+ CG3Client(const CG3Client &);
+
+ // destructor
+ virtual ~CG3Client() {};
+
+ // identity
+ int GetProtocol(void) const { return PROTOCOL_G3; }
+ const char *GetProtocolName(void) const { return "Terminal/AP"; }
+ int GetCodec(void) const { return CODEC_AMBEPLUS; }
+ bool IsNode(void) const { return true; }
+
+ // status
+ bool IsAlive(void) const;
+
+protected:
+ // data
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cg3client_h */
diff --git a/src/G3Protocol.cpp b/src/G3Protocol.cpp
new file mode 100644
index 0000000..965118c
--- /dev/null
+++ b/src/G3Protocol.cpp
@@ -0,0 +1,785 @@
+//
+// cg3protocol.cpp
+// xlxd
+//
+// Created by Marius Petrescu (YO2LOJ) on 03/06/2019.
+// Copyright © 2019 Marius Petrescu (YO2LOJ). All rights reserved.
+// Copyright © 2020 Thomas A. Early, N7TAE
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include
+#include
+#include "G3Client.h"
+#include "G3Protocol.h"
+#include "Reflector.h"
+#include "GateKeeper.h"
+
+#include
+#include
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation
+
+bool CG3Protocol::Initialize(const char */*type*/, const int /*type*/, const uint16 /*port*/, const bool /*has_ipv4*/, const bool /*has_ipv6*/)
+// everything is hard coded until ICOM gets their act together and start supporting IPv6
+{
+ ReadOptions();
+
+ // init reflector apparent callsign
+ m_ReflectorCallsign = g_Reflector.GetCallsign();
+
+ // reset stop flag
+ keep_running = true;
+
+ // update the reflector callsign
+ //m_ReflectorCallsign.PatchCallsign(0, (const uint8 *)"XLX", 3);
+
+ // create our sockets
+ auto s = g_Reflector.m_Address.GetV4Address(PROTOCOL_G3);
+ CIp ip(AF_INET, G3_DV_PORT, s.c_str());
+ if ( ip.IsSet() )
+ {
+ if (! m_Socket4.Open(ip))
+ return false;
+ }
+ else
+ return false;
+
+ std::cout << "Listening on " << ip << std::endl;
+
+ //create helper socket
+ ip.SetPort(G3_PRESENCE_PORT);
+ if (! m_PresenceSocket.Open(ip))
+ {
+ std::cerr << "Error opening socket on port UDP" << G3_PRESENCE_PORT << " on ip " << ip << std::endl;
+ return false;
+ }
+
+ ip.SetPort(G3_CONFIG_PORT);
+ if (! m_ConfigSocket.Open(ip))
+ {
+ std::cerr << "Error opening G3 config socket on port UDP" << G3_CONFIG_PORT << " on ip " << ip << std::endl;
+ return false;
+ }
+
+ if (! m_IcmpRawSocket.Open(IPPROTO_ICMP))
+ {
+ std::cerr << "Error opening raw socket for ICMP" << std::endl;
+ return false;
+ }
+
+ // start helper threads
+ m_Future = std::async(std::launch::async, &CG3Protocol::Thread, this);
+ m_PresenceFuture = std::async(std::launch::async, &CG3Protocol::PresenceThread, this);
+ m_ConfigFuture = std::async(std::launch::async, &CG3Protocol::ConfigThread, this);
+ m_IcmpFuture = std::async(std::launch::async, &CG3Protocol::IcmpThread, this);
+
+ // update time
+ m_LastKeepaliveTime.Now();
+
+ std::cout << "Initialized G3 Protocol, all threads started" << std::endl;
+ return true;
+}
+
+void CG3Protocol::Close(void)
+{
+ if (m_PresenceFuture.valid())
+ {
+ m_PresenceFuture.get();
+ }
+
+ if (m_ConfigFuture.valid())
+ {
+ m_ConfigFuture.get();
+ }
+
+ if (m_IcmpFuture.valid())
+ {
+ m_IcmpFuture.get();
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// private threads
+
+void CG3Protocol::PresenceThread()
+{
+ while (keep_running)
+ {
+ PresenceTask();
+ }
+}
+
+void CG3Protocol::ConfigThread()
+{
+ while (keep_running)
+ {
+ ConfigTask();
+ }
+}
+
+void CG3Protocol::IcmpThread()
+{
+ while (keep_running)
+ {
+ IcmpTask();
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// presence task
+
+void CG3Protocol::PresenceTask(void)
+{
+ CBuffer Buffer;
+ CIp ReqIp;
+ CCallsign Callsign;
+ CCallsign Owner;
+ CCallsign Terminal;
+
+
+ if ( m_PresenceSocket.Receive(Buffer, ReqIp, 20) )
+ {
+
+ CIp Ip(ReqIp);
+ Ip.SetPort(G3_DV_PORT);
+
+ if (Buffer.size() == 32)
+ {
+ Callsign.SetCallsign(&Buffer.data()[8], 8);
+ Owner.SetCallsign(&Buffer.data()[16], 8);
+ Terminal.SetCallsign(&Buffer.data()[24], 8);
+
+ std::cout << "Presence from owner " << Owner << " on " << Ip << " as " << Callsign << " on terminal " << Terminal << std::endl;
+
+ // accept
+ Buffer.data()[2] = 0x80; // response
+ Buffer.data()[3] = 0x00; // ok
+
+ if (m_GwAddress == 0)
+ {
+ Buffer.Append(*(uint32 *)m_ConfigSocket.GetLocalAddr());
+ }
+ else
+ {
+ Buffer.Append(m_GwAddress);
+ }
+
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrextant = nullptr;
+ while ( (extant = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ CIp ClIp = extant->GetIp();
+ if (ClIp.GetAddr() == Ip.GetAddr())
+ {
+ break;
+ }
+ }
+
+ if (extant == nullptr)
+ {
+ it = clients->begin();
+
+ // do we already have a client with the same call (IP changed)?
+ while ( (extant = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ if (extant->GetCallsign().HasSameCallsign(Terminal))
+ {
+ //delete old client
+ clients->RemoveClient(extant);
+ break;
+ }
+ }
+
+ // create new client and append
+ clients->AddClient(std::make_shared(Terminal, Ip));
+ }
+ else
+ {
+ // client changed callsign
+ if (!extant->GetCallsign().HasSameCallsign(Terminal))
+ {
+ //delete old client
+ clients->RemoveClient(extant);
+
+ // create new client and append
+ clients->AddClient(std::make_shared(Terminal, Ip));
+ }
+ }
+ g_Reflector.ReleaseClients();
+
+ m_PresenceSocket.Send(Buffer, ReqIp);
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// configuration task
+
+void CG3Protocol::ConfigTask(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Call;
+ bool isRepeaterCall;
+
+ if ( m_ConfigSocket.Receive(&Buffer, &Ip, 20) != -1 )
+ {
+ if (Buffer.size() == 16)
+ {
+ if (memcmp(&Buffer.data()[8], " ", 8) == 0)
+ {
+ Call.SetCallsign(GetReflectorCallsign(), 8);
+ }
+ else
+ {
+ Call.SetCallsign(&Buffer.data()[8], 8);
+ }
+
+ isRepeaterCall = ((Buffer.data()[2] & 0x10) == 0x10);
+
+ std::cout << "Config request from " << Ip << " for " << Call << " (" << ((char *)(isRepeaterCall)?"repeater":"routed") << ")" << std::endl;
+
+ //std::cout << "Local address: " << inet_ntoa(*m_ConfigSocket.GetLocalAddr()) << std::endl;
+
+ Buffer.data()[2] |= 0x80; // response
+
+ if (isRepeaterCall)
+ {
+ if ((Call.HasSameCallsign(GetReflectorCallsign())) && (g_Reflector.IsValidModule(Call.GetModule())))
+ {
+ Buffer.data()[3] = 0x00; // ok
+ }
+ else
+ {
+ std::cout << "Module " << Call << " invalid" << std::endl;
+ Buffer.data()[3] = 0x01; // reject
+ }
+ }
+ else
+ {
+ // reject routed calls for now
+ Buffer.data()[3] = 0x01; // reject
+ }
+
+ char module = Call.GetModule();
+
+ if (!strchr(m_Modules.c_str(), module) && !strchr(m_Modules.c_str(), '*'))
+ {
+ // restricted
+ std::cout << "Module " << Call << " restricted by configuration" << std::endl;
+ Buffer.data()[3] = 0x01; // reject
+ }
+
+ // UR
+ Buffer.resize(8);
+ Buffer.Append((uint8 *)(const char *)Call, CALLSIGN_LEN - 1);
+ Buffer.Append((uint8)module);
+
+ // RPT1
+ Buffer.Append((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN - 1);
+ Buffer.Append((uint8)'G');
+
+ // RPT2
+ Buffer.Append((uint8 *)(const char *)GetReflectorCallsign(), CALLSIGN_LEN - 1);
+
+ if (isRepeaterCall)
+ {
+ Buffer.Append((uint8)Call.GetModule());
+ }
+ else
+ {
+ // routed - no module for now
+ Buffer.Append((uint8)' ');
+ }
+
+ if (Buffer.data()[3] == 0x00)
+ {
+ std::cout << "External G3 gateway address " << inet_ntoa(*(in_addr *)&m_GwAddress) << std::endl;
+
+ if (m_GwAddress == 0)
+ {
+ Buffer.Append(*(uint32 *)m_ConfigSocket.GetLocalAddr());
+ }
+ else
+ {
+ Buffer.Append(m_GwAddress);
+ }
+ }
+ else
+ {
+ Buffer.Append(0u);
+ }
+
+ m_ConfigSocket.Send(Buffer, Ip);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// icmp task
+
+void CG3Protocol::IcmpTask(void)
+{
+ uint8_t Buffer[RAW_BUFFER_LENMAX];
+ CIp Ip;
+ int iIcmpType;
+
+ if ((iIcmpType = m_IcmpRawSocket.IcmpReceive(Buffer, &Ip, 20)) != -1)
+ {
+ if (iIcmpType == ICMP_DEST_UNREACH)
+ {
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ CIp ClientIp = client->GetIp();
+ if (ClientIp.GetAddr() == Ip.GetAddr())
+ {
+ clients->RemoveClient(client);
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DV task
+
+void CG3Protocol::Task(void)
+{
+ CBuffer Buffer;
+ CIp Ip;
+ CCallsign Callsign;
+ char ToLinkModule;
+ int ProtRev;
+ std::unique_ptr Header;
+ std::unique_ptr Frame;
+ std::unique_ptr LastFrame;
+
+ // any incoming packet ?
+ if ( m_Socket4.Receive(Buffer, Ip, 20) )
+ {
+ CIp ClIp;
+ CIp *BaseIp = nullptr;
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ ClIp = client->GetIp();
+ if (ClIp.GetAddr() == Ip.GetAddr())
+ {
+ BaseIp = &ClIp;
+ client->Alive();
+ // supress host checks - no ping needed to trigger potential ICMPs
+ // the regular data flow will do it
+ m_LastKeepaliveTime.Now();
+ break;
+ }
+ }
+ g_Reflector.ReleaseClients();
+
+ if (BaseIp != nullptr)
+ {
+ // crack the packet
+ if ( IsValidDvFramePacket(Buffer, Frame) )
+ {
+ OnDvFramePacketIn(Frame, BaseIp);
+ }
+ else if ( IsValidDvHeaderPacket(Buffer, Header) )
+ {
+ // callsign muted?
+ if ( g_GateKeeper.MayTransmit(Header->GetMyCallsign(), Ip, PROTOCOL_G3, Header->GetRpt2Module()) )
+ {
+ // handle it
+ OnDvHeaderPacketIn(Header, *BaseIp);
+ }
+ }
+ else if ( IsValidDvLastFramePacket(Buffer, LastFrame) )
+ {
+ OnDvLastFramePacketIn(LastFrame, BaseIp);
+ }
+ }
+ }
+
+ // handle end of streaming timeout
+ CheckStreamsTimeout();
+
+ // handle queue from reflector
+ HandleQueue();
+
+ // keep alive during idle if needed
+ if ( m_LastKeepaliveTime.DurationSinceNow() > G3_KEEPALIVE_PERIOD )
+ {
+ // handle keep alives
+ HandleKeepalives();
+
+ // update time
+ m_LastKeepaliveTime.Now();
+
+ // reload option if needed - called once every G3_KEEPALIVE_PERIOD
+ NeedReload();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// queue helper
+
+void CG3Protocol::HandleQueue(void)
+{
+ m_Queue.Lock();
+ while ( !m_Queue.empty() )
+ {
+ // supress host checks
+ m_LastKeepaliveTime.Now();
+
+ // get the packet
+ auto packet = m_Queue.front();
+ m_Queue.pop();
+
+ // encode it
+ CBuffer buffer;
+ if ( EncodeDvPacket(*packet, &buffer) )
+ {
+ // and push it to all our clients linked to the module and who are not streaming in
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ // is this client busy ?
+ if ( !client->IsAMaster() && (client->GetReflectorModule() == packet->GetModuleId()) )
+ {
+ // not busy, send the packet
+ int n = packet->IsDvHeader() ? 5 : 1;
+ for ( int i = 0; i < n; i++ )
+ {
+ Send(buffer, client->GetIp());
+ }
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+ m_Queue.Unlock();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// keepalive helpers
+
+void CG3Protocol::HandleKeepalives(void)
+{
+ // G3 Terminal mode does not support keepalive
+ // We will send some short packed and expect
+ // A ICMP unreachable on failure
+ CBuffer keepalive((uint8 *)"PING", 4);
+
+ // iterate on clients
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ if (!client->IsAlive())
+ {
+ clients->RemoveClient(client);
+ }
+ else
+ {
+ // send keepalive packet
+ Send(keepalive, client->GetIp());
+ }
+ }
+ g_Reflector.ReleaseClients();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// streams helpers
+
+void CG3Protocol::OnDvHeaderPacketIn(std::unique_ptr &Header, const CIp &Ip)
+{
+ // find the stream
+ CPacketStream *stream = GetStream(Header->GetStreamId(), &Ip);
+
+ if ( stream )
+ {
+ // stream already open
+ // skip packet, but tickle the stream
+ stream->Tickle();
+ }
+ else
+ {
+ // no stream open yet, open a new one
+ CCallsign my(Header->GetMyCallsign());
+ CCallsign rpt1(Header->GetRpt1Callsign());
+ CCallsign rpt2(Header->GetRpt2Callsign());
+
+ // find this client
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ CIp ClIp = client->GetIp();
+ if (ClIp.GetAddr() == Ip.GetAddr())
+ {
+ break;
+ }
+ }
+
+ if ( client )
+ {
+
+ // move it to the proper module
+ if (m_ReflectorCallsign.HasSameCallsign(rpt2))
+ {
+ if (client->GetReflectorModule() != rpt2.GetModule())
+ {
+ auto new_module = rpt2.GetModule();
+ if (strchr(m_Modules.c_str(), '*') || strchr(m_Modules.c_str(), new_module))
+ {
+ client->SetReflectorModule(new_module);
+ }
+ else
+ {
+ g_Reflector.ReleaseClients();
+ return;
+ }
+ }
+
+ // get client callsign
+ rpt1 = client->GetCallsign();
+
+ // and try to open the stream
+ if ( (stream = g_Reflector.OpenStream(Header, client)) != nullptr )
+ {
+ // keep the handle
+ m_Streams.push_back(stream);
+ }
+
+ // update last heard
+ g_Reflector.GetUsers()->Hearing(my, rpt1, rpt2);
+ g_Reflector.ReleaseUsers();
+ }
+ }
+ // release
+ g_Reflector.ReleaseClients();
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet decoding helpers
+
+bool CG3Protocol::IsValidDvHeaderPacket(const CBuffer &Buffer, std::unique_ptr &header)
+{
+ if ( 56==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 4) && 0x10U==Buffer.data()[4] && 0x20U==Buffer.data()[8] )
+ {
+ // create packet
+ header = std::unique_ptr(new CDvHeaderPacket((struct dstar_header *)&(Buffer.data()[15]), *((uint16 *)&(Buffer.data()[12])), 0x80));
+ // check validity of packet
+ if ( header && header->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CG3Protocol::IsValidDvFramePacket(const CBuffer &Buffer, std::unique_ptr &dvframe)
+{
+ if ( 27==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 4) && 0x20U==Buffer.data()[4] && 0x20U==Buffer.data()[8] && 0U==(Buffer.data()[14] & 0x40U) )
+ {
+ // create packet
+ dvframe = std::unique_ptr(new CDvFramePacket((struct dstar_dvframe *)&(Buffer.data()[15]), *((uint16 *)&(Buffer.data()[12])), Buffer.data()[14]));
+ // check validity of packet
+ if ( dvframe && dvframe->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+bool CG3Protocol::IsValidDvLastFramePacket(const CBuffer &Buffer, std::unique_ptr &dvframe)
+{
+ if ( 27==Buffer.size() && 0==Buffer.Compare((uint8 *)"DSVT", 4) && 0x20U==Buffer.data()[4] && 0x20U==Buffer.data()[8] && (Buffer.data()[14] & 0x40U) )
+ {
+ // create packet
+ dvframe = std::unique_ptr(new CDvLastFramePacket((struct dstar_dvframe *)&(Buffer.data()[15]), *((uint16 *)&(Buffer.data()[12])), Buffer.data()[14]));
+ // check validity of packet
+ if ( dvframe && dvframe->IsValid() )
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// packet encoding helpers
+
+bool CG3Protocol::EncodeDvHeaderPacket(const CDvHeaderPacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','S','V','T',0x10,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+ struct dstar_header DstarHeader;
+
+ Packet.ConvertToDstarStruct(&DstarHeader);
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)0x80);
+ Buffer->Append((uint8 *)&DstarHeader, sizeof(struct dstar_header));
+
+ return true;
+}
+
+bool CG3Protocol::EncodeDvFramePacket(const CDvFramePacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag[] = { 'D','S','V','T',0x20,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+
+ Buffer->Set(tag, sizeof(tag));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)(Packet.GetPacketId() % 21));
+ Buffer->Append((uint8 *)Packet.GetAmbe(), AMBE_SIZE);
+ Buffer->Append((uint8 *)Packet.GetDvData(), DVDATA_SIZE);
+
+ return true;
+
+}
+
+bool CG3Protocol::EncodeDvLastFramePacket(const CDvLastFramePacket &Packet, CBuffer *Buffer) const
+{
+ uint8 tag1[] = { 'D','S','V','T',0x20,0x00,0x00,0x00,0x20,0x00,0x01,0x02 };
+ uint8 tag2[] = { 0x55,0xC8,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x25,0x1A,0xC6 };
+
+ Buffer->Set(tag1, sizeof(tag1));
+ Buffer->Append(Packet.GetStreamId());
+ Buffer->Append((uint8)((Packet.GetPacketId() % 21) | 0x40));
+ Buffer->Append(tag2, sizeof(tag2));
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// option helpers
+
+char *CG3Protocol::TrimWhiteSpaces(char *str)
+{
+ char *end;
+ while ((*str == ' ') || (*str == '\t')) str++;
+ if (*str == 0)
+ return str;
+ end = str + strlen(str) - 1;
+ while ((end > str) && ((*end == ' ') || (*end == '\t') || (*end == '\r')))
+ end--;
+ *(end + 1) = 0;
+ return str;
+}
+
+
+void CG3Protocol::NeedReload(void)
+{
+ struct stat fileStat;
+
+ if (::stat(TERMINALOPTIONS_PATH, &fileStat) != -1)
+ {
+ if (m_LastModTime != fileStat.st_mtime)
+ {
+ ReadOptions();
+
+ // we have new options - iterate on clients for potential removal
+ CClients *clients = g_Reflector.GetClients();
+ auto it = clients->begin();
+ std::shared_ptrclient = nullptr;
+ while ( (client = clients->FindNextClient(PROTOCOL_G3, it)) != nullptr )
+ {
+ char module = client->GetReflectorModule();
+ if (!strchr(m_Modules.c_str(), module) && !strchr(m_Modules.c_str(), '*'))
+ {
+ clients->RemoveClient(client);
+ }
+ }
+ g_Reflector.ReleaseClients();
+ }
+ }
+}
+
+void CG3Protocol::ReadOptions(void)
+{
+ char sz[256];
+ int opts = 0;
+
+
+ std::ifstream file(TERMINALOPTIONS_PATH);
+ if (file.is_open())
+ {
+ m_GwAddress = 0u;
+ m_Modules = "*";
+
+ while (file.getline(sz, sizeof(sz)).good())
+ {
+ char *szt = TrimWhiteSpaces(sz);
+ char *szval;
+
+ if ((::strlen(szt) > 0) && szt[0] != '#')
+ {
+ if ((szt = ::strtok(szt, " ,\t")) != nullptr)
+ {
+ if ((szval = ::strtok(nullptr, " ,\t")) != nullptr)
+ {
+ if (::strncmp(szt, "address", 7) == 0)
+ {
+ in_addr addr = { .s_addr = inet_addr(szval) };
+ if (addr.s_addr)
+ {
+ std::cout << "G3 handler address set to " << inet_ntoa(addr) << std::endl;
+ m_GwAddress = addr.s_addr;
+ opts++;
+ }
+ }
+ else if (strncmp(szt, "modules", 7) == 0)
+ {
+ std::cout << "G3 handler module list set to " << szval << std::endl;
+ m_Modules = szval;
+ opts++;
+ }
+ else
+ {
+ // unknown option - ignore
+ }
+ }
+ }
+ }
+ }
+ std::cout << "G3 handler loaded " << opts << " options from file " << TERMINALOPTIONS_PATH << std::endl;
+ file.close();
+
+ struct stat fileStat;
+
+ if (::stat(TERMINALOPTIONS_PATH, &fileStat) != -1)
+ {
+ m_LastModTime = fileStat.st_mtime;
+ }
+ }
+}
diff --git a/src/G3Protocol.h b/src/G3Protocol.h
new file mode 100644
index 0000000..ea260ca
--- /dev/null
+++ b/src/G3Protocol.h
@@ -0,0 +1,133 @@
+//
+// G3Protocol.h
+// xlxd
+//
+// Created by Marius Petrescu (YO2LOJ) on 03/06/2019.
+// Copyright © 2019 Marius Petrescu (YO2LOJ). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cg3protocol_h
+#define cg3protocol_h
+
+#include
+#include "Timer.h"
+#include "DCSProtocol.h"
+#include "DVHeaderPacket.h"
+#include "DVFramePacket.h"
+#include "DVLastFramePacket.h"
+#include "RawSocket.h"
+#include "UDPMsgSocket.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+// note on the G3 terminal/AP protocol:
+//
+// There are 3 steps in handling an incoming connection
+//
+// 1 - Notification of terminal call on port UDP 12346, Presence port, a CUdpSocket.
+// - Call will be rejected if in blacklisted
+//
+// 2 - Destination request on port UDP 12345, Config port, a CUdpMsgSocket.
+// - Calls to specific callsigns will be accepted for a default module
+// - Repeater calls will be accepted for local modules
+// - All other calls are rejected
+//
+// 3 - Actual D-star flow like in Dextra to/from port 40000
+// 2 distinct sockets where used in the initial protocol
+// later firmwares implement a single bidirectional socket
+//
+// Alive monitoring is done via a "PING" to remote port 40000. We will get an
+// ICMP unreachable on terminal mode close or on station shut down if routing is done
+// correctly. Otherwise a long timeout is used (e.g. 1h)
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CG3Protocol : public CProtocol
+{
+public:
+ // constructor
+ CG3Protocol() : m_GwAddress(0u), m_Modules("*"), m_LastModTime(0) {}
+
+ // initialization
+ bool Initialize(const char *type, const int ptype, const uint16 port, const bool has_ipv4, const bool has_ipv6);
+
+ // close
+ void Close(void);
+
+ // task
+ void Task(void);
+
+protected:
+ // threads
+ void PresenceThread(void);
+ void ConfigThread(void);
+ void IcmpThread(void);
+
+ // helper tasks
+ void PresenceTask(void);
+ void ConfigTask(void);
+ void IcmpTask(void);
+
+ // config
+ void ReadOptions(void);
+
+ // helper
+ char *TrimWhiteSpaces(char *);
+ void NeedReload(void);
+
+ // queue helper
+ void HandleQueue(void);
+
+ // keepalive helpers
+ void HandleKeepalives(void);
+
+ // stream helpers
+ void OnDvHeaderPacketIn(std::unique_ptr &, const CIp &);
+
+ // packet decoding helpers
+ bool IsValidDvHeaderPacket(const CBuffer &, std::unique_ptr &);
+ bool IsValidDvFramePacket(const CBuffer &, std::unique_ptr &);
+ bool IsValidDvLastFramePacket(const CBuffer &, std::unique_ptr &);
+
+ // packet encoding helpers
+ bool EncodeDvHeaderPacket(const CDvHeaderPacket &, CBuffer *) const;
+ bool EncodeDvFramePacket(const CDvFramePacket &, CBuffer *) const;
+ bool EncodeDvLastFramePacket(const CDvLastFramePacket &, CBuffer *) const;
+
+protected:
+ std::future m_PresenceFuture, m_ConfigFuture, m_IcmpFuture;
+
+ // time
+ CTimePoint m_LastKeepaliveTime;
+
+ // sockets
+ CUdpSocket m_PresenceSocket;
+ CUdpMsgSocket m_ConfigSocket;
+ CRawSocket m_IcmpRawSocket;
+
+ // optional params
+ uint32 m_GwAddress;
+ std::string m_Modules;
+ time_t m_LastModTime;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////
+#endif /* cg3protocol_h */
diff --git a/src/GateKeeper.cpp b/src/GateKeeper.cpp
new file mode 100644
index 0000000..744331a
--- /dev/null
+++ b/src/GateKeeper.cpp
@@ -0,0 +1,280 @@
+//
+// cgatekeeper.cpp
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 07/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#include "Main.h"
+#include "Timer.h"
+#include "GateKeeper.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+CGateKeeper g_GateKeeper;
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// constructor
+
+CGateKeeper::CGateKeeper()
+{
+ keep_running = true;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// destructor
+
+CGateKeeper::~CGateKeeper()
+{
+ Close();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// init & clode
+
+bool CGateKeeper::Init(void)
+{
+
+ // load lists from files
+ m_NodeWhiteList.LoadFromFile(WHITELIST_PATH);
+ m_NodeBlackList.LoadFromFile(BLACKLIST_PATH);
+ m_PeerList.LoadFromFile(INTERLINKLIST_PATH);
+
+ // reset run flag
+ keep_running = true;
+
+ // start thread;
+ m_Future = std::async(std::launch::async, &CGateKeeper::Thread, this);
+
+ return true;
+}
+
+void CGateKeeper::Close(void)
+{
+ // kill threads
+ keep_running = false;
+ if ( m_Future.valid() )
+ {
+ m_Future.get();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// authorisations
+
+bool CGateKeeper::MayLink(const CCallsign &callsign, const CIp &ip, int protocol, char *modules) const
+{
+ bool ok = true;
+
+ switch (protocol)
+ {
+ // repeaters
+ case PROTOCOL_DEXTRA:
+ case PROTOCOL_DPLUS:
+ case PROTOCOL_DCS:
+#ifndef NO_XLX
+ case PROTOCOL_DMRPLUS:
+ case PROTOCOL_DMRMMDVM:
+ case PROTOCOL_YSF:
+#endif
+#ifndef NO_G3
+ case PROTOCOL_G3:
+#endif
+ // first check is IP & callsigned listed OK
+ ok &= IsNodeListedOk(callsign, ip);
+ // todo: then apply any protocol specific authorisation for the operation
+ break;
+
+#ifndef NO_XLX
+ // XLX interlinks
+ case PROTOCOL_XLX:
+ ok &= IsPeerListedOk(callsign, ip, modules);
+ break;
+#endif
+
+ // unsupported
+ case PROTOCOL_NONE:
+ default:
+ ok = false;
+ break;
+ }
+
+ // report
+ if ( !ok )
+ {
+ std::cout << "Gatekeeper blocking linking of " << callsign << " @ " << ip << " using protocol " << protocol << std::endl;
+ }
+
+ // done
+ return ok;
+}
+
+bool CGateKeeper::MayTransmit(const CCallsign &callsign, const CIp &ip, int protocol, char module) const
+{
+ bool ok = true;
+
+ switch (protocol)
+ {
+ // repeaters, protocol specific
+ case PROTOCOL_ANY:
+ case PROTOCOL_DEXTRA:
+ case PROTOCOL_DPLUS:
+ case PROTOCOL_DCS:
+#ifndef NO_XLX
+ case PROTOCOL_DMRPLUS:
+ case PROTOCOL_DMRMMDVM:
+ case PROTOCOL_YSF:
+#endif
+#ifndef NO_G3
+ case PROTOCOL_G3:
+#endif
+ // first check is IP & callsigned listed OK
+ ok = ok && IsNodeListedOk(callsign, ip, module);
+ // todo: then apply any protocol specific authorisation for the operation
+ break;
+
+#ifndef NO_XLX
+ // XLX interlinks
+ case PROTOCOL_XLX:
+ ok = ok && IsPeerListedOk(callsign, ip, module);
+ break;
+#endif
+
+ // unsupported
+ case PROTOCOL_NONE:
+ default:
+ ok = false;
+ break;
+ }
+
+ // report
+ if ( !ok )
+ {
+ std::cout << "Gatekeeper blocking transmitting of " << callsign << " @ " << ip << " using protocol " << protocol << std::endl;
+ }
+
+ // done
+ return ok;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// thread
+
+void CGateKeeper::Thread()
+{
+ while (keep_running)
+ {
+ // Wait 30 seconds
+ for (int i=0; i<15 && keep_running; i++)
+ CTimePoint::TaskSleepFor(2000);
+
+ // have lists files changed ?
+ if ( m_NodeWhiteList.NeedReload() )
+ {
+ m_NodeWhiteList.ReloadFromFile();
+ }
+ if ( m_NodeBlackList.NeedReload() )
+ {
+ m_NodeBlackList.ReloadFromFile();
+ }
+ if ( m_PeerList.NeedReload() )
+ {
+ m_PeerList.ReloadFromFile();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// operation helpers
+
+bool CGateKeeper::IsNodeListedOk(const CCallsign &callsign, const CIp &ip, char module) const
+{
+ bool ok = true;
+
+ // first check IP
+
+ // next, check callsign
+ if ( ok )
+ {
+ // first check if callsign is in white list
+ // note if white list is empty, everybody is authorized
+ const_cast(m_NodeWhiteList).Lock();
+ if ( !m_NodeWhiteList.empty() )
+ {
+ ok = m_NodeWhiteList.IsCallsignListedWithWildcard(callsign, module);
+ }
+ const_cast(m_NodeWhiteList).Unlock();
+
+ // then check if not blacklisted
+ const_cast(m_NodeBlackList).Lock();
+ ok &= !m_NodeBlackList.IsCallsignListedWithWildcard(callsign);
+ const_cast(m_NodeBlackList).Unlock();
+ }
+
+ // done
+ return ok;
+
+}
+
+bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char module) const
+{
+ bool ok = true;
+
+ // first check IP
+
+ // next, check callsign
+ if ( ok )
+ {
+ // look for an exact match in the list
+ const_cast(m_PeerList).Lock();
+ if ( !m_PeerList.empty() )
+ {
+ ok = m_PeerList.IsCallsignListed(callsign, module);
+ }
+ const_cast(m_PeerList).Unlock();
+ }
+
+ // done
+ return ok;
+}
+
+bool CGateKeeper::IsPeerListedOk(const CCallsign &callsign, const CIp &ip, char *modules) const
+{
+ bool ok = true;
+
+ // first check IP
+
+ // next, check callsign
+ if ( ok )
+ {
+ // look for an exact match in the list
+ const_cast(m_PeerList).Lock();
+ if ( !m_PeerList.empty() )
+ {
+ ok = m_PeerList.IsCallsignListed(callsign, modules);
+ }
+ const_cast(m_PeerList).Unlock();
+ }
+
+ // done
+ return ok;
+}
diff --git a/src/GateKeeper.h b/src/GateKeeper.h
new file mode 100644
index 0000000..bff57ed
--- /dev/null
+++ b/src/GateKeeper.h
@@ -0,0 +1,80 @@
+//
+// GateKeeper.h
+// xlxd
+//
+// Created by Jean-Luc Deltombe (LX3JL) on 07/11/2015.
+// Copyright © 2015 Jean-Luc Deltombe (LX3JL). All rights reserved.
+//
+// ----------------------------------------------------------------------------
+// This file is part of xlxd.
+//
+// xlxd 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.
+//
+// xlxd 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 Foobar. If not, see .
+// ----------------------------------------------------------------------------
+
+#ifndef cgatekeeper_h
+#define cgatekeeper_h
+
+#include "Main.h"
+#include "Callsign.h"
+#include "IP.h"
+#include "CallsignList.h"
+#include "PeerCallsignList.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// class
+
+class CGateKeeper
+{
+public:
+ // constructor
+ CGateKeeper();
+
+ // destructor
+ virtual ~CGateKeeper();
+
+ // init & clode
+ bool Init(void);
+ void Close(void);
+
+ // authorizations
+ bool MayLink(const CCallsign &, const CIp &, int, char * = nullptr) const;
+ bool MayTransmit(const CCallsign &, const CIp &, int = PROTOCOL_ANY, char = ' ') const;
+
+ // peer list handeling
+ CPeerCallsignList *GetPeerList(void) { m_PeerList.Lock(); return &m_PeerList; }
+ void ReleasePeerList(void) { m_PeerList.Unlock(); }
+
+protected:
+ // thread
+ void Thread();
+
+ // operation helpers
+ bool IsNodeListedOk(const CCallsign &, const CIp &, char = ' ') const;
+ bool IsPeerListedOk(const CCallsign &, const CIp &, char) const;
+ bool IsPeerListedOk(const CCallsign &, const CIp &, char *) const;
+
+protected:
+ // data
+ CCallsignList m_NodeWhiteList;
+ CCallsignList m_NodeBlackList;
+ CPeerCallsignList m_PeerList;
+
+ // thread
+ std::atomic keep_running;
+ std::future