Compare commits

..

No commits in common. "master" and "v1.0" have entirely different histories.
master ... v1.0

20 changed files with 297 additions and 609 deletions

2
.gitignore vendored
View file

@ -2,9 +2,7 @@ Debug
Release
x64
DAPNETGateway
GitVersion.h
*.o
*.d
*.opendb
*.bak
*.obj

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018,2020,2023,2025 by Jonathan Naylor G4KLX
* Copyright (C) 2018,2020,2025 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
@ -31,7 +31,6 @@ enum class SECTION {
NONE,
GENERAL,
LOG,
MQTT,
DAPNET
};
@ -47,14 +46,10 @@ m_myAddress(),
m_myPort(0U),
m_daemon(false),
m_logDisplayLevel(0U),
m_logMQTTLevel(0U),
m_mqttAddress("127.0.0.1"),
m_mqttPort(1883U),
m_mqttKeepalive(60U),
m_mqttName("dapnet-gateway"),
m_mqttAuthEnabled(false),
m_mqttUsername(),
m_mqttPassword(),
m_logFileLevel(0U),
m_logFilePath(),
m_logFileRoot(),
m_logFileRotate(true),
m_dapnetAddress(),
m_dapnetPort(0U),
m_dapnetAuthKey(),
@ -86,8 +81,6 @@ bool CConf::read()
section = SECTION::GENERAL;
else if (::strncmp(buffer, "[Log]", 5U) == 0)
section = SECTION::LOG;
else if (::strncmp(buffer, "[MQTT]", 6U) == 0)
section = SECTION::MQTT;
else if (::strncmp(buffer, "[DAPNET]", 8U) == 0)
section = SECTION::DAPNET;
else
@ -158,25 +151,16 @@ bool CConf::read()
else if (::strcmp(key, "Daemon") == 0)
m_daemon = ::atoi(value) == 1;
} else if (section == SECTION::LOG) {
if (::strcmp(key, "MQTTLevel") == 0)
m_logMQTTLevel = (unsigned int)::atoi(value);
if (::strcmp(key, "FilePath") == 0)
m_logFilePath = value;
else if (::strcmp(key, "FileRoot") == 0)
m_logFileRoot = value;
else if (::strcmp(key, "FileLevel") == 0)
m_logFileLevel = (unsigned int)::atoi(value);
else if (::strcmp(key, "DisplayLevel") == 0)
m_logDisplayLevel = (unsigned int)::atoi(value);
} else if (section == SECTION::MQTT) {
if (::strcmp(key, "Address") == 0)
m_mqttAddress = value;
else if (::strcmp(key, "Port") == 0)
m_mqttPort = (unsigned short)::atoi(value);
else if (::strcmp(key, "Keepalive") == 0)
m_mqttKeepalive = (unsigned int)::atoi(value);
else if (::strcmp(key, "Name") == 0)
m_mqttName = value;
else if (::strcmp(key, "Auth") == 0)
m_mqttAuthEnabled = ::atoi(value) == 1;
else if (::strcmp(key, "Username") == 0)
m_mqttUsername = value;
else if (::strcmp(key, "Password") == 0)
m_mqttPassword = value;
else if (::strcmp(key, "FileRotate") == 0)
m_logFileRotate = ::atoi(value) == 1;
} else if (section == SECTION::DAPNET) {
if (::strcmp(key, "Address") == 0)
m_dapnetAddress = value;
@ -252,44 +236,24 @@ unsigned int CConf::getLogDisplayLevel() const
return m_logDisplayLevel;
}
unsigned int CConf::getLogMQTTLevel() const
unsigned int CConf::getLogFileLevel() const
{
return m_logMQTTLevel;
return m_logFileLevel;
}
std::string CConf::getMQTTAddress() const
std::string CConf::getLogFilePath() const
{
return m_mqttAddress;
return m_logFilePath;
}
unsigned short CConf::getMQTTPort() const
std::string CConf::getLogFileRoot() const
{
return m_mqttPort;
return m_logFileRoot;
}
unsigned int CConf::getMQTTKeepalive() const
bool CConf::getLogFileRotate() const
{
return m_mqttKeepalive;
}
std::string CConf::getMQTTName() const
{
return m_mqttName;
}
bool CConf::getMQTTAuthEnabled() const
{
return m_mqttAuthEnabled;
}
std::string CConf::getMQTTUsername() const
{
return m_mqttUsername;
}
std::string CConf::getMQTTPassword() const
{
return m_mqttPassword;
return m_logFileRotate;
}
std::string CConf::getDAPNETAddress() const

