├── .gitignore ├── Conf.cpp ├── Conf.h ├── DAPNETGateway.cpp ├── DAPNETGateway.h ├── DAPNETGateway.ini ├── DAPNETGateway.sln ├── DAPNETGateway.vcxproj ├── DAPNETGateway.vcxproj.filters ├── DAPNETNetwork.cpp ├── DAPNETNetwork.h ├── LICENCE ├── Log.cpp ├── Log.h ├── Makefile ├── POCSAGMessage.cpp ├── POCSAGMessage.h ├── POCSAGNetwork.cpp ├── POCSAGNetwork.h ├── README.REGEX ├── README.md ├── REGEX.cpp ├── REGEX.h ├── StopWatch.cpp ├── StopWatch.h ├── TCPSocket.cpp ├── TCPSocket.h ├── Thread.cpp ├── Thread.h ├── Timer.cpp ├── Timer.h ├── UDPSocket.cpp ├── UDPSocket.h ├── Utils.cpp ├── Utils.h ├── Version.h └── prebuild.cmd /.gitignore: -------------------------------------------------------------------------------- 1 | Debug 2 | Release 3 | x64 4 | DAPNETGateway 5 | *.o 6 | *.opendb 7 | *.bak 8 | *.obj 9 | *~ 10 | *.sdf 11 | *.log 12 | *.zip 13 | *.exe 14 | *.user 15 | *.VC.db 16 | .vs 17 | GitVersion.h 18 | 19 | -------------------------------------------------------------------------------- /Conf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018,2020,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Conf.h" 20 | #include "Log.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | const int BUFFER_SIZE = 500; 28 | 29 | enum class SECTION { 30 | NONE, 31 | GENERAL, 32 | LOG, 33 | DAPNET 34 | }; 35 | 36 | CConf::CConf(const std::string& file) : 37 | m_file(file), 38 | m_callsign(), 39 | m_whiteList(), 40 | m_blacklistRegexfile(), 41 | m_whitelistRegexfile(), 42 | m_rptAddress(), 43 | m_rptPort(0U), 44 | m_myAddress(), 45 | m_myPort(0U), 46 | m_daemon(false), 47 | m_logDisplayLevel(0U), 48 | m_logFileLevel(0U), 49 | m_logFilePath(), 50 | m_logFileRoot(), 51 | m_logFileRotate(true), 52 | m_dapnetAddress(), 53 | m_dapnetPort(0U), 54 | m_dapnetAuthKey(), 55 | m_dapnetDebug(false) 56 | { 57 | } 58 | 59 | CConf::~CConf() 60 | { 61 | } 62 | 63 | bool CConf::read() 64 | { 65 | FILE* fp = ::fopen(m_file.c_str(), "rt"); 66 | if (fp == nullptr) { 67 | ::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str()); 68 | return false; 69 | } 70 | 71 | SECTION section = SECTION::NONE; 72 | 73 | char buffer[BUFFER_SIZE]; 74 | while (::fgets(buffer, BUFFER_SIZE, fp) != nullptr) { 75 | if (buffer[0U] == '#') 76 | continue; 77 | 78 | if (buffer[0U] == '[') { 79 | if (::strncmp(buffer, "[General]", 9U) == 0) 80 | section = SECTION::GENERAL; 81 | else if (::strncmp(buffer, "[Log]", 5U) == 0) 82 | section = SECTION::LOG; 83 | else if (::strncmp(buffer, "[DAPNET]", 8U) == 0) 84 | section = SECTION::DAPNET; 85 | else 86 | section = SECTION::NONE; 87 | 88 | continue; 89 | } 90 | 91 | char* key = ::strtok(buffer, " \t=\r\n"); 92 | if (key == nullptr) 93 | continue; 94 | 95 | char* value = ::strtok(nullptr, "\r\n"); 96 | if (value == nullptr) 97 | continue; 98 | 99 | // Remove quotes from the value 100 | size_t len = ::strlen(value); 101 | if (len > 1U && *value == '"' && value[len - 1U] == '"') { 102 | value[len - 1U] = '\0'; 103 | value++; 104 | } else { 105 | char *p; 106 | 107 | // if value is not quoted, remove after # (to make comment) 108 | if ((p = strchr(value, '#')) != nullptr) 109 | *p = '\0'; 110 | 111 | // remove trailing tab/space 112 | for (p = value + strlen(value) - 1U; p >= value && (*p == '\t' || *p == ' '); p--) 113 | *p = '\0'; 114 | } 115 | 116 | if (section == SECTION::GENERAL) { 117 | if (::strcmp(key, "Callsign") == 0) { 118 | for (unsigned int i = 0U; value[i] != '\0'; i++) { 119 | if (!::isspace(value[i])) 120 | m_callsign.insert(m_callsign.end(), 1, value[i]); 121 | } 122 | } else if (::strcmp(key, "WhiteList") == 0) { 123 | char* p = ::strtok(value, ",\r\n"); 124 | while (p != nullptr) { 125 | unsigned int ric = (unsigned int)::atoi(p); 126 | if (ric > 0U) 127 | m_whiteList.push_back(ric); 128 | p = ::strtok(nullptr, ",\r\n"); 129 | } 130 | } else if (::strcmp(key, "BlackList") == 0) { 131 | char* p = ::strtok(value, ",\r\n"); 132 | while (p != nullptr) { 133 | unsigned int ric = (unsigned int)::atoi(p); 134 | if (ric > 0U) 135 | m_blackList.push_back(ric); 136 | p = ::strtok(nullptr, ",\r\n"); 137 | } 138 | } else if (::strcmp(key,"BlacklistRegexfile") == 0) 139 | m_blacklistRegexfile = value; 140 | else if (::strcmp(key,"WhitelistRegexfile") == 0) 141 | m_whitelistRegexfile = value; 142 | else if (::strcmp(key, "RptAddress") == 0) 143 | m_rptAddress = value; 144 | else if (::strcmp(key, "RptPort") == 0) 145 | m_rptPort = (unsigned short)::atoi(value); 146 | else if (::strcmp(key, "LocalAddress") == 0) 147 | m_myAddress = value; 148 | else if (::strcmp(key, "LocalPort") == 0) 149 | m_myPort = (unsigned short)::atoi(value); 150 | else if (::strcmp(key, "Daemon") == 0) 151 | m_daemon = ::atoi(value) == 1; 152 | } else if (section == SECTION::LOG) { 153 | if (::strcmp(key, "FilePath") == 0) 154 | m_logFilePath = value; 155 | else if (::strcmp(key, "FileRoot") == 0) 156 | m_logFileRoot = value; 157 | else if (::strcmp(key, "FileLevel") == 0) 158 | m_logFileLevel = (unsigned int)::atoi(value); 159 | else if (::strcmp(key, "DisplayLevel") == 0) 160 | m_logDisplayLevel = (unsigned int)::atoi(value); 161 | else if (::strcmp(key, "FileRotate") == 0) 162 | m_logFileRotate = ::atoi(value) == 1; 163 | } else if (section == SECTION::DAPNET) { 164 | if (::strcmp(key, "Address") == 0) 165 | m_dapnetAddress = value; 166 | else if (::strcmp(key, "Port") == 0) 167 | m_dapnetPort = (unsigned short)::atoi(value); 168 | else if (::strcmp(key, "AuthKey") == 0) { 169 | for (unsigned int i = 0U; value[i] != '\0'; i++) { 170 | if (!::isspace(value[i])) 171 | m_dapnetAuthKey.insert(m_dapnetAuthKey.end(), 1, value[i]); 172 | } 173 | } else if (::strcmp(key, "Debug") == 0) 174 | m_dapnetDebug = ::atoi(value) == 1; 175 | } 176 | } 177 | 178 | ::fclose(fp); 179 | 180 | return true; 181 | } 182 | 183 | std::string CConf::getCallsign() const 184 | { 185 | return m_callsign; 186 | } 187 | 188 | std::vector CConf::getWhiteList() const 189 | { 190 | return m_whiteList; 191 | } 192 | 193 | std::vector CConf::getBlackList() const 194 | { 195 | return m_blackList; 196 | } 197 | 198 | std::string CConf::getblacklistRegexfile() const 199 | { 200 | return m_blacklistRegexfile; 201 | } 202 | 203 | std::string CConf::getwhitelistRegexfile() const 204 | { 205 | return m_whitelistRegexfile; 206 | } 207 | 208 | std::string CConf::getRptAddress() const 209 | { 210 | return m_rptAddress; 211 | } 212 | 213 | unsigned short CConf::getRptPort() const 214 | { 215 | return m_rptPort; 216 | } 217 | 218 | std::string CConf::getMyAddress() const 219 | { 220 | return m_myAddress; 221 | } 222 | 223 | unsigned short CConf::getMyPort() const 224 | { 225 | return m_myPort; 226 | } 227 | 228 | bool CConf::getDaemon() const 229 | { 230 | return m_daemon; 231 | } 232 | 233 | unsigned int CConf::getLogDisplayLevel() const 234 | { 235 | return m_logDisplayLevel; 236 | } 237 | 238 | unsigned int CConf::getLogFileLevel() const 239 | { 240 | return m_logFileLevel; 241 | } 242 | 243 | std::string CConf::getLogFilePath() const 244 | { 245 | return m_logFilePath; 246 | } 247 | 248 | std::string CConf::getLogFileRoot() const 249 | { 250 | return m_logFileRoot; 251 | } 252 | 253 | bool CConf::getLogFileRotate() const 254 | { 255 | return m_logFileRotate; 256 | } 257 | 258 | std::string CConf::getDAPNETAddress() const 259 | { 260 | return m_dapnetAddress; 261 | } 262 | 263 | unsigned short CConf::getDAPNETPort() const 264 | { 265 | return m_dapnetPort; 266 | } 267 | 268 | std::string CConf::getDAPNETAuthKey() const 269 | { 270 | return m_dapnetAuthKey; 271 | } 272 | 273 | bool CConf::getDAPNETDebug() const 274 | { 275 | return m_dapnetDebug; 276 | } 277 | -------------------------------------------------------------------------------- /Conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(CONF_H) 20 | #define CONF_H 21 | 22 | #include 23 | #include 24 | 25 | class CConf 26 | { 27 | public: 28 | CConf(const std::string& file); 29 | ~CConf(); 30 | 31 | bool read(); 32 | 33 | // The General section 34 | std::string getCallsign() const; 35 | std::vector getWhiteList() const; 36 | std::vector getBlackList() const; 37 | std::string getblacklistRegexfile() const; 38 | std::string getwhitelistRegexfile() const; 39 | std::string getRptAddress() const; 40 | unsigned short getRptPort() const; 41 | std::string getMyAddress() const; 42 | unsigned short getMyPort() const; 43 | bool getDaemon() const; 44 | 45 | // The Log section 46 | unsigned int getLogDisplayLevel() const; 47 | unsigned int getLogFileLevel() const; 48 | std::string getLogFilePath() const; 49 | std::string getLogFileRoot() const; 50 | bool getLogFileRotate() const; 51 | 52 | // The DAPNET section 53 | std::string getDAPNETAddress() const; 54 | unsigned short getDAPNETPort() const; 55 | std::string getDAPNETAuthKey() const; 56 | bool getDAPNETDebug() const; 57 | 58 | private: 59 | std::string m_file; 60 | 61 | std::string m_callsign; 62 | std::vector m_whiteList; 63 | std::vector m_blackList; 64 | 65 | std::string m_blacklistRegexfile; 66 | std::string m_whitelistRegexfile; 67 | std::string m_rptAddress; 68 | unsigned short m_rptPort; 69 | std::string m_myAddress; 70 | unsigned short m_myPort; 71 | bool m_daemon; 72 | 73 | unsigned int m_logDisplayLevel; 74 | unsigned int m_logFileLevel; 75 | std::string m_logFilePath; 76 | std::string m_logFileRoot; 77 | bool m_logFileRotate; 78 | 79 | std::string m_dapnetAddress; 80 | unsigned short m_dapnetPort; 81 | std::string m_dapnetAuthKey; 82 | bool m_dapnetDebug; 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /DAPNETGateway.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018,2020,2024,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "DAPNETGateway.h" 20 | #include "StopWatch.h" 21 | #include "Version.h" 22 | #include "Thread.h" 23 | #include "Timer.h" 24 | #include "Log.h" 25 | #include "GitVersion.h" 26 | 27 | #include "REGEX.h" 28 | #include 29 | 30 | #if defined(_WIN32) || defined(_WIN64) 31 | #include 32 | #else 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #endif 39 | 40 | #if defined(_WIN32) || defined(_WIN64) 41 | const char* DEFAULT_INI_FILE = "DAPNETGateway.ini"; 42 | #else 43 | const char* DEFAULT_INI_FILE = "/etc/DAPNETGateway.ini"; 44 | #endif 45 | 46 | static bool m_killed = false; 47 | static int m_signal = 0; 48 | 49 | #if !defined(_WIN32) && !defined(_WIN64) 50 | static void sigHandler(int signum) 51 | { 52 | m_killed = true; 53 | m_signal = signum; 54 | } 55 | #endif 56 | 57 | #include 58 | #include 59 | 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | const unsigned int FRAME_LENGTH_CODEWORDS = 2U; 68 | const unsigned int BATCH_LENGTH_CODEWORDS = 17U; 69 | const unsigned int PREAMBLE_LENGTH_CODEWORDS = BATCH_LENGTH_CODEWORDS + 1U; 70 | 71 | const unsigned int CODEWORD_TIME_US = 26667U; // 26.667ms 72 | const unsigned int FRAME_TIME_US = CODEWORD_TIME_US * FRAME_LENGTH_CODEWORDS; // 53.333ms 73 | const unsigned int BATCH_TIME_US = CODEWORD_TIME_US * BATCH_LENGTH_CODEWORDS; // 453.333ms 74 | 75 | const unsigned int PREAMBLE_TIME_US = CODEWORD_TIME_US * PREAMBLE_LENGTH_CODEWORDS; // 480.006ms 76 | 77 | const unsigned int SLOT_TIME_US = 6400000U; // 6.4s 78 | const unsigned int SLOT_TIME_MS = SLOT_TIME_US / 1000U; // 6.4s 79 | const unsigned int CODEWORDS_PER_SLOT = SLOT_TIME_US / CODEWORD_TIME_US; // 240 80 | const unsigned int BATCHES_PER_SLOT = SLOT_TIME_US / BATCH_TIME_US; // 14 81 | 82 | const unsigned char FUNCTIONAL_NUMERIC = 0U; 83 | const unsigned char FUNCTIONAL_ALERT1 = 1U; 84 | const unsigned char FUNCTIONAL_ALERT2 = 2U; 85 | const unsigned char FUNCTIONAL_ALPHANUMERIC = 3U; 86 | 87 | const unsigned int MAX_TIME_TO_HOLD_TIME_MESSAGES = 15000U; // 15s 88 | 89 | int main(int argc, char** argv) 90 | { 91 | const char* iniFile = DEFAULT_INI_FILE; 92 | if (argc > 1) { 93 | for (int currentArg = 1; currentArg < argc; ++currentArg) { 94 | std::string arg = argv[currentArg]; 95 | if ((arg == "-v") || (arg == "--version")) { 96 | ::fprintf(stdout, "DAPNETGateway version %s git #%.7s\n", VERSION, gitversion); 97 | return 0; 98 | } else if (arg.substr(0, 1) == "-") { 99 | ::fprintf(stderr, "Usage: DAPNETGateway [-v|--version] [filename]\n"); 100 | return 1; 101 | } else { 102 | iniFile = argv[currentArg]; 103 | } 104 | } 105 | } 106 | 107 | #if !defined(_WIN32) && !defined(_WIN64) 108 | ::signal(SIGINT, sigHandler); 109 | ::signal(SIGTERM, sigHandler); 110 | ::signal(SIGHUP, sigHandler); 111 | #endif 112 | 113 | int ret = 0; 114 | 115 | do { 116 | m_signal = 0; 117 | m_killed = false; 118 | 119 | CDAPNETGateway* gateway = new CDAPNETGateway(std::string(iniFile)); 120 | ret = gateway->run(); 121 | 122 | delete gateway; 123 | 124 | switch (m_signal) { 125 | case 0: 126 | break; 127 | case 2: 128 | ::LogInfo("DAPNETGateway-%s exited on receipt of SIGINT", VERSION); 129 | break; 130 | case 15: 131 | ::LogInfo("DAPNETGateway-%s exited on receipt of SIGTERM", VERSION); 132 | break; 133 | case 1: 134 | ::LogInfo("DAPNETGateway-%s is restarting on receipt of SIGHUP", VERSION); 135 | break; 136 | default: 137 | ::LogInfo("DAPNETGateway-%s exited on receipt of an unknown signal", VERSION); 138 | break; 139 | } 140 | } while (m_signal == 1); 141 | 142 | ::LogFinalise(); 143 | 144 | return ret; 145 | } 146 | 147 | CDAPNETGateway::CDAPNETGateway(const std::string& configFile) : 148 | m_conf(configFile), 149 | m_dapnetNetwork(nullptr), 150 | m_pocsagNetwork(nullptr), 151 | m_queue(), 152 | m_slotTimer(), 153 | m_schedule(nullptr), 154 | m_allSlots(false), 155 | m_currentSlot(0U), 156 | m_sentCodewords(0U), 157 | m_regexBlacklist(), 158 | m_regexWhitelist(), 159 | m_mmdvmFree(false) 160 | { 161 | CUDPSocket::startup(); 162 | } 163 | 164 | CDAPNETGateway::~CDAPNETGateway() 165 | { 166 | for (std::deque::iterator it = m_queue.begin(); it != m_queue.end(); ++it) 167 | delete *it; 168 | 169 | m_queue.clear(); 170 | 171 | CUDPSocket::shutdown(); 172 | } 173 | 174 | int CDAPNETGateway::run() 175 | { 176 | bool ret = m_conf.read(); 177 | if (!ret) { 178 | ::fprintf(stderr, "DAPNETGateway: cannot read the .ini file\n"); 179 | return 1; 180 | } 181 | 182 | setlocale(LC_ALL, "C"); 183 | 184 | #if !defined(_WIN32) && !defined(_WIN64) 185 | bool m_daemon = m_conf.getDaemon(); 186 | if (m_daemon) { 187 | // Create new process 188 | pid_t pid = ::fork(); 189 | if (pid == -1) { 190 | ::fprintf(stderr, "Couldn't fork() , exiting\n"); 191 | return 1; 192 | } else if (pid != 0) { 193 | exit(EXIT_SUCCESS); 194 | } 195 | 196 | // Create new session and process group 197 | if (::setsid() == -1) { 198 | ::fprintf(stderr, "Couldn't setsid(), exiting\n"); 199 | return 1; 200 | } 201 | 202 | // Set the working directory to the root directory 203 | if (::chdir("/") == -1) { 204 | ::fprintf(stderr, "Couldn't cd /, exiting\n"); 205 | return 1; 206 | } 207 | 208 | // If we are currently root... 209 | if (getuid() == 0) { 210 | struct passwd* user = ::getpwnam("mmdvm"); 211 | if (user == nullptr) { 212 | ::fprintf(stderr, "Could not get the mmdvm user, exiting\n"); 213 | return 1; 214 | } 215 | 216 | uid_t mmdvm_uid = user->pw_uid; 217 | gid_t mmdvm_gid = user->pw_gid; 218 | 219 | // Set user and group ID's to mmdvm:mmdvm 220 | if (setgid(mmdvm_gid) != 0) { 221 | ::fprintf(stderr, "Could not set mmdvm GID, exiting\n"); 222 | return 1; 223 | } 224 | 225 | if (setuid(mmdvm_uid) != 0) { 226 | ::fprintf(stderr, "Could not set mmdvm UID, exiting\n"); 227 | return 1; 228 | } 229 | 230 | // Double check it worked (AKA Paranoia) 231 | if (setuid(0) != -1) { 232 | ::fprintf(stderr, "It's possible to regain root - something is wrong!, exiting\n"); 233 | return 1; 234 | } 235 | } 236 | } 237 | #endif 238 | 239 | #if !defined(_WIN32) && !defined(_WIN64) 240 | ret = ::LogInitialise(m_daemon, m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel(), m_conf.getLogFileRotate()); 241 | #else 242 | ret = ::LogInitialise(false, m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel(), m_conf.getLogFileRotate()); 243 | #endif 244 | if (!ret) { 245 | ::fprintf(stderr, "DAPNETGateway: unable to open the log file\n"); 246 | return 1; 247 | } 248 | 249 | #if !defined(_WIN32) && !defined(_WIN64) 250 | if (m_daemon) { 251 | ::close(STDIN_FILENO); 252 | ::close(STDOUT_FILENO); 253 | ::close(STDERR_FILENO); 254 | } 255 | #endif 256 | 257 | bool debug = m_conf.getDAPNETDebug(); 258 | 259 | std::string rptAddress = m_conf.getRptAddress(); 260 | unsigned short rptPort = m_conf.getRptPort(); 261 | std::string myAddress = m_conf.getMyAddress(); 262 | unsigned short myPort = m_conf.getMyPort(); 263 | 264 | m_pocsagNetwork = new CPOCSAGNetwork(myAddress, myPort, rptAddress, rptPort, debug); 265 | ret = m_pocsagNetwork->open(); 266 | if (!ret) { 267 | ::LogError("Cannot open the repeater network port"); 268 | return 1; 269 | } 270 | 271 | std::string callsign = m_conf.getCallsign(); 272 | std::string dapnetAddress = m_conf.getDAPNETAddress(); 273 | unsigned short dapnetPort = m_conf.getDAPNETPort(); 274 | std::string dapnetAuthKey = m_conf.getDAPNETAuthKey(); 275 | 276 | if (dapnetAuthKey.length() == 0 || dapnetAuthKey == "TOPSECRET") { 277 | ::LogError("AuthKey not set or invalid"); 278 | return 1; 279 | } 280 | 281 | m_dapnetNetwork = new CDAPNETNetwork(dapnetAddress, dapnetPort, callsign, dapnetAuthKey, VERSION, false, 1, debug); 282 | ret = m_dapnetNetwork->open(); 283 | if (!ret) { 284 | m_pocsagNetwork->close(); 285 | delete m_pocsagNetwork; 286 | delete m_dapnetNetwork; 287 | 288 | ::LogError("Cannot open the DAPNET network port"); 289 | 290 | return 1; 291 | } 292 | 293 | LogMessage("Starting DAPNETGateway-%s", VERSION); 294 | 295 | ret = m_dapnetNetwork->login(); 296 | if (!ret) { 297 | m_pocsagNetwork->close(); 298 | m_dapnetNetwork->close(); 299 | delete m_pocsagNetwork; 300 | delete m_dapnetNetwork; 301 | 302 | ::LogError("Cannot login to the DAPNET network"); 303 | 304 | return 1; 305 | } 306 | 307 | std::vector whiteList = m_conf.getWhiteList(); 308 | std::vector blackList = m_conf.getBlackList(); 309 | 310 | std::vector regexBlacklist; 311 | std::vector regexWhitelist; 312 | 313 | LogMessage("Initializing blacklist"); 314 | m_regexBlacklist = new CREGEX(m_conf.getblacklistRegexfile()); 315 | if (m_regexBlacklist->load()) 316 | regexBlacklist = m_regexBlacklist->get(); 317 | 318 | LogMessage("Initializing whitelist"); 319 | m_regexWhitelist = new CREGEX(m_conf.getwhitelistRegexfile()); 320 | if (m_regexWhitelist->load()) 321 | regexWhitelist = m_regexWhitelist->get(); 322 | 323 | 324 | while (!m_killed) { 325 | unsigned char buffer[200U]; 326 | 327 | if (m_pocsagNetwork->read(buffer) > 0U) { 328 | switch (buffer[0U]) { 329 | case 0x00U: 330 | // The MMDVM is idle 331 | if (!m_mmdvmFree) { 332 | // LogDebug("*** MMDVM is free"); 333 | m_mmdvmFree = true; 334 | m_sentCodewords = (m_slotTimer.elapsed() * 1000U) / CODEWORD_TIME_US; 335 | } 336 | break; 337 | case 0xFFU: 338 | // The MMDVM is busy 339 | // LogDebug("*** MMDVM is busy"); 340 | m_mmdvmFree = false; 341 | break; 342 | default: 343 | // The MMDVM is sending crap 344 | LogWarning("Unknown data from the MMDVM - 0x%02X", buffer[0U]); 345 | break; 346 | } 347 | } 348 | 349 | bool ok = m_dapnetNetwork->read(); 350 | if (!ok) 351 | recover(); 352 | 353 | CPOCSAGMessage* message = m_dapnetNetwork->readMessage(); 354 | if (message != nullptr) { 355 | bool found = true; 356 | bool blackListRIC = false; 357 | bool blacklistRegexmatch = false; 358 | bool whitelistRegexmatch = true; 359 | 360 | // If we have a white list of RICs, use it. 361 | if (!whiteList.empty()) 362 | found = std::find(whiteList.begin(), whiteList.end(), message->m_ric) != whiteList.end(); 363 | 364 | // If we have a black list of RICs, use it. 365 | if (!blackList.empty()) 366 | blackListRIC = std::find(blackList.begin(), blackList.end(), message->m_ric) != blackList.end(); 367 | if (blackListRIC) 368 | LogDebug("Blacklist match: Not queueing message to %07u, type %u, message: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 369 | 370 | std::string messageBody(reinterpret_cast(message->m_message)); 371 | //If we have a list of blacklist REGEXes, use them 372 | if (!regexBlacklist.empty()) { 373 | for (std::regex regex : regexBlacklist) { 374 | bool ret = std::regex_match(messageBody,regex); 375 | //If the regex matches the message body, don't send the message 376 | if (ret) { 377 | blacklistRegexmatch = true; 378 | LogDebug("Blacklist REGEX match: Not queueing message to %07u, type %u, message: \"%.*s\"", message->m_ric, message->m_type, message->m_length, messageBody.c_str()); 379 | } 380 | } 381 | } 382 | 383 | if(!regexWhitelist.empty() && !blacklistRegexmatch) { 384 | for (std::regex regex : regexWhitelist) { 385 | bool ret = std::regex_match(messageBody,regex); 386 | //If the regex does not match the message body, don't send the message 387 | if (!ret) { 388 | whitelistRegexmatch = false; 389 | LogDebug("No whitelist REGEX match: Not queueing message to %07u, type %u, message: \"%.*s\"", message->m_ric, message->m_type, message->m_length, messageBody.c_str()); 390 | } 391 | } 392 | } 393 | 394 | if (found && !blackListRIC && !blacklistRegexmatch && whitelistRegexmatch) { 395 | switch (message->m_functional) { 396 | case FUNCTIONAL_ALPHANUMERIC: 397 | LogDebug("Queueing message to %07u, type %u, func Alphanumeric: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 398 | break; 399 | case FUNCTIONAL_ALERT2: 400 | LogDebug("Queueing message to %07u, type %u, func Alert 2: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 401 | break; 402 | case FUNCTIONAL_NUMERIC: 403 | LogDebug("Queueing message to %07u, type %u, func Numeric: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 404 | break; 405 | case FUNCTIONAL_ALERT1: 406 | LogDebug("Queueing message to %07u, type %u, func Alert 1", message->m_ric, message->m_type); 407 | break; 408 | default: 409 | break; 410 | } 411 | 412 | m_queue.push_front(message); 413 | LogDebug("Messages in Queue %04u", m_queue.size()); 414 | } 415 | } 416 | 417 | unsigned int t = (m_slotTimer.time() / 100ULL) % 1024ULL; 418 | unsigned int slot = t / 64U; 419 | if (slot != m_currentSlot) { 420 | // LogDebug("Start of slot %u", slot); 421 | m_currentSlot = slot; 422 | if (m_schedule == nullptr || m_currentSlot == 0U) 423 | loadSchedule(); 424 | m_sentCodewords = 0U; 425 | m_slotTimer.start(); 426 | } 427 | 428 | sendMessages(); 429 | 430 | CThread::sleep(10U); 431 | } 432 | 433 | m_pocsagNetwork->close(); 434 | delete m_pocsagNetwork; 435 | 436 | m_dapnetNetwork->close(); 437 | delete m_dapnetNetwork; 438 | 439 | return 0; 440 | } 441 | 442 | void CDAPNETGateway::sendMessages() 443 | { 444 | // If the MMDVM is busy, we can't send anything. 445 | if (!m_mmdvmFree) 446 | return; 447 | 448 | // Do we have a schedule? 449 | if (m_schedule == nullptr) 450 | return; 451 | 452 | // Check to see if we're allowed to send within a slot. 453 | if (!m_schedule[m_currentSlot]) 454 | return; 455 | 456 | // If we have data to send, see if we have time to do so in the current schedule. 457 | if (m_queue.empty()) 458 | return; 459 | 460 | CPOCSAGMessage* message = m_queue.back(); 461 | assert(message != nullptr); 462 | 463 | // Special case, only test if slots are being used. 464 | if (m_allSlots) { 465 | sendMessage(message); 466 | m_queue.pop_back(); 467 | delete message; 468 | return; 469 | } 470 | 471 | unsigned int codewords = calculateCodewords(message); 472 | 473 | // Do we have too much data already sent in this slot? 474 | unsigned int totalCodewords = m_sentCodewords + PREAMBLE_LENGTH_CODEWORDS + codewords; 475 | if (totalCodewords >= CODEWORDS_PER_SLOT) { 476 | // LogDebug("Too many codewords sent in slot %u already %u + %u + %u = %u >= %u", m_currentSlot, m_sentCodewords, PREAMBLE_LENGTH_CODEWORDS, codewords, totalCodewords, CODEWORDS_PER_SLOT); 477 | return; 478 | } 479 | 480 | // Is there enough time to send it in this slot before it ends? 481 | unsigned int sendTime = (PREAMBLE_TIME_US + codewords * CODEWORD_TIME_US) / 1000U; 482 | unsigned int timeLeft = SLOT_TIME_MS - m_slotTimer.elapsed(); 483 | if (sendTime >= timeLeft) { 484 | // LogDebug("Too little time to send the message in slot %u, %u + %u + %u = %u >= %u = %u - %u", m_currentSlot, PREAMBLE_TIME_US, codewords, CODEWORD_TIME_US, sendTime, timeLeft, SLOT_TIME_MS, m_slotTimer.elapsed()); 485 | return; 486 | } 487 | 488 | bool ret = sendMessage(message); 489 | if (ret) 490 | m_sentCodewords = totalCodewords; 491 | 492 | m_queue.pop_back(); 493 | delete message; 494 | } 495 | 496 | bool CDAPNETGateway::recover() 497 | { 498 | 499 | for (;;) { 500 | m_dapnetNetwork->close(); 501 | bool ok = m_dapnetNetwork->open(); 502 | if (ok) { 503 | ok = m_dapnetNetwork->login(); 504 | if (ok) 505 | return true; 506 | } 507 | } 508 | 509 | return false; 510 | } 511 | 512 | bool CDAPNETGateway::isTimeMessage(const CPOCSAGMessage* message) const 513 | { 514 | if (message->m_type == 5U && message->m_functional == FUNCTIONAL_NUMERIC) 515 | return true; 516 | 517 | if (message->m_type == 6U && message->m_functional == FUNCTIONAL_ALPHANUMERIC && ::memcmp(message->m_message, "XTIME=", 6U) == 0) 518 | return true; 519 | 520 | return false; 521 | } 522 | 523 | unsigned int CDAPNETGateway::calculateCodewords(const CPOCSAGMessage* message) const 524 | { 525 | assert(message != nullptr); 526 | 527 | unsigned int len = 0U; 528 | switch (message->m_functional) { 529 | case FUNCTIONAL_NUMERIC: 530 | len = message->m_length / 5U; // For the number packing, five to a word 531 | break; 532 | case FUNCTIONAL_ALPHANUMERIC: 533 | case FUNCTIONAL_ALERT2: 534 | len = (message->m_length * 7U) / 20U; // For the ASCII packing, 7/20 to a word 535 | break; 536 | case FUNCTIONAL_ALERT1: 537 | default: 538 | break; 539 | } 540 | 541 | len++; // For the address word 542 | 543 | if ((len % 2U) == 1U) // Always an even number of words 544 | len++; 545 | 546 | len += len % 16U; // A very long message will include sync words 547 | 548 | return len; 549 | } 550 | 551 | void CDAPNETGateway::loadSchedule() 552 | { 553 | bool* schedule = m_dapnetNetwork->readSchedule(); 554 | if (schedule == nullptr) 555 | return; 556 | 557 | delete[] m_schedule; 558 | m_schedule = schedule; 559 | 560 | m_allSlots = true; 561 | 562 | std::string text; 563 | for (unsigned int i = 0U; i < 16U; i++) { 564 | if (m_schedule[i]) { 565 | text += "*"; 566 | } else { 567 | text += "-"; 568 | m_allSlots = false; 569 | } 570 | } 571 | 572 | if (m_allSlots) 573 | LogMessage("All slots are available for transmission"); 574 | else 575 | LogMessage("Loaded new schedule: %s", text.c_str()); 576 | } 577 | 578 | bool CDAPNETGateway::sendMessage(CPOCSAGMessage* message) const 579 | { 580 | assert(message != nullptr); 581 | 582 | bool ret = isTimeMessage(message); 583 | if (ret && message->m_timeQueued.elapsed() >= MAX_TIME_TO_HOLD_TIME_MESSAGES) { 584 | switch (message->m_functional) { 585 | case FUNCTIONAL_ALPHANUMERIC: 586 | LogDebug("Rejecting message to %07u, type %u, func Alphanumeric: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 587 | break; 588 | case FUNCTIONAL_ALERT2: 589 | LogDebug("Rejecting message to %07u, type %u, func Alert 2: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 590 | break; 591 | case FUNCTIONAL_NUMERIC: 592 | LogDebug("Rejecting message to %07u, type %u, func Numeric: \"%.*s\"", message->m_ric, message->m_type, message->m_length, message->m_message); 593 | break; 594 | case FUNCTIONAL_ALERT1: 595 | LogDebug("Rejecting message to %07u, type %u, func Alert 1", message->m_ric, message->m_type); 596 | break; 597 | default: 598 | break; 599 | } 600 | 601 | return false; 602 | } else { 603 | switch (message->m_functional) { 604 | case FUNCTIONAL_ALPHANUMERIC: 605 | LogMessage("Sending message in slot %u to %07u, type %u, func Alphanumeric: \"%.*s\"", m_currentSlot, message->m_ric, message->m_type, message->m_length, message->m_message); 606 | break; 607 | case FUNCTIONAL_ALERT2: 608 | LogMessage("Sending message in slot %u to %07u, type %u, func Alert 2: \"%.*s\"", m_currentSlot, message->m_ric, message->m_type, message->m_length, message->m_message); 609 | break; 610 | case FUNCTIONAL_NUMERIC: 611 | LogMessage("Sending message in slot %u to %07u, type %u, func Numeric: \"%.*s\"", m_currentSlot, message->m_ric, message->m_type, message->m_length, message->m_message); 612 | break; 613 | case FUNCTIONAL_ALERT1: 614 | LogMessage("Sending message in slot %u to %07u, type %u, func Alert 1", m_currentSlot, message->m_ric, message->m_type); 615 | break; 616 | default: 617 | break; 618 | } 619 | 620 | m_pocsagNetwork->write(message); 621 | return true; 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /DAPNETGateway.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(DAPNETGateway_H) 20 | #define DAPNETGateway_H 21 | 22 | #include "DAPNETNetwork.h" 23 | #include "POCSAGNetwork.h" 24 | #include "POCSAGMessage.h" 25 | #include "StopWatch.h" 26 | #include "Conf.h" 27 | #include "REGEX.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | class CDAPNETGateway 35 | { 36 | public: 37 | CDAPNETGateway(const std::string& configFile); 38 | ~CDAPNETGateway(); 39 | 40 | int run(); 41 | 42 | private: 43 | CConf m_conf; 44 | CDAPNETNetwork* m_dapnetNetwork; 45 | CPOCSAGNetwork* m_pocsagNetwork; 46 | std::deque m_queue; 47 | CStopWatch m_slotTimer; 48 | bool* m_schedule; 49 | bool m_allSlots; 50 | unsigned int m_currentSlot; 51 | unsigned int m_sentCodewords; 52 | CREGEX* m_regexBlacklist; 53 | CREGEX* m_regexWhitelist; 54 | bool m_mmdvmFree; 55 | 56 | 57 | void sendMessages(); 58 | bool recover(); 59 | bool isTimeMessage(const CPOCSAGMessage* message) const; 60 | unsigned int calculateCodewords(const CPOCSAGMessage* message) const; 61 | void loadSchedule(); 62 | bool sendMessage(CPOCSAGMessage* message) const; 63 | 64 | }; 65 | 66 | #endif 67 | 68 | -------------------------------------------------------------------------------- /DAPNETGateway.ini: -------------------------------------------------------------------------------- 1 | [General] 2 | Callsign=g9bf 3 | #WhiteList=12345,78901 4 | #BlackList= 5 | #BlacklistRegexfile=/tmp/blregexes.txt 6 | #WhitelistRegexfile=/tmp/wlregexes.txt 7 | RptAddress=127.0.0.1 8 | RptPort=3800 9 | LocalAddress=127.0.0.1 10 | LocalPort=4800 11 | Daemon=0 12 | 13 | [Log] 14 | # Logging levels, 0=No logging 15 | DisplayLevel=1 16 | FileLevel=1 17 | FilePath=. 18 | FileRoot=DAPNETGateway 19 | FileRotate=1 20 | 21 | [DAPNET] 22 | Address=dapnet.afu.rwth-aachen.de 23 | Port=43434 24 | AuthKey=TOPSECRET 25 | Debug=0 26 | -------------------------------------------------------------------------------- /DAPNETGateway.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DAPNETGateway", "DAPNETGateway.vcxproj", "{55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Debug|x64.ActiveCfg = Debug|x64 17 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Debug|x64.Build.0 = Debug|x64 18 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Debug|x86.ActiveCfg = Debug|Win32 19 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Debug|x86.Build.0 = Debug|Win32 20 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Release|x64.ActiveCfg = Release|x64 21 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Release|x64.Build.0 = Release|x64 22 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Release|x86.ActiveCfg = Release|Win32 23 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {AB378848-91BD-4C59-9477-1CCF6BFCBA62} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /DAPNETGateway.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 15.0 54 | {55BEF4BF-9A1A-42F0-B1B7-7A0E05EBF0FF} 55 | Win32Proj 56 | DAPNETGateway 57 | 10.0 58 | 59 | 60 | 61 | Application 62 | true 63 | v142 64 | Unicode 65 | 66 | 67 | Application 68 | false 69 | v142 70 | true 71 | Unicode 72 | 73 | 74 | Application 75 | true 76 | v142 77 | Unicode 78 | 79 | 80 | Application 81 | false 82 | v142 83 | true 84 | Unicode 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | true 106 | 107 | 108 | true 109 | 110 | 111 | false 112 | 113 | 114 | false 115 | 116 | 117 | 118 | NotUsing 119 | Level3 120 | Disabled 121 | true 122 | HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS 123 | true 124 | 125 | 126 | Console 127 | true 128 | ws2_32.lib;%(AdditionalDependencies) 129 | 130 | 131 | prebuild.cmd 132 | 133 | 134 | 135 | 136 | NotUsing 137 | Level3 138 | Disabled 139 | true 140 | HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS 141 | true 142 | 143 | 144 | Console 145 | true 146 | ws2_32.lib;%(AdditionalDependencies) 147 | 148 | 149 | prebuild.cmd 150 | 151 | 152 | 153 | 154 | NotUsing 155 | Level3 156 | MaxSpeed 157 | true 158 | true 159 | true 160 | HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS 161 | true 162 | 163 | 164 | Console 165 | true 166 | true 167 | true 168 | ws2_32.lib;%(AdditionalDependencies) 169 | 170 | 171 | prebuild.cmd 172 | 173 | 174 | 175 | 176 | NotUsing 177 | Level3 178 | MaxSpeed 179 | true 180 | true 181 | true 182 | HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS 183 | true 184 | 185 | 186 | Console 187 | true 188 | true 189 | true 190 | ws2_32.lib;%(AdditionalDependencies) 191 | 192 | 193 | prebuild.cmd 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /DAPNETGateway.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | 14 | 15 | Header Files 16 | 17 | 18 | Header Files 19 | 20 | 21 | Header Files 22 | 23 | 24 | Header Files 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | 58 | 59 | Source Files 60 | 61 | 62 | Source Files 63 | 64 | 65 | Source Files 66 | 67 | 68 | Source Files 69 | 70 | 71 | Source Files 72 | 73 | 74 | Source Files 75 | 76 | 77 | Source Files 78 | 79 | 80 | Source Files 81 | 82 | 83 | Source Files 84 | 85 | 86 | Source Files 87 | 88 | 89 | Source Files 90 | 91 | 92 | Source Files 93 | 94 | 95 | Source Files 96 | 97 | 98 | -------------------------------------------------------------------------------- /DAPNETNetwork.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "DAPNETNetwork.h" 20 | #include "Thread.h" 21 | #include "Utils.h" 22 | #include "Log.h" 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | const unsigned int BACKOFF[] = { 2000U, 4000U, 8000U, 10000U, 20000U, 60000U, 120000U, 240000U, 480000U, 600000U }; 31 | 32 | const unsigned int BUFFER_LENGTH = 200U; 33 | 34 | CDAPNETNetwork::CDAPNETNetwork(const std::string& address, unsigned short port, const std::string& callsign, const std::string& authKey, const char* version, bool loggedIn, int failCount, bool debug) : 35 | m_socket(address, port), 36 | m_callsign(callsign), 37 | m_authKey(authKey), 38 | m_version(version), 39 | m_loggedIn(false), 40 | m_failCount(failCount), 41 | m_debug(debug), 42 | m_message(nullptr), 43 | m_schedule(nullptr) 44 | { 45 | assert(!callsign.empty()); 46 | assert(!authKey.empty()); 47 | assert(version != nullptr); 48 | } 49 | 50 | CDAPNETNetwork::~CDAPNETNetwork() 51 | { 52 | delete m_message; 53 | delete[] m_schedule; 54 | } 55 | 56 | bool CDAPNETNetwork::open() 57 | { 58 | LogMessage("Opening DAPNET connection"); 59 | 60 | return m_socket.open(); 61 | } 62 | 63 | bool CDAPNETNetwork::login() 64 | { 65 | LogMessage("Logging into DAPNET"); 66 | 67 | std::transform(m_callsign.begin(), m_callsign.end(), m_callsign.begin(), ::tolower); 68 | 69 | char login[200U]; 70 | ::snprintf(login, 200, "[DAPNETGateway v%s %s %s]\r\n", m_version, m_callsign.c_str(), m_authKey.c_str()); 71 | 72 | return write((unsigned char*)login); 73 | } 74 | 75 | bool CDAPNETNetwork::read() 76 | { 77 | unsigned char buffer[BUFFER_LENGTH]; 78 | 79 | int length = m_socket.read(buffer, BUFFER_LENGTH, 0U); 80 | if (length == -1) // Error 81 | return false; 82 | if (length == -2) // Connection lost 83 | return false; 84 | if (length == 0) 85 | return true; 86 | 87 | if (m_debug) 88 | CUtils::dump(1U, "DAPNET Data Received", buffer, length); 89 | 90 | // Turn it into a C string 91 | buffer[length] = 0x00U; 92 | 93 | if (buffer[0U] == '+') { 94 | // Success 95 | } else if (buffer[0U] == '-') { 96 | // Error 97 | LogWarning("An error has been reported by DAPNET"); 98 | } else if (buffer[0U] == '2') { 99 | // First time sync paket indicated successful login 100 | if (!m_loggedIn) { 101 | m_loggedIn = true; 102 | LogMessage("Logged into the DAPNET network"); 103 | } 104 | // Time synchronisation 105 | char* p = ::strchr((char*)buffer, '\n'); 106 | if (p != nullptr) 107 | ::strcpy(p, ":0000\r\n"); 108 | else 109 | ::strcat((char*)buffer, ":0000\r\n"); 110 | 111 | bool ok = write(buffer); 112 | if (!ok) 113 | return false; 114 | 115 | return write((unsigned char*)"+\r\n"); 116 | } else if (buffer[0U] == '3') { 117 | // ??? 118 | return write((unsigned char*)"+\r\n"); 119 | } else if (buffer[0U] == '4') { 120 | // Timeslot information 121 | return parseSchedule(buffer); 122 | } else if (buffer[0U] == '7') { 123 | // Login failed 124 | return parseFailedLogin(buffer); 125 | } else if (buffer[0U] == '#') { 126 | // A message 127 | return parseMessage(buffer, length); 128 | } else { 129 | CUtils::dump(3U, "An unknown message from DAPNET", buffer, length); 130 | return write((unsigned char*)"-\r\n"); 131 | } 132 | 133 | return true; 134 | } 135 | 136 | bool* CDAPNETNetwork::readSchedule() 137 | { 138 | bool* schedule = m_schedule; 139 | 140 | m_schedule = nullptr; 141 | 142 | return schedule; 143 | } 144 | 145 | CPOCSAGMessage* CDAPNETNetwork::readMessage() 146 | { 147 | if (m_message == nullptr) 148 | return nullptr; 149 | 150 | CPOCSAGMessage* message = m_message; 151 | 152 | m_message = nullptr; 153 | 154 | return message; 155 | } 156 | 157 | void CDAPNETNetwork::close() 158 | { 159 | m_socket.close(); 160 | 161 | LogMessage("Closing DAPNET connection"); 162 | } 163 | 164 | bool CDAPNETNetwork::write(unsigned char* data) 165 | { 166 | assert(data != nullptr); 167 | 168 | unsigned int length = (unsigned int)::strlen((char*)data); 169 | 170 | if (m_debug) 171 | CUtils::dump(1U, "DAPNET Data Transmitted", data, length); 172 | 173 | bool ok = m_socket.write(data, length); 174 | if (!ok) 175 | LogWarning("Error when writing to DAPNET"); 176 | 177 | return ok; 178 | } 179 | 180 | bool CDAPNETNetwork::parseMessage(unsigned char* buffer, unsigned int length) 181 | { 182 | assert(buffer != nullptr); 183 | 184 | unsigned int id = ::strtoul((char*)buffer + 1U, nullptr, 16); 185 | 186 | char* p1 = ::strtok((char*)buffer + 4U, ":\r\n"); 187 | char* p2 = ::strtok(nullptr, ":\r\n"); 188 | char* p3 = ::strtok(nullptr, ":\r\n"); 189 | char* p4 = ::strtok(nullptr, ":\r\n"); 190 | char* p5 = ::strtok(nullptr, "\r\n"); 191 | 192 | if (p1 == nullptr || p2 == nullptr || p3 == nullptr || p4 == nullptr || p5 == nullptr) { 193 | CUtils::dump(3U, "Received a malformed message from DAPNET", buffer, length); 194 | 195 | id = (id + 1U) % 256UL; 196 | 197 | char reply[20U]; 198 | ::snprintf(reply, 20U, "#%02X -\r\n", id); 199 | return write((unsigned char*)reply); 200 | } else { 201 | unsigned int type = ::strtoul(p1, nullptr, 10); 202 | unsigned int addr = ::strtoul(p3, nullptr, 16); 203 | unsigned int func = ::strtoul(p4, nullptr, 10); 204 | 205 | m_message = new CPOCSAGMessage(type, addr, func, (unsigned char*)p5, (unsigned int)::strlen(p5)); 206 | 207 | id = (id + 1U) % 256UL; 208 | 209 | char reply[20U]; 210 | ::snprintf(reply, 20U, "#%02X +\r\n", id); 211 | return write((unsigned char*)reply); 212 | } 213 | } 214 | 215 | bool CDAPNETNetwork::parseSchedule(unsigned char* data) 216 | { 217 | assert(data != nullptr); 218 | 219 | char* p = ::strtok((char*)data + 2U, "\r\n"); 220 | assert(p != nullptr); 221 | 222 | LogMessage("Schedule information received: %s", p); 223 | 224 | delete[] m_schedule; 225 | m_schedule = new bool[16U]; 226 | 227 | for (unsigned int i = 0U; i < 16U; i++) 228 | m_schedule[i] = false; 229 | 230 | if (::strchr(p, '0') != nullptr) 231 | m_schedule[0U] = true; 232 | if (::strchr(p, '1') != nullptr) 233 | m_schedule[1U] = true; 234 | if (::strchr(p, '2') != nullptr) 235 | m_schedule[2U] = true; 236 | if (::strchr(p, '3') != nullptr) 237 | m_schedule[3U] = true; 238 | if (::strchr(p, '4') != nullptr) 239 | m_schedule[4U] = true; 240 | if (::strchr(p, '5') != nullptr) 241 | m_schedule[5U] = true; 242 | if (::strchr(p, '6') != nullptr) 243 | m_schedule[6U] = true; 244 | if (::strchr(p, '7') != nullptr) 245 | m_schedule[7U] = true; 246 | if (::strchr(p, '8') != nullptr) 247 | m_schedule[8U] = true; 248 | if (::strchr(p, '9') != nullptr) 249 | m_schedule[9U] = true; 250 | if (::strchr(p, 'A') != nullptr) 251 | m_schedule[10U] = true; 252 | if (::strchr(p, 'B') != nullptr) 253 | m_schedule[11U] = true; 254 | if (::strchr(p, 'C') != nullptr) 255 | m_schedule[12U] = true; 256 | if (::strchr(p, 'D') != nullptr) 257 | m_schedule[13U] = true; 258 | if (::strchr(p, 'E') != nullptr) 259 | m_schedule[14U] = true; 260 | if (::strchr(p, 'F') != nullptr) 261 | m_schedule[15U] = true; 262 | 263 | return write((unsigned char*)"+\r\n"); 264 | } 265 | 266 | bool CDAPNETNetwork::parseFailedLogin(unsigned char* data) 267 | { 268 | assert(data != nullptr); 269 | 270 | char* p = ::strtok((char*)data + 2U, "\r\n"); 271 | assert(p != nullptr); 272 | 273 | LogMessage("Login failed: %s", p); 274 | 275 | CThread::sleep(BACKOFF[m_failCount]); 276 | if (m_failCount < 9) 277 | m_failCount++; 278 | 279 | return write((unsigned char*)"+\r\n"); 280 | } 281 | -------------------------------------------------------------------------------- /DAPNETNetwork.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef DAPNETNetwork_H 20 | #define DAPNETNetwork_H 21 | 22 | #include "POCSAGMessage.h" 23 | #include "TCPSocket.h" 24 | #include "Timer.h" 25 | 26 | #include 27 | #include 28 | 29 | class CDAPNETNetwork { 30 | public: 31 | CDAPNETNetwork(const std::string& address, unsigned short port, const std::string& callsign, const std::string& authKey, const char* version, bool loggedIn, int failCount, bool debug); 32 | ~CDAPNETNetwork(); 33 | 34 | bool open(); 35 | 36 | bool login(); 37 | 38 | bool read(); 39 | 40 | bool* readSchedule(); 41 | 42 | CPOCSAGMessage* readMessage(); 43 | 44 | void close(); 45 | 46 | private: 47 | CTCPSocket m_socket; 48 | std::string m_callsign; 49 | std::string m_authKey; 50 | const char* m_version; 51 | bool m_loggedIn; 52 | int m_failCount; 53 | bool m_debug; 54 | CPOCSAGMessage* m_message; 55 | bool* m_schedule; 56 | 57 | bool parseMessage(unsigned char* data, unsigned int length); 58 | bool parseSchedule(unsigned char* data); 59 | bool parseFailedLogin(unsigned char* data); 60 | bool write(unsigned char* data); 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Log.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2020,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Log.h" 20 | 21 | #if defined(_WIN32) || defined(_WIN64) 22 | #include 23 | #else 24 | #include 25 | #include 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | static unsigned int m_fileLevel = 2U; 36 | static std::string m_filePath; 37 | static std::string m_fileRoot; 38 | static bool m_fileRotate = true; 39 | 40 | static FILE* m_fpLog = nullptr; 41 | static bool m_daemon = false; 42 | 43 | static unsigned int m_displayLevel = 2U; 44 | 45 | static struct tm m_tm; 46 | 47 | static char LEVELS[] = " DMIWEF"; 48 | 49 | static bool logOpenRotate() 50 | { 51 | bool status = false; 52 | 53 | if (m_fileLevel == 0U) 54 | return true; 55 | 56 | time_t now; 57 | ::time(&now); 58 | 59 | struct tm* tm = ::gmtime(&now); 60 | 61 | if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) { 62 | if (m_fpLog != nullptr) 63 | return true; 64 | } else { 65 | if (m_fpLog != nullptr) 66 | ::fclose(m_fpLog); 67 | } 68 | 69 | char filename[200U]; 70 | #if defined(_WIN32) || defined(_WIN64) 71 | ::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); 72 | #else 73 | ::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); 74 | #endif 75 | 76 | if ((m_fpLog = ::fopen(filename, "a+t")) != nullptr) { 77 | status = true; 78 | 79 | #if !defined(_WIN32) && !defined(_WIN64) 80 | if (m_daemon) 81 | dup2(fileno(m_fpLog), fileno(stderr)); 82 | #endif 83 | } 84 | 85 | m_tm = *tm; 86 | 87 | return status; 88 | } 89 | 90 | static bool logOpenNoRotate() 91 | { 92 | bool status = false; 93 | 94 | if (m_fileLevel == 0U) 95 | return true; 96 | 97 | if (m_fpLog != nullptr) 98 | return true; 99 | 100 | char filename[200U]; 101 | #if defined(_WIN32) || defined(_WIN64) 102 | ::sprintf(filename, "%s\\%s.log", m_filePath.c_str(), m_fileRoot.c_str()); 103 | #else 104 | ::sprintf(filename, "%s/%s.log", m_filePath.c_str(), m_fileRoot.c_str()); 105 | #endif 106 | 107 | if ((m_fpLog = ::fopen(filename, "a+t")) != nullptr) { 108 | status = true; 109 | 110 | #if !defined(_WIN32) && !defined(_WIN64) 111 | if (m_daemon) 112 | dup2(fileno(m_fpLog), fileno(stderr)); 113 | #endif 114 | } 115 | 116 | return status; 117 | } 118 | 119 | bool LogOpen() 120 | { 121 | if (m_fileRotate) 122 | return logOpenRotate(); 123 | else 124 | return logOpenNoRotate(); 125 | } 126 | 127 | bool LogInitialise(bool daemon, const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel, bool rotate) 128 | { 129 | m_filePath = filePath; 130 | m_fileRoot = fileRoot; 131 | m_fileLevel = fileLevel; 132 | m_displayLevel = displayLevel; 133 | m_daemon = daemon; 134 | m_fileRotate = rotate; 135 | 136 | if (m_daemon) 137 | m_displayLevel = 0U; 138 | 139 | return ::LogOpen(); 140 | } 141 | 142 | void LogFinalise() 143 | { 144 | if (m_fpLog != nullptr) 145 | ::fclose(m_fpLog); 146 | } 147 | 148 | void Log(unsigned int level, const char* fmt, ...) 149 | { 150 | assert(fmt != nullptr); 151 | 152 | char buffer[501U]; 153 | #if defined(_WIN32) || defined(_WIN64) 154 | SYSTEMTIME st; 155 | ::GetSystemTime(&st); 156 | 157 | ::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); 158 | #else 159 | struct timeval now; 160 | ::gettimeofday(&now, nullptr); 161 | 162 | struct tm* tm = ::gmtime(&now.tv_sec); 163 | 164 | ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lld ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000LL); 165 | #endif 166 | 167 | va_list vl; 168 | va_start(vl, fmt); 169 | 170 | ::vsnprintf(buffer + ::strlen(buffer), 500 - ::strlen(buffer), fmt, vl); 171 | 172 | va_end(vl); 173 | 174 | if (level >= m_fileLevel && m_fileLevel != 0U) { 175 | bool ret = ::LogOpen(); 176 | if (!ret) 177 | return; 178 | 179 | ::fprintf(m_fpLog, "%s\n", buffer); 180 | ::fflush(m_fpLog); 181 | } 182 | 183 | if (level >= m_displayLevel && m_displayLevel != 0U) { 184 | ::fprintf(stdout, "%s\n", buffer); 185 | ::fflush(stdout); 186 | } 187 | 188 | if (level == 6U) { // Fatal 189 | ::fclose(m_fpLog); 190 | exit(1); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(LOG_H) 20 | #define LOG_H 21 | 22 | #include 23 | 24 | #define LogDebug(fmt, ...) Log(1U, fmt, ##__VA_ARGS__) 25 | #define LogMessage(fmt, ...) Log(2U, fmt, ##__VA_ARGS__) 26 | #define LogInfo(fmt, ...) Log(3U, fmt, ##__VA_ARGS__) 27 | #define LogWarning(fmt, ...) Log(4U, fmt, ##__VA_ARGS__) 28 | #define LogError(fmt, ...) Log(5U, fmt, ##__VA_ARGS__) 29 | #define LogFatal(fmt, ...) Log(6U, fmt, ##__VA_ARGS__) 30 | 31 | extern void Log(unsigned int level, const char* fmt, ...); 32 | 33 | extern bool LogInitialise(bool daemon, const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel, bool rotate); 34 | extern void LogFinalise(); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | CXX = c++ 3 | CFLAGS = -g -O3 -Wall -DHAVE_LOG_H -std=c++0x -pthread 4 | LIBS = -lm -lpthread 5 | LDFLAGS = -g 6 | 7 | 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 8 | 9 | all: DAPNETGateway 10 | 11 | DAPNETGateway: GitVersion.h $(OBJECTS) 12 | $(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o DAPNETGateway 13 | 14 | %.o: %.cpp 15 | $(CXX) $(CFLAGS) -c -o $@ $< 16 | 17 | DAPNETGateway.o: GitVersion.h FORCE 18 | 19 | .PHONY: GitVersion.h 20 | 21 | FORCE: 22 | 23 | 24 | install: 25 | install -m 755 DAPNETGateway /usr/local/bin/ 26 | 27 | clean: 28 | $(RM) DAPNETGateway *.o *.d *.bak *~ 29 | 30 | # Export the current git version if the index file exists, else 000... 31 | GitVersion.h: 32 | ifneq ("$(wildcard .git/index)","") 33 | echo "const char *gitversion = \"$(shell git rev-parse HEAD)\";" > $@ 34 | else 35 | echo "const char *gitversion = \"0000000000000000000000000000000000000000\";" > $@ 36 | endif 37 | 38 | -------------------------------------------------------------------------------- /POCSAGMessage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "POCSAGMessage.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | CPOCSAGMessage::CPOCSAGMessage(unsigned char type, unsigned int ric, unsigned char functional, unsigned char* message, unsigned int length) : 26 | m_type(type), 27 | m_ric(ric), 28 | m_functional(functional), 29 | m_message(nullptr), 30 | m_length(length), 31 | m_timeQueued() 32 | { 33 | assert(functional < 4U); 34 | assert(message != nullptr); 35 | assert(length > 0U); 36 | 37 | m_message = new unsigned char[length + 1]; 38 | 39 | ::memcpy(m_message, message, length); 40 | m_message[length] = '\0'; 41 | 42 | m_timeQueued.start(); 43 | } 44 | 45 | CPOCSAGMessage::~CPOCSAGMessage() 46 | { 47 | delete[] m_message; 48 | } 49 | -------------------------------------------------------------------------------- /POCSAGMessage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef POCSAGMessage_H 20 | #define POCSAGMessage_H 21 | 22 | #include "StopWatch.h" 23 | 24 | #include 25 | #include 26 | 27 | class CPOCSAGMessage { 28 | public: 29 | CPOCSAGMessage(unsigned char type, unsigned int ric, unsigned char functional, unsigned char* message, unsigned int length); 30 | ~CPOCSAGMessage(); 31 | 32 | unsigned char m_type; 33 | unsigned int m_ric; 34 | unsigned char m_functional; 35 | unsigned char* m_message; 36 | unsigned int m_length; 37 | CStopWatch m_timeQueued; 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /POCSAGNetwork.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "POCSAGNetwork.h" 20 | #include "Utils.h" 21 | #include "Log.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | CPOCSAGNetwork::CPOCSAGNetwork(const std::string& localAddress, unsigned short localPort, const std::string& remoteAddress, unsigned short remotePort, bool debug) : 29 | m_socket(localAddress, localPort), 30 | m_addr(), 31 | m_addrLen(0U), 32 | m_debug(debug) 33 | { 34 | assert(!remoteAddress.empty()); 35 | assert(remotePort > 0U); 36 | 37 | if (CUDPSocket::lookup(remoteAddress, remotePort, m_addr, m_addrLen) != 0) 38 | m_addrLen = 0U; 39 | } 40 | 41 | CPOCSAGNetwork::~CPOCSAGNetwork() 42 | { 43 | } 44 | 45 | bool CPOCSAGNetwork::open() 46 | { 47 | if (m_addrLen == 0U) { 48 | LogError("Unable to resolve the address of the host"); 49 | return false; 50 | } 51 | 52 | LogMessage("Opening POCSAG network connection"); 53 | 54 | return m_socket.open(m_addr); 55 | } 56 | 57 | bool CPOCSAGNetwork::write(CPOCSAGMessage* message) 58 | { 59 | assert(message != nullptr); 60 | 61 | unsigned char data[200U]; 62 | data[0U] = 'P'; 63 | data[1U] = 'O'; 64 | data[2U] = 'C'; 65 | data[3U] = 'S'; 66 | data[4U] = 'A'; 67 | data[5U] = 'G'; 68 | 69 | data[6U] = message->m_ric >> 16; 70 | data[7U] = message->m_ric >> 8; 71 | data[8U] = message->m_ric >> 0; 72 | 73 | data[9U] = message->m_functional; 74 | 75 | ::memcpy(data + 10U, message->m_message, message->m_length); 76 | 77 | if (m_debug) 78 | CUtils::dump(1U, "POCSAG Network Data Sent", data, message->m_length + 10U); 79 | 80 | return m_socket.write(data, message->m_length + 10U, m_addr, m_addrLen); 81 | } 82 | 83 | unsigned int CPOCSAGNetwork::read(unsigned char* data) 84 | { 85 | assert(data != nullptr); 86 | 87 | sockaddr_storage address; 88 | unsigned int addrLen; 89 | int length = m_socket.read(data, 1U, address, addrLen); 90 | if (length <= 0) 91 | return 0U; 92 | 93 | if (!CUDPSocket::match(address, m_addr)) { 94 | LogWarning("Received a packet from an unknown address"); 95 | return 0U; 96 | } 97 | 98 | if (m_debug) 99 | CUtils::dump(1U, "POCSAG Network Data Received", data, length); 100 | 101 | return length; 102 | } 103 | 104 | void CPOCSAGNetwork::close() 105 | { 106 | m_socket.close(); 107 | 108 | LogMessage("Closing POCSAG network connection"); 109 | } 110 | -------------------------------------------------------------------------------- /POCSAGNetwork.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef POCSAGNetwork_H 20 | #define POCSAGNetwork_H 21 | 22 | #include "POCSAGMessage.h" 23 | #include "UDPSocket.h" 24 | 25 | #include 26 | #include 27 | 28 | class CPOCSAGNetwork { 29 | public: 30 | CPOCSAGNetwork(const std::string& localAddress, unsigned short localPort, const std::string& remoteAddress, unsigned short remotePort, bool debug); 31 | ~CPOCSAGNetwork(); 32 | 33 | bool open(); 34 | 35 | bool write(CPOCSAGMessage* message); 36 | 37 | unsigned int read(unsigned char* data); 38 | 39 | void close(); 40 | 41 | private: 42 | CUDPSocket m_socket; 43 | sockaddr_storage m_addr; 44 | unsigned int m_addrLen; 45 | bool m_debug; 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /README.REGEX: -------------------------------------------------------------------------------- 1 | The REGEX functionality allows you to whitelist or blacklist messages based on 2 | pattern matching the message body itself (not the RIC). 3 | 4 | The options for this are: 5 | 6 | 7 | WhitelistRegexfile= 8 | BlacklistRegexfile= 9 | 10 | REGEXs are placed in the the respective textfile, one per line. These need to 11 | be a full match, i.e. they need to match the entire message, begining with ^ 12 | and ending with $. 13 | 14 | For example: 15 | 16 | ^NAGIOS.*$ - matches all Nagios alerts 17 | 18 | ^(G|M|2).+$ - Matches messages with the first character as a G.M or 2 - i.e. 19 | match all UK callsigns. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The DAPNET Gateway links the MMDVM Host to the DAPNET network to receive paging messages. 2 | 3 | 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. 4 | 5 | They build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2017 on x86 and x64. 6 | 7 | This software is licenced under the GPL v2 and is primarily intended for amateur and educational use. 8 | -------------------------------------------------------------------------------- /REGEX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016,2017,2025 by Jonathan Naylor G4KLX 3 | * 4 | * Contributed by Simon G7RZU 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #include "REGEX.h" 22 | #include "Log.h" 23 | 24 | #include 25 | #include 26 | 27 | 28 | CREGEX::CREGEX(const std::string& regexFile) : 29 | m_regexFile(regexFile), 30 | m_regex() 31 | { 32 | } 33 | 34 | bool CREGEX::load() 35 | /* Older versions of GCC appear to support REGEX but silently fail to match. Even 36 | * though the headers exist and the code compiles and runs cleanly. The below #if block 37 | * stops the REGEXs being loaded in these cases, effectively disabling REGEX functionality. 38 | * The loading code is replaced with a log entry to show the user this has happened. 39 | */ 40 | { 41 | #if defined(__GNUC__) && (__GNUC__ <= 4) || (__GNUC__ == 4 && __GNUC_MINOR__ <= 9) 42 | LogError("REGEX is not properly supported in GCC versions below 4.9. Not loading REGEX"); 43 | return false; 44 | } 45 | #else 46 | 47 | 48 | FILE* fp = ::fopen(m_regexFile.c_str(), "rt"); 49 | if (fp != nullptr) { 50 | char buffer[100U]; 51 | std::regex regex; 52 | while (::fgets(buffer, 100U, fp) != nullptr) { 53 | if (buffer[0U] == '#') 54 | continue; 55 | 56 | const char* regexStr = ::strtok(buffer, "\r\n"); 57 | 58 | if (regexStr != nullptr) { 59 | try { 60 | regex = std::regex(regexStr); 61 | } 62 | 63 | catch(const std::regex_error& e) { 64 | LogDebug("error in regex %s (%s), skipping",regexStr,e.what()); 65 | continue; 66 | } 67 | m_regex.push_back(regex); 68 | } 69 | } 70 | 71 | ::fclose(fp); 72 | } 73 | 74 | size_t size = m_regex.size(); 75 | LogInfo("Loaded %u REGEX from file %s", size, m_regexFile.c_str()); 76 | 77 | size = m_regex.size(); 78 | if (size == 0U) 79 | return false; 80 | 81 | return true; 82 | } 83 | 84 | #endif 85 | 86 | std::vector CREGEX::get() 87 | { 88 | return(m_regex); 89 | } 90 | -------------------------------------------------------------------------------- /REGEX.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016,2017 by Jonathan Naylor G4KLX 3 | * 4 | * Contributed by Simon G7RZU 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | */ 20 | 21 | #if !defined(REGEX_H) 22 | #define REGEX_H 23 | 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | class CREGEX { 30 | public: 31 | 32 | std::vector get(); 33 | bool load(); 34 | CREGEX(const std::string& regexFile); 35 | ~CREGEX(); 36 | 37 | private: 38 | 39 | std::string m_regexFile; 40 | std::vector m_regex; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /StopWatch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2018,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "StopWatch.h" 20 | 21 | #if defined(_WIN32) || defined(_WIN64) 22 | 23 | CStopWatch::CStopWatch() : 24 | m_frequencyS(), 25 | m_frequencyMS(), 26 | m_start() 27 | { 28 | ::QueryPerformanceFrequency(&m_frequencyS); 29 | 30 | m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL; 31 | } 32 | 33 | CStopWatch::~CStopWatch() 34 | { 35 | } 36 | 37 | unsigned long long CStopWatch::time() const 38 | { 39 | LARGE_INTEGER now; 40 | ::QueryPerformanceCounter(&now); 41 | 42 | return (unsigned long long)(now.QuadPart / m_frequencyMS.QuadPart); 43 | } 44 | 45 | unsigned long long CStopWatch::start() 46 | { 47 | ::QueryPerformanceCounter(&m_start); 48 | 49 | return (unsigned long long)(m_start.QuadPart / m_frequencyS.QuadPart); 50 | } 51 | 52 | unsigned int CStopWatch::elapsed() 53 | { 54 | LARGE_INTEGER now; 55 | ::QueryPerformanceCounter(&now); 56 | 57 | LARGE_INTEGER temp; 58 | temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000; 59 | 60 | return (unsigned int)(temp.QuadPart / m_frequencyS.QuadPart); 61 | } 62 | 63 | #else 64 | 65 | #include 66 | #include 67 | 68 | CStopWatch::CStopWatch() : 69 | m_startMS(0ULL) 70 | { 71 | } 72 | 73 | CStopWatch::~CStopWatch() 74 | { 75 | } 76 | 77 | unsigned long long CStopWatch::time() const 78 | { 79 | struct timeval now; 80 | ::gettimeofday(&now, nullptr); 81 | 82 | return now.tv_sec * 1000ULL + now.tv_usec / 1000ULL; 83 | } 84 | 85 | unsigned long long CStopWatch::start() 86 | { 87 | struct timespec now; 88 | ::clock_gettime(CLOCK_MONOTONIC, &now); 89 | 90 | m_startMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL; 91 | 92 | return m_startMS; 93 | } 94 | 95 | unsigned int CStopWatch::elapsed() 96 | { 97 | struct timespec now; 98 | ::clock_gettime(CLOCK_MONOTONIC, &now); 99 | 100 | unsigned long long nowMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL; 101 | 102 | return nowMS - m_startMS; 103 | } 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /StopWatch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(STOPWATCH_H) 20 | #define STOPWATCH_H 21 | 22 | #if defined(_WIN32) || defined(_WIN64) 23 | #define _WINSOCKAPI_ 24 | #include 25 | #include 26 | #else 27 | #include 28 | #endif 29 | 30 | class CStopWatch 31 | { 32 | public: 33 | CStopWatch(); 34 | ~CStopWatch(); 35 | 36 | unsigned long long time() const; 37 | 38 | unsigned long long start(); 39 | unsigned int elapsed(); 40 | 41 | private: 42 | #if defined(_WIN32) || defined(_WIN64) 43 | LARGE_INTEGER m_frequencyS; 44 | LARGE_INTEGER m_frequencyMS; 45 | LARGE_INTEGER m_start; 46 | #else 47 | unsigned long long m_startMS; 48 | #endif 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /TCPSocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2013,2016,2018,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "TCPSocket.h" 20 | #include "UDPSocket.h" 21 | #include "Log.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #if defined(_WIN32) || defined(_WIN64) 28 | typedef int ssize_t; 29 | #else 30 | #include 31 | #endif 32 | 33 | CTCPSocket::CTCPSocket(const std::string& address, unsigned int port) : 34 | m_address(address), 35 | m_port(port), 36 | #if defined(_WIN32) || defined(_WIN64) 37 | m_fd(INVALID_SOCKET) 38 | #else 39 | m_fd(-1) 40 | #endif 41 | { 42 | assert(!address.empty()); 43 | assert(port > 0U); 44 | 45 | #if defined(_WIN32) || defined(_WIN64) 46 | WSAData data; 47 | int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); 48 | if (wsaRet != 0) 49 | LogError("Error from WSAStartup"); 50 | #endif 51 | } 52 | 53 | CTCPSocket::~CTCPSocket() 54 | { 55 | #if defined(_WIN32) || defined(_WIN64) 56 | ::WSACleanup(); 57 | #endif 58 | } 59 | 60 | bool CTCPSocket::open() 61 | { 62 | #if defined(_WIN32) || defined(_WIN64) 63 | if (m_fd != INVALID_SOCKET) 64 | return true; 65 | #else 66 | if (m_fd != -1) 67 | return true; 68 | #endif 69 | 70 | if (m_address.empty() || m_port == 0U) 71 | return false; 72 | 73 | /* to determine protocol family, call lookup() first.*/ 74 | sockaddr_storage addr; 75 | unsigned int addrlen; 76 | if (CUDPSocket::lookup(m_address, m_port, addr, addrlen) != 0) 77 | return false; 78 | 79 | m_fd = ::socket(addr.ss_family, SOCK_STREAM, 0); 80 | if (m_fd < 0) { 81 | #if defined(_WIN32) || defined(_WIN64) 82 | LogError("Cannot create the TCP client socket, err=%d", ::GetLastError()); 83 | #else 84 | LogError("Cannot create the TCP client socket, err=%d", errno); 85 | #endif 86 | return false; 87 | } 88 | 89 | if (::connect(m_fd, (sockaddr*)&addr, addrlen) == -1) { 90 | #if defined(_WIN32) || defined(_WIN64) 91 | LogError("Cannot connect the TCP client socket, err=%d", ::GetLastError()); 92 | #else 93 | LogError("Cannot connect the TCP client socket, err=%d", errno); 94 | #endif 95 | close(); 96 | return false; 97 | } 98 | 99 | int noDelay = 1; 100 | if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&noDelay, sizeof(noDelay)) == -1) { 101 | #if defined(_WIN32) || defined(_WIN64) 102 | LogError("Cannot set the TCP client socket option for TCP_NODELAY, err=%d", ::GetLastError()); 103 | #else 104 | LogError("Cannot set the TCP client socket option for TCP_NODELAY, err=%d", errno); 105 | #endif 106 | close(); 107 | return false; 108 | } 109 | 110 | int keepAlive = 1; 111 | if (::setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepAlive, sizeof(keepAlive)) == -1) { 112 | #if defined(_WIN32) || defined(_WIN64) 113 | LogError("Cannot set the TCP client socket option for SO_KEEPALIVE, err=%d", ::GetLastError()); 114 | #else 115 | LogError("Cannot set the TCP client socket option for SO_KEEPALIVE, err=%d", errno); 116 | #endif 117 | close(); 118 | return false; 119 | } 120 | 121 | return true; 122 | } 123 | 124 | int CTCPSocket::read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs) 125 | { 126 | assert(buffer != nullptr); 127 | assert(length > 0U); 128 | #if defined(_WIN32) || defined(_WIN64) 129 | assert(m_fd != INVALID_SOCKET); 130 | #else 131 | assert(m_fd != -1); 132 | #endif 133 | 134 | // Check that the recv() won't block 135 | fd_set readFds; 136 | FD_ZERO(&readFds); 137 | #if defined(_WIN32) || defined(_WIN64) 138 | FD_SET((unsigned int)m_fd, &readFds); 139 | #else 140 | FD_SET(m_fd, &readFds); 141 | #endif 142 | 143 | // Return after timeout 144 | timeval tv; 145 | tv.tv_sec = secs; 146 | tv.tv_usec = msecs * 1000; 147 | 148 | int ret = ::select(m_fd + 1, &readFds, nullptr, nullptr, &tv); 149 | if (ret < 0) { 150 | #if defined(_WIN32) || defined(_WIN64) 151 | LogError("Error returned from TCP client select, err=%d", ::GetLastError()); 152 | #else 153 | LogError("Error returned from TCP client select, err=%d", errno); 154 | #endif 155 | return -1; 156 | } 157 | 158 | #if defined(_WIN32) || defined(_WIN64) 159 | if (!FD_ISSET((unsigned int)m_fd, &readFds)) 160 | return 0; 161 | #else 162 | if (!FD_ISSET(m_fd, &readFds)) 163 | return 0; 164 | #endif 165 | 166 | ssize_t len = ::recv(m_fd, (char*)buffer, length, 0); 167 | if (len == 0) { 168 | return -2; 169 | } else if (len < 0) { 170 | #if defined(_WIN32) || defined(_WIN64) 171 | LogError("Error returned from recv, err=%d", ::GetLastError()); 172 | #else 173 | LogError("Error returned from recv, err=%d", errno); 174 | #endif 175 | return -1; 176 | } 177 | 178 | return len; 179 | } 180 | 181 | int CTCPSocket::readLine(std::string& line, unsigned int secs) 182 | { 183 | // Maybe there is a better way to do this like reading blocks, pushing them for later calls 184 | // Nevermind, we'll read one char at a time for the time being. 185 | int resultCode; 186 | int len = 0; 187 | 188 | line.clear(); 189 | 190 | char c[2U]; 191 | c[1U] = 0x00U; 192 | 193 | do 194 | { 195 | resultCode = read((unsigned char*)c, 1U, secs); 196 | if (resultCode == 1){ 197 | line.append(c); 198 | len++; 199 | } 200 | } while (c[0U] != '\n' && resultCode == 1); 201 | 202 | return resultCode <= 0 ? resultCode : len; 203 | } 204 | 205 | bool CTCPSocket::write(const unsigned char* buffer, unsigned int length) 206 | { 207 | assert(buffer != nullptr); 208 | assert(length > 0U); 209 | #if defined(_WIN32) || defined(_WIN64) 210 | assert(m_fd != INVALID_SOCKET); 211 | #else 212 | assert(m_fd != -1); 213 | #endif 214 | 215 | ssize_t ret = ::send(m_fd, (char *)buffer, length, 0); 216 | if (ret != ssize_t(length)) { 217 | #if defined(_WIN32) || defined(_WIN64) 218 | LogError("Error returned from send, err=%d", ::GetLastError()); 219 | #else 220 | LogError("Error returned from send, err=%d", errno); 221 | #endif 222 | return false; 223 | } 224 | 225 | return true; 226 | } 227 | 228 | bool CTCPSocket::writeLine(const std::string& line) 229 | { 230 | std::string lineCopy(line); 231 | if (lineCopy.length() > 0 && lineCopy.at(lineCopy.length() - 1) != '\n') 232 | lineCopy.append("\n"); 233 | 234 | //stupidly write one char after the other 235 | size_t len = lineCopy.length(); 236 | bool result = true; 237 | for (size_t i = 0U; i < len && result; i++){ 238 | unsigned char c = lineCopy.at(i); 239 | result = write(&c , 1); 240 | } 241 | 242 | return result; 243 | } 244 | 245 | void CTCPSocket::close() 246 | { 247 | #if defined(_WIN32) || defined(_WIN64) 248 | if (m_fd != INVALID_SOCKET) { 249 | ::closesocket(m_fd); 250 | m_fd = INVALID_SOCKET; 251 | } 252 | #else 253 | if (m_fd != -1) { 254 | ::close(m_fd); 255 | m_fd = -1; 256 | } 257 | #endif 258 | } 259 | -------------------------------------------------------------------------------- /TCPSocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010,2011,2012,2013,2016,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef TCPSocket_H 20 | #define TCPSocket_H 21 | 22 | #if !defined(_WIN32) && !defined(_WIN64) 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #else 33 | #include 34 | #include 35 | #endif 36 | 37 | #include 38 | 39 | class CTCPSocket { 40 | public: 41 | CTCPSocket(const std::string& address, unsigned int port); 42 | ~CTCPSocket(); 43 | 44 | bool open(); 45 | 46 | int read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs = 0U); 47 | int readLine(std::string& line, unsigned int secs); 48 | bool write(const unsigned char* buffer, unsigned int length); 49 | bool writeLine(const std::string& line); 50 | 51 | void close(); 52 | 53 | private: 54 | std::string m_address; 55 | unsigned short m_port; 56 | #if defined(_WIN32) || defined(_WIN64) 57 | SOCKET m_fd; 58 | #else 59 | int m_fd; 60 | #endif 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Thread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016,2020,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Thread.h" 20 | 21 | #if defined(_WIN32) || defined(_WIN64) 22 | 23 | CThread::CThread() : 24 | m_handle() 25 | { 26 | } 27 | 28 | CThread::~CThread() 29 | { 30 | } 31 | 32 | bool CThread::run() 33 | { 34 | m_handle = ::CreateThread(nullptr, 0, &helper, this, 0, nullptr); 35 | 36 | return m_handle != nullptr; 37 | } 38 | 39 | 40 | void CThread::wait() 41 | { 42 | ::WaitForSingleObject(m_handle, INFINITE); 43 | 44 | ::CloseHandle(m_handle); 45 | } 46 | 47 | 48 | DWORD CThread::helper(LPVOID arg) 49 | { 50 | CThread* p = (CThread*)arg; 51 | 52 | p->entry(); 53 | 54 | return 0UL; 55 | } 56 | 57 | void CThread::sleep(unsigned int ms) 58 | { 59 | ::Sleep(ms); 60 | } 61 | 62 | #else 63 | 64 | #include 65 | 66 | CThread::CThread() : 67 | m_thread() 68 | { 69 | } 70 | 71 | CThread::~CThread() 72 | { 73 | } 74 | 75 | bool CThread::run() 76 | { 77 | return ::pthread_create(&m_thread, nullptr, helper, this) == 0; 78 | } 79 | 80 | 81 | void CThread::wait() 82 | { 83 | ::pthread_join(m_thread, nullptr); 84 | } 85 | 86 | 87 | void* CThread::helper(void* arg) 88 | { 89 | CThread* p = (CThread*)arg; 90 | 91 | p->entry(); 92 | 93 | return nullptr; 94 | } 95 | 96 | void CThread::sleep(unsigned int ms) 97 | { 98 | struct timespec ts; 99 | 100 | ts.tv_sec = ms / 1000U; 101 | ts.tv_nsec = (ms % 1000U) * 1000000U; 102 | 103 | ::nanosleep(&ts, nullptr); 104 | } 105 | 106 | #endif 107 | 108 | -------------------------------------------------------------------------------- /Thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(THREAD_H) 20 | #define THREAD_H 21 | 22 | #if defined(_WIN32) || defined(_WIN64) 23 | #include 24 | #else 25 | #include 26 | #endif 27 | 28 | class CThread 29 | { 30 | public: 31 | CThread(); 32 | virtual ~CThread(); 33 | 34 | virtual bool run(); 35 | 36 | virtual void entry() = 0; 37 | 38 | virtual void wait(); 39 | 40 | static void sleep(unsigned int ms); 41 | 42 | private: 43 | #if defined(_WIN32) || defined(_WIN64) 44 | HANDLE m_handle; 45 | #else 46 | pthread_t m_thread; 47 | #endif 48 | 49 | #if defined(_WIN32) || defined(_WIN64) 50 | static DWORD __stdcall helper(LPVOID arg); 51 | #else 52 | static void* helper(void* arg); 53 | #endif 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /Timer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2015 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "Timer.h" 20 | 21 | #include 22 | #include 23 | 24 | CTimer::CTimer(unsigned int ticksPerSec, unsigned int secs, unsigned int msecs) : 25 | m_ticksPerSec(ticksPerSec), 26 | m_timeout(0U), 27 | m_timer(0U) 28 | { 29 | assert(ticksPerSec > 0U); 30 | 31 | if (secs > 0U || msecs > 0U) { 32 | // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; 33 | unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec; 34 | m_timeout = (unsigned int)(temp / 1000ULL + 1ULL); 35 | } 36 | } 37 | 38 | CTimer::~CTimer() 39 | { 40 | } 41 | 42 | void CTimer::setTimeout(unsigned int secs, unsigned int msecs) 43 | { 44 | if (secs > 0U || msecs > 0U) { 45 | // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; 46 | unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec; 47 | m_timeout = (unsigned int)(temp / 1000ULL + 1ULL); 48 | } else { 49 | m_timeout = 0U; 50 | m_timer = 0U; 51 | } 52 | } 53 | 54 | unsigned int CTimer::getTimeout() const 55 | { 56 | if (m_timeout == 0U) 57 | return 0U; 58 | 59 | return (m_timeout - 1U) / m_ticksPerSec; 60 | } 61 | 62 | unsigned int CTimer::getTimer() const 63 | { 64 | if (m_timer == 0U) 65 | return 0U; 66 | 67 | return (m_timer - 1U) / m_ticksPerSec; 68 | } 69 | -------------------------------------------------------------------------------- /Timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2010,2011,2014 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef Timer_H 20 | #define Timer_H 21 | 22 | class CTimer { 23 | public: 24 | CTimer(unsigned int ticksPerSec, unsigned int secs = 0U, unsigned int msecs = 0U); 25 | ~CTimer(); 26 | 27 | void setTimeout(unsigned int secs, unsigned int msecs = 0U); 28 | 29 | unsigned int getTimeout() const; 30 | unsigned int getTimer() const; 31 | 32 | unsigned int getRemaining() 33 | { 34 | if (m_timeout == 0U || m_timer == 0U) 35 | return 0U; 36 | 37 | if (m_timer >= m_timeout) 38 | return 0U; 39 | 40 | return (m_timeout - m_timer) / m_ticksPerSec; 41 | } 42 | 43 | bool isRunning() 44 | { 45 | return m_timer > 0U; 46 | } 47 | 48 | void start(unsigned int secs, unsigned int msecs = 0U) 49 | { 50 | setTimeout(secs, msecs); 51 | 52 | start(); 53 | } 54 | 55 | void start() 56 | { 57 | if (m_timeout > 0U) 58 | m_timer = 1U; 59 | } 60 | 61 | void stop() 62 | { 63 | m_timer = 0U; 64 | } 65 | 66 | bool hasExpired() 67 | { 68 | if (m_timeout == 0U || m_timer == 0U) 69 | return false; 70 | 71 | if (m_timer >= m_timeout) 72 | return true; 73 | 74 | return false; 75 | } 76 | 77 | void clock(unsigned int ticks = 1U) 78 | { 79 | if (m_timer > 0U && m_timeout > 0U) 80 | m_timer += ticks; 81 | } 82 | 83 | private: 84 | unsigned int m_ticksPerSec; 85 | unsigned int m_timeout; 86 | unsigned int m_timer; 87 | }; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /UDPSocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006-2016,2020,2024,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include "UDPSocket.h" 20 | 21 | #include 22 | 23 | #if !defined(_WIN32) && !defined(_WIN64) 24 | #include 25 | #include 26 | #endif 27 | 28 | #if defined(HAVE_LOG_H) 29 | #include "Log.h" 30 | #else 31 | #define LogMessage(fmt, ...) ::fprintf(stderr, fmt "\n", ## __VA_ARGS__) 32 | #define LogError(fmt, ...) ::fprintf(stderr, fmt "\n", ## __VA_ARGS__) 33 | #define LogInfo(fmt, ...) ::fprintf(stderr, fmt "\n", ## __VA_ARGS__) 34 | #endif 35 | 36 | CUDPSocket::CUDPSocket(const std::string& address, unsigned short port) : 37 | m_localAddress(address), 38 | m_localPort(port), 39 | m_fd(-1), 40 | m_af(AF_UNSPEC) 41 | { 42 | } 43 | 44 | CUDPSocket::CUDPSocket(unsigned short port) : 45 | m_localAddress(), 46 | m_localPort(port), 47 | m_fd(-1), 48 | m_af(AF_UNSPEC) 49 | { 50 | } 51 | 52 | CUDPSocket::~CUDPSocket() 53 | { 54 | } 55 | 56 | void CUDPSocket::startup() 57 | { 58 | #if defined(_WIN32) || defined(_WIN64) 59 | WSAData data; 60 | int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); 61 | if (wsaRet != 0) 62 | LogError("Error from WSAStartup"); 63 | #endif 64 | } 65 | 66 | void CUDPSocket::shutdown() 67 | { 68 | #if defined(_WIN32) || defined(_WIN64) 69 | ::WSACleanup(); 70 | #endif 71 | } 72 | 73 | int CUDPSocket::lookup(const std::string& hostname, unsigned short port, sockaddr_storage& addr, unsigned int& address_length) 74 | { 75 | struct addrinfo hints; 76 | ::memset(&hints, 0, sizeof(hints)); 77 | 78 | return lookup(hostname, port, addr, address_length, hints); 79 | } 80 | 81 | int CUDPSocket::lookup(const std::string& hostname, unsigned short port, sockaddr_storage& addr, unsigned int& address_length, struct addrinfo& hints) 82 | { 83 | std::string portstr = std::to_string(port); 84 | struct addrinfo *res; 85 | 86 | /* Port is always digits, no needs to lookup service */ 87 | hints.ai_flags |= AI_NUMERICSERV; 88 | 89 | int err = ::getaddrinfo(hostname.empty() ? nullptr : hostname.c_str(), portstr.c_str(), &hints, &res); 90 | if (err != 0) { 91 | sockaddr_in* paddr = (sockaddr_in*)&addr; 92 | ::memset(paddr, 0x00U, address_length = sizeof(sockaddr_in)); 93 | paddr->sin_family = AF_INET; 94 | paddr->sin_port = htons(port); 95 | paddr->sin_addr.s_addr = htonl(INADDR_NONE); 96 | LogError("Cannot find address for host %s", hostname.c_str()); 97 | return err; 98 | } 99 | 100 | ::memcpy(&addr, res->ai_addr, address_length = (unsigned int)res->ai_addrlen); 101 | 102 | ::freeaddrinfo(res); 103 | 104 | return 0; 105 | } 106 | 107 | bool CUDPSocket::match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type) 108 | { 109 | if (addr1.ss_family != addr2.ss_family) 110 | return false; 111 | 112 | if (type == IPMATCHTYPE::ADDRESS_AND_PORT) { 113 | switch (addr1.ss_family) { 114 | case AF_INET: 115 | struct sockaddr_in *in_1, *in_2; 116 | in_1 = (struct sockaddr_in*)&addr1; 117 | in_2 = (struct sockaddr_in*)&addr2; 118 | return (in_1->sin_addr.s_addr == in_2->sin_addr.s_addr) && (in_1->sin_port == in_2->sin_port); 119 | case AF_INET6: 120 | struct sockaddr_in6 *in6_1, *in6_2; 121 | in6_1 = (struct sockaddr_in6*)&addr1; 122 | in6_2 = (struct sockaddr_in6*)&addr2; 123 | return IN6_ARE_ADDR_EQUAL(&in6_1->sin6_addr, &in6_2->sin6_addr) && (in6_1->sin6_port == in6_2->sin6_port); 124 | default: 125 | return false; 126 | } 127 | } else if (type == IPMATCHTYPE::ADDRESS_ONLY) { 128 | switch (addr1.ss_family) { 129 | case AF_INET: 130 | struct sockaddr_in *in_1, *in_2; 131 | in_1 = (struct sockaddr_in*)&addr1; 132 | in_2 = (struct sockaddr_in*)&addr2; 133 | return in_1->sin_addr.s_addr == in_2->sin_addr.s_addr; 134 | case AF_INET6: 135 | struct sockaddr_in6 *in6_1, *in6_2; 136 | in6_1 = (struct sockaddr_in6*)&addr1; 137 | in6_2 = (struct sockaddr_in6*)&addr2; 138 | return IN6_ARE_ADDR_EQUAL(&in6_1->sin6_addr, &in6_2->sin6_addr); 139 | default: 140 | return false; 141 | } 142 | } else { 143 | return false; 144 | } 145 | } 146 | 147 | bool CUDPSocket::isNone(const sockaddr_storage& addr) 148 | { 149 | struct sockaddr_in *in = (struct sockaddr_in *)&addr; 150 | 151 | return ((addr.ss_family == AF_INET) && (in->sin_addr.s_addr == htonl(INADDR_NONE))); 152 | } 153 | 154 | bool CUDPSocket::open(const sockaddr_storage& address) 155 | { 156 | m_af = address.ss_family; 157 | 158 | return open(); 159 | } 160 | 161 | bool CUDPSocket::open() 162 | { 163 | assert(m_fd == -1); 164 | 165 | sockaddr_storage addr; 166 | unsigned int addrlen; 167 | struct addrinfo hints; 168 | 169 | ::memset(&hints, 0, sizeof(hints)); 170 | hints.ai_flags = AI_PASSIVE; 171 | hints.ai_family = m_af; 172 | 173 | // To determine protocol family, call lookup() on the local address first. 174 | int err = lookup(m_localAddress, m_localPort, addr, addrlen, hints); 175 | if (err != 0) { 176 | LogError("The local address is invalid - %s", m_localAddress.c_str()); 177 | return false; 178 | } 179 | 180 | m_af = addr.ss_family; 181 | 182 | m_fd = ::socket(m_af, SOCK_DGRAM, 0); 183 | if (m_fd < 0) { 184 | #if defined(_WIN32) || defined(_WIN64) 185 | LogError("Cannot create the UDP socket, err: %lu", ::GetLastError()); 186 | #else 187 | LogError("Cannot create the UDP socket, err: %d", errno); 188 | #endif 189 | return false; 190 | } 191 | 192 | if (m_localPort > 0U) { 193 | int reuse = 1; 194 | if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { 195 | #if defined(_WIN32) || defined(_WIN64) 196 | LogError("Cannot set the UDP socket option, err: %lu", ::GetLastError()); 197 | #else 198 | LogError("Cannot set the UDP socket option, err: %d", errno); 199 | #endif 200 | close(); 201 | return false; 202 | } 203 | 204 | if (::bind(m_fd, (sockaddr*)&addr, addrlen) == -1) { 205 | #if defined(_WIN32) || defined(_WIN64) 206 | LogError("Cannot bind the UDP address, err: %lu", ::GetLastError()); 207 | #else 208 | LogError("Cannot bind the UDP address, err: %d", errno); 209 | #endif 210 | close(); 211 | return false; 212 | } 213 | 214 | LogInfo("Opening UDP port on %hu", m_localPort); 215 | } 216 | 217 | return true; 218 | } 219 | 220 | int CUDPSocket::read(unsigned char* buffer, unsigned int length, sockaddr_storage& address, unsigned int &addressLength) 221 | { 222 | assert(buffer != nullptr); 223 | assert(length > 0U); 224 | assert(m_fd >= 0); 225 | 226 | // Check that the readfrom() won't block 227 | struct pollfd pfd; 228 | pfd.fd = m_fd; 229 | pfd.events = POLLIN; 230 | pfd.revents = 0; 231 | 232 | // Return immediately 233 | #if defined(_WIN32) || defined(_WIN64) 234 | int ret = WSAPoll(&pfd, 1, 0); 235 | #else 236 | int ret = ::poll(&pfd, 1, 0); 237 | #endif 238 | if (ret < 0) { 239 | #if defined(_WIN32) || defined(_WIN64) 240 | LogError("Error returned from UDP poll, err: %lu", ::GetLastError()); 241 | #else 242 | LogError("Error returned from UDP poll, err: %d", errno); 243 | #endif 244 | return -1; 245 | } 246 | 247 | if ((pfd.revents & POLLIN) == 0) 248 | return 0; 249 | 250 | #if defined(_WIN32) || defined(_WIN64) 251 | int size = sizeof(sockaddr_storage); 252 | #else 253 | socklen_t size = sizeof(sockaddr_storage); 254 | #endif 255 | 256 | #if defined(_WIN32) || defined(_WIN64) 257 | int len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&address, &size); 258 | #else 259 | ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&address, &size); 260 | #endif 261 | if (len <= 0) { 262 | #if defined(_WIN32) || defined(_WIN64) 263 | LogError("Error returned from recvfrom, err: %lu", ::GetLastError()); 264 | #else 265 | LogError("Error returned from recvfrom, err: %d", errno); 266 | 267 | if (len == -1 && errno == ENOTSOCK) { 268 | LogMessage("Re-opening UDP port on %hu", m_localPort); 269 | close(); 270 | open(); 271 | } 272 | #endif 273 | return -1; 274 | } 275 | 276 | addressLength = size; 277 | 278 | return len; 279 | } 280 | 281 | bool CUDPSocket::write(const unsigned char* buffer, unsigned int length, const sockaddr_storage& address, unsigned int addressLength) 282 | { 283 | assert(buffer != nullptr); 284 | assert(length > 0U); 285 | assert(m_fd >= 0); 286 | 287 | bool result = false; 288 | 289 | #if defined(_WIN32) || defined(_WIN64) 290 | int ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&address, addressLength); 291 | #else 292 | ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&address, addressLength); 293 | #endif 294 | 295 | if (ret < 0) { 296 | #if defined(_WIN32) || defined(_WIN64) 297 | LogError("Error returned from sendto, err: %lu", ::GetLastError()); 298 | #else 299 | LogError("Error returned from sendto, err: %d", errno); 300 | #endif 301 | } else { 302 | #if defined(_WIN32) || defined(_WIN64) 303 | if (ret == int(length)) 304 | result = true; 305 | #else 306 | if (ret == ssize_t(length)) 307 | result = true; 308 | #endif 309 | } 310 | 311 | return result; 312 | } 313 | 314 | void CUDPSocket::close() 315 | { 316 | if (m_fd >= 0) { 317 | #if defined(_WIN32) || defined(_WIN64) 318 | ::closesocket(m_fd); 319 | #else 320 | ::close(m_fd); 321 | #endif 322 | m_fd = -1; 323 | } 324 | } 325 | 326 | -------------------------------------------------------------------------------- /UDPSocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2011,2013,2015,2016,2020,2024 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #ifndef UDPSocket_H 20 | #define UDPSocket_H 21 | 22 | #include 23 | 24 | #if !defined(_WIN32) && !defined(_WIN64) 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #else 35 | #include 36 | #endif 37 | 38 | enum class IPMATCHTYPE { 39 | ADDRESS_AND_PORT, 40 | ADDRESS_ONLY 41 | }; 42 | 43 | class CUDPSocket { 44 | public: 45 | CUDPSocket(const std::string& address, unsigned short port = 0U); 46 | CUDPSocket(unsigned short port = 0U); 47 | ~CUDPSocket(); 48 | 49 | bool open(); 50 | bool open(const sockaddr_storage& address); 51 | 52 | int read(unsigned char* buffer, unsigned int length, sockaddr_storage& address, unsigned int &addressLength); 53 | bool write(const unsigned char* buffer, unsigned int length, const sockaddr_storage& address, unsigned int addressLength); 54 | 55 | void close(); 56 | 57 | static void startup(); 58 | static void shutdown(); 59 | 60 | static int lookup(const std::string& hostName, unsigned short port, sockaddr_storage& address, unsigned int& addressLength); 61 | static int lookup(const std::string& hostName, unsigned short port, sockaddr_storage& address, unsigned int& addressLength, struct addrinfo& hints); 62 | 63 | static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type = IPMATCHTYPE::ADDRESS_AND_PORT); 64 | 65 | static bool isNone(const sockaddr_storage& addr); 66 | 67 | private: 68 | std::string m_localAddress; 69 | unsigned short m_localPort; 70 | #if defined(_WIN32) || defined(_WIN64) 71 | SOCKET m_fd; 72 | int m_af; 73 | #else 74 | int m_fd; 75 | sa_family_t m_af; 76 | #endif 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /Utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2014,2015,2016,2025 Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #include "Utils.h" 15 | #include "Log.h" 16 | 17 | #include 18 | #include 19 | 20 | void CUtils::dump(const std::string& title, const unsigned char* data, unsigned int length) 21 | { 22 | assert(data != nullptr); 23 | 24 | dump(2U, title, data, length); 25 | } 26 | 27 | void CUtils::dump(int level, const std::string& title, const unsigned char* data, unsigned int length) 28 | { 29 | assert(data != nullptr); 30 | 31 | ::Log(level, "%s", title.c_str()); 32 | 33 | unsigned int offset = 0U; 34 | 35 | while (length > 0U) { 36 | std::string output; 37 | 38 | unsigned int bytes = (length > 16U) ? 16U : length; 39 | 40 | for (unsigned i = 0U; i < bytes; i++) { 41 | char temp[10U]; 42 | ::sprintf(temp, "%02X ", data[offset + i]); 43 | output += temp; 44 | } 45 | 46 | for (unsigned int i = bytes; i < 16U; i++) 47 | output += " "; 48 | 49 | output += " *"; 50 | 51 | for (unsigned i = 0U; i < bytes; i++) { 52 | unsigned char c = data[offset + i]; 53 | 54 | if (::isprint(c)) 55 | output += c; 56 | else 57 | output += '.'; 58 | } 59 | 60 | output += '*'; 61 | 62 | ::Log(level, "%04X: %s", offset, output.c_str()); 63 | 64 | offset += 16U; 65 | 66 | if (length >= 16U) 67 | length -= 16U; 68 | else 69 | length = 0U; 70 | } 71 | } 72 | 73 | void CUtils::dump(const std::string& title, const bool* bits, unsigned int length) 74 | { 75 | assert(bits != nullptr); 76 | 77 | dump(2U, title, bits, length); 78 | } 79 | 80 | void CUtils::dump(int level, const std::string& title, const bool* bits, unsigned int length) 81 | { 82 | assert(bits != nullptr); 83 | 84 | unsigned char bytes[100U]; 85 | unsigned int nBytes = 0U; 86 | for (unsigned int n = 0U; n < length; n += 8U, nBytes++) 87 | bitsToByteBE(bits + n, bytes[nBytes]); 88 | 89 | dump(level, title, bytes, nBytes); 90 | } 91 | 92 | void CUtils::byteToBitsBE(unsigned char byte, bool* bits) 93 | { 94 | assert(bits != nullptr); 95 | 96 | bits[0U] = (byte & 0x80U) == 0x80U; 97 | bits[1U] = (byte & 0x40U) == 0x40U; 98 | bits[2U] = (byte & 0x20U) == 0x20U; 99 | bits[3U] = (byte & 0x10U) == 0x10U; 100 | bits[4U] = (byte & 0x08U) == 0x08U; 101 | bits[5U] = (byte & 0x04U) == 0x04U; 102 | bits[6U] = (byte & 0x02U) == 0x02U; 103 | bits[7U] = (byte & 0x01U) == 0x01U; 104 | } 105 | 106 | void CUtils::byteToBitsLE(unsigned char byte, bool* bits) 107 | { 108 | assert(bits != nullptr); 109 | 110 | bits[0U] = (byte & 0x01U) == 0x01U; 111 | bits[1U] = (byte & 0x02U) == 0x02U; 112 | bits[2U] = (byte & 0x04U) == 0x04U; 113 | bits[3U] = (byte & 0x08U) == 0x08U; 114 | bits[4U] = (byte & 0x10U) == 0x10U; 115 | bits[5U] = (byte & 0x20U) == 0x20U; 116 | bits[6U] = (byte & 0x40U) == 0x40U; 117 | bits[7U] = (byte & 0x80U) == 0x80U; 118 | } 119 | 120 | void CUtils::bitsToByteBE(const bool* bits, unsigned char& byte) 121 | { 122 | assert(bits != nullptr); 123 | 124 | byte = bits[0U] ? 0x80U : 0x00U; 125 | byte |= bits[1U] ? 0x40U : 0x00U; 126 | byte |= bits[2U] ? 0x20U : 0x00U; 127 | byte |= bits[3U] ? 0x10U : 0x00U; 128 | byte |= bits[4U] ? 0x08U : 0x00U; 129 | byte |= bits[5U] ? 0x04U : 0x00U; 130 | byte |= bits[6U] ? 0x02U : 0x00U; 131 | byte |= bits[7U] ? 0x01U : 0x00U; 132 | } 133 | 134 | void CUtils::bitsToByteLE(const bool* bits, unsigned char& byte) 135 | { 136 | assert(bits != nullptr); 137 | 138 | byte = bits[0U] ? 0x01U : 0x00U; 139 | byte |= bits[1U] ? 0x02U : 0x00U; 140 | byte |= bits[2U] ? 0x04U : 0x00U; 141 | byte |= bits[3U] ? 0x08U : 0x00U; 142 | byte |= bits[4U] ? 0x10U : 0x00U; 143 | byte |= bits[5U] ? 0x20U : 0x00U; 144 | byte |= bits[6U] ? 0x40U : 0x00U; 145 | byte |= bits[7U] ? 0x80U : 0x00U; 146 | } 147 | -------------------------------------------------------------------------------- /Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009,2014,2015 by Jonathan Naylor, G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; version 2 of the License. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | #ifndef Utils_H 15 | #define Utils_H 16 | 17 | #include 18 | 19 | class CUtils { 20 | public: 21 | static void dump(const std::string& title, const unsigned char* data, unsigned int length); 22 | static void dump(int level, const std::string& title, const unsigned char* data, unsigned int length); 23 | 24 | static void dump(const std::string& title, const bool* bits, unsigned int length); 25 | static void dump(int level, const std::string& title, const bool* bits, unsigned int length); 26 | 27 | static void byteToBitsBE(unsigned char byte, bool* bits); 28 | static void byteToBitsLE(unsigned char byte, bool* bits); 29 | 30 | static void bitsToByteBE(const bool* bits, unsigned char& byte); 31 | static void bitsToByteLE(const bool* bits, unsigned char& byte); 32 | 33 | private: 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018,2020,2024,2025 by Jonathan Naylor G4KLX 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #if !defined(VERSION_H) 20 | #define VERSION_H 21 | 22 | const char* VERSION = "20250318"; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /prebuild.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This pre-build file is for MSVS VC++. It parses the git master hash and 3 | REM converts it into GitVersion.h for compiling into builds. [George M1GEO] 4 | 5 | cd %1 6 | setlocal enabledelayedexpansion 7 | set HEADFILE=.git\HEAD 8 | set HASHFILE=0 9 | if exist %HEADFILE% ( 10 | for /F "tokens=4 delims=/:" %%a in ('type %HEADFILE%') do set HEADBRANCH=%%a 11 | set HASHFILE=.git\refs\heads\!HEADBRANCH! 12 | echo Found Git HEAD file: %HEADFILE% 13 | echo Git HEAD branch: !HEADBRANCH! 14 | echo Git HASH file: !HASHFILE! 15 | call :USEHASH 16 | ) else ( 17 | echo No head file :( 18 | call :USENULL 19 | ) 20 | 21 | goto :EOF 22 | 23 | :USENULL 24 | set GITHASH=0000000000000000000000000000000000000000 25 | goto :WRITEGITVERSIONHEADER 26 | 27 | :USEHASH 28 | for /f %%i in ('type !HASHFILE!') do set GITHASH=%%i 29 | goto :WRITEGITVERSIONHEADER 30 | 31 | :WRITEGITVERSIONHEADER 32 | echo // File contains Git commit ID SHA1 present at buildtime (prebuild.cmd) > GitVersion.h 33 | echo const char *gitversion = "%GITHASH%"; >> GitVersion.h 34 | echo Current Git HASH: %GITHASH% 35 | goto :FINISHED 36 | 37 | :FINISHED 38 | echo GitVersion.h written... 39 | --------------------------------------------------------------------------------