Merge branch 'release/v0.6'

master v0.6
Geoffrey Merck 3 years ago
commit 4add55a64e

3
.gitignore vendored

@ -46,3 +46,6 @@ Sandbox/*
dstargateway
Tests/dstargateway_tests
DGWRemoteControl/dgwremotecontrol
DGWVoiceTransmit/dgwvoicetransmit
DGWTextTransmit/dgwtexttransmit
DGWTimeServer/dgwtimeserver

@ -25,6 +25,10 @@
"description": "Définir la version désassemblage sur Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},
{
"description": "Tell GDB to follow forks child",
"text": "-gdb-set follow-fork-mode child"
}
]
},
@ -52,6 +56,78 @@
}
]
},
{
"name": "(gdb) dgwtextransmit",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/DGWTextTransmit/dgwtexttransmit",
"args": ["F4FXL B", "-file", "${workspaceFolder}/Sandbox/text.txt"],//["F4FXL B", "-text", "test de julien a nouveau"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Activer l'impression en mode Pretty pour gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Définir la version désassemblage sur Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) dgwtimeserver",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/DGWTimeServer/dgwtimeserver",
"args": ["${workspaceFolder}/Sandbox/__timeserver_test.cfg"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Activer l'impression en mode Pretty pour gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Définir la version désassemblage sur Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
{
"name": "(gdb) dgwvoicetransmit",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/DGWVoiceTransmit/dgwvoicetransmit",
"args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool", "-text", "www.F5KAV.fr", "-dprs", "!4858.72N/00736.91Er/"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Activer l'impression en mode Pretty pour gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Définir la version désassemblage sur Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
{
"name": "Tests",
"type": "cppdbg",
@ -59,7 +135,7 @@
"program": "${workspaceFolder}/Tests/dstargateway_tests",
"args": [ ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}/Tests/",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",

47
.vscode/tasks.json vendored

@ -8,7 +8,7 @@
"type": "shell",
"command": "make",
"args": [
"-j3",
"-j9",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"all"
@ -24,7 +24,7 @@
"type": "shell",
"command": "make",
"args": [
"-j3",
"-j9",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DStarGateway/dstargateway"
@ -37,7 +37,7 @@
"type": "shell",
"command": "make",
"args": [
"-j3",
"-j9",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DGWRemoteControl/dgwremotecontrol"
@ -45,12 +45,51 @@
"group": "build",
"problemMatcher": []
},
{
"label": "Build DGWTextTransmit",
"type": "shell",
"command": "make",
"args": [
"-j9",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DGWTextTransmit/dgwtexttransmit"
],
"group": "build",
"problemMatcher": []
},
{
"label": "Build DGWTimeServer",
"type": "shell",
"command": "make",
"args": [
"-j9",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DGWTimeServer/dgwtimeserver"
],
"group": "build",
"problemMatcher": []
},
{
"label": "Build DGWVoiceTransmit",
"type": "shell",
"command": "make",
"args": [
"-j9",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DGWVoiceTransmit/dgwvoicetransmit"
],
"group": "build",
"problemMatcher": []
},
{
"label": "Build Tests",
"type": "shell",
"command": "make",
"args": [
"-j3",
"-j9",
"tests",
"ENABLE_DEBUG=1",
"USE_GPSD=1"

@ -37,8 +37,12 @@ unsigned int CAPRSUtils::calcGPSAIcomCRC(const std::string& gpsa)
{
unsigned int icomcrc = 0xFFFFU;
unsigned int dataBegin = 0U;
if(boost::starts_with(gpsa, "$$CRC") && gpsa.length() >= 10 && gpsa[9] == ',')
dataBegin = 10U;
auto length = gpsa.length();
for (unsigned int j = 10U; j < length; j++) {
for (unsigned int j = dataBegin; j < length; j++) {
unsigned char ch = (unsigned char)gpsa[j];
for (unsigned int i = 0U; i < 8U; i++) {

@ -0,0 +1,206 @@
/*
* Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
/*
* Losely based on https://github.com/jirihnidek/daemon/blob/master/src/daemon.c
*/
#include <unistd.h>
#include <cstdlib>
#include <csignal>
#include <sys/stat.h>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/file.h>
#include "Daemon.h"
#include "Log.h"
int CDaemon::m_pid_fd = -1;
std::string CDaemon::m_pidFileName("");
DAEMONIZE_RESULT CDaemon::daemonise(const std::string& pidFile, const std::string& userName)
{
// get user
struct passwd* user = nullptr;
if(!userName.empty() && getuid() == 0) {
user = getpwnam(userName.c_str());
if(user == nullptr) {
CLog::logFatal("Failed to get %s user", userName.c_str());
return DR_FAILURE;
}
}
// Create PID file if needed
if (!pidFile.empty()) {
auto tempFd = tryGetLock(pidFile);
if (tempFd < 0) {
CLog::logFatal("Failed to acquire lock on pidfile %s : %s", pidFile.c_str(), strerror(errno));
return DR_PIDFILE_FAILED;
}
releaseLock(tempFd, "");
if(user != nullptr) {
int res = chown(pidFile.c_str(), user->pw_uid, user->pw_gid);
if(res != 0) {
CLog::logFatal("Failed to set ownership of pidfile to user %s : %s", userName.c_str(), strerror(errno));
return DR_FAILURE;
}
}
}
// change process ownership
if(user != nullptr) {
if(setgid(user->pw_gid) != 0) {
CLog::logFatal("Failed to set %s GID : %s", userName.c_str(), strerror(errno));
return DR_FAILURE;
}
if(setuid(user->pw_uid) != 0) {
CLog::logFatal("Failed to set %s UID : %s", userName.c_str(), strerror(errno));
return DR_FAILURE;
}
// Double check it worked (AKA Paranoia)
if (setuid(0) != -1){
CLog::logFatal("It's possible to regain root - something is wrong!, exiting");
return DR_FAILURE;
}
}
pid_t pid = 0;
/* Fork off the parent process */
pid = fork();
/* An error occurred */
if (pid < 0) {
CLog::logFatal("Forking failed, exiting");
return DR_FAILURE;
}
/* Success: Let the parent terminate */
if (pid > 0) {
return DR_PARENT;
}
// On success: The child process becomes session leader
if (setsid() < 0) {
CLog::logFatal("Failed to set session id, exiting");
return DR_FAILURE;
}
/* Ignore signal sent from child to parent process */
signal(SIGCHLD, SIG_IGN);
#ifdef DOUBLE_FORK
// Fork off for the second time. Some litterature says it is best to fork 2 times so that the process never can open a terminal.
// However it messes up systemd, event when unit is set as forking
pid = fork();
// An error occurred
if (pid < 0) {
CLog::logFatal("Second forking failed, exiting");
return DR_FAILURE;
}
// Success: Let the parent terminate
if (pid > 0) {
return DR_PARENT;
}
#endif
// Set new file permissions
umask(0);
/* Change the working directory to the root directory */
/* or another appropriated directory */
if(chdir("/") != 0) {
CLog::logFatal("Faild to cd, exiting");
return DR_FAILURE;
}
#ifdef CLOSE_FILE_DESC
// Close all open file descriptors
for (int fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) {
close(fd);
}
// Reopen stdin (fd = 0), stdout (fd = 1), stderr (fd = 2)
stdin = fopen("/dev/null", "r");
stdout = fopen("/dev/null", "w+");
stderr = fopen("/dev/null", "w+");
#endif
// Try to write PID of daemon to lockfile
if (!pidFile.empty())
{
m_pid_fd = tryGetLock(pidFile);
if (m_pid_fd < 0) {
/* Can't open lockfile */
return DR_PIDFILE_FAILED;
}
if (lockf(m_pid_fd, F_TLOCK, 0) < 0) {
/* Can't lock file */
return DR_PIDFILE_FAILED;
}
m_pidFileName.assign(pidFile);
/* Get current PID */
char str[256];
sprintf(str, "%d\n", getpid());
/* Write PID to lockfile */
write(m_pid_fd, str, strlen(str));
}
return DR_CHILD;
}
void CDaemon::finalise()
{
releaseLock(m_pid_fd, m_pidFileName);
if(!m_pidFileName.empty()) {
unlink(m_pidFileName.c_str());
}
}
int CDaemon::tryGetLock( const std::string& file )
{
mode_t m = umask( 0 );
int fd = open( file.c_str(), O_RDWR|O_CREAT, 0640 );
umask( m );
if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 ) {
close( fd );
fd = -1;
}
return fd;
}
void CDaemon::releaseLock(int fd, const std::string& file)
{
if( fd < 0 )
return;
remove(file.c_str());
close(fd);
}

@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <string>
enum DAEMONIZE_RESULT
{
DR_PARENT,
DR_CHILD,
DR_FAILURE,
DR_PIDFILE_FAILED
};
class CDaemon
{
public:
static DAEMONIZE_RESULT daemonise(const std::string& pidFile, const std::string& user);
static void finalise();
private:
static int tryGetLock(const std::string& file);
static void releaseLock(int fd, const std::string& file);
static int m_pid_fd;
static std::string m_pidFileName;
};

@ -30,4 +30,5 @@ CLogTarget(logLevel)
void CLogConsoleTarget::printLogInt(const std::string& msg)
{
std::cout << msg;
std::cout.flush();
}