103
Conf.h
View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018,2023,2025 by Jonathan Naylor G4KLX
* Copyright (C) 2018 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
@ -25,72 +25,61 @@
class CConf
{
public:
CConf(const std::string& file);
~CConf();
CConf(const std::string& file);
~CConf();
bool read();
bool read();
// The General section
std::string getCallsign() const;
std::vector<unsigned int> getWhiteList() const;
std::vector<unsigned int> getBlackList() const;
std::string getblacklistRegexfile() const;
std::string getwhitelistRegexfile() const;
std::string getRptAddress() const;
unsigned short getRptPort() const;
std::string getMyAddress() const;
unsigned short getMyPort() const;
bool getDaemon() const;
// The General section
std::string getCallsign() const;
std::vector<unsigned int> getWhiteList() const;
std::vector<unsigned int> getBlackList() const;
std::string getblacklistRegexfile() const;
std::string getwhitelistRegexfile() const;
std::string getRptAddress() const;
unsigned short getRptPort() const;
std::string getMyAddress() const;
unsigned short getMyPort() const;
bool getDaemon() const;
// The Log section
unsigned int getLogDisplayLevel() const;
unsigned int getLogMQTTLevel() const;
// The Log section
unsigned int getLogDisplayLevel() const;
unsigned int getLogFileLevel() const;
std::string getLogFilePath() const;
std::string getLogFileRoot() const;
bool getLogFileRotate() const;
// The MQTT section
std::string getMQTTAddress() const;
unsigned short getMQTTPort() const;
unsigned int getMQTTKeepalive() const;
std::string getMQTTName() const;
bool getMQTTAuthEnabled() const;
std::string getMQTTUsername() const;
std::string getMQTTPassword() const;
// The DAPNET section
std::string getDAPNETAddress() const;
unsigned short getDAPNETPort() const;
std::string getDAPNETAuthKey() const;
bool getDAPNETDebug() const;
// The DAPNET section
std::string getDAPNETAddress() const;
unsigned short getDAPNETPort() const;
std::string getDAPNETAuthKey() const;
bool getDAPNETDebug() const;
private:
std::string m_file;
std::string m_file;
std::string m_callsign;
std::vector<unsigned int> m_whiteList;
std::vector<unsigned int> m_blackList;
std::string m_callsign;
std::vector<unsigned int> m_whiteList;
std::vector<unsigned int> m_blackList;
std::string m_blacklistRegexfile;
std::string m_whitelistRegexfile;
std::string m_rptAddress;
unsigned short m_rptPort;
std::string m_myAddress;
unsigned short m_myPort;
bool m_daemon;
std::string m_blacklistRegexfile;
std::string m_whitelistRegexfile;
std::string m_rptAddress;
unsigned short m_rptPort;
std::string m_myAddress;
unsigned short m_myPort;
bool m_daemon;
unsigned int m_logDisplayLevel;
unsigned int m_logMQTTLevel;
unsigned int m_logDisplayLevel;
unsigned int m_logFileLevel;
std::string m_logFilePath;
std::string m_logFileRoot;
bool m_logFileRotate;
std::string m_mqttAddress;
unsigned short m_mqttPort;
unsigned int m_mqttKeepalive;
std::string m_mqttName;
bool m_mqttAuthEnabled;
std::string m_mqttUsername;
std::string m_mqttPassword;
std::string m_dapnetAddress;
unsigned short m_dapnetPort;
std::string m_dapnetAuthKey;
bool m_dapnetDebug;
std::string m_dapnetAddress;
unsigned short m_dapnetPort;
std::string m_dapnetAuthKey;
bool m_dapnetDebug;
};
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018,2020,2023,2024,2025 by Jonathan Naylor G4KLX
* Copyright (C) 2018,2020,2024,2025 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
@ -16,18 +16,15 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "MQTTConnection.h"
#include "DAPNETGateway.h"
#include "StopWatch.h"
#include "Version.h"
#include "Thread.h"
#include "Timer.h"
#include "REGEX.h"
#include "Utils.h"
#include "Log.h"
#include "GitVersion.h"
#include "REGEX.h"
#include <regex>
#if defined(_WIN32) || defined(_WIN64)
@ -46,9 +43,6 @@ const char* DEFAULT_INI_FILE = "DAPNETGateway.ini";
const char* DEFAULT_INI_FILE = "/etc/DAPNETGateway.ini";
#endif
// In Log.cpp
extern CMQTTConnection* m_mqtt;
static bool m_killed = false;
static int m_signal = 0;
@ -92,7 +86,6 @@ const unsigned char FUNCTIONAL_ALPHANUMERIC = 3U;
const unsigned int MAX_TIME_TO_HOLD_TIME_MESSAGES = 15000U; // 15s
int main(int argc, char** argv)
{
const char* iniFile = DEFAULT_INI_FILE;
@ -243,6 +236,16 @@ int CDAPNETGateway::run()
}
#endif
#if !defined(_WIN32) && !defined(_WIN64)
ret = ::LogInitialise(m_daemon, m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel(), m_conf.getLogFileRotate());
#else
ret = ::LogInitialise(false, m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel(), m_conf.getLogFileRotate());
#endif
if (!ret) {
::fprintf(stderr, "DAPNETGateway: unable to open the log file\n");
return 1;
}
#if !defined(_WIN32) && !defined(_WIN64)
if (m_daemon) {
::close(STDIN_FILENO);
@ -251,16 +254,6 @@ int CDAPNETGateway::run()
}
#endif
::LogInitialise(m_conf.getLogDisplayLevel(), m_conf.getLogMQTTLevel());
std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>> subscriptions;
m_mqtt = new CMQTTConnection(m_conf.getMQTTAddress(), m_conf.getMQTTPort(), m_conf.getMQTTName(), m_conf.getMQTTAuthEnabled(), m_conf.getMQTTUsername(), m_conf.getMQTTPassword(), subscriptions, m_conf.getMQTTKeepalive());
ret = m_mqtt->open();
if (!ret) {
delete m_mqtt;
return -1;
}
bool debug = m_conf.getDAPNETDebug();
std::string rptAddress = m_conf.getRptAddress();
@ -289,26 +282,24 @@ int CDAPNETGateway::run()
ret = m_dapnetNetwork->open();
if (!ret) {
m_pocsagNetwork->close();
delete m_pocsagNetwork;
delete m_dapnetNetwork;
::LogError("Cannot open the DAPNET network port");
writeJSONLink("failed", "socket");
return 1;
}
LogMessage("Starting DAPNETGateway-%s", VERSION);
ret = m_dapnetNetwork->login();
if (!ret) {
m_pocsagNetwork->close();
m_dapnetNetwork->close();
delete m_pocsagNetwork;
delete m_dapnetNetwork;
::LogError("Cannot login to the DAPNET network");
writeJSONLink("failed", "socket");
return 1;
}
@ -329,10 +320,6 @@ int CDAPNETGateway::run()
if (m_regexWhitelist->load())
regexWhitelist = m_regexWhitelist->get();
LogInfo("DAPNETGateway-%s is starting", VERSION);
LogInfo("Built %s %s (GitID #%.7s)", __TIME__, __DATE__, gitversion);
writeJSONStatus("DAPNETGateway is starting");
while (!m_killed) {
unsigned char buffer[200U];
@ -360,10 +347,8 @@ int CDAPNETGateway::run()
}
bool ok = m_dapnetNetwork->read();
if (!ok) {
writeJSONLink("unlinked", "lost");
if (!ok)
recover();
}
CPOCSAGMessage* message = m_dapnetNetwork->readMessage();
if (message != nullptr) {
@ -445,10 +430,6 @@ int CDAPNETGateway::run()
CThread::sleep(10U);
}
LogInfo("DAPNETGateway is stopping");
writeJSONStatus("DAPNETGateway is stopping");
writeJSONLink("unlinked", "");
m_pocsagNetwork->close();
delete m_pocsagNetwork;
@ -640,14 +621,3 @@ bool CDAPNETGateway::sendMessage(CPOCSAGMessage* message) const
return true;
}
}
void CDAPNETGateway::writeJSONStatus(const std::string& status)
{
nlohmann::json json;
json["timestamp"] = CUtils::createTimestamp();
json["message"] = status;
WriteJSON("status", json, false);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018,2023 by Jonathan Naylor G4KLX
* Copyright (C) 2018 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
@ -61,7 +61,6 @@ private:
void loadSchedule();
bool sendMessage(CPOCSAGMessage* message) const;
void writeJSONStatus(const std::string& status);
};
#endif

