Merge branch 'feature/AddTimeServer_#20' into develop Closes #20

pull/32/head
Geoffrey Merck 4 years ago
commit a1cca3fbea

1
.gitignore vendored

@ -48,3 +48,4 @@ Tests/dstargateway_tests
DGWRemoteControl/dgwremotecontrol
DGWVoiceTransmit/dgwvoicetransmit
DGWTextTransmit/dgwtexttransmit
DGWTimeServer/dgwtimeserver

@ -80,6 +80,30 @@
}
]
},
{
"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",
@ -111,7 +135,7 @@
"program": "${workspaceFolder}/Tests/dstargateway_tests",
"args": [ ],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"cwd": "${workspaceFolder}/Tests/",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",

23
.vscode/tasks.json vendored

@ -13,10 +13,7 @@
"USE_GPSD=1",
"all"
],
"group": {
"kind": "build",
"isDefault": true
},
"group": "build",
"problemMatcher": []
},
{
@ -58,6 +55,19 @@
"group": "build",
"problemMatcher": []
},
{
"label": "Build DGWTimeServer",
"type": "shell",
"command": "make",
"args": [
"-j3",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DGWTimeServer/dgwtimeserver"
],
"group": "build",
"problemMatcher": []
},
{
"label": "Build DGWVoiceTransmit",
"type": "shell",
@ -81,7 +91,10 @@
"ENABLE_DEBUG=1",
"USE_GPSD=1"
],
"group": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
}
]

@ -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;

@ -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;
if (m_out >= m_data.size()) {
m_out = 0U;
m_status = AS_IDLE;
m_timer.stop();
return;
}
}
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,191 +236,79 @@ 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)
{
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;
}
if (memcmp(buffer, "AMBE", 4)) {
CLog::logError("Invalid header from %s\n", fileName.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", fileName.c_str());
fclose(file);
delete[] m_ambe;
m_ambe = NULL;
return false;
}
fclose(file);
return true;
}
bool CAudioUnit::readIndex(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;
}
}
FILE *file = fopen(fileName.c_str(), "r");
if (NULL == file) {
CLog::logInfo("Cannot open %s for reading\n", fileName.c_str());
return false;
}
// Add a silence entry at the beginning
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH);
CLog::logTrace("Audio Unit sendStatus");
CLog::logInfo("Reading %s\n", fileName.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);
}
// do some clean up, delete old message
for (auto item : m_data) {
delete item;
}
}
fclose(file);
return true;
}
void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text)
{
m_encoder.setTextData(text);
m_data.clear();
// Create the message
unsigned int id = CHeaderData::createId();
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
bool found;
switch (status) {
case LS_NONE:
lookup(id, "notlinked");
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 = lookup(id, "linkedto");
found = m_ambeFilereader->lookup("linkedto", m_data);
if (!found) {
lookup(id, "linked");
lookup(id, "2");
m_ambeFilereader->lookup("linked", m_data);
m_ambeFilereader->lookup("2", m_data);
}
spellReflector(id, reflector);
spellReflector(reflector);
break;
default:
found = lookup(id, "linkingto");
found = m_ambeFilereader->lookup("linkingto", m_data);
if (!found) {
lookup(id, "linking");
lookup(id, "2");
m_ambeFilereader->lookup("linking", m_data);
m_ambeFilereader->lookup("2", m_data);
}
spellReflector(id, reflector);
spellReflector(reflector);
break;
}
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
lookup(id, " ");
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
unsigned int id = CHeaderData::createId();
// RPT1 and RPT2 will be filled in later
CHeaderData header;
header.setMyCall1(m_callsign);
@ -498,5 +316,33 @@ void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, co
header.setYourCall("CQCQCQ ");
header.setId(id);
CSlowDataEncoder slowDataEncoder;
slowDataEncoder.setTextData(text);
unsigned int seqNo = 0U;
// 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);
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
m_data[i]->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);
slowDataEncoder.sync();
} else {
slowDataEncoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES);
}
m_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES);
seqNo++;
if(seqNo >= 21U) seqNo = 0U;
}
m_data[m_data.size() - 1]->setEnd(true);
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);
};

@ -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, voiceandtext, defaults to voice and text
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,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;
};

@ -115,8 +115,8 @@ int main(int argc, char *argv[])
TLog logConf;
config->getLog(logConf);
CLog::finalise();
if(logConf.m_displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.m_displayLevel));
if(logConf.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.m_fileLevel, logConf.logDir, logConf.m_fileRotate));
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) {

@ -153,34 +153,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;
}
@ -199,6 +197,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;

@ -86,10 +86,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 {

@ -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:

@ -178,7 +178,7 @@ port=4242
password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field
# 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 system handles the user under which the program is started. This is provided as convenience for people who might
# 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

@ -40,7 +40,7 @@ endif
.PHONY: all
all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWVoiceTransmit/dgwvoicetransmit #tests
all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests
APRS/APRS.a: BaseCommon/BaseCommon.a FORCE
$(MAKE) -C APRS
@ -63,6 +63,9 @@ DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DS
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
@ -99,6 +102,7 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol
# install accessories
$(MAKE) -C DGWRemoteControl install
$(MAKE) -C DGWTextTransmit install
$(MAKE) -C DGWTimeServer install
$(MAKE) -C DGWVoiceTransmit install
# create user for daemon
@ -115,18 +119,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

@ -135,26 +135,27 @@ the testing framwework used is Google Test.
# 5. Version History
## 5.1. Version 0.6
- [Improvement] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21))
- [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))
- [**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
- [**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))
- [**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 libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling.
- [**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))
- [**Bugfix**] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1))
- Code sanitization
## 5.6. Version 0.1
First working version
@ -163,10 +164,9 @@ I started this during my 2021 seasons holiday. It took me almost 8 days to get t
- &#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; 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

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";
}
}

@ -15,7 +15,9 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB
.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,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
Loading…
Cancel
Save

Powered by TurnKey Linux.