@ -21,19 +21,19 @@
#include <fstream>
#include <chrono>
#include <ctime>
#include <cassert>
#include "LogFileTarget.h"
#define LOG_FILE_ROOT "dstargateway"
CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) :
CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, const std::string& fileRoot, bool rotate) :
CLogTarget(logLevel),
m_dir(dir),
m_fileRoot(fileRoot),
m_rotate(rotate),
m_file(),
m_day(0)
{
assert(!fileRoot.empty());
}
CLogFileTarget::~CLogFileTarget()
@ -53,7 +53,7 @@ void CLogFileTarget::printLogIntFixed(const std::string& msg)
std::string filename(m_dir);
if(filename[filename.length() - 1U] != '/') filename.push_back('/');
filename.append(LOG_FILE_ROOT).append(".log");
filename.append(m_fileRoot).append(".log");
m_file.open(filename, std::ios::app);
if(m_file.is_open()) {
@ -80,7 +80,7 @@ void CLogFileTarget::printLogIntRotate(const std::string& msg)
if(filename[filename.length() - 1U] != '/') filename.push_back('/');
char buf[64];
std::strftime(buf, 42, "-%Y-%m-%d", now_tm);
filename.append(LOG_FILE_ROOT).append(buf).append(".log");
filename.append(m_fileRoot).append(buf).append(".log");
m_file.open(filename, std::ios::app);
if(!m_file.is_open()) {
std::cerr << "FAILED TO OPEN LOG FILE :" << filename;

@ -26,7 +26,7 @@
class CLogFileTarget : public CLogTarget
{
public:
CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, bool rotate);
CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, const std::string& fileRoot, bool rotate);
~CLogFileTarget();
protected:
@ -37,6 +37,7 @@ private:
void printLogIntFixed(const std::string& msg);
std::string buildFileName();
std::string m_dir;
std::string m_fileRoot;
bool m_rotate;
std::fstream m_file;
int m_day;

@ -0,0 +1,267 @@
/*
* Copyright (C) 2011 by Jonathan Naylor G4KLX
* Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 "TCPReaderWriterServer.h"
#include "Log.h"
#include "NetUtils.h"
#include <cerrno>
#include <cassert>
CTCPReaderWriterServer::CTCPReaderWriterServer(const std::string& address, unsigned int port) :
CThread("TCP server"),
m_address(address),
m_port(port),
m_fd(-1),
m_client(NULL),
m_stopped(false)
{
assert(port > 0U);
}
CTCPReaderWriterServer::~CTCPReaderWriterServer()
{
}
bool CTCPReaderWriterServer::start()
{
bool ret = open();
if (!ret) {
close();
return false;
}
Create();
Run();
return true;
}
int CTCPReaderWriterServer::read(unsigned char* buffer, unsigned int length, unsigned int secs)
{
assert(buffer != NULL);
assert(length > 0U);
if (m_client != NULL) {
int ret = m_client->read(buffer, length, secs);
if (ret < 0) {
CLog::logInfo("Lost TCP connection to port %u", m_port);
m_client->close();
delete m_client;
m_client = NULL;
open();
return 0;
}
return ret;
}
return 0;
}
bool CTCPReaderWriterServer::write(const unsigned char* buffer, unsigned int length)
{
assert(buffer != NULL);
assert(length > 0U);
if (m_client != NULL) {
bool ret = m_client->write(buffer, length);
if (!ret) {
CLog::logInfo("Lost TCP connection to port %u", m_port);
m_client->close();
delete m_client;
m_client = NULL;
open();
return false;
}
return true;
}
return true;
}
void* CTCPReaderWriterServer::Entry()
{
#ifndef DEBUG_DSTARGW
try {
#endif
while (!m_stopped) {
int ret = accept();
switch (ret) {
case -2:
break;
case -1:
break;
default:
CLog::logInfo("Incoming TCP connection to port %u", m_port);
m_client = new CTCPReaderWriterClient(ret);
close();
break;
}
Sleep(1000UL);
}
if (m_client != NULL) {
m_client->close();
delete m_client;
}
close();
#ifndef DEBUG_DSTARGW
}
catch (std::exception& e) {
std::string message(e.what());
CLog::logError("Exception raised in the TCP Reader-Writer Server thread - \"%s\"", message.c_str());
}
catch (...) {
CLog::logError("Unknown exception raised in the TCP Reader-Writer Server thread");
}
#endif
return NULL;
}
void CTCPReaderWriterServer::stop()
{
m_stopped = true;
Wait();
}
bool CTCPReaderWriterServer::open()
{
m_fd = ::socket(PF_INET, SOCK_STREAM, 0);
if (m_fd < 0) {
CLog::logError("Cannot create the TCP server socket, err=%d", errno);
return false;
}
struct sockaddr_in addr;
::memset(&addr, 0x00, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(m_port);
if (m_address.empty())
addr.sin_addr.s_addr = htonl(INADDR_ANY);
else
addr.sin_addr = lookup(m_address);
if (addr.sin_addr.s_addr == INADDR_NONE) {
CLog::logError("The address is invalid - %s", m_address.c_str());
close();
return false;
}
int reuse = 1;
if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) {
CLog::logError("Cannot set the TCP server socket option, err=%d", errno);
close();
return false;
}
if (::bind(m_fd, (sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) {
CLog::logError("Cannot bind the TCP server address, err=%d", errno);
close();
return false;
}
::listen(m_fd, 5);
return true;
}
int CTCPReaderWriterServer::accept()
{
if (m_fd == -1)
return -1;
// Check that the accept() won't block
fd_set readFds;
FD_ZERO(&readFds);
#if defined(__WINDOWS__)
FD_SET((unsigned int)m_fd, &readFds);
#else
FD_SET(m_fd, &readFds);
#endif
// Return after timeout
timeval tv;
tv.tv_sec = 0L;
tv.tv_usec = 0L;
int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv);
if (ret < 0) {
CLog::logError("Error returned from TCP server select, err=%d", errno);
return -2;
}
#if defined(__WINDOWS__)
if (!FD_ISSET((unsigned int)m_fd, &readFds))
return -1;
#else
if (!FD_ISSET(m_fd, &readFds))
return -1;
#endif
struct sockaddr_in addr;
#if defined(__WINDOWS__)
int len = sizeof(struct sockaddr_in);
#else
socklen_t len = sizeof(struct sockaddr_in);
#endif
ret = ::accept(m_fd, (sockaddr*)&addr, &len);
if (ret < 0) {
CLog::logError("Error returned from TCP server accept, err=%d", errno);
}
return ret;
}
void CTCPReaderWriterServer::close()
{
if (m_fd != -1) {
::close(m_fd);
m_fd = -1;
}
}
in_addr CTCPReaderWriterServer::lookup(const std::string& hostname) const
{
in_addr addrv4;
addrv4.s_addr = INADDR_NONE;
sockaddr_storage addr;
auto res = CNetUtils::lookupV4(hostname, addr);
if(res) {
addrv4 = TOIPV4(addr)->sin_addr;
}
return addrv4;
}

@ -0,0 +1,61 @@
/*
* Copyright (C) 2011 by Jonathan Naylor G4KLX
* Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include "TCPReaderWriterClient.h"
#include "Thread.h"
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string>
class CTCPReaderWriterServer : public CThread {
public:
CTCPReaderWriterServer(const std::string& address, unsigned int port);
virtual ~CTCPReaderWriterServer();
virtual bool start();
virtual bool write(const unsigned char* buffer, unsigned int length);
virtual int read(unsigned char* buffer, unsigned int length, unsigned int secs);
virtual void stop();
virtual void* Entry();
private:
std::string m_address;
unsigned short m_port;
int m_fd;
CTCPReaderWriterClient* m_client;
bool m_stopped;
bool open();
int accept();
void close();
in_addr lookup(const std::string& hostname) const;
};

@ -18,10 +18,12 @@
#include <cassert>
#include "Thread.h"
#include "Log.h"
using namespace std;
CThread::CThread()
CThread::CThread(const std::string& name) :
m_name(name)
{
}
@ -53,5 +55,7 @@ void CThread::Wait()
void CThread::EntryRunner(CThread * thread)
{
assert(thread != nullptr);
thread->Entry();
CLog::logTrace("Exiting %s thread", thread->m_name.c_str());
}

@ -23,10 +23,11 @@
#define Thread_H
#include <thread>
#include <string>
class CThread {
public:
CThread();
CThread(const std::string& name);
virtual ~CThread();
void Create();
void Run();
@ -39,6 +40,7 @@ protected:
private:
static void EntryRunner(CThread * thread);
std::string m_name;
std::thread m_thread;
};

@ -157,23 +157,6 @@ bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, c
TOIPV4(addr)->sin_port = htons(port);
return write(buffer, length, addr);
// sockaddr_in addr;
// ::memset(&addr, 0x00, sizeof(sockaddr_in));
// addr.sin_family = AF_INET;
// addr.sin_addr = address;
// addr.sin_port = htons(port);
// ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in));
// if (ret < 0) {
// CLog::logError("Error returned from sendto (port: %u), err: %s\n", m_port, strerror(errno));
// return false;
// }
// if (ret != ssize_t(length))
// return false;
// return true;
}
bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, const struct sockaddr_storage& addr)

@ -87,4 +87,14 @@ public:
return u;
}
// Ersatz for macro wxINT32_SWAP_ON_LE
template <typename T>
static T swap_endian_le(T u)
{
if(!is_big_endian())
return swap_endian(u);
return u;
}
};

@ -226,13 +226,13 @@ bool CAPRSHandler::isConnected() const
void CAPRSHandler::close()
{
m_thread->stop();
if(m_idFrameProvider != nullptr) {
m_idFrameProvider->close();
delete m_idFrameProvider;
m_idFrameProvider = nullptr;
}
m_thread->stop();
}
void CAPRSHandler::addReadAPRSCallback(IReadAPRSFrameCallback* cb)

@ -37,7 +37,7 @@ const unsigned int APRS_READ_TIMEOUT = 1U;
const unsigned int APRS_KEEP_ALIVE_TIMEOUT = 60U;
CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) :
CThread(),
CThread("APRS"),
m_username(callsign),
m_password(password),
m_ssid(callsign),
@ -65,7 +65,7 @@ m_clientName(FULL_PRODUCT_NAME)
}
CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter) :
CThread(),
CThread("APRS"),
m_username(callsign),
m_password(password),
m_ssid(callsign),

@ -30,9 +30,8 @@
#include "Utils.h"
#include "Log.h"
unsigned char* CAudioUnit::m_ambe = NULL;
unsigned int CAudioUnit::m_ambeLength = 0U;
std::map<std::string, CIndexRecord *> CAudioUnit::m_index;
CAMBEFileReader * CAudioUnit::m_ambeFilereader = nullptr;
TEXT_LANG CAudioUnit::m_language = TL_ENGLISH_UK;
@ -46,6 +45,11 @@ void CAudioUnit::initialise()
void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language)
{
if(m_ambeFilereader != nullptr) {
delete m_ambeFilereader;
m_ambeFilereader = nullptr;
}
m_language = language;
std::string ambeFileName;
@ -94,32 +98,22 @@ void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language)
break;
}
bool ret = readAMBE(dir, ambeFileName);
if (!ret) {
delete[] m_ambe;
m_ambe = NULL;
return;
}
ret = readIndex(dir, indxFileName);
if (!ret) {
delete[] m_ambe;
m_ambe = NULL;
m_ambeFilereader = new CAMBEFileReader(dir + "/" + indxFileName, dir + "/" + ambeFileName);
bool ret = m_ambeFilereader->read();
if(!ret) {
delete m_ambeFilereader;
m_ambeFilereader = nullptr;
}
}
void CAudioUnit::finalise()
{
for (std::map<std::string, CIndexRecord *>::iterator it = m_index.begin(); it != m_index.end(); ++it)
delete it->second;
delete[] m_ambe;
delete m_ambeFilereader;
}
CAudioUnit::CAudioUnit(IRepeaterCallback* handler, const std::string& callsign) :
m_handler(handler),
m_callsign(callsign),
m_encoder(),
m_status(AS_IDLE),
m_linkStatus(LS_NONE),
m_tempLinkStatus(LS_NONE),
@ -129,28 +123,24 @@ m_reflector(),
m_tempReflector(),
m_hasTemporary(false),
m_timer(1000U, REPLY_TIME),
m_data(NULL),
m_in(0U),
m_out(0U),
m_seqNo(0U) //,
m_data(),
m_out(0U)
//m_time()
{
assert(handler != NULL);
m_data = new CAMBEData*[MAX_FRAMES];
for (unsigned int i = 0U; i < MAX_FRAMES; i++)
m_data[i] = NULL;
}
CAudioUnit::~CAudioUnit()
{
delete[] m_data;
for (auto item : m_data) {
delete item;
}
m_data.clear();
}
void CAudioUnit::sendStatus()
{
if (m_ambe == NULL)
if (m_ambeFilereader == nullptr)
return;
if (m_status != AS_IDLE)
@ -190,7 +180,6 @@ void CAudioUnit::clock(unsigned int ms)
m_timer.stop();
m_out = 0U;
m_seqNo = 0U;
m_status = AS_TRANSMIT;
m_time = std::chrono::high_resolution_clock::now();
@ -199,29 +188,20 @@ void CAudioUnit::clock(unsigned int ms)
}
if (m_status == AS_TRANSMIT) {
std::chrono::high_resolution_clock::time_point hrctp = std::chrono::high_resolution_clock::now();
auto elapse = std::chrono::duration_cast<std::chrono::milliseconds>(hrctp - m_time);
unsigned int needed = elapse.count() / DSTAR_FRAME_TIME_MS;
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_time).count();
needed /= DSTAR_FRAME_TIME_MS;
while (m_out < needed) {
while (m_out < needed && m_out < m_data.size()) {
CAMBEData* data = m_data[m_out];
m_data[m_out] = NULL;
m_out++;
if (m_in == m_out)
data->setEnd(true);
// CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size());
m_handler->process(*data, DIR_INCOMING, AS_INFO);
}
delete data;
if (m_in == m_out) {
m_in = 0U;
m_out = 0U;
m_status = AS_IDLE;
m_timer.stop();
return;
}
if (m_out >= m_data.size()) {
m_out = 0U;
m_status = AS_IDLE;
m_timer.stop();
}
return;
@ -230,63 +210,14 @@ void CAudioUnit::clock(unsigned int ms)
void CAudioUnit::cancel()
{
for (unsigned int i = 0U; i < MAX_FRAMES; i++) {
if (m_data[i] != NULL) {
delete m_data[i];
m_data[i] = NULL;
}
}
CLog::logTrace("Audio Unit Cancel");
m_status = AS_IDLE;
m_out = 0U;
m_in = 0U;
m_timer.stop();
}
bool CAudioUnit::lookup(unsigned int id, const std::string &name)
{
CIndexRecord* info = m_index[name];
if (info == NULL) {
// CLog::logError("Cannot find the AMBE index for *%s*", name.c_str());
return false;
}
unsigned int start = info->getStart();
unsigned int length = info->getLength();
for (unsigned int i = 0U; i < length; i++) {
unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES;
CAMBEData* dataOut = new CAMBEData;
dataOut->setSeq(m_seqNo);
dataOut->setId(id);
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES);
// Insert sync bytes when the sequence number is zero, slow data otherwise
if (m_seqNo == 0U) {
memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
m_encoder.sync();
} else {
m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES);
}
dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES);
m_seqNo++;
if (m_seqNo == 21)
m_seqNo = 0;
m_data[m_in] = dataOut;
m_in++;
}
return true;
}
void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector)
void CAudioUnit::spellReflector(const std::string &reflector)
{
unsigned int length = reflector.size();
@ -294,11 +225,10 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector)
std::string c = reflector.substr(i, 1);
if (c.compare(" "))
lookup(id, c);
m_ambeFilereader->lookup(c, m_data);
}
char c = reflector.at(length - 1);
if (c == ' ')
return;
@ -306,197 +236,113 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector)
cstr.push_back(c);
if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS ||
m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) {
lookup(id, cstr);
m_ambeFilereader->lookup(cstr, m_data);
return;
}
switch (c) {
case 'A':
lookup(id, "alpha");
m_ambeFilereader->lookup("alpha", m_data);
break;
case 'B':
lookup(id, "bravo");
m_ambeFilereader->lookup("bravo", m_data);
break;
case 'C':
lookup(id, "charlie");
m_ambeFilereader->lookup("charlie", m_data);
break;
case 'D':
lookup(id, "delta");
m_ambeFilereader->lookup("delta", m_data);
break;
default:
lookup(id, cstr);
m_ambeFilereader->lookup(cstr, m_data);
break;
}
}
bool CAudioUnit::readAMBE(const std::string& dir, const std::string& name)
void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text)
{
std::string fileName = dir + "/" + name;
struct stat sbuf;
if (stat(fileName.c_str(), &sbuf)) {
CLog::logInfo("File %s not readable\n", fileName.c_str());
fileName.append("/data/");
fileName += name;
if (stat(fileName.c_str(), &sbuf)) {
CLog::logInfo("File %s not readable\n", fileName.c_str());
return false;
}
}
unsigned int fsize = sbuf.st_size;
FILE *file = fopen(fileName.c_str(), "rb");
if (NULL == file) {
CLog::logInfo("Cannot open %s for reading\n", fileName.c_str());
return false;
}
CLog::logInfo("Reading %s\n", fileName.c_str());
unsigned char buffer[VOICE_FRAME_LENGTH_BYTES];
size_t n = fread(buffer, sizeof(unsigned char), 4, file);
if (n != 4) {
CLog::logError("Unable to read the header from %s\n", fileName.c_str());
fclose(file);
return false;
}
CLog::logTrace("Audio Unit sendStatus");
if (memcmp(buffer, "AMBE", 4)) {
CLog::logError("Invalid header from %s\n", fileName.c_str());
fclose(file);
return false;
// do some clean up, delete old message
for (auto item : m_data) {
delete item;
}
m_data.clear();
// Length of the file minus the header
unsigned int length = fsize - 4U;
// Hold the file data plus silence at the end
m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES];
m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES;
// Add silence to the beginning of the buffer
unsigned char* p = m_ambe;
for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES)
memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
n = fread(p, 1, length, file);
if (n != length) {
CLog::logError("Unable to read the AMBE data from %s\n", fileName.c_str());
fclose(file);
delete[] m_ambe;
m_ambe = NULL;
return false;
}
// Create the message
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
fclose(file);
bool found;
return true;
}
bool CAudioUnit::readIndex(const std::string& dir, const std::string& name)
{
std::string fileName = dir + "/" + name;
struct stat sbuf;
if (stat(fileName.c_str(), &sbuf)) {
CLog::logInfo("File %s not readable\n", fileName.c_str());
fileName.append("/data/");
fileName += name;
if (stat(fileName.c_str(), &sbuf)) {
CLog::logInfo("File %s not readable\n", fileName.c_str());
return false;
}
}
FILE *file = fopen(fileName.c_str(), "r");
if (NULL == file) {
CLog::logInfo("Cannot open %s for reading\n", fileName.c_str());
return false;
switch (status) {
case LS_NONE:
m_ambeFilereader->lookup("notlinked", m_data);
break;
case LS_LINKED_CCS:
case LS_LINKED_DCS:
case LS_LINKED_DPLUS:
case LS_LINKED_DEXTRA:
case LS_LINKED_LOOPBACK:
found = m_ambeFilereader->lookup("linkedto", m_data);
if (!found) {
m_ambeFilereader->lookup("linked", m_data);
m_ambeFilereader->lookup("2", m_data);
}
spellReflector(reflector);
break;
default:
found = m_ambeFilereader->lookup("linkingto", m_data);
if (!found) {
m_ambeFilereader->lookup("linking", m_data);
m_ambeFilereader->lookup("2", m_data);
}
spellReflector(reflector);
break;
}
// Add a silence entry at the beginning
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
CLog::logInfo("Reading %s\n", fileName.c_str());
unsigned int id = CHeaderData::createId();
// RPT1 and RPT2 will be filled in later
CHeaderData header;
header.setMyCall1(m_callsign);
header.setMyCall2("INFO");
header.setYourCall("CQCQCQ ");
header.setId(id);
char line[128];
while (fgets(line, 128, file)) {
CSlowDataEncoder slowDataEncoder;
slowDataEncoder.setTextData(text);
unsigned int seqNo = 0U;
if (strlen(line) && '#'!=line[0]) {
const std::string space(" \t\r\n");
std::string name(strtok(line, space.c_str()));
std::string strt(strtok(NULL, space.c_str()));
std::string leng(strtok(NULL, space.c_str()));
// add the slow data, id, seq num etc ...
for(unsigned int i = 0U; i < m_data.size(); i++) {
m_data[i]->setId(id);
m_data[i]->setSeq(seqNo);
if (name.size() && strt.size() && leng.size()) {
unsigned long start = std::stoul(strt);
unsigned long length = std::stoul(leng);
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
m_data[i]->getData(buffer, DV_FRAME_LENGTH_BYTES);
if (start >= m_ambeLength || (start + length) >= m_ambeLength)
CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length);
else
m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length);
}
// Insert sync bytes when the sequence number is zero, slow data otherwise
if (seqNo == 0U) {
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
slowDataEncoder.sync();
} else {
slowDataEncoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES);
}
}
fclose(file);
return true;
}
void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text)
{
m_encoder.setTextData(text);
// Create the message
unsigned int id = CHeaderData::createId();
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
bool found;
switch (status) {
case LS_NONE:
lookup(id, "notlinked");
break;
case LS_LINKED_CCS:
case LS_LINKED_DCS:
case LS_LINKED_DPLUS:
case LS_LINKED_DEXTRA:
case LS_LINKED_LOOPBACK:
found = lookup(id, "linkedto");
if (!found) {
lookup(id, "linked");
lookup(id, "2");
}
spellReflector(id, reflector);
break;
default:
found = lookup(id, "linkingto");
if (!found) {
lookup(id, "linking");
lookup(id, "2");
}
spellReflector(id, reflector);
break;
}
m_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES);
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
seqNo++;
if(seqNo >= 21U) seqNo = 0U;
}
// RPT1 and RPT2 will be filled in later
CHeaderData header;
header.setMyCall1(m_callsign);
header.setMyCall2("INFO");
header.setYourCall("CQCQCQ ");
header.setId(id);
m_data[m_data.size() - 1]->setEnd(true);
m_handler->process(header, DIR_INCOMING, AS_INFO);
m_handler->process(header, DIR_INCOMING, AS_INFO);
}

@ -23,42 +23,14 @@
#include <string>
#include <map>
#include <chrono>
#include <vector>
#include "RepeaterCallback.h"
#include "SlowDataEncoder.h"
#include "AMBEData.h"
#include "Timer.h"
#include "Defs.h"
class CIndexRecord {
public:
CIndexRecord(const std::string& name, unsigned int start, unsigned int length) :
m_name(name),
m_start(start),
m_length(length)
{
}
std::string getName() const
{
return m_name;
}
unsigned int getStart() const
{
return m_start;
}
unsigned int getLength() const
{
return m_length;
}
private:
std::string m_name;
unsigned int m_start;
unsigned int m_length;
};
#include "AMBEFileReader.h"
enum AUDIO_STATUS {
AS_IDLE,
@ -87,14 +59,10 @@ public:
static void finalise();
private:
static std::map<std::string, CIndexRecord *> m_index;
static unsigned char* m_ambe;
static unsigned int m_ambeLength;
static TEXT_LANG m_language;
IRepeaterCallback* m_handler;
std::string m_callsign;
CSlowDataEncoder m_encoder;
AUDIO_STATUS m_status;
LINK_STATUS m_linkStatus;
LINK_STATUS m_tempLinkStatus;
@ -104,17 +72,12 @@ private:
std::string m_tempReflector;
bool m_hasTemporary;
CTimer m_timer;
CAMBEData** m_data;
unsigned int m_in;
std::vector<CAMBEData*> m_data;
static CAMBEFileReader* m_ambeFilereader;
unsigned int m_out;
unsigned int m_seqNo;
std::chrono::high_resolution_clock::time_point m_time;
bool lookup(unsigned int id, const std::string& name);
void spellReflector(unsigned int id, const std::string& reflector);
void spellReflector(const std::string& reflector);
void sendStatus(LINK_STATUS status, const std::string& reflector, const std::string& text);
static bool readAMBE(const std::string& dir, const std::string& name);
static bool readIndex(const std::string& dir, const std::string& name);
};

@ -34,6 +34,7 @@ const unsigned int OPENDSTAR_PORT = 20001U;
const unsigned int TCP_TIMEOUT = 10U;
CDPlusAuthenticator::CDPlusAuthenticator(const std::string& loginCallsign, const std::string& gatewayCallsign, const std::string& address, CCacheManager* cache) :
CThread("DPlus"),
m_loginCallsign(loginCallsign),
m_gatewayCallsign(gatewayCallsign),
m_address(address),
@ -106,7 +107,7 @@ void* CDPlusAuthenticator::Entry()
void CDPlusAuthenticator::stop()
{
m_killed = true;
CLog::logInfo("Stopping DPpus Authenticator");
Wait();
}

@ -0,0 +1,384 @@
/*
* Copyright (C) 2011-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 "DStarDefines.h"
#include "DRATSServer.h"
#include "Utils.h"
#include "Log.h"
#include <cassert>
#include <string.h>
#include <chrono>
// #define LOOPBACK
const unsigned int BUFFER_LENGTH = 30000U;
CDRATSServer::CDRATSServer(const std::string& address, unsigned int port, const std::string& callsign, IRepeaterCallback* handler) :
CThread("DRats"),
m_address(address),
m_port(port),
m_callsign(callsign),
m_handler(handler),
m_socket(NULL),
m_stopped(false),
m_readState(SS_FIRST),
m_readBuffer(NULL),
m_readLength(0U),
m_readPos(0U),
m_readEnd(false),
m_writeText(NULL),
m_writeState(SS_FIRST),
m_writeBuffer(NULL),
m_writeLength(0U)
{
assert(handler != NULL);
assert(port > 0U);
m_readBuffer = new unsigned char[BUFFER_LENGTH];
m_writeBuffer = new unsigned char[BUFFER_LENGTH];
m_writeText = new unsigned char[6U];
}
CDRATSServer::~CDRATSServer()
{
delete[] m_readBuffer;
delete[] m_writeBuffer;
delete[] m_writeText;
}
bool CDRATSServer::open()
{
m_socket = new CTCPReaderWriterServer(m_address, m_port);
bool ret = m_socket->start();
if (!ret) {
delete m_socket;
m_socket = NULL;
return false;
}
Create();
Run();
return true;
}
void CDRATSServer::writeHeader(const CHeaderData&)
{
m_writeState = SS_FIRST;
if (m_writeLength > 0U && m_socket != NULL) {
CUtils::dump("From RF", m_writeBuffer, m_writeLength);
m_socket->write(m_writeBuffer, m_writeLength);
}
m_writeLength = 0U;
}
void CDRATSServer::writeData(const CAMBEData& data)
{
// Sync data isn't sent on
if (data.isSync()) {
m_writeState = SS_FIRST;
return;
}
if (data.isEnd()) {
if (m_writeLength > 0U && m_socket != NULL) {
CUtils::dump("From RF", m_writeBuffer, m_writeLength);
m_socket->write(m_writeBuffer, m_writeLength);
}
m_writeLength = 0U;
return;
}
unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES];
unsigned int length = data.getData(buffer, DV_FRAME_MAX_LENGTH_BYTES);
if (length != DV_FRAME_LENGTH_BYTES)
return;
unsigned char byte1 = buffer[VOICE_FRAME_LENGTH_BYTES + 0U] ^ SCRAMBLER_BYTE1;
unsigned char byte2 = buffer[VOICE_FRAME_LENGTH_BYTES + 1U] ^ SCRAMBLER_BYTE2;
unsigned char byte3 = buffer[VOICE_FRAME_LENGTH_BYTES + 2U] ^ SCRAMBLER_BYTE3;
switch (m_writeState) {
case SS_FIRST:
m_writeText[0U] = byte1;
m_writeText[1U] = byte2;
m_writeText[2U] = byte3;
m_writeState = SS_SECOND;
return;
case SS_SECOND:
m_writeText[3U] = byte1;
m_writeText[4U] = byte2;
m_writeText[5U] = byte3;
m_writeState = SS_FIRST;
break;
}
if ((m_writeText[0U] & SLOW_DATA_TYPE_MASK) != SLOW_DATA_TYPE_GPS)
return;
length = m_writeText[0U] & 0x07; // Maximum value of 5
if (length > 5U)
length = 5U;
for (unsigned int i = 0U; i < length; i++) {
m_writeBuffer[m_writeLength++] = m_writeText[i + 1U];
// Check for [EOB] in the buffer to signal the end of the D-RATS data.
// To allow strstr() to run correctly
m_writeBuffer[m_writeLength] = 0x00U;
if (::strstr((char*)m_writeBuffer, "[EOB]") != NULL) {
if (m_socket != NULL) {
CUtils::dump("From RF", m_writeBuffer, m_writeLength);
m_socket->write(m_writeBuffer, m_writeLength);
}
m_writeLength = 0U;
}
}
}
void CDRATSServer::writeEnd()
{
if (m_writeLength > 0U && m_socket != NULL) {
CUtils::dump("From RF", m_writeBuffer, m_writeLength);
m_socket->write(m_writeBuffer, m_writeLength);
}
m_writeLength = 0U;
}
void* CDRATSServer::Entry()
{
CLog::logInfo("Starting the D-RATS Server thread for %s", m_callsign.c_str());
bool sending = false;
unsigned int id = 0U;
unsigned char seqNo = 0U;
unsigned int sent = 0U;
std::chrono::high_resolution_clock::time_point time;
#ifndef DEBUG_DSTARGW
try {
#endif
while (!m_stopped) {
serviceSocket();
if (m_readEnd && !sending) {
id = CHeaderData::createId();
// Write header
CHeaderData header;
header.setMyCall1(m_callsign);
header.setMyCall2("DATA");
header.setYourCall("CQCQCQ ");
header.setId(id);
#if defined(LOOPBACK)
writeHeader(header);
#else
m_handler->process(header, DIR_INCOMING, AS_DRATS);
#endif
m_readState = SS_FIRST;
m_readPos = 0U;
sending = true;
seqNo = 0U;
sent = 0U;
time = std::chrono::high_resolution_clock::now();
}
if (m_readEnd && sending) {
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - time).count() / DSTAR_FRAME_TIME_MS;
while (sent < needed && sending) {
// Write AMBE data
CAMBEData data;
data.setId(id);
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
// Insert sync bytes when the sequence number is zero, slow data otherwise
if (seqNo == 0U) {
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
m_readState = SS_FIRST;
} else {
if (m_readState == SS_FIRST) {
unsigned char readText[3U];
::memset(readText, 'f', 3U);
unsigned int length = m_readLength - m_readPos;
unsigned char bytes = 5U;
if (length < 5U)
bytes = length;
readText[0U] = SLOW_DATA_TYPE_GPS | bytes;
for (unsigned int i = 0U; i < 2U && m_readPos < m_readLength; i++)
readText[i + 1U] = m_readBuffer[m_readPos++];
readText[0U] ^= SCRAMBLER_BYTE1;
readText[1U] ^= SCRAMBLER_BYTE2;
readText[2U] ^= SCRAMBLER_BYTE3;
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES);
m_readState = SS_SECOND;
} else {
unsigned char readText[3U];
::memset(readText, 'f', 3U);
for (unsigned int i = 0U; i < 3U && m_readPos < m_readLength; i++)
readText[i] = m_readBuffer[m_readPos++];
readText[0U] ^= SCRAMBLER_BYTE1;
readText[1U] ^= SCRAMBLER_BYTE2;
readText[2U] ^= SCRAMBLER_BYTE3;
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES);
m_readState = SS_FIRST;
}
}
data.setSeq(seqNo);
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
sent++;
#if defined(LOOPBACK)
writeData(data);
#else
m_handler->process(data, DIR_INCOMING, AS_DRATS);
#endif
if (m_readPos == m_readLength) {
if (m_readState == SS_SECOND) {
seqNo++;
if (seqNo == 21U)
seqNo = 0U;
unsigned char readText[3U];
readText[0U] = 'f' ^ SCRAMBLER_BYTE1;
readText[1U] = 'f' ^ SCRAMBLER_BYTE2;
readText[2U] = 'f' ^ SCRAMBLER_BYTE3;
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES);
data.setSeq(seqNo);
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
sent++;
#if defined(LOOPBACK)
writeData(data);
#else
m_handler->process(data, DIR_INCOMING, AS_DRATS);
#endif
}
seqNo++;
if (seqNo == 21U)
seqNo = 0U;
if (seqNo == 0U)
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
else
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, NULL_SLOW_DATA_BYTES, DATA_FRAME_LENGTH_BYTES);
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
data.setSeq(seqNo);
data.setEnd(true);
sent++;
#if defined(LOOPBACK)
writeData(data);
#else
m_handler->process(data, DIR_INCOMING, AS_DRATS);
#endif
m_readLength = 0U;
m_readPos = 0U;
m_readEnd = false;
sending = false;
sent = 0U;
}
seqNo++;
if (seqNo == 21U)
seqNo = 0U;
}
}
// 50ms
Sleep(50UL);
}
if (m_socket != NULL)
m_socket->stop();
#ifndef DEBUG_DSTARGW
}
catch (std::exception& e) {
std::string message(e.what());
CLog::logError("Exception raised in the D-RATS Server thread - \"%s\""), message.c_str();
}
catch (...) {
CLog::logError("Unknown exception raised in the D-RATS Server thread");
}
#endif
CLog::logInfo("Stopping the D-RATS Server thread for %s", m_callsign.c_str());
return NULL;
}
void CDRATSServer::close()
{
m_stopped = true;
Wait();
}
void CDRATSServer::serviceSocket()
{
if (m_socket == NULL) {
m_readLength = 0U;
m_readPos = 0U;
m_readEnd = false;
return;
}
int len = m_socket->read(m_readBuffer + m_readLength, BUFFER_LENGTH - m_readLength, 0U);
if (len > 0) {
m_readLength += len;
if (!m_readEnd) {
// To allow strstr() to run correctly
m_readBuffer[m_readLength] = 0x00U;
if (::strstr((char*)m_readBuffer, "[EOB]") != NULL) {
CUtils::dump("To RF", m_readBuffer, m_readLength);
m_readEnd = true;
}
}
}
}

@ -0,0 +1,66 @@
/*
* Copyright (C) 2011,2012 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.
*/
#ifndef DRATSServer_H
#define DRATSServer_H
#include "TCPReaderWriterServer.h"
#include "RepeaterCallback.h"
#include "HeaderData.h"
#include "AMBEData.h"
#include "Defs.h"
#include "Thread.h"
#include <string>
class CDRATSServer : public CThread {
public:
CDRATSServer(const std::string& address, unsigned int port, const std::string& callsign, IRepeaterCallback* handler);
virtual ~CDRATSServer();
virtual bool open();
virtual void writeHeader(const CHeaderData& header);
virtual void writeData(const CAMBEData& data);
virtual void writeEnd();
virtual void close();
virtual void* Entry();
private:
std::string m_address;
unsigned int m_port;
std::string m_callsign;
IRepeaterCallback* m_handler;
CTCPReaderWriterServer* m_socket;
bool m_stopped;
SLOWDATA_STATE m_readState;
unsigned char* m_readBuffer;
unsigned int m_readLength;
unsigned int m_readPos;
bool m_readEnd;
unsigned char* m_writeText;
SLOWDATA_STATE m_writeState;
unsigned char* m_writeBuffer;
unsigned int m_writeLength;
void serviceSocket();
};
#endif

@ -32,6 +32,7 @@ const unsigned int LOOP_DELAY = 5UL;
const unsigned int LOOP_TICKS = 200U;
CIcomRepeaterProtocolHandler::CIcomRepeaterProtocolHandler(const std::string& address, unsigned int port, const std::string& icomAddress, unsigned int icomPort) :
CThread("Icom Protocol Handler"),
m_socket(address, port),
m_icomAddress(),
m_icomPort(icomPort),
@ -527,7 +528,7 @@ CAMBEData* CIcomRepeaterProtocolHandler::readBusyAMBE()
void CIcomRepeaterProtocolHandler::close()
{
m_killed = true;
CLog::logInfo("Stopping Icom Repeater protocol handler thread");
Wait();
}

@ -63,11 +63,8 @@ CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL;
CCallsignList* CRepeaterHandler::m_restrictList = NULL;
#ifdef USE_DRATS
CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unigned char band3) :
#else
CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) :
#endif
CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) :
m_index(0x00U),
m_rptCallsign(),
m_gwyCallsign(),
@ -137,9 +134,7 @@ m_wxAudio(NULL),
m_wxNeeded(false),
#endif
m_version(NULL),
#ifdef USE_DRATS
m_drats(NULL),
#endif
m_dtmf(),
m_pollTimer(1000U, 900U), // 15 minutes
#ifdef USE_CSS
@ -228,7 +223,7 @@ m_heardTimer(1000U, 0U, 100U) // 100ms
m_version = new CVersionUnit(this, callsign);
m_aprsUnit = new CAPRSUnit(this);
#ifdef USE_DRATS
if (dratsEnabled) {
m_drats = new CDRATSServer(m_localAddress, port, callsign, this);
bool ret = m_drats->open();
@ -237,7 +232,6 @@ m_heardTimer(1000U, 0U, 100U) // 100ms
m_drats = NULL;
}
}
#endif
}
CRepeaterHandler::~CRepeaterHandler()
@ -250,10 +244,8 @@ CRepeaterHandler::~CRepeaterHandler()
#endif
delete m_version;
#ifdef USE_DRATS
if (m_drats != NULL)
m_drats->close();
#endif
}
void CRepeaterHandler::initialise(unsigned int maxRepeaters)
@ -272,21 +264,14 @@ void CRepeaterHandler::setIndex(unsigned int index)
m_index = index;
}
#ifdef USE_DRATS
void CRepeaterHandler::add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3)
#else
void CRepeaterHandler::add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3)
#endif
{
assert(!callsign.empty());
assert(port > 0U);
assert(handler != NULL);
#ifdef USE_DRATS
CRepeaterHandler* repeater = new CRepeaterHandler(callsign, band, address, port, hwType, reflector, atStartup, reconnect, dratsEnabled, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3);
#else
CRepeaterHandler* repeater = new CRepeaterHandler(callsign, band, address, port, hwType, reflector, atStartup, reconnect, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3);
#endif
for (unsigned int i = 0U; i < m_maxRepeaters; i++) {
if (m_repeaters[i] == NULL) {
@ -438,11 +423,6 @@ void CRepeaterHandler::finalise()
m_repeaters[i] = NULL;
}
if (m_aprsWriter != NULL) {
m_aprsWriter->close();
delete m_aprsWriter;
}
delete[] m_repeaters;
}
@ -611,10 +591,8 @@ void CRepeaterHandler::processRepeater(CHeaderData& header)
// The Icom heard timer
m_heardTimer.stop();
#ifdef USE_DRATS
if (m_drats != NULL)
m_drats->writeHeader(header);
#endif
// Reset the statistics
m_frames = 0U;
@ -841,10 +819,9 @@ void CRepeaterHandler::processRepeater(CAMBEData& data)
// CCS gets everything
m_ccsHandler->writeAMBE(data);
#endif
#ifdef USE_DRATS
if (m_drats != NULL)
m_drats->writeData(data);
#endif
if (m_aprsWriter != NULL)
m_aprsWriter->writeData(m_rptCallsign, data);
@ -1577,10 +1554,10 @@ void CRepeaterHandler::clockInt(unsigned int ms)
if (m_repeaterId != 0x00U) {
if (m_text.empty())
sendHeard();
#ifdef USE_DRATS
if (m_drats != NULL)
m_drats->writeEnd();
#endif
sendStats();

@ -20,10 +20,6 @@
#ifndef RepeaterHandler_H
#define RepeaterHandler_H
//#define USE_CCS
//#define USE_STARNET
//#define USE_DRATS
#include "RepeaterProtocolHandler.h"
#include "DExtraProtocolHandler.h"
#include "DPlusProtocolHandler.h"
@ -39,9 +35,7 @@
#include "CacheManager.h"
#include "HeaderLogger.h"
#include "CallsignList.h"
#ifdef USE_DRATS
#include "DRATSServer.h"
#endif
#include "CCSCallback.h"
#include "VersionUnit.h"
#ifdef USE_CCS
@ -68,11 +62,7 @@ class CRepeaterHandler : public IRepeaterCallback, public IReflectorCallback, pu
public:
static void initialise(unsigned int maxRepeaters);
#ifdef USE_DRATS
static void add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3);
#else
static void add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3);
#endif
static void setLocalAddress(const std::string& address);
static void setG2HandlerPool(CG2ProtocolHandlerPool* handler);
@ -147,11 +137,8 @@ public:
virtual void readAPRSFrame(CAPRSFrame& frame);
protected:
#ifdef USE_DRATS
CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3);
#else
CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3);
#endif
virtual ~CRepeaterHandler();
void resolveUserInt(const std::string& user, const std::string& repeater, const std::string& gateway, const std::string& address);
@ -282,10 +269,8 @@ private:
// APRS to DPRS
CAPRSUnit* m_aprsUnit;
#ifdef USE_DRATS
// D-RATS handler
CDRATSServer* m_drats;
#endif
// DTMF commands
CDTMF m_dtmf;