View file

@ -13,16 +13,10 @@ Daemon=0
[Log]
# Logging levels, 0=No logging
DisplayLevel=1
MQTTLevel=1
[MQTT]
Address=127.0.0.1
Port=1883
Keepalive=60
Auth=0
Username=mmdvm
Password=mmdvm
Name=dapnet-gateway
FileLevel=1
FilePath=.
FileRoot=DAPNETGateway
FileRotate=1
[DAPNET]
Address=dapnet.afu.rwth-aachen.de

View file

@ -23,7 +23,6 @@
<ClInclude Include="DAPNETGateway.h" />
<ClInclude Include="DAPNETNetwork.h" />
<ClInclude Include="Log.h" />
<ClInclude Include="MQTTConnection.h" />
<ClInclude Include="POCSAGMessage.h" />
<ClInclude Include="POCSAGNetwork.h" />
<ClInclude Include="REGEX.h" />
@ -40,7 +39,6 @@
<ClCompile Include="DAPNETGateway.cpp" />
<ClCompile Include="DAPNETNetwork.cpp" />
<ClCompile Include="Log.cpp" />
<ClCompile Include="MQTTConnection.cpp" />
<ClCompile Include="POCSAGMessage.cpp" />
<ClCompile Include="POCSAGNetwork.cpp" />
<ClCompile Include="REGEX.cpp" />
@ -62,26 +60,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@ -123,13 +121,11 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>prebuild.cmd</Command>
@ -143,13 +139,11 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>prebuild.cmd</Command>
@ -165,15 +159,13 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>prebuild.cmd</Command>
@ -189,15 +181,13 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>prebuild.cmd</Command>

