diff --git a/.gitignore b/.gitignore index c94f6f5..d13f157 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,7 @@ Debug Release x64 DAPNETGateway -GitVersion.h *.o -*.d *.opendb *.bak *.obj diff --git a/Conf.cpp b/Conf.cpp index 315080a..d6efd84 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -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 diff --git a/Conf.h b/Conf.h index 1f8ec6b..4faf12d 100644 --- a/Conf.h +++ b/Conf.h @@ -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 getWhiteList() const; - std::vector 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 getWhiteList() const; + std::vector 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 m_whiteList; - std::vector m_blackList; + std::string m_callsign; + std::vector m_whiteList; + std::vector 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 diff --git a/DAPNETGateway.cpp b/DAPNETGateway.cpp index 313ac76..2488607 100644 --- a/DAPNETGateway.cpp +++ b/DAPNETGateway.cpp @@ -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 #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> 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); -} - diff --git a/DAPNETGateway.h b/DAPNETGateway.h index 55d16fd..d3dbb17 100644 --- a/DAPNETGateway.h +++ b/DAPNETGateway.h @@ -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 diff --git a/DAPNETGateway.ini b/DAPNETGateway.ini index 6fccbbb..1610a0d 100644 --- a/DAPNETGateway.ini +++ b/DAPNETGateway.ini @@ -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 diff --git a/DAPNETGateway.vcxproj b/DAPNETGateway.vcxproj index cd87f6f..0a39333 100644 --- a/DAPNETGateway.vcxproj +++ b/DAPNETGateway.vcxproj @@ -23,7 +23,6 @@ - @@ -40,7 +39,6 @@ - @@ -62,26 +60,26 @@ Application true - v143 + v142 Unicode Application false - v143 + v142 true Unicode Application true - v143 + v142 Unicode Application false - v143 + v142 true Unicode @@ -123,13 +121,11 @@ true HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS true - C:\Program Files\mosquitto\devel;C:\Program Files Console true - ws2_32.lib;mosquitto.lib;%(AdditionalDependencies) - C:\Program Files\mosquitto\devel + ws2_32.lib;%(AdditionalDependencies) prebuild.cmd @@ -143,13 +139,11 @@ true HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS true - C:\Program Files\mosquitto\devel;C:\Program Files Console true - ws2_32.lib;mosquitto.lib;%(AdditionalDependencies) - C:\Program Files\mosquitto\devel + ws2_32.lib;%(AdditionalDependencies) prebuild.cmd @@ -165,15 +159,13 @@ true HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS true - C:\Program Files\mosquitto\devel;C:\Program Files Console true true true - ws2_32.lib;mosquitto.lib;%(AdditionalDependencies) - C:\Program Files\mosquitto\devel + ws2_32.lib;%(AdditionalDependencies) prebuild.cmd @@ -189,15 +181,13 @@ true HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS true - C:\Program Files\mosquitto\devel;C:\Program Files Console true true true - ws2_32.lib;mosquitto.lib;%(AdditionalDependencies) - C:\Program Files\mosquitto\devel + ws2_32.lib;%(AdditionalDependencies) prebuild.cmd diff --git a/DAPNETGateway.vcxproj.filters b/DAPNETGateway.vcxproj.filters index e3841f4..442cacc 100644 --- a/DAPNETGateway.vcxproj.filters +++ b/DAPNETGateway.vcxproj.filters @@ -53,9 +53,6 @@ Header Files - - Header Files - @@ -97,8 +94,5 @@ Source Files - - Source Files - \ No newline at end of file diff --git a/DAPNETNetwork.cpp b/DAPNETNetwork.cpp index 40661eb..de535ca 100644 --- a/DAPNETNetwork.cpp +++ b/DAPNETNetwork.cpp @@ -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) diff --git a/Log.cpp b/Log.cpp index 6e2751f..9aa2f96 100644 --- a/Log.cpp +++ b/Log.cpp @@ -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 @@ -34,27 +32,117 @@ #include #include -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); -} - diff --git a/Log.h b/Log.h index f4e012f..ae95b60 100644 --- a/Log.h +++ b/Log.h @@ -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 -#include - #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 diff --git a/MQTTConnection.cpp b/MQTTConnection.cpp deleted file mode 100644 index 6277a4b..0000000 --- a/MQTTConnection.cpp +++ /dev/null @@ -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 -#include -#include - -#if defined(_WIN32) || defined(_WIN64) -#include -#else -#include -#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>& 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(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(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(obj); - p->m_connected = true; - - for (std::vector>::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(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(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(obj); - - for (std::vector>::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(obj); - p->m_connected = false; -} - diff --git a/MQTTConnection.h b/MQTTConnection.h deleted file mode 100644 index 86d8c2e..0000000 --- a/MQTTConnection.h +++ /dev/null @@ -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 - -#include -#include - -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>& 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> 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 - diff --git a/Makefile b/Makefile index 9768912..ff6d6dc 100644 --- a/Makefile +++ b/Makefile @@ -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/ diff --git a/README.md b/README.md index 729f864..1beab61 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/TCPSocket.cpp b/TCPSocket.cpp index b971629..bf52b35 100644 --- a/TCPSocket.cpp +++ b/TCPSocket.cpp @@ -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()); diff --git a/Utils.cpp b/Utils.cpp index 31a42fd..4461eee 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -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 #include -#if defined(_WIN32) || defined(_WIN64) -#include -#else -#include -#include -#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; +} diff --git a/Utils.h b/Utils.h index 17c1a35..ade28c0 100644 --- a/Utils.h +++ b/Utils.h @@ -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: }; diff --git a/Version.h b/Version.h index 3c678f8..4ef4b10 100644 --- a/Version.h +++ b/Version.h @@ -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 diff --git a/schema.json b/schema.json deleted file mode 100644 index bf9d0e3..0000000 --- a/schema.json +++ /dev/null @@ -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"] - } -} -