@ -6,7 +6,7 @@ dgwremotecontrol: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a .
$(CC) $(CPPFLAGS) -o dgwremotecontrol $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
%.o : %.cpp
$(CC) -I../APRS -I../Common -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@
$(CC) -I../Common -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@
.PHONY clean:
clean:
@ -19,8 +19,6 @@ install: dgwremotecontrol
# copy and adjust config
@cp -fn example.cfg $(CFG_DIR)/dgwremotecontrol.cfg
@sed -i "s|path=/var/log/dgwremotecontrol/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwremotecontrol.cfg
@sed -i "s|data=/usr/local/share/dgwremotecontrol.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwremotecontrol.cfg
../BaseCommon/BaseCommon.a:
../DStarBase/DStarBase.a:

@ -0,0 +1,22 @@
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
dgwtexttransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a
$(CC) $(CPPFLAGS) -o dgwtexttransmit $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
%.o : %.cpp
$(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@
.PHONY clean:
clean:
$(RM) *.o *.d dgwtexttransmit
.PHONY install:
install: dgwtexttransmit
# copy executable
@cp -f dgwtexttransmit $(BIN_DIR)
../BaseCommon/BaseCommon.a:
../DStarBase/DStarBase.a:
../VersionInfo/GitVersion.h:

@ -0,0 +1,206 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <unordered_map>
#include <vector>
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <thread>
#include <chrono>
#include "SlowDataEncoder.h"
#include "DStarDefines.h"
#include "TextTransmit.h"
#include "ProgramArgs.h"
int main(int argc, const char* argv[])
{
std::string repeater, text, filename;
if (!parseCLIArgs(argc, argv, repeater, text, filename)) {
::fprintf(stderr, "dgwtexttransmit: invalid command line usage: dgwtexttransmit <repeater> -text <text>|-file <filename>, exiting\n");
return 1;
}
CHeaderData::initialise();
if (!filename.empty()) {
std::ifstream file;
file.open(filename);
if (!file.is_open()) {
::fprintf(stderr, "dgwtexttransmit: unable to open the file, exiting\n");
return 1;
}
std::getline(file, text);
file.close();
}
text.resize(20U, ' ');
CTextTransmit tt(repeater, text);
bool ret = tt.run();
return ret ? 0 : 1;
}
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::string& text, std::string& file)
{
repeater.clear();
text.clear();
file.clear();
if(argc < 4)
return false;
std::unordered_map<std::string, std::string> namedArgs;
std::vector<std::string> positionalArgs;
CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs);
if(namedArgs.count("text") == 0 && namedArgs.count("file") == 0)
return false;
if(positionalArgs.size() != 1)
return false;
repeater = boost::to_upper_copy(positionalArgs[0]);
boost::replace_all(repeater, "_", " ");
repeater.resize(LONG_CALLSIGN_LENGTH, ' ');
if(namedArgs.count("text") == 1) {
text.assign(namedArgs["text"]);
}
else if(namedArgs.count("file") == 1){
file.assign(namedArgs["file"]);
}
return true;
}
CTextTransmit::CTextTransmit(const std::string& callsign, const std::string& text) :
m_socket("", 0U),
m_callsign(callsign),
m_text(text)
{
}
CTextTransmit::~CTextTransmit()
{
}
bool CTextTransmit::run()
{
bool opened = m_socket.open();
if (!opened)
return false;
in_addr address = CUDPReaderWriter::lookup("127.0.0.1");
unsigned int id = CHeaderData::createId();
std::string callsignG = m_callsign.substr(0U, LONG_CALLSIGN_LENGTH - 1U);
callsignG.push_back('G');
CHeaderData header;
header.setId(id);
header.setMyCall1(m_callsign);
header.setMyCall2("INFO");
header.setRptCall1(callsignG);
header.setRptCall2(m_callsign);
header.setYourCall("CQCQCQ ");
header.setDestination(address, G2_DV_PORT);
sendHeader(header);
CSlowDataEncoder encoder;
encoder.setHeaderData(header);
encoder.setTextData(m_text);
CAMBEData data;
data.setDestination(address, G2_DV_PORT);
data.setId(id);
auto start = std::chrono::high_resolution_clock::now();
unsigned int out = 0U;
for (;;) {
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
needed /= DSTAR_FRAME_TIME_MS;
while (out < needed) {
data.setSeq(out);
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
// Insert sync bytes when the sequence number is zero, slow data otherwise
if (out == 0U) {
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
encoder.sync();
} else {
encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES);
}
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
sendData(data);
out++;
if (out == 21U) {
data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES);
data.setSeq(0U);
data.setEnd(true);
sendData(data);
m_socket.close();
return true;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10U));
}
}
bool CTextTransmit::sendHeader(const CHeaderData& header)
{
unsigned char buffer[60U];
unsigned int length = header.getG2Data(buffer, 60U, true);
for (unsigned int i = 0U; i < 2U; i++) {
bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort());
if (!res)
return false;
}
return true;
}
bool CTextTransmit::sendData(const CAMBEData& data)
{
unsigned char buffer[40U];
unsigned int length = data.getG2Data(buffer, 40U);
return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort());
}