View file

@ -53,9 +53,6 @@
<ClInclude Include="REGEX.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MQTTConnection.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Conf.cpp">
@ -97,8 +94,5 @@
<ClCompile Include="REGEX.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MQTTConnection.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -100,7 +100,6 @@ bool CDAPNETNetwork::read()
if (!m_loggedIn) {
m_loggedIn = true;
LogMessage("Logged into the DAPNET network");
writeJSONLink("linking", "");
}
// Time synchronisation
char* p = ::strchr((char*)buffer, '\n');
@ -272,7 +271,6 @@ bool CDAPNETNetwork::parseFailedLogin(unsigned char* data)
assert(p != nullptr);
LogMessage("Login failed: %s", p);
writeJSONLink("failed", "auth");
CThread::sleep(BACKOFF[m_failCount]);
if (m_failCount < 9)

148
Log.cpp
View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015,2016,2020,2022,2023,2025 by Jonathan Naylor G4KLX
* Copyright (C) 2015,2016,2020,2025 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
@ -17,8 +17,6 @@
*/
#include "Log.h"
#include "MQTTConnection.h"
#include "Utils.h"
#if defined(_WIN32) || defined(_WIN64)
#include <Windows.h>
@ -34,27 +32,117 @@
#include <cassert>
#include <cstring>
CMQTTConnection* m_mqtt = nullptr;
static unsigned int m_fileLevel = 2U;
static std::string m_filePath;
static std::string m_fileRoot;
static bool m_fileRotate = true;
static unsigned int m_mqttLevel = 2U;
static FILE* m_fpLog = nullptr;
static bool m_daemon = false;
static unsigned int m_displayLevel = 2U;
static struct tm m_tm;
static char LEVELS[] = " DMIWEF";
void LogInitialise(unsigned int displayLevel, unsigned int mqttLevel)
static bool logOpenRotate()
{
m_mqttLevel = mqttLevel;
bool status = false;
if (m_fileLevel == 0U)
return true;
time_t now;
::time(&now);
struct tm* tm = ::gmtime(&now);
if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) {
if (m_fpLog != nullptr)
return true;
} else {
if (m_fpLog != nullptr)
::fclose(m_fpLog);
}
char filename[200U];
#if defined(_WIN32) || defined(_WIN64)
::sprintf(filename, "%s\\%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
#else
::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
#endif
if ((m_fpLog = ::fopen(filename, "a+t")) != nullptr) {
status = true;
#if !defined(_WIN32) && !defined(_WIN64)
if (m_daemon)
dup2(fileno(m_fpLog), fileno(stderr));
#endif
}
m_tm = *tm;
return status;
}
static bool logOpenNoRotate()
{
bool status = false;
if (m_fileLevel == 0U)
return true;
if (m_fpLog != nullptr)
return true;
char filename[200U];
#if defined(_WIN32) || defined(_WIN64)
::sprintf(filename, "%s\\%s.log", m_filePath.c_str(), m_fileRoot.c_str());
#else
::sprintf(filename, "%s/%s.log", m_filePath.c_str(), m_fileRoot.c_str());
#endif
if ((m_fpLog = ::fopen(filename, "a+t")) != nullptr) {
status = true;
#if !defined(_WIN32) && !defined(_WIN64)
if (m_daemon)
dup2(fileno(m_fpLog), fileno(stderr));
#endif
}
return status;
}
bool LogOpen()
{
if (m_fileRotate)
return logOpenRotate();
else
return logOpenNoRotate();
}
bool LogInitialise(bool daemon, const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel, bool rotate)
{
m_filePath = filePath;
m_fileRoot = fileRoot;
m_fileLevel = fileLevel;
m_displayLevel = displayLevel;
m_daemon = daemon;
m_fileRotate = rotate;
if (m_daemon)
m_displayLevel = 0U;
return ::LogOpen();
}
void LogFinalise()
{
if (m_mqtt != nullptr) {
m_mqtt->close();
delete m_mqtt;
m_mqtt = nullptr;
}
if (m_fpLog != nullptr)
::fclose(m_fpLog);
}
void Log(unsigned int level, const char* fmt, ...)
@ -83,38 +171,22 @@ void Log(unsigned int level, const char* fmt, ...)
va_end(vl);
if (m_mqtt != nullptr && level >= m_mqttLevel && m_mqttLevel != 0U)
m_mqtt->publish("log", buffer);
if (level >= m_fileLevel && m_fileLevel != 0U) {
bool ret = ::LogOpen();
if (!ret)
return;
::fprintf(m_fpLog, "%s\n", buffer);
::fflush(m_fpLog);
}
if (level >= m_displayLevel && m_displayLevel != 0U) {
::fprintf(stdout, "%s\n", buffer);
::fflush(stdout);
}
if (level == 6U) // Fatal
if (level == 6U) { // Fatal
::fclose(m_fpLog);
exit(1);
}
void WriteJSON(const std::string& topLevel, nlohmann::json& json, bool retain)
{
if (m_mqtt != nullptr) {
nlohmann::json top;
top[topLevel] = json;
m_mqtt->publish("json", top.dump(), retain);
}
}
void writeJSONLink(const std::string& action, const std::string& reason)
{
nlohmann::json json;
json["timestamp"] = CUtils::createTimestamp();
json["action"] = action;
if (!reason.empty())
json["reason"] = reason;
WriteJSON("link", json, true);
}