@ -0,0 +1,47 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
#ifndef TextTransmit_H
#define TextTransmit_H
#include <string>
#include "UDPReaderWriter.h"
#include "HeaderData.h"
#include "AMBEData.h"
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::string& text, std::string& file);
class CTextTransmit {
public:
CTextTransmit(const std::string& callsign, const std::string& text);
~CTextTransmit();
bool run();
private:
CUDPReaderWriter m_socket;
std::string m_callsign;
std::string m_text;
bool sendHeader(const CHeaderData& header);
bool sendData(const CAMBEData& data);
};
#endif

@ -0,0 +1,206 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <string>
#include <iostream>
#include <cassert>
#include <sstream>
#include <csignal>
#ifdef DEBUG_DSTARGW
#include <boost/stacktrace.hpp>
#endif
#include "DGWTimeServerApp.h"
#include "Version.h"
#include "Log.h"
#include "Daemon.h"
#include "LogConsoleTarget.h"
#include "LogFileTarget.h"
CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr;
const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str());
const std::string BANNER_2 = "DGWTimeServer comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n";
const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n";
int main(int argc, char * argv[])
{
std::set_terminate(CDGWTimeServerApp::terminateHandler);
signal(SIGSEGV, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGILL, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGFPE, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGABRT, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGTERM, CDGWTimeServerApp::sigHandler);
signal(SIGINT, CDGWTimeServerApp::sigHandler);
if (2 != argc) {
printf("usage: %s path_to_config_file\n", argv[0]);
printf(" %s --version\n", argv[0]);
return 1;
}
std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3;
if(argv[1][0] == '-') {
return 0;
}
// Load config
std::string configfile(argv[1]);
CTimeServerConfig config(configfile);
if(!config.load())
return 1;
// Do daemon stuff
TDaemon daemon;
config.getDameon(daemon);
if (daemon.daemon) {
CLog::logInfo("Configured as a daemon, detaching ...");
auto res = CDaemon::daemonise(daemon.pidFile, daemon.user);
switch (res)
{
case DR_PARENT:
return 0;
case DR_CHILD:
break;
case DR_PIDFILE_FAILED:
case DR_FAILURE:
default:
CLog::logFatal("Failed to run as daemon");
CLog::finalise();
return 1;
}
}
// Setup Log
TLog logConf;
config.getLog(logConf);
CLog::finalise();
if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel));
if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate));
// Start the app
CDGWTimeServerApp app(&config);
if(app.init()) {
app.run();
return 0;
}
return 1;
}
CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) :
m_config(config)
{
g_app = this;
assert(config != nullptr);
}
CDGWTimeServerApp::~CDGWTimeServerApp()
{
delete m_thread;
}
bool CDGWTimeServerApp::init()
{
return createThread();
}
void CDGWTimeServerApp::run()
{
m_thread->Run();
m_thread->Wait();
}
bool CDGWTimeServerApp::createThread()
{
m_thread = new CTimeServerThread();
TTimeServer timeserver;
m_config->getTimeServer(timeserver);
std::vector<std::string> rptrs = { "", "", "", "" };
TRepeater repeater;
for(unsigned int i = 0u; i < m_config->getRepeaterCount(); i++) {
m_config->getRepeater(i, repeater);
rptrs[i].assign(repeater.band);
}
TPaths paths;
m_config->getPaths(paths);
m_thread = new CTimeServerThread();
bool ret = m_thread->setGateway(timeserver.callsign, rptrs[0], rptrs[1], rptrs[2], rptrs[3], timeserver.address, paths.data);
if(ret) {
m_thread->setAnnouncements(timeserver.language, timeserver.format, timeserver.interval);
}
return ret;
}
void CDGWTimeServerApp::sigHandler(int sig)
{
CLog::logInfo("Caught signal : %s, shutting down time server", strsignal(sig));
if(g_app != nullptr && g_app->m_thread != nullptr) {
g_app->m_thread->kill();
}
}
void CDGWTimeServerApp::sigHandlerFatal(int sig)
{
CLog::logFatal("Caught signal : %s", strsignal(sig));
fprintf(stderr, "Caught signal : %s\n", strsignal(sig));
#ifdef DEBUG_DSTARGW
std::stringstream stackTrace;
stackTrace << boost::stacktrace::stacktrace();
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str());
#endif
exit(3);
}
void CDGWTimeServerApp::terminateHandler()
{
#ifdef DEBUG_DSTARGW
std::stringstream stackTrace;
stackTrace << boost::stacktrace::stacktrace();
#endif
std::exception_ptr eptr;
eptr = std::current_exception();
try {
if (eptr != nullptr) {
std::rethrow_exception(eptr);
}
else {
CLog::logFatal("Unhandled unknown exception occured");
fprintf(stderr, "Unknown ex\n");
}
} catch(const std::exception& e) {
CLog::logFatal("Unhandled exception occured %s", e.what());
fprintf(stderr, "Unhandled ex %s\n", e.what());
}
#ifdef DEBUG_DSTARGW
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
#endif
exit(2);
}

@ -0,0 +1,46 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include "TimeServerDefs.h"
#include "TimeServerConfig.h"
#include "TimeServerThread.h"
class CDGWTimeServerApp
{
public:
CDGWTimeServerApp(const CTimeServerConfig * config);
~CDGWTimeServerApp();
bool init();
void run();
static void sigHandler(int sig);
static void sigHandlerFatal(int sig);
static void terminateHandler();
private:
bool createThread();
static CDGWTimeServerApp * g_app;
const CTimeServerConfig * m_config;
CTimeServerThread * m_thread;
};

@ -0,0 +1,36 @@
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a
$(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
%.o : %.cpp
$(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@
.PHONY clean:
clean:
$(RM) *.o *.d dgwtimeserver
.PHONY install:
install: dgwtimeserver
# copy executable
@cp -f dgwtimeserver $(BIN_DIR)
# copy and adjust config
@cp -fn example.cfg $(CFG_DIR)/dgwtimeserver.cfg
@sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
@sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
# SystemD service install
@cp -f ../debian/dgwtimeserver.service /lib/systemd/system/
@sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dgwtimeserver.service
systemctl enable dgwtimeserver.service
@systemctl daemon-reload
@echo "\n"
@echo "DGWTimeserver Install complete, edit $(CFG_DIR)dgwtimeserver.cfg and start the daemon with 'systemctl start dgwtimeserver.service'"
@echo "\n"
../BaseCommon/BaseCommon.a:
../DStarBase/DStarBase.a:
../VersionInfo/GitVersion.h:

@ -0,0 +1,11 @@
Time Server for DStarGateway. Installs as a systemd service.
If you did not change build settings, the configuration can be found in
```
/usr/local/etc/dgwtimeserver.cfg
```
After configuring enable and start the dgwtimeserver systemd service using :
```
sudo systemctl enable dgwtimeserver
sudo systemctl start dgwtimeserver
```

@ -0,0 +1,216 @@
/*
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <algorithm>
#include <boost/algorithm/string.hpp>
#include "TimeServerConfig.h"
#include "StringUtils.h"
CTimeServerConfig::CTimeServerConfig(const std::string &pathname) :
m_fileName(pathname),
m_repeaters()
{
}
CTimeServerConfig::~CTimeServerConfig()
{
for(auto repeater : m_repeaters) {
delete repeater;
}
m_repeaters.clear();
}
bool CTimeServerConfig::load()
{
bool ret = false;
CLog::logInfo("Loading Configuration from %s", m_fileName.c_str());
CConfig cfg(m_fileName);
ret = open(cfg);
if(ret) {
ret = loadTimeServer(cfg) && ret;
ret = loadRepeaters(cfg) && ret;
ret = loadDaemon(cfg) && ret;
ret = loadPaths(cfg) && ret;
ret = loadLog(cfg) && ret;
}
return ret;
}
bool CTimeServerConfig::open(CConfig & cfg)
{
try {
return cfg.load();
}
catch(...) {
CLog::logError("Can't read %s\n", m_fileName.c_str());
return false;
}
return true;
}
bool CTimeServerConfig::loadRepeaters(const CConfig & cfg)
{
m_repeaters.clear();
for(unsigned int i = 0; i < 4; i++) {
std::string section = CStringUtils::string_format("repeater_%d", i+ 1);
bool repeaterEnabled;
bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false);
if(!ret || !repeaterEnabled)
continue;
TRepeater * repeater = new TRepeater;
ret = cfg.getValue(section, "band", repeater->band, 1, 2, "") && ret;
if(!ret) {
delete repeater;
continue;
}
boost::to_upper(repeater->band);
bool alreadyConfigured = std::any_of(m_repeaters.begin(), m_repeaters.end(), [repeater](TRepeater * r) { return r->band == repeater->band;});
if(alreadyConfigured) {
CLog::logWarning("%s-%s repeater already configured, ignoring", m_timeServer.callsign.c_str(), repeater->band.c_str());
delete repeater;
continue;
}
m_repeaters.push_back(repeater);
}
return m_repeaters.size() > 0U;
}
bool CTimeServerConfig::loadTimeServer(const CConfig & cfg)
{
bool ret = cfg.getValue("timeserver", "callsign", m_timeServer.callsign, 3, 8, "");
boost::to_upper(m_timeServer.callsign);
ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret;
std::string format;
ret = cfg.getValue("timeserver", "format", format, "voice", {"voice", "text" }) && ret;
if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME;
else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME;
std::string lang;
ret = cfg.getValue("timeserver", "language", lang, "english_uk_1", {"english_uk_1", "english_uk_2", "english_us_1", "english_us_2", "deutsch_1", "deutsch_2", "francais", "nederlands", "svenska", "espanol", "norsk", "portugues"}) && ret;;
if (lang == "english_uk_1") m_timeServer.language = LANG_ENGLISH_UK_1;
else if(lang == "english_uk_2") m_timeServer.language = LANG_ENGLISH_UK_2;
else if(lang == "english_us_1") m_timeServer.language = LANG_ENGLISH_US_1;
else if(lang == "english_us_2") m_timeServer.language = LANG_ENGLISH_US_2;
else if(lang == "deutsch_1" ) m_timeServer.language = LANG_DEUTSCH_1;
else if(lang == "detusch_2" ) m_timeServer.language = LANG_DEUTSCH_2;
else if(lang == "francais" ) m_timeServer.language = LANG_FRANCAIS;
else if(lang == "nederlands" ) m_timeServer.language = LANG_NEDERLANDS;
else if(lang == "svenska" ) m_timeServer.language = LANG_SVENSKA;
else if(lang == "espanol" ) m_timeServer.language = LANG_ESPANOL;
else if(lang == "norsk" ) m_timeServer.language = LANG_NORSK;
else if(lang == "portugues" ) m_timeServer.language = LANG_PORTUGUES;
std::string interval;
ret = cfg.getValue("timeserver", "interval", interval, "30", {"15", "30", "60"}) && ret;
if(interval == "15") m_timeServer.interval = INTERVAL_15MINS;
else if(interval == "30") m_timeServer.interval = INTERVAL_30MINS;
else if(interval == "60") m_timeServer.interval = INTERVAL_60MINS;
return ret;
}
bool CTimeServerConfig::loadDaemon(const CConfig & cfg)
{
bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false);
ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret;
ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret;
return ret;
}
bool CTimeServerConfig::loadPaths(const CConfig & cfg)
{
bool ret = cfg.getValue("paths", "data", m_paths.data, 1, 1024, "");
return ret;
}
bool CTimeServerConfig::loadLog(const CConfig & cfg)
{
bool ret = cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/");
if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') {
m_log.logDir.push_back('/');
}
ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dgwtimeserver") && ret;
ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret;
std::string levelStr;
ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) {
if(levelStr == "trace") m_log.fileLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.fileLevel = LOG_INFO;
else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING;
else if(levelStr == "error") m_log.fileLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL;
else if(levelStr == "none") m_log.fileLevel = LOG_NONE;
}
ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) {
if(levelStr == "trace") m_log.displayLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.displayLevel = LOG_INFO;
else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING;
else if(levelStr == "error") m_log.displayLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL;
else if(levelStr == "none") m_log.displayLevel = LOG_NONE;
}
return ret;
}
void CTimeServerConfig::getTimeServer(TTimeServer& timeserver) const
{
timeserver = m_timeServer;
}
void CTimeServerConfig::getDameon(TDaemon& daemon) const
{
daemon = m_daemon;
}
unsigned int CTimeServerConfig::getRepeaterCount() const
{
return m_repeaters.size();
}
void CTimeServerConfig::getRepeater(unsigned int idx, TRepeater& repeater) const
{
repeater = *(m_repeaters[idx]);
}
void CTimeServerConfig::getPaths(TPaths& paths) const
{
paths = m_paths;
}
void CTimeServerConfig::getLog(TLog& log) const
{
log = m_log;
}

@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <string>
#include <vector>
#include "Config.h"
#include "TimeServerDefs.h"
#include "Log.h"
typedef struct {
std::string callsign;
std::string address;
FORMAT format;
LANGUAGE language;
INTERVAL interval;
} TTimeServer;
typedef struct {
std::string band;
} TRepeater;
typedef struct {
bool daemon;
std::string pidFile;
std::string user;
} TDaemon;
typedef struct {
std::string data;
} TPaths;
typedef struct {
std::string logDir;
LOG_SEVERITY displayLevel;
LOG_SEVERITY fileLevel;
std::string fileRoot;
bool fileRotate;
} TLog;
class CTimeServerConfig
{
public:
CTimeServerConfig(const std::string &pathname);
~CTimeServerConfig();
bool load();
void getTimeServer(TTimeServer& timeserver) const;
void getDameon(TDaemon& daemon) const;
unsigned int getRepeaterCount() const;
void getRepeater(unsigned int idx, TRepeater& repeater) const;
void getPaths(TPaths& paths) const;
void getLog(TLog& log) const;
private:
bool open(CConfig & cfg);
bool loadRepeaters(const CConfig & cfg);
bool loadTimeServer(const CConfig & cfg);
bool loadDaemon(const CConfig & cfg);
bool loadPaths(const CConfig & cfg);
bool loadLog(const CConfig & cfg);
std::string m_fileName;
std::vector<TRepeater *> m_repeaters;
TTimeServer m_timeServer;
TDaemon m_daemon;
TPaths m_paths;
TLog m_log;
};

@ -0,0 +1,48 @@
/*
* Copyright (C) 2012,2013,2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
const std::string APPLICATION_NAME("DStarGateway Time Server");
enum LANGUAGE {
LANG_ENGLISH_UK_1,
LANG_ENGLISH_UK_2,
LANG_ENGLISH_US_1,
LANG_ENGLISH_US_2,
LANG_DEUTSCH_1,
LANG_DEUTSCH_2,
LANG_FRANCAIS,
LANG_NEDERLANDS,
LANG_SVENSKA,
LANG_ESPANOL,
LANG_NORSK,
LANG_PORTUGUES
};
enum INTERVAL {
INTERVAL_15MINS,
INTERVAL_30MINS,
INTERVAL_60MINS
};
enum FORMAT {
FORMAT_VOICE_TIME,
FORMAT_TEXT_TIME
};

File diff suppressed because it is too large Load Diff

@ -0,0 +1,83 @@
/*
* Copyright (C) 2012,2013 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <unordered_map>
#include "SlowDataEncoder.h"
#include "UDPReaderWriter.h"
#include "TimeServerDefs.h"
#include "HeaderData.h"
#include "AMBEData.h"
#include "Thread.h"
#include "AMBEFileReader.h"
class CTimeServerThread : public CThread
{
public:
CTimeServerThread();
~CTimeServerThread();
bool setGateway(const std::string& callsign, const std::string& rpt1, const std::string& rpt2, const std::string& rpt3, const std::string& rpt4, const std::string& address, const std::string& dataPath);
void setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval);
void * Entry();
void kill();
private:
std::string m_callsign;
std::vector<std::string> m_repeaters;
std::string m_callsignG;
in_addr m_address;
std::string m_addressStr;
LANGUAGE m_language;
FORMAT m_format;
INTERVAL m_interval;
CSlowDataEncoder m_encoder;
std::vector<CAMBEData*> m_data;
bool m_killed;
std::string m_dataPath;
CAMBEFileReader * m_ambeFileReader;
void sendTime(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnGB1(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnGB2(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnUS1(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnUS2(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeDeDE1(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeDeDE2(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeFrFR(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeNlNL(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeSeSE(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEsES(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeNoNO(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimePtPT(unsigned int hour, unsigned int min);
bool send(const std::vector<std::string>& words, unsigned int hour, unsigned int min);
bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header);
bool sendData(CUDPReaderWriter& socket, const CAMBEData& data);
bool loadAMBE();
bool lookup(const std::string& id);
void buildAudio(const std::vector<std::string>& words, CSlowDataEncoder& slowDataEncoder);
};

@ -0,0 +1,41 @@
[TimeServer]
callsign= # call of the gateway to send time beacons without G letter
address= # address of the gateway, defaults to 127.0.0.1
format= # possible values are voice, text, defaults to voice. note that voice also sends text along.
language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1
interval= # valid values are 15, 30 and 60, defaults to 30
[Paths]
data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found
# Up to 4 repeaters can be enabled to transmit time beacons
[Repeater_1]
enabled=true # enable time beacons on this repeater
band=B # Module letter of the repeater
[Repeater_2]
enabled=false # enable time beacons on this repeater
band= # Module letter of the repeater
[Repeater_3]
enabled=false # enable time beacons on this repeater
band= # Module letter of the repeater
[Repeater_4]
enabled=false # enable time beacons on this repeater
band= # Module letter of the repeater
[Log]
path=/var/log/dstargateway/
fileRoot= # defaults to dgwtimeserver
fileRotate= # rotate log files daily, defaults to true
fileLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
displayLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd
# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might
# run the program using sysv or any other old school init system.
[Daemon]
daemon=false
pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice
user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root

@ -0,0 +1,23 @@
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
dgwvoicetransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../APRS/APRS.a ../BaseCommon/BaseCommon.a
$(CC) $(CPPFLAGS) -o dgwvoicetransmit $(OBJS) ../DStarBase/DStarBase.a ../APRS/APRS.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
%.o : %.cpp
$(CC) -I../BaseCommon -I../APRS -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@
.PHONY clean:
clean:
$(RM) *.o *.d dgwvoicetransmit
.PHONY install:
install: dgwvoicetransmit
# copy executable
@cp -f dgwvoicetransmit $(BIN_DIR)
../APRS/APRS.a:
../BaseCommon/BaseCommon.a:
../DStarBase/DStarBase.a:
../VersionInfo/GitVersion.h:

@ -0,0 +1,31 @@
DGWVoiceTransmit allow you to transmit .dvtool files through your repeater.
It must be run on the same machine where DStarGateway is running.
- [1. Usage Examples](#1-usage-examples)
- [1.1. Transmit one or more files](#11-transmit-one-or-more-files)
- [1.2. Override or insert Text Data](#12-override-or-insert-text-data)
- [1.3. Override or insert DPRS Data](#13-override-or-insert-dprs-data)
# 1. Usage Examples
Here are a few usage examples and explanation
## 1.1. Transmit one or more files
This wil transmit one file after the other tthoug repeater N0CALL B. You can either specify the call sign using spaces or underscores. When using spaces, make sure you put it into quotes.
```
dgwvoicetransmit N0CALL_B file1.dvtool file2.dvtool
dgwvoicetransmit "N0CALL B" file1.dvtool file2.dvtool
```
## 1.2. Override or insert Text Data
It is possible to ovveride the text slow data contained in the .dvtool file. For this, use -text flag. If no text data is present, the specified text data will be inserted into the transmsssion
```
dgwvoicetransmit N0CALL_B -text "Override text data" file1.dvtool
```
## 1.3. Override or insert DPRS Data
You cann also override or insert DPRS data. This will cause modern DStar Radio to display a popup with distance, bearing and other DPRS information etc.
You have to insert an "Icom compatible" DPRS frame. No validatin is made wether the frame is valid or not.
```
dgwvoicetransmit N0CALL_B -dprs "!4898.03N/00844.64Er/DPRS Comment" file1.dvtool
This can be combined with -text flag.
```

@ -0,0 +1,124 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 "DStarDefines.h"
#include "VoiceStore.h"
CVoiceStore::CVoiceStore(const std::vector<std::string>& filenames) :
m_filenames(),
m_header(NULL),
m_fileNumber(0U),
m_file()
{
for(auto f : filenames) {
m_filenames.push_back(f);
}
}
CVoiceStore::~CVoiceStore()
{
m_filenames.clear();
}
bool CVoiceStore::open()
{
std::string fileName = m_filenames.at(0U);
bool ret = m_file.open(fileName);
if (!ret)
return false;
DVTFR_TYPE type = m_file.read();
if (type != DVTFR_HEADER) {
m_file.close();
return false;
}
m_header = m_file.readHeader();
m_fileNumber = 0U;
return true;
}
CHeaderData* CVoiceStore::getHeader()
{
CHeaderData* header = m_header;
m_header = NULL;
return header;
}
CAMBEData* CVoiceStore::getAMBE()
{
DVTFR_TYPE type = m_file.read();
if (type == DVTFR_DATA) {
CAMBEData* ambe = m_file.readData();
if (ambe != NULL)
return ambe;
}
for (;;) {
m_file.close();
m_fileNumber++;
// The end of the last file?
if (m_fileNumber == m_filenames.size())
return NULL;
std::string filename = m_filenames.at(m_fileNumber);
bool ret = m_file.open(filename);
if (!ret)
return NULL;
// This should get the header
type = m_file.read();
if (type == DVTFR_HEADER) {
// Throw the header away
CHeaderData* header = m_file.readHeader();
delete header;
} else if (type == DVTFR_DATA) {
// Shouldn't usually happen
CAMBEData* ambe = m_file.readData();
if (ambe != NULL)
return ambe;
else
continue;
} else {
// !!!!
continue;
}
// This should get the first data record
type = m_file.read();
if (type == DVTFR_DATA) {
CAMBEData* ambe = m_file.readData();
if (ambe != NULL)
return ambe;
}
}
}
void CVoiceStore::close()
{
m_file.close();
}

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
#ifndef VoiceStore_H
#define VoiceStore_H
#include <vector>
#include <string>
#include "DVTOOLFileReader.h"
#include "HeaderData.h"
#include "AMBEData.h"
class CVoiceStore {
public:
CVoiceStore(const std::vector<std::string>& filenames);
~CVoiceStore();
bool open();
CHeaderData* getHeader();
CAMBEData* getAMBE();
void close();
private:
std::vector<std::string> m_filenames;
CHeaderData* m_header;
unsigned int m_fileNumber;
CDVTOOLFileReader m_file;
};
#endif

@ -0,0 +1,234 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <cassert>
#include <boost/algorithm/string.hpp>
#include <thread>
#include <chrono>
#include "ProgramArgs.h"
#include "DStarDefines.h"
#include "VoiceTransmit.h"
#include "SlowDataEncoder.h"
#include "APRSUtils.h"
#include "StringUtils.h"
int main(int argc, const char * argv[])
{
std::string repeater, text, dprs;
std::vector<std::string> filenames;
if (!parseCLIArgs(argc, argv, repeater, filenames, text, dprs)) {
::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit [-text text] [-dprs dprs] <repeater> <file1> <file2> ..., exiting\n");
return 1;
}
CHeaderData::initialise();
CVoiceStore store(filenames);
bool opened = store.open();
if (!opened) {
::fprintf(stderr, "dgwvoicetransmit: unable to open one of the files, exiting\n");
return 1;
}
CVoiceTransmit tt(repeater, &store, text, dprs);
bool ret = tt.run();
store.close();
return ret ? 0 : 1;
}
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector<std::string>& files, std::string& text, std::string& dprs)
{
if(argc < 3)
return false;
std::unordered_map<std::string, std::string> namedArgs;
std::vector<std::string> positionalArgs;
CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs);
if(positionalArgs.size() < 2U)
return false;
repeater.assign(boost::replace_all_copy(boost::to_upper_copy(positionalArgs[0]), "_", " "));
files.assign(positionalArgs.begin() + 1, positionalArgs.end());
if(namedArgs.count("text") > 0U) {
text.assign(namedArgs["text"]);
}
else {
text.assign("");
}
if(namedArgs.count("dprs") > 0U) {
std::string dprsRepeater(repeater);
CAPRSUtils::dstarCallsignToAPRS(dprsRepeater);
std::string dprsnoCrc = CStringUtils::string_format("%s>DPRS:%s\r", dprsRepeater.c_str(), namedArgs["dprs"].c_str());
dprs = CStringUtils::string_format("$$CRC%04X,%s", CAPRSUtils::calcGPSAIcomCRC(dprsnoCrc), dprsnoCrc.c_str());
}
else {
dprs.assign("");
}
return true;
}
CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text, const std::string& dprs) :
m_socket("", 0U),
m_callsign(callsign),
m_text(text),
m_dprs(dprs),
m_store(store)
{
assert(store != NULL);
}
CVoiceTransmit::~CVoiceTransmit()
{
}
bool CVoiceTransmit::run()
{
CSlowDataEncoder * slowData = nullptr;
bool opened = m_socket.open();
if (!opened)
return false;
in_addr address = CUDPReaderWriter::lookup("127.0.0.1");
unsigned int id = CHeaderData::createId();
std::string callsignG = m_callsign.substr(0U, LONG_CALLSIGN_LENGTH - 1U);
callsignG.push_back('G');
CHeaderData* header = m_store->getHeader();
if (header == NULL) {
m_socket.close();
return false;
}
header->setId(id);
header->setRptCall1(callsignG);
header->setRptCall2(m_callsign);
header->setDestination(address, G2_DV_PORT);
if(!m_text.empty()) {
slowData = new CSlowDataEncoder();
// slowData->setHeaderData(*header);
if(!m_text.empty()) slowData->setTextData(m_text);
if(!m_dprs.empty()) slowData->setGPSData(m_dprs);
}
sendHeader(header);
delete header;
auto start = std::chrono::high_resolution_clock::now();
unsigned int out = 0U;
unsigned int seqNo = 0U;
bool loop = true;
while (loop) {
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
needed /= DSTAR_FRAME_TIME_MS;
while (out < needed) {
CAMBEData* ambe = m_store->getAMBE();
if (ambe == NULL) {
CAMBEData data;
data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES);
data.setDestination(address, G2_DV_PORT);
data.setId(id);
data.setSeq(seqNo);
data.setEnd(true);
sendData(&data);
m_socket.close();
loop = false;
break;
}
if(slowData != nullptr) { // Override slowdata if specified so
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
ambe->getData(buffer, DV_FRAME_LENGTH_BYTES);
// Insert sync bytes when the sequence number is zero, slow data otherwise
if (seqNo == 0U) {
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
} else {
slowData->getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES);
}
ambe->setData(buffer, DV_FRAME_LENGTH_BYTES);
}
ambe->setSeq(seqNo);
ambe->setDestination(address, G2_DV_PORT);
ambe->setEnd(false);
ambe->setId(id);
sendData(ambe);
delete ambe;
out++;
seqNo++;
if(seqNo >= 21U) seqNo = 0U;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10U));
}
if(slowData != nullptr) delete slowData;
return true;
}
bool CVoiceTransmit::sendHeader(CHeaderData* header)
{
assert(header != NULL);
unsigned char buffer[60U];
unsigned int length = header->getG2Data(buffer, 60U, true);
for (unsigned int i = 0U; i < 2U; i++) {
bool res = m_socket.write(buffer, length, header->getYourAddress(), header->getYourPort());
if (!res)
return false;
}
return true;
}
bool CVoiceTransmit::sendData(CAMBEData* data)
{
assert(data != NULL);
unsigned char buffer[40U];
unsigned int length = data->getG2Data(buffer, 40U);
return m_socket.write(buffer, length, data->getYourAddress(), data->getYourPort());
}

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
#ifndef VoiceTransmit_H
#define VoiceTransmit_H
#include <string>
#include <vector>
#include "UDPReaderWriter.h"
#include "VoiceStore.h"
#include "HeaderData.h"
#include "AMBEData.h"
bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector<std::string>& vector, std::string& text, std::string& dprs);
class CVoiceTransmit {
public:
CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text, const std::string& dprs);
~CVoiceTransmit();
bool run();
private:
CUDPReaderWriter m_socket;
std::string m_callsign;
std::string m_text;
std::string m_dprs;
CVoiceStore* m_store;
bool sendHeader(CHeaderData* header);
bool sendData(CAMBEData* data);
};
#endif

@ -0,0 +1,179 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <sys/stat.h>
#include <cstdio>
#include <cstring>
#include "AMBEFileReader.h"
#include "DStarDefines.h"
#include "Log.h"
const unsigned int SILENCE_LENGTH = 10U;
CAMBEFileReader::CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile) :
m_indexFile(indexFile),
m_ambeFile(ambeFile),
m_ambe(nullptr),
m_ambeLength(0U),
m_index()
{
}
CAMBEFileReader::~CAMBEFileReader()
{
if(m_ambe != nullptr) {
delete[] m_ambe;
}
}
bool CAMBEFileReader::read()
{
bool ret = readAmbe() && readIndex();
return ret;
}
bool CAMBEFileReader::readAmbe()
{
struct stat sbuf;
if (stat(m_ambeFile.c_str(), &sbuf)) {
CLog::logWarning("File %s not readable\n", m_ambeFile.c_str());
return false;
}
unsigned int fsize = sbuf.st_size;
FILE *file = fopen(m_ambeFile.c_str(), "rb");
if (NULL == file) {
CLog::logError("Cannot open %s for reading\n", m_ambeFile.c_str());
return false;
}
CLog::logInfo("Reading %s\n", m_ambeFile.c_str());
unsigned char buffer[VOICE_FRAME_LENGTH_BYTES];
size_t n = fread(buffer, sizeof(unsigned char), 4, file);
if (n != 4) {
CLog::logError("Unable to read the header from %s\n", m_ambeFile.c_str());
fclose(file);
return false;
}
if (memcmp(buffer, "AMBE", 4)) {
CLog::logError("Invalid header from %s\n", m_ambeFile.c_str());
fclose(file);
return false;
}
// Length of the file minus the header
unsigned int length = fsize - 4U;
// Hold the file data plus silence at the end
m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES];
m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES;
// Add silence to the beginning of the buffer
unsigned char* p = m_ambe;
for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES)
memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
n = fread(p, 1, length, file);
if (n != length) {
CLog::logError("Unable to read the AMBE data from %s\n", m_ambeFile.c_str());
fclose(file);
delete[] m_ambe;
m_ambeLength = 0U;
m_ambe = nullptr;
return false;
}
fclose(file);
return true;
}
bool CAMBEFileReader::readIndex()
{
struct stat sbuf;
if (stat(m_indexFile.c_str(), &sbuf)) {
CLog::logError("File %s not readable\n", m_indexFile.c_str());
return false;
}
FILE *file = fopen(m_indexFile.c_str(), "r");
if (file == nullptr) {
CLog::logError("Cannot open %s for reading\n", m_indexFile.c_str());
return false;
}
// Add a silence entry at the beginning
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH);
CLog::logInfo("Reading %s\n", m_indexFile.c_str());
char line[128];
while (fgets(line, 128, file)) {
if (strlen(line) && '#'!=line[0]) {
const std::string space(" \t\r\n");
std::string name(strtok(line, space.c_str()));
std::string strt(strtok(NULL, space.c_str()));
std::string leng(strtok(NULL, space.c_str()));
if (name.size() && strt.size() && leng.size()) {
unsigned long start = std::stoul(strt);
unsigned long length = std::stoul(leng);
if (start >= m_ambeLength || (start + length) >= m_ambeLength)
CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length);
else
m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length);
}
}
}
fclose(file);
return true;
}
bool CAMBEFileReader::lookup(const std::string &id, std::vector<CAMBEData *>& data)
{
if(m_index.count(id) == 0U) {
CLog::logError("Cannot find the AMBE index for *%s*", id.c_str());
return false;
}
CIndexRecord* info = m_index[id];
unsigned int start = info->getStart();
unsigned int length = info->getLength();
for (unsigned int i = 0U; i < length; i++) {
unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES;
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES);
CAMBEData* dataOut = new CAMBEData;
dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES);
data.push_back(dataOut);
}
return true;
}

@ -0,0 +1,74 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#include "AMBEData.h"
class CIndexRecord {
public:
CIndexRecord(const std::string& name, unsigned int start, unsigned int length) :
m_name(name),
m_start(start),
m_length(length)
{
}
std::string getName() const
{
return m_name;
}
unsigned int getStart() const
{
return m_start;
}
unsigned int getLength() const
{
return m_length;
}
private:
std::string m_name;
unsigned int m_start;
unsigned int m_length;
};
class CAMBEFileReader
{
public:
CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile);
~CAMBEFileReader();
bool read();
bool lookup(const std::string &id, std::vector<CAMBEData *>& data);
private:
bool readAmbe();
bool readIndex();
std::string m_indexFile;
std::string m_ambeFile;
unsigned char* m_ambe;
unsigned int m_ambeLength;
std::unordered_map<std::string, CIndexRecord*> m_index;
};

@ -23,6 +23,7 @@
#include "DVTOOLFileReader.h"
#include "DStarDefines.h"
#include "Utils.h"
static const char DVTOOL_SIGNATURE[] = "DVTOOL";
static const unsigned int DVTOOL_SIGNATURE_LENGTH = 6U;
@ -48,7 +49,8 @@ m_records(0U),
m_type(DVTFR_NONE),
m_buffer(NULL),
m_length(0U),
m_seqNo(0U)
m_seqNo(0U),
m_closed(true)
{
m_buffer = new unsigned char[BUFFER_LENGTH];
}
@ -95,8 +97,9 @@ bool CDVTOOLFileReader::open(const std::string& fileName)
return false;
}
m_records = ntohl(uint32);
m_records = CUtils::swap_endian_le(uint32);
m_seqNo = 0U;
m_closed = false;
return true;
}
@ -108,7 +111,7 @@ DVTFR_TYPE CDVTOOLFileReader::read()
if (n != 1)
return DVTFR_NONE;
m_length = htons(uint16) - 15U;
m_length = CUtils::swap_endian_be(uint16) - 15U;
unsigned char bytes[FIXED_DATA_LENGTH];
n = fread(bytes, 1, DSVT_SIGNATURE_LENGTH, m_file);
@ -180,5 +183,8 @@ CAMBEData* CDVTOOLFileReader::readData()
void CDVTOOLFileReader::close()
{
fclose(m_file);
if(!m_closed)
fclose(m_file);
m_closed = true;
}

@ -58,4 +58,5 @@ private:
unsigned char* m_buffer;
unsigned int m_length;
unsigned char m_seqNo;
bool m_closed;
};

@ -22,6 +22,8 @@
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include "HeaderData.h"
#include "NetUtils.h"
#include "CCITTChecksum.h"
@ -30,7 +32,7 @@
void CHeaderData::initialise()
{
srand(time(NULL));
srand(time(NULL) + getpid());
}
void CHeaderData::finalise()

@ -25,6 +25,8 @@
#include <vector>
#include <signal.h>
#include <exception>
#include <cassert>
#include <unistd.h>
#ifdef DEBUG_DSTARGW
#include <boost/stacktrace.hpp>
#endif
@ -45,6 +47,12 @@
#include "LogConsoleTarget.h"
#include "APRSGPSDIdFrameProvider.h"
#include "APRSFixedIdFrameProvider.h"
#include "Daemon.h"
CDStarGatewayApp * CDStarGatewayApp::g_app = nullptr;
const std::string BANNER_1 = CStringUtils::string_format("%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str());
const std::string BANNER_2 = "DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n";
const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n";
#ifdef UNIT_TESTS
int fakemain(int argc, char *argv[])
@ -58,7 +66,8 @@ int main(int argc, char *argv[])
signal(SIGILL, CDStarGatewayApp::sigHandlerFatal);
signal(SIGFPE, CDStarGatewayApp::sigHandlerFatal);
signal(SIGABRT, CDStarGatewayApp::sigHandlerFatal);
// signal(SIGTERM, CDStarGatewayApp::sigHandler);
signal(SIGTERM, CDStarGatewayApp::sigHandler);
signal(SIGINT, CDStarGatewayApp::sigHandler);
setbuf(stdout, NULL);
if (2 != argc) {
@ -67,16 +76,56 @@ int main(int argc, char *argv[])
return 1;
}
std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3;
if ('-' == argv[1][0]) {
printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str());
printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n");
printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n");
return 0;
}
std::string cfgFile(argv[1]);
CDStarGatewayConfig * config = new CDStarGatewayConfig(std::string((argv[1])));
if(!config->load()) {
CLog::logFatal("Invalid configuration, aborting");
return false;
}
TDaemon daemon;
config->getDaemon(daemon);
if (daemon.daemon) {
CLog::logInfo("Configured as a daemon, detaching ...");
auto res = CDaemon::daemonise(daemon.pidFile, daemon.user);
switch (res)
{
case DR_PARENT:
return 0;
case DR_CHILD:
break;
case DR_PIDFILE_FAILED:
case DR_FAILURE:
default:
CLog::logFatal("Failed to run as daemon");
delete config;
CLog::finalise();
return 1;
}
}
CDStarGatewayApp gateway(cfgFile);
// Setup Log
TLog logConf;
config->getLog(logConf);
CLog::finalise();
if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel));
if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate));
//write banner in log file if we are dameon
if(daemon.daemon) {
CLog::logInfo(BANNER_1);
CLog::logInfo(BANNER_2);
CLog::logInfo(BANNER_3);
}
CDStarGatewayApp gateway(config);
if (!gateway.init()) {
return 1;
@ -84,11 +133,19 @@ int main(int argc, char *argv[])
gateway.run();
if(daemon.daemon) {
CDaemon::finalise();
}
return 0;
}
CDStarGatewayApp::CDStarGatewayApp(const std::string &configFile) : m_configFile(configFile), m_thread(NULL)
CDStarGatewayApp::CDStarGatewayApp(CDStarGatewayConfig * config) :
m_config(config),
m_thread(NULL)
{
assert(config != nullptr);
g_app = this;
}
CDStarGatewayApp::~CDStarGatewayApp()
@ -98,7 +155,6 @@ CDStarGatewayApp::~CDStarGatewayApp()
bool CDStarGatewayApp::init()
{
return createThread();
//2021-12-31 F4FXL This is ugly, if we failed to create the thread we do not clean up ... :(
}
void CDStarGatewayApp::run()
@ -111,30 +167,18 @@ void CDStarGatewayApp::run()
bool CDStarGatewayApp::createThread()
{
printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str());
printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n");
printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n");
CDStarGatewayConfig config(m_configFile);
if(!config.load()) {
CLog::logFatal("Invalid configuration, aborting");
return false;
}
// Setup Log
// Log
TLog log;
config.getLog(log);
CLog::finalise();
if(log.m_displayLevel != LOG_NONE) CLog::addTarget(new CLogConsoleTarget(log.m_displayLevel));
if(log.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(log.m_fileLevel, log.logDir, log.m_fileRotate));
m_config->getLog(log);
// Paths
Tpaths paths;
config.getPaths(paths);
m_config->getPaths(paths);
m_thread = new CDStarGatewayThread(log.logDir, paths.dataDir, "");
// Setup the gateway
TGateway gatewayConfig;
config.getGateway(gatewayConfig);
m_config->getGateway(gatewayConfig);
m_thread->setGateway(gatewayConfig.type, gatewayConfig.callsign, gatewayConfig.address);
m_thread->setLanguage(gatewayConfig.language);
m_thread->setLocation(gatewayConfig.latitude, gatewayConfig.longitude);
@ -142,12 +186,12 @@ bool CDStarGatewayApp::createThread()
#ifdef USE_GPSD
// Setup GPSD
TGPSD gpsdConfig;
config.getGPSD(gpsdConfig);
m_config->getGPSD(gpsdConfig);
#endif
// Setup APRS
TAPRS aprsConfig;
config.getAPRS(aprsConfig);
m_config->getAPRS(aprsConfig);
CAPRSHandler * aprsWriter = NULL;
if(aprsConfig.enabled && !aprsConfig.password.empty()) {
aprsWriter = new CAPRSHandler(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address);
@ -168,13 +212,45 @@ bool CDStarGatewayApp::createThread()
}
}
// Setup access control
TAccessControl accessControl;
m_config->getAccessControl(accessControl);
CCallsignList * whiteList = new CCallsignList(accessControl.whiteList);
if(whiteList->load() && whiteList->getCount() > 0U) {
m_thread->setWhiteList(whiteList);
}
else {
delete whiteList;
}
CCallsignList * blackList = new CCallsignList(accessControl.blackList);
if(blackList->load() && blackList->getCount() > 0U) {
m_thread->setBlackList(blackList);
}
else {
delete blackList;
}
CCallsignList * restrictList = new CCallsignList(accessControl.restrictList);
if(restrictList->load() && restrictList->getCount() > 0U) {
m_thread->setRestrictList(restrictList);
}
else {
delete restrictList;
}
// Drats
TDRats drats;
m_config->getDRats(drats);
// Setup the repeaters
bool ddEnabled = false;
bool atLeastOneRepeater = false;
CRepeaterProtocolHandlerFactory repeaterProtocolFactory;
for(unsigned int i = 0U; i < config.getRepeaterCount(); i++) {
for(unsigned int i = 0U; i < m_config->getRepeaterCount(); i++) {
TRepeater rptrConfig;
config.getRepeater(i, rptrConfig);
m_config->getRepeater(i, rptrConfig);
auto repeaterProtocolHandler = repeaterProtocolFactory.getRepeaterProtocolHandler(rptrConfig.hwType, gatewayConfig, rptrConfig.address, rptrConfig.port);
if(repeaterProtocolHandler == nullptr)
continue;
@ -187,6 +263,7 @@ bool CDStarGatewayApp::createThread()
rptrConfig.reflector,
rptrConfig.reflectorAtStartup,
rptrConfig.reflectorReconnect,
drats.enabled,
rptrConfig.frequency,
rptrConfig.offset,
rptrConfig.range,
@ -217,9 +294,9 @@ bool CDStarGatewayApp::createThread()
// Setup ircddb
auto ircddbVersionInfo = "linux_" + PRODUCT_NAME + "-" + VERSION;
std::vector<CIRCDDB *> clients;
for(unsigned int i=0; i < config.getIrcDDBCount(); i++) {
for(unsigned int i=0; i < m_config->getIrcDDBCount(); i++) {
TircDDB ircDDBConfig;
config.getIrcDDB(i, ircDDBConfig);
m_config->getIrcDDB(i, ircDDBConfig);
CLog::logInfo("ircDDB Network %d set to %s user: %s, Quadnet %d", i + 1,ircDDBConfig.hostname.c_str(), ircDDBConfig.username.c_str(), ircDDBConfig.isQuadNet);
CIRCDDB * ircDDB = new CIRCDDBClient(ircDDBConfig.hostname, 9007U, ircDDBConfig.username, ircDDBConfig.password, ircddbVersionInfo, gatewayConfig.address, ircDDBConfig.isQuadNet);
clients.push_back(ircDDB);
@ -236,31 +313,31 @@ bool CDStarGatewayApp::createThread()
// Setup Dextra
TDextra dextraConfig;
config.getDExtra(dextraConfig);
m_config->getDExtra(dextraConfig);
CLog::logInfo("DExtra enabled: %d, max. dongles: %u", int(dextraConfig.enabled), dextraConfig.maxDongles);
m_thread->setDExtra(dextraConfig.enabled, dextraConfig.maxDongles);
// Setup DCS
TDCS dcsConfig;
config.getDCS(dcsConfig);
m_config->getDCS(dcsConfig);
CLog::logInfo("DCS enabled: %d", int(dcsConfig.enabled));
m_thread->setDCS(dcsConfig.enabled);
// Setup DPlus
TDplus dplusConfig;
config.getDPlus(dplusConfig);
m_config->getDPlus(dplusConfig);
CLog::logInfo("D-Plus enabled: %d, max. dongles: %u, login: %s", int(dplusConfig.enabled), dplusConfig.maxDongles, dplusConfig.login.c_str());
m_thread->setDPlus(dplusConfig.enabled, dplusConfig.maxDongles, dplusConfig.login);
// Setup XLX
TXLX xlxConfig;
config.getXLX(xlxConfig);
m_config->getXLX(xlxConfig);
CLog::logInfo("XLX enabled: %d, Hosts file url: %s", int(xlxConfig.enabled), xlxConfig.url.c_str());
m_thread->setXLX(xlxConfig.enabled, xlxConfig.enabled ? CXLXHostsFileDownloader::download(xlxConfig.url) : "");
// Setup Remote
TRemote remoteConfig;
config.getRemote(remoteConfig);
m_config->getRemote(remoteConfig);
CLog::logInfo("Remote enabled: %d, port %u", int(remoteConfig.enabled), remoteConfig.port);
m_thread->setRemote(remoteConfig.enabled, remoteConfig.password, remoteConfig.port);
@ -276,20 +353,30 @@ bool CDStarGatewayApp::createThread()
return true;
}
void CDStarGatewayApp::sigHandler(int sig)
{
CLog::logInfo("Caught signal : %s, shutting down gateway", strsignal(sig));
if(g_app != nullptr && g_app->m_thread != nullptr) {
g_app->m_thread->kill();
}
}
void CDStarGatewayApp::sigHandlerFatal(int sig)
{
CLog::logFatal("Caught signal : %s", strsignal(sig));
fprintf(stderr, "Caught signal : %s\n", strsignal(sig));
#ifdef DEBUG_DSTARGW
std::stringstream stackTrace;
stackTrace << boost::stacktrace::stacktrace();
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str());
#endif
exit(1);
exit(3);
}
void CDStarGatewayApp::terminateHandler()
{
#ifdef DEBUG_DSTARGW
std::stringstream stackTrace;
stackTrace << boost::stacktrace::stacktrace();
@ -304,13 +391,15 @@ void CDStarGatewayApp::terminateHandler()
}
else {
CLog::logFatal("Unhandled unknown exception occured");
fprintf(stderr, "Unknown ex\n");
}
} catch(const std::exception& e) {
CLog::logFatal("Unhandled exception occured %s", e.what());
fprintf(stderr, "Unhandled ex %s\n", e.what());
}
#ifdef DEBUG_DSTARGW
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
#endif
exit(1);
exit(2);
}

@ -20,23 +20,24 @@
#pragma once
#include "DStarGatewayThread.h"
void __sigHandler(int sig);
#include "DStarGatewayConfig.h"
class CDStarGatewayApp
{
private:
std::string m_configFile;
CDStarGatewayThread *m_thread;
CDStarGatewayConfig * m_config;
CDStarGatewayThread * m_thread;
bool createThread();
static CDStarGatewayApp * g_app;
public:
CDStarGatewayApp(const std::string &configFile);
CDStarGatewayApp(CDStarGatewayConfig * config);
~CDStarGatewayApp();
bool init();
void run();
static void sigHandlerFatal(int sig);
static void sigHandler(int sig);
static void terminateHandler();
};

@ -55,6 +55,9 @@ bool CDStarGatewayConfig::load()
#ifdef USE_GPSD
ret = loadGPSD(cfg) && ret;
#endif
ret = loadDaemon(cfg) && ret;
ret = loadAccessControl(cfg) && ret;
ret = loadDRats(cfg) && ret;
}
if(ret) {
@ -69,6 +72,14 @@ bool CDStarGatewayConfig::load()
return ret;
}
bool CDStarGatewayConfig::loadDaemon(const CConfig & cfg)
{
bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false);
ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret;
ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret;
return ret;
}
bool CDStarGatewayConfig::loadXLX(const CConfig & cfg)
{
bool ret = cfg.getValue("xlx", "enabled", m_xlx.enabled, true);
@ -144,34 +155,32 @@ bool CDStarGatewayConfig::loadLog(const CConfig & cfg)
m_log.logDir.push_back('/');
}
ret = cfg.getValue("log", "fileRoot", m_log.m_fileRoot, 0, 64, "dstargateway") && ret;
ret = cfg.getValue("log", "fileRotate", m_log.m_fileRotate, true) && ret;
ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dstargateway") && ret;
ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret;
std::string levelStr;
ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) {
if(levelStr == "trace") m_log.m_fileLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.m_fileLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.m_fileLevel = LOG_INFO;
else if(levelStr == "warning") m_log.m_fileLevel = LOG_WARNING;
else if(levelStr == "error") m_log.m_fileLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.m_fileLevel = LOG_FATAL;
else if(levelStr == "none") m_log.m_fileLevel = LOG_NONE;
if(levelStr == "trace") m_log.fileLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.fileLevel = LOG_INFO;
else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING;
else if(levelStr == "error") m_log.fileLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL;
else if(levelStr == "none") m_log.fileLevel = LOG_NONE;
}
ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) {
if(levelStr == "trace") m_log.m_displayLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.m_displayLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.m_displayLevel = LOG_INFO;
else if(levelStr == "warning") m_log.m_displayLevel = LOG_WARNING;
else if(levelStr == "error") m_log.m_displayLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.m_displayLevel = LOG_FATAL;
else if(levelStr == "none") m_log.m_displayLevel = LOG_NONE;
if(levelStr == "trace") m_log.displayLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.displayLevel = LOG_INFO;
else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING;
else if(levelStr == "error") m_log.displayLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL;
else if(levelStr == "none") m_log.displayLevel = LOG_NONE;
}
//TODO 20211226 check if directories are accessible
return ret;
}
@ -190,6 +199,7 @@ bool CDStarGatewayConfig::loadPaths(const CConfig & cfg)
bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg)
{
m_repeaters.clear();
for(unsigned int i = 0; i < 4; i++) {
std::string section = CStringUtils::string_format("repeater_%d", i+ 1);
bool repeaterEnabled;
@ -237,9 +247,9 @@ bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg)
ret = cfg.getValue(section, "latitude", repeater->latitude, -90.0, 90.0, m_gateway.latitude) && ret;
ret = cfg.getValue(section, "longitude", repeater->longitude, -180.0, 180.0, m_gateway.longitude) && ret;
ret = cfg.getValue(section, "agl", repeater->agl, 0, 1000.0, 0.0) && ret;
ret = cfg.getValue(section, "description1", m_gateway.description1, 0, 1024, "") && ret;
ret = cfg.getValue(section, "description2", m_gateway.description2, 0, 1024, "") && ret;
ret = cfg.getValue(section, "url", m_gateway.url, 0, 1024, "") && ret;;
ret = cfg.getValue(section, "description1", repeater->description1, 0, 1024, m_gateway.description1) && ret;
ret = cfg.getValue(section, "description2", repeater->description2, 0, 1024, m_gateway.description2) && ret;
ret = cfg.getValue(section, "url", repeater->url, 0, 1024, m_gateway.url) && ret;;
ret = cfg.getValue(section, "band1", repeater->band1, 0, 255, 0) && ret;
ret = cfg.getValue(section, "band2", repeater->band2, 0, 255, 0) && ret;
ret = cfg.getValue(section, "band3", repeater->band3, 0, 255, 0) && ret;
@ -342,6 +352,22 @@ bool CDStarGatewayConfig::loadGPSD(const CConfig & cfg)
}
#endif
bool CDStarGatewayConfig::loadAccessControl(const CConfig & cfg)
{
bool ret = cfg.getValue("AccessControl", "whiteList", m_accessControl.whiteList, 0U, 2048U, "");
ret = cfg.getValue("AccessControl", "blackList", m_accessControl.blackList, 0U, 2048U, "") && ret;
ret = cfg.getValue("AccessControl", "restrictList", m_accessControl.restrictList, 0U, 2048U, "") && ret;
return ret;
}
bool CDStarGatewayConfig::loadDRats(const CConfig & cfg)
{
bool ret = cfg.getValue("DRats", "enabled", m_drats.enabled, false);
return ret;
}
bool CDStarGatewayConfig::open(CConfig & cfg)
{
try {
@ -438,3 +464,18 @@ void CDStarGatewayConfig::getGPSD(TGPSD & gpsd) const
gpsd = m_gpsd;
}
#endif
void CDStarGatewayConfig::getDaemon(TDaemon & gen) const
{
gen = m_daemon;
}
void CDStarGatewayConfig::getAccessControl(TAccessControl & accessControl) const
{
accessControl = m_accessControl;
}
void CDStarGatewayConfig::getDRats(TDRats & drats) const
{
drats = m_drats;
}

@ -25,6 +25,12 @@
#include "Config.h"
#include "LogSeverity.h"
typedef struct {
bool daemon;
std::string pidFile;
std::string user;
} TDaemon;
typedef struct {
GATEWAY_TYPE type;
std::string callsign;
@ -50,9 +56,7 @@ typedef struct {
HW_TYPE hwType;
bool reflectorAtStartup;
RECONNECT reflectorReconnect;
#ifdef USE_DRATS
bool dRatsEnabled;
#endif
double frequency;
double offset;
double range;
@ -80,10 +84,10 @@ typedef struct {
typedef struct {
std::string logDir;
LOG_SEVERITY m_displayLevel;
LOG_SEVERITY m_fileLevel;
std::string m_fileRoot;
bool m_fileRotate;
LOG_SEVERITY displayLevel;
LOG_SEVERITY fileLevel;
std::string fileRoot;
bool fileRotate;
} TLog;
typedef struct {
@ -109,6 +113,10 @@ typedef struct {
bool enabled;
} TDCS;
typedef struct {
bool enabled;
} TDRats;
typedef struct {
bool enabled;
std::string url;
@ -128,6 +136,12 @@ typedef struct {
} TGPSD;
#endif
typedef struct {
std::string whiteList;
std::string blackList;
std::string restrictList;
} TAccessControl;
class CDStarGatewayConfig {
public:
CDStarGatewayConfig(const std::string &pathname);
@ -150,6 +164,9 @@ public:
#ifdef USE_GPSD
void getGPSD(TGPSD & gpsd) const;
#endif
void getDaemon(TDaemon & gen) const;
void getAccessControl(TAccessControl & accessControl) const;
void getDRats(TDRats & drats) const;
private:
bool open(CConfig & cfg);
@ -167,6 +184,9 @@ private:
#ifdef USE_GPSD
bool loadGPSD(const CConfig & cfg);
#endif
bool loadDaemon(const CConfig & cfg);
bool loadAccessControl(const CConfig & cfg);
bool loadDRats(const CConfig & cfg);
std::string m_fileName;
TGateway m_gateway;
@ -181,6 +201,10 @@ private:
#ifdef USE_GPSD
TGPSD m_gpsd;
#endif
TDaemon m_daemon;
TAccessControl m_accessControl;
TDRats m_drats;
std::vector<TRepeater *> m_repeaters;
std::vector<TircDDB *> m_ircDDB;
};

@ -21,7 +21,7 @@
#include <string>
const std::string APPLICATION_NAME("ircDDB Gateway");
const std::string APPLICATION_NAME("dstargateway");
const std::string CONFIG_FILE_NAME("dstargateway");

@ -60,7 +60,7 @@ const std::string LOOPBACK_ADDRESS("127.0.0.1");
const unsigned int REMOTE_DUMMY_PORT = 65016U;
CDStarGatewayThread::CDStarGatewayThread(const std::string& logDir, const std::string& dataDir, const std::string& name) :
CThread(),
CThread("Gateway"),
m_logDir(logDir),
m_dataDir(dataDir),
m_name(name),
@ -131,19 +131,7 @@ m_restrictList(NULL)
CDStarGatewayThread::~CDStarGatewayThread()
{
CHeaderData::finalise();
CG2Handler::finalise();
CDExtraHandler::finalise();
CDPlusHandler::finalise();
CDCSHandler::finalise();
CRepeaterHandler::finalise();
#ifdef USE_STARNET
CStarNetHandler::finalise();
#endif
#ifdef USE_CCS
CCCSHandler::finalise();
#endif
CAudioUnit::finalise();
}
void* CDStarGatewayThread::Entry()
@ -481,11 +469,30 @@ void* CDStarGatewayThread::Entry()
delete m_remote;
}
if(m_aprsWriter != nullptr) {
m_aprsWriter->close();
delete m_aprsWriter;
}
if (headerLogger != NULL) {
headerLogger->close();
delete headerLogger;
}
CHeaderData::finalise();
CG2Handler::finalise();
CDExtraHandler::finalise();
CDPlusHandler::finalise();
CDCSHandler::finalise();
CRepeaterHandler::finalise();
#ifdef USE_STARNET
CStarNetHandler::finalise();
#endif
#ifdef USE_CCS
CCCSHandler::finalise();
#endif
CAudioUnit::finalise();
return NULL;
}
@ -504,15 +511,10 @@ void CDStarGatewayThread::setGateway(GATEWAY_TYPE gatewayType, const std::string
m_gatewayAddress = gatewayAddress;
}
#ifdef USE_DRATS
void CDStarGatewayThread::addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3)
{
CRepeaterHandler::add(callsign, band, address, port, hwType, reflector, atStartup, reconnect, dratsEnabled, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3);
#else
void CDStarGatewayThread::addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3)
{
CRepeaterHandler::add(callsign, band, address, port, hwType, reflector, atStartup, reconnect, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3);
#endif
std::string repeater = callsign;
repeater.resize(LONG_CALLSIGN_LENGTH - 1U);

@ -44,11 +44,8 @@ public:
virtual ~CDStarGatewayThread();
virtual void setGateway(GATEWAY_TYPE type, const std::string& callsign, const std::string& address);
#ifdef USE_DRATS
virtual void addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1 = 0x00U, unsigned char band2 = 0x00U, unsigned char band3 = 0x00U);
#else
virtual void addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1 = 0x00U, unsigned char band2 = 0x00U, unsigned char band3 = 0x00U);
#endif
virtual void addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1 = 0x00U, unsigned char band2 = 0x00U, unsigned char band3 = 0x00U);
#ifdef USE_STARNET
#if defined(DEXTRA_LINK) || defined(DCS_LINK)
virtual void addStarNet(const std::string& callsign, const std::string& logoff, const std::string& repeater, const std::string& infoText, const std::string& permanent, unsigned int userTimeout, unsigned int groupTimeout, STARNET_CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch, const std::string& reflector);

@ -22,6 +22,15 @@ install: dstargateway
@sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dstargateway.cfg
@sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dstargateway.cfg
# SystemD service install
@cp -f ../debian/dstargateway.service /lib/systemd/system/
@sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dstargateway.service
systemctl enable dstargateway.service
@systemctl daemon-reload
@echo "\n"
@echo "DStarGateway Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dstargateway.service'"
@echo "\n"
../APRS/APRS.a:
../Common/Common.a:
../DStarBase/DStarBase.a:

@ -2,7 +2,6 @@
# The order of the sections or key/values pairs inside the sections does not matter nor does casing.
# Boolean values can be set using true, false, 1 or 0
# Floating point values must use . (point) as decimal separator
[Gateway]
type= # repeater, hotspot, dongle. Defaults to repeater
callsign=
@ -22,7 +21,7 @@ language= # valid values: english_uk, deutsch, dansk, francais, ita
[ircddb_1]
enabled=true
hostname=ircv4.openquad.net
username= # The ircDDB username default to the value defined for gateway callsign.
username= # The ircDDB username defaults to the value defined for gateway callsign.
password=
[ircddb_2]
@ -173,8 +172,24 @@ enabled=true # There is no reason to disable this
enabled=true
hostfileUrl=http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster
[DRats]
enabled=false # Defaults to false. The program need to be compiled with DRats support for DRats to be actually enabled
[Remote]
enabled=false
port=4242
password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field
# Should only be used with respect to your local regulation! Many countries prohibit setting up private repeaters !
[AccessControl]
whiteList= # Only affects network
blackList= # Only affects network
restrictList= # Only affects RF, call signs present in this list are now allowed to change reflector or unlink the repeater
# The Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd
# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might
# run the program using sysv or any other old school init system.
[Daemon]
daemon=false
pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice
user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root

@ -38,9 +38,8 @@ export CPPFLAGS+= -DUSE_GPSD
export LDFLAGS+= -lgps
endif
.PHONY: all
all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol #tests
all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests
APRS/APRS.a: BaseCommon/BaseCommon.a FORCE
$(MAKE) -C APRS
@ -60,6 +59,15 @@ DStarGateway/dstargateway : VersionInfo/GitVersion.h $(OBJS) APRS/APRS.a Common
DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE
$(MAKE) -C DGWRemoteControl
DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE
$(MAKE) -C DGWTextTransmit
DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE
$(MAKE) -C DGWTimeServer
DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE
$(MAKE) -C DGWVoiceTransmit
IRCDDB/IRCDDB.a: VersionInfo/GitVersion.h BaseCommon/BaseCommon.a FORCE
$(MAKE) -C IRCDDB
@ -70,8 +78,12 @@ VersionInfo/GitVersion.h: FORCE
clean:
$(MAKE) -C Tests clean
$(MAKE) -C APRS clean
$(MAKE) -C Common clean
$(MAKE) -C BaseCommon clean
$(MAKE) -C Common clean
$(MAKE) -C DGWRemoteControl clean
$(MAKE) -C DGWTextTransmit clean
$(MAKE) -C DGWTimeServer clean
$(MAKE) -C DGWVoiceTransmit clean
$(MAKE) -C DStarBase clean
$(MAKE) -C DStarGateway clean
$(MAKE) -C IRCDDB clean
@ -90,8 +102,12 @@ newhostfiles :
.PHONY: install
install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol
# install remote control
# install accessories
$(MAKE) -C DGWRemoteControl install
$(MAKE) -C DGWTextTransmit install
$(MAKE) -C DGWTimeServer install
$(MAKE) -C DGWVoiceTransmit install
# create user for daemon
@useradd --user-group -M --system dstar --shell /bin/false || true
@ -106,18 +122,9 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol
$(MAKE) -C Data install
@chown -R dstar:dstar $(DATA_DIR)
#install executables
# install services executables
$(MAKE) -C DStarGateway install
# SystemD service install
@cp -f debian/dstargateway.service /lib/systemd/system/
@sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dstargateway.service
systemctl enable dstargateway.service
@systemctl daemon-reload
@echo "\n\n"
@echo "Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dstargateway.service'"
@echo "\n\n"
.PHONY: uninstall
uninstall :
systemctl stop dstargateway.service || true

@ -5,7 +5,7 @@
- [2.3. Thanks](#23-thanks)
- [2.4. Features](#24-features)
- [2.4.1. Features that where left out :](#241-features-that-where-left-out-)
- [2.4.2. Additional Features :](#242-additional-features-)
- [2.4.2. Additional Features compared to ircddbGateway:](#242-additional-features-compared-to-ircddbgateway)
- [3. Building and installing](#3-building-and-installing)
- [3.1. Initial setup](#31-initial-setup)
- [3.2. Get latest stable version (recommended)](#32-get-latest-stable-version-recommended)
@ -16,15 +16,17 @@
- [3.5.0.2. Debug Build](#3502-debug-build)
- [3.6. Installing](#36-installing)
- [3.7. Configuring](#37-configuring)
- [3.8. Updating host files](#38-updating-host-files)
- [4. Contributing](#4-contributing)
- [4.1. Work Flow](#41-work-flow)
- [4.2. Continuous Integration](#42-continuous-integration)
- [5. Version History](#5-version-history)
- [5.1. Version 0.5](#51-version-05)
- [5.2. Version 0.4](#52-version-04)
- [5.3. Version 0.3](#53-version-03)
- [5.4. Version 0.2](#54-version-02)
- [5.5. Version 0.1](#55-version-01)
- [5.1. Version 0.6](#51-version-06)
- [5.2. Version 0.5](#52-version-05)
- [5.3. Version 0.4](#53-version-04)
- [5.4. Version 0.3](#54-version-03)
- [5.5. Version 0.2](#55-version-02)
- [5.6. Version 0.1](#56-version-01)
- [6. Future](#6-future)
@ -33,7 +35,7 @@ This is a port of G4KLX Jonathan Naylor's [ircddbGateway](https://github.com/g4k
# 2. Current State
## 2.1. Code sanity
The current code is working, yet ugly IMHO as it is a mix of C and C++ of various ages. I realised that G4KLx started programming this over a decade ago, when C++11 was not yet a thing !
The current code is working, yet it is a mix of C and C++ of various ages and there is plenty of room for rework. I realised that G4KLx started programming this over a decade ago, when C++11 was not yet a thing !
The code has also been amended to no longer rely on compiler defines for paths like log or data. These can be set in configuration file.
@ -44,21 +46,20 @@ Quite a few classes are more or less copy/paste from each other some sanitizatio
- Geoffrey Merck F4FXL / KC3FRA [That's me !](https://github.com/F4FXL/)
## 2.3. Thanks
- Cyrille F1MHV / DF1CHB for the testing
- Jonathan Naylor G4KLX for all the work ahead
- Jonathan Naylor G4KLX for all the work ahead and the rock solid code base
## 2.4. Features
All the features found in ircddbGateway are supposed to be working. Except the ones listed below
### 2.4.1. Features that where left out :
- CCS: is still being used? I always considered this as trojan horse to push some DMR Agenda into DStar an more or les a burdain to use. Call sign routing is by far more flexible and superior.
- CCS: is still being used? I always considered this as trojan horse to push some DMR Agenda into DStar and a burdain to use. Call sign routing is by far more flexible and superior.
- Starnet: You might consider running [Smart Group Server XL](https://github.com/F4FXL/smart-group-server-xl) from a dedicated computer instead.
- Announcement: same can be achieved using VoiceTransmit.
- APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not, depending on the system owner wish. I run mostly DStar Only repeaters, having an additional program to maintain is unnecessary burden.
- DRats : Is on the to-do list see [#6](#6)
- APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not, depending on the system owner wish. I run mostly DStar Only repeaters, having an additional program to maintain is unnecessary overkill.
- CallSign Server : this is a legacy from the dead project xreflector.net, I will most probably drop it for good.
### 2.4.2. Additional Features :
- DPlus, DExtra and G2 NAT Traversal using ircddb network as rendez-vous server
- Forward RSMS1A app messages from/to APRS-IS Network, yes you can send/receive messages to and from aprs.
### 2.4.2. Additional Features compared to ircddbGateway:
- DPlus, DExtra and G2 NAT Traversal using ircddb network as rendez-vous server. I.e. it is not required to open firewall ports for Callsign Routing or Gateway calls. however it is still recommended to do so. But NAT Traversal will bring more flexibility when operating on CGNAT (Mobile) Networks.
- Forward RSMS1A app messages from/to APRS-IS Network, yes you can send/receive messages to and from aprs. Yes, you can send messages to APRS stations and Vice Versa. Additionnally part of the message is sent as Text Dat in the slow data. This allows you to read the message dirdclty on your radio screen.
- Repeater Link status is sent to APRS-IS as a status frame
# 3. Building and installing
@ -103,7 +104,7 @@ make USE_GPS=1
```
make ENABLE_DEBUG=1
```
Note that this will link with libl
Note that this will will add libl dependency. Building this way will output the stack trace in case of a crash.
## 3.6. Installing
The program is meant to run as a systemd service. All bits an pieces are provided.
```
@ -115,13 +116,21 @@ After installing you have to edit the configuration file. If you went with defau
The configuration format is quite straight forward. It is organised in sections and key/value pairs.
The order of the sections or key/values pairs inside the sections does not matter nor does casing.
Boolean values can be set using true, false, 1 or 0
Floating point values must use . (point) as decimal separatorsensitive.
Floating point values must use . (point) as decimal separator.
When done with configuration, the daemon will be started automatically on boot. To manual start and stop it use the usual systemd commands
When done with configuration, the daemon will be started automatically on next boot. To manual start and stop it, use the usual systemd commands
```
sudo systemctl start dstargateway.service
sudo systemctl stop dstargateway.service
```
## 3.8. Updating host files
To update host files, from within the source code directory, run
```
sudo make newhostfiles
sudo systemctl restart dstargateway.service
```
# 4. Contributing
## 4.1. Work Flow
I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my workflow. PR are welcome but pleasee observe following rules :
@ -130,39 +139,46 @@ I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my work
- Code formating rules are observed (these are very lousy though)
## 4.2. Continuous Integration
I have added some basic CI using CircleCI [![F4FXL](https://circleci.com/gh/F4FXL/DStarGateway.svg?style=svg)](https://app.circleci.com/pipelines/github/F4FXL/DStarGateway?filter=all) I am trying to rewrite the code so that it can be put into some Behavior Driven Development scheme. This is a long haul task and I'll try do do it on the go while changing/adding stuff.
the testing framwework used is Google Test.
The testing framwework used is Google Test.
# 5. Version History
## 5.1. Version 0.5
- [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17))
- [Bugfix] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16))
- [Improvement] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5))
- [Improvement] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9))
- [Bugfix] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14))
- [Bugfix] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13))
- [Bugfix] Trying to connect to ghost ircDDB when no ircDDB is configured
## 5.2. Version 0.4
- [Improvement] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8))
- [Bugfix] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7))
- [Improvement] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6))
- [Improvement] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4))
## 5.3. Version 0.3
- [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling.
## 5.4. Version 0.2
- [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1))
## 5.1. Version 0.6
- [**Improvement**] Add DRats Support ([#22](https://github.com/F4FXL/DStarGateway/issues/22))
- [**Improvement**] Add call sign lists ([#22](https://github.com/F4FXL/DStarGateway/issues/22))
- [**Improvement**] Add a way to override Slow Data in VoiceTransmit ([#23](https://github.com/F4FXL/DStarGateway/issues/23))
- [**Improvement**] Add time server
- [**Improvement**] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)). DStarGateway can also be run as a "forking" daemon. This might be required for distros still using sysv. Systemd can live without it.
- [**Improvement**] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18))
- [**Improvement**] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18))
## 5.2. Version 0.5
- [**Improvement**] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17))
- [**Bugfix**] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16))
- [**Improvement**] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5))
- [**Improvement**] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9))
- [**Bugfix**] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14))
- [**Bugfix**] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13))
- [**Bugfix**] Trying to connect to ghost ircDDB when no ircDDB is configured
## 5.3. Version 0.4
- [**Improvement**] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8))
- [**Bugfix**] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7))
- [**Improvement**] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6))
- [**Improvement**] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4))
## 5.4. Version 0.3
- [**Improvement**] Get ride of libcongig++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling.
## 5.5. Version 0.2
- [**Bugfix**] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1))
- Code sanitization
## 5.5. Version 0.1
## 5.6. Version 0.1
First working version
# 6. Future
I started this during my 2021 seasons holiday. It took me almost 8 days to get to a workable version. Here are a couple of stuff I'd like to do :
- &#9745; Better NatTraversal
- No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer
- Support for all protocols (G2, DExtra, DPlus) DCS does nto make sense as it was historically never used as protocol for linking repeaters
- A [branch](https://github.com/F4FXL/DStarGateway/tree/feature/NatTraversal) already exists for this
- &#9745; Send the connection status to APRS-IS as a status frame
- &#9746; Reinstantiate DRATS
- &#9746; Migrate all the "accessories" (VoiceTransmit, RemoteControl ...)
- &#9745; Reinstantiate DRATS
- &#9745; Migrate all the "accessories" (VoiceTransmit, RemoteControl ...)
- &#9746; Automatic refresh of host files
- &#9746; Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ?
- &#9746; Forward messages from RS-MS1A to APRS and vice versa
- &#9745; Forward messages from RS-MS1A to APRS and vice versa
- Everything that might come handy to make dstar the most powerful system ever :)

Binary file not shown.

@ -0,0 +1,44 @@
0 94 29
1 140 16
2 173 20
3 215 19
4 255 24
5 298 29
6 344 34
7 398 25
8 440 24
9 481 23
alpha 521 29
bravo 568 27
charlie 615 30
delta 666 30
A 714 21
B 753 27
C 796 34
D 848 30
E 895 23
F 936 27
G 982 27
H 1026 30
I 1075 20
J 1113 29
K 1162 22
L 1201 28
M 1248 28
N 1295 28
O 1341 23
P 1386 21
Q 1426 23
R 1466 28
S 1511 31
T 1567 21
U 1606 22
V 1646 30
W 1693 46
X 1756 31
Y 1806 21
Z 1844 14
linkedto 1858 35
notlinked 1941 46
linkingto 2008 38
isbusy 2091 47

@ -0,0 +1,115 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include <filesystem>
#include <string>
#include <vector>
#include "AMBEFileReader.h"
#include "AMBEData.h"
namespace AMBEFileReaderTests
{
class AMBEFileReader_lookup : public ::testing::Test {
};
TEST_F(AMBEFileReader_lookup, nonExistentFiles)
{
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent files";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_FALSE(res) << "read shall return false on non existent files";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, onlyIndexFileExists)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_FALSE(res) << "read shall return false on non existent file";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists)
{
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_FALSE(res) << "read shall return false on non existent file";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, validId)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader(indexFile, ambeFile);
bool res = reader.read();
EXPECT_TRUE(res) << "read shall return true on existent files";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_TRUE(res) << "read shall return true on existent files and valid Id";
EXPECT_NE(data.size(), 0U) << "Vector shall contain data";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, invalidId)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader(indexFile, ambeFile);
bool res = reader.read();
EXPECT_TRUE(res) << "read shall return true on existent files";
std::vector<CAMBEData *> data;
res = reader.lookup("This Id does not exist", data);
EXPECT_FALSE(res) << "read shall return false on existent files and invalid Id";
EXPECT_EQ(data.size(), 0U) << "Vector shall not contain data";
for(auto d : data) {
delete d;
}
}
}

@ -0,0 +1,64 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include <filesystem>
#include <string>
#include "AMBEFileReader.h"
#include "Log.h"
namespace AMBEFileReaderTests
{
class AMBEFileReader_read : public ::testing::Test {
};
TEST_F(AMBEFileReader_read, nonExistentFiles)
{
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent files";
}
TEST_F(AMBEFileReader_read, onlyIndexFileExists)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
}
TEST_F(AMBEFileReader_read, onlyAmbeFileExists)
{
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
}
TEST_F(AMBEFileReader_read, bothFileExist)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader(indexFile, ambeFile);
bool res = reader.read();
EXPECT_TRUE(res) << "read shall return true on existent files";
}
}

@ -0,0 +1,41 @@
/*
* Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "APRSUtils.h"
namespace APRStoDPRSTests
{
class APRSUtils_calcGPSAIcomCRC : public ::testing::Test {
};
TEST_F(APRSUtils_calcGPSAIcomCRC, withCRCHeader)
{
auto crc = CAPRSUtils::calcGPSAIcomCRC("$$CRC6F5E,ABCDEF");
EXPECT_EQ(crc, 0x6f5e) << "CRC shall be valid";
}
TEST_F(APRSUtils_calcGPSAIcomCRC, withoutCRCHeader)
{
auto crc = CAPRSUtils::calcGPSAIcomCRC("ABCDEF");
EXPECT_EQ(crc, 0x6f5e) << "CRC shall be valid";
}
}

@ -10,12 +10,14 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB
-include $(DEPS)
.PHONY run-tests: dstargateway_tests
.PHONY run-tests: dstargateway_tests
./dstargateway_tests
.PHONY clean :
clean :
@$(RM) *.o *.d dstargateway_tests
find . -name "*.o" -type f -delete
find . -name "*.d" -type f -delete
$(RM) *.o *.d dstargateway_tests
../APRS/APRS.a:
../Common/Common.a:

@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "Utils.h"
#include "StringUtils.h"
#include "Log.h"
class Utils_swap_endian_le : public ::testing::Test {
};
// This test will fail on big endian systems....
TEST_F(Utils_swap_endian_le, SwapUINT32_be) {
uint32_t test = 0x12345678U;
uint32_t res = CUtils::swap_endian_le(test);
EXPECT_EQ(res, 0x78563412U);
}

@ -26,7 +26,7 @@
const std::string PRODUCT_NAME("DStarGateway");
const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA and Contributors");
const std::string VERSION("0.5");
const std::string VERSION("0.6");
const std::string LONG_VERSION = VERSION + "-" + gitversion;
const std::string FULL_PRODUCT_NAME = PRODUCT_NAME + " v" + VERSION + "-" + gitversion;
const std::string SHORT_PRODUCT_NAME = "DStarGW v" + VERSION + "-" + gitversion;

@ -0,0 +1,13 @@
[Unit]
Description=D-STAR Time Server Daemon
After=network.target,network-online.target
Wants=network-online.target
[Service]
User=dstar
Type=simple
ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dgwtimeserver.cfg
Restart=on-failure
[Install]
WantedBy=multi-user.target

@ -1,10 +1,10 @@
[Unit]
User=dstar
Description=D-STAR Gateway Daemon
After=network.target,network-online.target
Wants=network-online.target
[Service]
User=dstar
Type=simple
ExecStart=/usr/local/bin/dstargateway %CFG_DIR%/dstargateway.cfg
Restart=on-failure
@ -14,5 +14,3 @@ StartLimitBurst=0
[Install]
WantedBy=multi-user.target

Loading…
Cancel
Save

Powered by TurnKey Linux.