14
Log.h
View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015,2016,2020,2022,2023 by Jonathan Naylor G4KLX
* Copyright (C) 2015,2016,2020 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
@ -21,8 +21,6 @@
#include <string>
#include <nlohmann/json.hpp>
#define LogDebug(fmt, ...) Log(1U, fmt, ##__VA_ARGS__)
#define LogMessage(fmt, ...) Log(2U, fmt, ##__VA_ARGS__)
#define LogInfo(fmt, ...) Log(3U, fmt, ##__VA_ARGS__)
@ -32,15 +30,7 @@
extern void Log(unsigned int level, const char* fmt, ...);
extern void LogInitialise(unsigned int displayLevel, unsigned int mqttLevel);
extern bool LogInitialise(bool daemon, const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel, bool rotate);
extern void LogFinalise();
extern void WriteJSON(const std::string& topLevel, nlohmann::json& json, bool retain);
// Publishes a "link" Kind directly, for connectivity events (DAPNET login
// success/failure/loss) that CDAPNETNetwork detects but has no WriteJSON
// access of its own (it isn't CDAPNETGateway) -- same free-function shape
// as APRSGateway's writeJSONLink.
extern void writeJSONLink(const std::string& action, const std::string& reason);
#endif

View file

@ -1,232 +0,0 @@
/*
* Copyright (C) 2022,2023,2025 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 "MQTTConnection.h"
#include <cassert>
#include <cstdio>
#include <cstring>
#if defined(_WIN32) || defined(_WIN64)
#include <process.h>
#else
#include <unistd.h>
#endif
CMQTTConnection::CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const bool authEnabled, const std::string& username, const std::string& password, const std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>& subs, unsigned int keepalive, MQTT_QOS qos) :
m_host(host),
m_port(port),
m_name(name),
m_authEnabled(authEnabled),
m_username(username),
m_password(password),
m_subs(subs),
m_keepalive(keepalive),
m_qos(qos),
m_mosq(nullptr),
m_connected(false)
{
assert(!host.empty());
assert(port > 0U);
assert(!name.empty());
assert(keepalive >= 5U);
::mosquitto_lib_init();
}
CMQTTConnection::~CMQTTConnection()
{
::mosquitto_lib_cleanup();
}
bool CMQTTConnection::open()
{
char name[50U];
#if defined(_WIN32) || defined(_WIN64)
::sprintf(name, "DAPNETGateway.%u", (unsigned)::_getpid());
#else
::sprintf(name, "DAPNETGateway.%u", (unsigned)::getpid());
#endif
::fprintf(stdout, "DAPNETGateway (%s) connecting to MQTT as %s\n", m_name.c_str(), name);
m_mosq = ::mosquitto_new(name, true, this);
if (m_mosq == nullptr){
::fprintf(stderr, "MQTT Error newing: Out of memory.\n");
return false;
}
if (m_authEnabled)
::mosquitto_username_pw_set(m_mosq, m_username.c_str(), m_password.c_str());
::mosquitto_connect_callback_set(m_mosq, onConnect);
::mosquitto_subscribe_callback_set(m_mosq, onSubscribe);
::mosquitto_message_callback_set(m_mosq, onMessage);
::mosquitto_disconnect_callback_set(m_mosq, onDisconnect);
int rc = ::mosquitto_connect(m_mosq, m_host.c_str(), m_port, m_keepalive);
if (rc != MOSQ_ERR_SUCCESS) {
::mosquitto_destroy(m_mosq);
m_mosq = nullptr;
::fprintf(stderr, "MQTT Error connecting: %s\n", ::mosquitto_strerror(rc));
return false;
}
rc = ::mosquitto_loop_start(m_mosq);
if (rc != MOSQ_ERR_SUCCESS) {
::mosquitto_disconnect(m_mosq);
::mosquitto_destroy(m_mosq);
m_mosq = nullptr;
::fprintf(stderr, "MQTT Error loop starting: %s\n", ::mosquitto_strerror(rc));
return false;
}
return true;
}
bool CMQTTConnection::publish(const char* topic, const char* text, bool retain)
{
assert(topic != nullptr);
assert(text != nullptr);
return publish(topic, (unsigned char*)text, (unsigned int)::strlen(text), retain);
}
bool CMQTTConnection::publish(const char* topic, const std::string& text, bool retain)
{
assert(topic != nullptr);
return publish(topic, (unsigned char*)text.c_str(), (unsigned int)text.size(), retain);
}
bool CMQTTConnection::publish(const char* topic, const unsigned char* data, unsigned int len, bool retain)
{
assert(topic != nullptr);
assert(data != nullptr);
if (!m_connected)
return false;
if (::strchr(topic, '/') == nullptr) {
char topicEx[100U];
::sprintf(topicEx, "%s/%s", m_name.c_str(), topic);
int rc = ::mosquitto_publish(m_mosq, nullptr, topicEx, len, data, static_cast<int>(m_qos), retain);
if (rc != MOSQ_ERR_SUCCESS) {
::fprintf(stderr, "MQTT Error publishing: %s\n", ::mosquitto_strerror(rc));
return false;
}
} else {
int rc = ::mosquitto_publish(m_mosq, nullptr, topic, len, data, static_cast<int>(m_qos), retain);
if (rc != MOSQ_ERR_SUCCESS) {
::fprintf(stderr, "MQTT Error publishing: %s\n", ::mosquitto_strerror(rc));
return false;
}
}
return true;
}
void CMQTTConnection::close()
{
if (m_mosq != nullptr) {
::mosquitto_disconnect(m_mosq);
::mosquitto_loop_stop(m_mosq, true);
::mosquitto_destroy(m_mosq);
m_mosq = nullptr;
}
}
void CMQTTConnection::onConnect(mosquitto* mosq, void* obj, int rc)
{
assert(mosq != nullptr);
assert(obj != nullptr);
::fprintf(stdout, "MQTT: on_connect: %s\n", ::mosquitto_connack_string(rc));
if (rc != 0) {
::mosquitto_disconnect(mosq);
return;
}
CMQTTConnection* p = static_cast<CMQTTConnection*>(obj);
p->m_connected = true;
for (std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) {
std::string topic = (*it).first;
if (topic.find_first_of('/') == std::string::npos) {
char topicEx[100U];
::sprintf(topicEx, "%s/%s", p->m_name.c_str(), topic.c_str());
rc = ::mosquitto_subscribe(mosq, nullptr, topicEx, static_cast<int>(p->m_qos));
if (rc != MOSQ_ERR_SUCCESS) {
::fprintf(stderr, "MQTT: error subscribing to %s - %s\n", topicEx, ::mosquitto_strerror(rc));
::mosquitto_disconnect(mosq);
}
} else {
rc = ::mosquitto_subscribe(mosq, nullptr, topic.c_str(), static_cast<int>(p->m_qos));
if (rc != MOSQ_ERR_SUCCESS) {
::fprintf(stderr, "MQTT: error subscribing to %s - %s\n", topic.c_str(), ::mosquitto_strerror(rc));
::mosquitto_disconnect(mosq);
}
}
}
}
void CMQTTConnection::onSubscribe(mosquitto* mosq, void* obj, int mid, int qosCount, const int* grantedQOS)
{
assert(mosq != nullptr);
assert(obj != nullptr);
assert(grantedQOS != nullptr);
for (int i = 0; i < qosCount; i++)
::fprintf(stdout, "MQTT: on_subscribe: %d:%d\n", i, grantedQOS[i]);
}
void CMQTTConnection::onMessage(mosquitto* mosq, void* obj, const mosquitto_message* message)
{
assert(mosq != nullptr);
assert(obj != nullptr);
assert(message != nullptr);
CMQTTConnection* p = static_cast<CMQTTConnection*>(obj);
for (std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) {
std::string topic = (*it).first;
char topicEx[100U];
::sprintf(topicEx, "%s/%s", p->m_name.c_str(), topic.c_str());
if (::strcmp(topicEx, message->topic) == 0) {
(*it).second((unsigned char*)message->payload, message->payloadlen);
break;
}
}
}
void CMQTTConnection::onDisconnect(mosquitto* mosq, void* obj, int rc)
{
assert(mosq != nullptr);
assert(obj != nullptr);
::fprintf(stdout, "MQTT: on_disconnect: %s\n", ::mosquitto_reason_string(rc));
CMQTTConnection* p = static_cast<CMQTTConnection*>(obj);
p->m_connected = false;
}

View file

@ -1,66 +0,0 @@
/*
* Copyright (C) 2022,2023,2025,2026 by Jonathan Naylor G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(MQTTPUBLISHER_H)
#define MQTTPUBLISHER_H
#include <mosquitto.h>
#include <vector>
#include <string>
enum class MQTT_QOS : int {
AT_MODE_ONCE = 0,
AT_LEAST_ONCE = 1,
EXACTLY_ONCE = 2
};
class CMQTTConnection {
public:
CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const bool authEnabled, const std::string& username, const std::string& password, const std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>& subs, unsigned int keepalive, MQTT_QOS qos = MQTT_QOS::EXACTLY_ONCE);
~CMQTTConnection();
bool open();
bool publish(const char* topic, const char* text, bool retain = false);
bool publish(const char* topic, const std::string& text, bool retain = false);
bool publish(const char* topic, const unsigned char* data, unsigned int len, bool retain = false);
void close();
private:
std::string m_host;
unsigned short m_port;
std::string m_name;
bool m_authEnabled;
std::string m_username;
std::string m_password;
std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>> m_subs;
unsigned int m_keepalive;
MQTT_QOS m_qos;
mosquitto* m_mosq;
bool m_connected;
static void onConnect(mosquitto* mosq, void* obj, int rc);
static void onSubscribe(mosquitto* mosq, void* obj, int mid, int qosCount, const int* grantedQOS);
static void onMessage(mosquitto* mosq, void* obj, const mosquitto_message* message);
static void onDisconnect(mosquitto* mosq, void* obj, int rc);
};
#endif

View file

@ -1,21 +1,18 @@
CC = cc
CXX = c++
CFLAGS = -g -O3 -Wall -std=c++0x -MMD -MD -pthread
LIBS = -lm -lpthread -lmosquitto
CFLAGS = -g -O3 -Wall -DHAVE_LOG_H -std=c++0x -pthread
LIBS = -lm -lpthread
LDFLAGS = -g
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
OBJECTS = Conf.o DAPNETGateway.o DAPNETNetwork.o Log.o POCSAGMessage.o POCSAGNetwork.o StopWatch.o TCPSocket.o Thread.o Timer.o UDPSocket.o Utils.o REGEX.o
all: DAPNETGateway
DAPNETGateway: GitVersion.h $(OBJS)
$(CXX) $(OBJS) $(CFLAGS) $(LIBS) -o DAPNETGateway
DAPNETGateway: GitVersion.h $(OBJECTS)
$(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o DAPNETGateway
%.o: %.cpp
$(CXX) $(CFLAGS) -c -o $@ $<
-include $(DEPS)
DAPNETGateway.o: GitVersion.h FORCE
@ -23,6 +20,7 @@ DAPNETGateway.o: GitVersion.h FORCE
FORCE:
install:
install -m 755 DAPNETGateway /usr/local/bin/

View file

@ -2,6 +2,6 @@ The DAPNET Gateway links the MMDVM Host to the DAPNET network to receive paging
The messages are buffered by the gateway when the host is busy with other modes, and then sends them to the host in order once the host becomes idle.
They build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2022 on x86 and x64.
They build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2017 on x86 and x64.
This software is licenced under the GPL v2 and is primarily intended for amateur and educational use.

View file

@ -145,7 +145,7 @@ int CTCPSocket::read(unsigned char* buffer, unsigned int length, unsigned int se
tv.tv_sec = secs;
tv.tv_usec = msecs * 1000;
int ret = ::select(int(m_fd) + 1, &readFds, nullptr, nullptr, &tv);
int ret = ::select(m_fd + 1, &readFds, nullptr, nullptr, &tv);
if (ret < 0) {
#if defined(_WIN32) || defined(_WIN64)
LogError("Error returned from TCP client select, err=%d", ::GetLastError());

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2009,2014,2015,2016,2023,2025 Jonathan Naylor, G4KLX
* Copyright (C) 2009,2014,2015,2016,2025 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
@ -17,13 +17,6 @@
#include <cstdio>
#include <cassert>
#if defined(_WIN32) || defined(_WIN64)
#include <Windows.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif
void CUtils::dump(const std::string& title, const unsigned char* data, unsigned int length)
{
assert(data != nullptr);
@ -77,24 +70,77 @@ void CUtils::dump(int level, const std::string& title, const unsigned char* data
}
}
std::string CUtils::createTimestamp()
void CUtils::dump(const std::string& title, const bool* bits, unsigned int length)
{
char buffer[100U];
assert(bits != nullptr);
#if defined(_WIN32) || defined(_WIN64)
SYSTEMTIME st;
::GetSystemTime(&st);
::sprintf(buffer, "%04u-%02u-%02uT%02u:%02u:%02u.%03uZ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
#else
struct timeval now;
::gettimeofday(&now, nullptr);
struct tm* tm = ::gmtime(&now.tv_sec);
::sprintf(buffer, "%04d-%02d-%02dT%02d:%02d:%02d.%03lldZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000LL);
#endif
return buffer;
dump(2U, title, bits, length);
}
void CUtils::dump(int level, const std::string& title, const bool* bits, unsigned int length)
{
assert(bits != nullptr);
unsigned char bytes[100U];
unsigned int nBytes = 0U;
for (unsigned int n = 0U; n < length; n += 8U, nBytes++)
bitsToByteBE(bits + n, bytes[nBytes]);
dump(level, title, bytes, nBytes);
}
void CUtils::byteToBitsBE(unsigned char byte, bool* bits)
{
assert(bits != nullptr);
bits[0U] = (byte & 0x80U) == 0x80U;
bits[1U] = (byte & 0x40U) == 0x40U;
bits[2U] = (byte & 0x20U) == 0x20U;
bits[3U] = (byte & 0x10U) == 0x10U;
bits[4U] = (byte & 0x08U) == 0x08U;
bits[5U] = (byte & 0x04U) == 0x04U;
bits[6U] = (byte & 0x02U) == 0x02U;
bits[7U] = (byte & 0x01U) == 0x01U;
}
void CUtils::byteToBitsLE(unsigned char byte, bool* bits)
{
assert(bits != nullptr);
bits[0U] = (byte & 0x01U) == 0x01U;
bits[1U] = (byte & 0x02U) == 0x02U;
bits[2U] = (byte & 0x04U) == 0x04U;
bits[3U] = (byte & 0x08U) == 0x08U;
bits[4U] = (byte & 0x10U) == 0x10U;
bits[5U] = (byte & 0x20U) == 0x20U;
bits[6U] = (byte & 0x40U) == 0x40U;
bits[7U] = (byte & 0x80U) == 0x80U;
}
void CUtils::bitsToByteBE(const bool* bits, unsigned char& byte)
{
assert(bits != nullptr);
byte = bits[0U] ? 0x80U : 0x00U;
byte |= bits[1U] ? 0x40U : 0x00U;
byte |= bits[2U] ? 0x20U : 0x00U;
byte |= bits[3U] ? 0x10U : 0x00U;
byte |= bits[4U] ? 0x08U : 0x00U;
byte |= bits[5U] ? 0x04U : 0x00U;
byte |= bits[6U] ? 0x02U : 0x00U;
byte |= bits[7U] ? 0x01U : 0x00U;
}
void CUtils::bitsToByteLE(const bool* bits, unsigned char& byte)
{
assert(bits != nullptr);
byte = bits[0U] ? 0x01U : 0x00U;
byte |= bits[1U] ? 0x02U : 0x00U;
byte |= bits[2U] ? 0x04U : 0x00U;
byte |= bits[3U] ? 0x08U : 0x00U;
byte |= bits[4U] ? 0x10U : 0x00U;
byte |= bits[5U] ? 0x20U : 0x00U;
byte |= bits[6U] ? 0x40U : 0x00U;
byte |= bits[7U] ? 0x80U : 0x00U;
}

11
Utils.h
View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2009,2014,2015,2023 by Jonathan Naylor, G4KLX
* Copyright (C) 2009,2014,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
@ -21,7 +21,14 @@ public:
static void dump(const std::string& title, const unsigned char* data, unsigned int length);
static void dump(int level, const std::string& title, const unsigned char* data, unsigned int length);
static std::string createTimestamp();
static void dump(const std::string& title, const bool* bits, unsigned int length);
static void dump(int level, const std::string& title, const bool* bits, unsigned int length);
static void byteToBitsBE(unsigned char byte, bool* bits);
static void byteToBitsLE(unsigned char byte, bool* bits);
static void bitsToByteBE(const bool* bits, unsigned char& byte);
static void bitsToByteLE(const bool* bits, unsigned char& byte);
private:
};

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2018,2020,2023,2024,2025,2026 by Jonathan Naylor G4KLX
* Copyright (C) 2018,2020,2024,2025 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
@ -19,6 +19,6 @@
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20260214";
const char* VERSION = "20250607";
#endif

View file

@ -1,23 +0,0 @@
{
"$defs": {
"timestamp": {"type": "string"},
"action": {"type": "string", "enum": ["linking", "unlinked", "failed"]},
"reason": {"type": "string", "enum": ["auth", "socket", "lost"]}
},
"status": {
"type": "object",
"timestamp": {"$ref": "#/$defs/timestamp"},
"message": {"type": "string"},
"required": ["timestamp", "message"]
},
"link": {
"type": "object",
"timestamp": {"$ref": "#/$defs/timestamp"},
"action": {"$ref": "#/$defs/action"},
"reason": {"$ref": "#/$defs/reason"},
"required": ["timestamp", "action"]
}
}