From e7618491fcd0a886fb76c2f9ec99aa2e5bd63df5 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 11:28:56 +0100 Subject: [PATCH 001/112] Bump version number --- README.md | 12 +++++++----- VersionInfo/Version.h | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8cdfa2b..392657d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [4.1. Work Flow](#41-work-flow) - [4.2. Continuous Integration](#42-continuous-integration) - [5. Version History](#5-version-history) + - [5.1. Version 0.6](#51-version-06) - [5.1. Version 0.5](#51-version-05) - [5.2. Version 0.4](#52-version-04) - [5.3. Version 0.3](#53-version-03) @@ -133,7 +134,8 @@ I have added some basic CI using CircleCI [![F4FXL](https://circleci.com/gh/F4FX the testing framwework used is Google Test. # 5. Version History -## 5.1. Version 0.5 +## 5.1. Version 0.6 +## 5.2. Version 0.5 - [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) - [Bugfix] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) - [Improvement] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5)) @@ -141,17 +143,17 @@ the testing framwework used is Google Test. - [Bugfix] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14)) - [Bugfix] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13)) - [Bugfix] Trying to connect to ghost ircDDB when no ircDDB is configured -## 5.2. Version 0.4 +## 5.3. Version 0.4 - [Improvement] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) - [Bugfix] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7)) - [Improvement] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6)) - [Improvement] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) -## 5.3. Version 0.3 +## 5.4. Version 0.3 - [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. -## 5.4. Version 0.2 +## 5.5. Version 0.2 - [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization -## 5.5. Version 0.1 +## 5.6. Version 0.1 First working version # 6. Future I started this during my 2021 seasons holiday. It took me almost 8 days to get to a workable version. Here are a couple of stuff I'd like to do : diff --git a/VersionInfo/Version.h b/VersionInfo/Version.h index ddad33d..51e465d 100644 --- a/VersionInfo/Version.h +++ b/VersionInfo/Version.h @@ -26,7 +26,7 @@ const std::string PRODUCT_NAME("DStarGateway"); const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA and Contributors"); -const std::string VERSION("0.5"); +const std::string VERSION("0.6"); const std::string LONG_VERSION = VERSION + "-" + gitversion; const std::string FULL_PRODUCT_NAME = PRODUCT_NAME + " v" + VERSION + "-" + gitversion; const std::string SHORT_PRODUCT_NAME = "DStarGW v" + VERSION + "-" + gitversion; From 6c3b967d35b04b008131ca6105aa303184862f2b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 13:18:26 +0100 Subject: [PATCH 002/112] Rmove unnecessary includes and config patching --- DGWRemoteControl/Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DGWRemoteControl/Makefile b/DGWRemoteControl/Makefile index 44e1fc3..f68b716 100644 --- a/DGWRemoteControl/Makefile +++ b/DGWRemoteControl/Makefile @@ -6,7 +6,7 @@ dgwremotecontrol: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a . $(CC) $(CPPFLAGS) -o dgwremotecontrol $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) %.o : %.cpp - $(CC) -I../APRS -I../Common -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ + $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ .PHONY clean: clean: @@ -19,8 +19,6 @@ install: dgwremotecontrol # copy and adjust config @cp -fn example.cfg $(CFG_DIR)/dgwremotecontrol.cfg - @sed -i "s|path=/var/log/dgwremotecontrol/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwremotecontrol.cfg - @sed -i "s|data=/usr/local/share/dgwremotecontrol.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwremotecontrol.cfg ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: From 5024206088835d3a3d72b29c0fa96a2699270de1 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:09:58 +0100 Subject: [PATCH 003/112] #18 add some more randomness --- DStarBase/HeaderData.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DStarBase/HeaderData.cpp b/DStarBase/HeaderData.cpp index 6747571..54d0612 100644 --- a/DStarBase/HeaderData.cpp +++ b/DStarBase/HeaderData.cpp @@ -22,6 +22,8 @@ #include #include #include +#include + #include "HeaderData.h" #include "NetUtils.h" #include "CCITTChecksum.h" @@ -30,7 +32,7 @@ void CHeaderData::initialise() { - srand(time(NULL)); + srand(time(NULL) + getpid()); } void CHeaderData::finalise() From 3280b24e5bc351fbef922c5b5a9456fc7c8bc318 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:21:39 +0100 Subject: [PATCH 004/112] #18 fix bugs when reading DVtool file --- DStarBase/DVTOOLFileReader.cpp | 14 ++++++++++---- DStarBase/DVTOOLFileReader.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/DStarBase/DVTOOLFileReader.cpp b/DStarBase/DVTOOLFileReader.cpp index 773dcb6..0e3e2b5 100644 --- a/DStarBase/DVTOOLFileReader.cpp +++ b/DStarBase/DVTOOLFileReader.cpp @@ -23,6 +23,7 @@ #include "DVTOOLFileReader.h" #include "DStarDefines.h" +#include "Utils.h" static const char DVTOOL_SIGNATURE[] = "DVTOOL"; static const unsigned int DVTOOL_SIGNATURE_LENGTH = 6U; @@ -48,7 +49,8 @@ m_records(0U), m_type(DVTFR_NONE), m_buffer(NULL), m_length(0U), -m_seqNo(0U) +m_seqNo(0U), +m_closed(true) { m_buffer = new unsigned char[BUFFER_LENGTH]; } @@ -95,8 +97,9 @@ bool CDVTOOLFileReader::open(const std::string& fileName) return false; } - m_records = ntohl(uint32); + m_records = CUtils::swap_endian_le(uint32); m_seqNo = 0U; + m_closed = false; return true; } @@ -108,7 +111,7 @@ DVTFR_TYPE CDVTOOLFileReader::read() if (n != 1) return DVTFR_NONE; - m_length = htons(uint16) - 15U; + m_length = CUtils::swap_endian_be(uint16) - 15U; unsigned char bytes[FIXED_DATA_LENGTH]; n = fread(bytes, 1, DSVT_SIGNATURE_LENGTH, m_file); @@ -180,5 +183,8 @@ CAMBEData* CDVTOOLFileReader::readData() void CDVTOOLFileReader::close() { - fclose(m_file); + if(!m_closed) + fclose(m_file); + + m_closed = true; } diff --git a/DStarBase/DVTOOLFileReader.h b/DStarBase/DVTOOLFileReader.h index 0625558..0ed316d 100644 --- a/DStarBase/DVTOOLFileReader.h +++ b/DStarBase/DVTOOLFileReader.h @@ -58,4 +58,5 @@ private: unsigned char* m_buffer; unsigned int m_length; unsigned char m_seqNo; + bool m_closed; }; From b59cd9cc2469033cdac964eb1407459ed42884b7 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:22:02 +0100 Subject: [PATCH 005/112] #18 update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 08410df..78fa9b1 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ Sandbox/* dstargateway Tests/dstargateway_tests DGWRemoteControl/dgwremotecontrol +DGWVoiceTransmit/dgwvoicetransmit From 52fcd4b70bdfb50b525257680a42eed27236dc7b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:22:32 +0100 Subject: [PATCH 006/112] #18 add swap_endian_on_le --- BaseCommon/Utils.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/BaseCommon/Utils.h b/BaseCommon/Utils.h index 0e60527..549392f 100644 --- a/BaseCommon/Utils.h +++ b/BaseCommon/Utils.h @@ -87,4 +87,14 @@ public: return u; } + + // Ersatz for macro wxINT32_SWAP_ON_LE + template + static T swap_endian_le(T u) + { + if(!is_big_endian()) + return swap_endian(u); + + return u; + } }; From ba3e4345ab10cd342e9471230b388d24842bf871 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:22:55 +0100 Subject: [PATCH 007/112] #18 add voice transmit --- .vscode/launch.json | 24 ++++ .vscode/tasks.json | 21 +++- DGWVoiceTransmit/Makefile | 22 ++++ DGWVoiceTransmit/VoiceStore.cpp | 124 +++++++++++++++++++ DGWVoiceTransmit/VoiceStore.h | 51 ++++++++ DGWVoiceTransmit/VoiceTransmit.cpp | 189 +++++++++++++++++++++++++++++ DGWVoiceTransmit/VoiceTransmit.h | 49 ++++++++ Makefile | 9 +- 8 files changed, 483 insertions(+), 6 deletions(-) create mode 100644 DGWVoiceTransmit/Makefile create mode 100644 DGWVoiceTransmit/VoiceStore.cpp create mode 100644 DGWVoiceTransmit/VoiceStore.h create mode 100644 DGWVoiceTransmit/VoiceTransmit.cpp create mode 100644 DGWVoiceTransmit/VoiceTransmit.h diff --git a/.vscode/launch.json b/.vscode/launch.json index 5cb32b0..7055ed5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,6 +52,30 @@ } ] }, + { + "name": "(gdb) dgwvoicetransmit", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWVoiceTransmit/dgwvoicetransmit", + "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "Tests", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 46b9b3b..d6bee3a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -45,6 +42,22 @@ "group": "build", "problemMatcher": [] }, + { + "label": "Build DGWVoiceTransmit", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWVoiceTransmit/dgwvoicetransmit" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, { "label": "Build Tests", "type": "shell", diff --git a/DGWVoiceTransmit/Makefile b/DGWVoiceTransmit/Makefile new file mode 100644 index 0000000..4579621 --- /dev/null +++ b/DGWVoiceTransmit/Makefile @@ -0,0 +1,22 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dgwvoicetransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a + $(CC) $(CPPFLAGS) -o dgwvoicetransmit $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + +%.o : %.cpp + $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ + +.PHONY clean: +clean: + $(RM) *.o *.d dgwvoicetransmit + +.PHONY install: +install: dgwvoicetransmit +# copy executable + @cp -f dgwvoicetransmit $(BIN_DIR) + +../BaseCommon/BaseCommon.a: +../DStarBase/DStarBase.a: +../VersionInfo/GitVersion.h: diff --git a/DGWVoiceTransmit/VoiceStore.cpp b/DGWVoiceTransmit/VoiceStore.cpp new file mode 100644 index 0000000..7456eae --- /dev/null +++ b/DGWVoiceTransmit/VoiceStore.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarDefines.h" +#include "VoiceStore.h" + +CVoiceStore::CVoiceStore(const std::vector& filenames) : +m_filenames(), +m_header(NULL), +m_fileNumber(0U), +m_file() +{ + for(auto f : filenames) { + m_filenames.push_back(f); + } +} + +CVoiceStore::~CVoiceStore() +{ + m_filenames.clear(); +} + +bool CVoiceStore::open() +{ + std::string fileName = m_filenames.at(0U); + + bool ret = m_file.open(fileName); + if (!ret) + return false; + + DVTFR_TYPE type = m_file.read(); + if (type != DVTFR_HEADER) { + m_file.close(); + return false; + } + + m_header = m_file.readHeader(); + m_fileNumber = 0U; + + return true; +} + +CHeaderData* CVoiceStore::getHeader() +{ + CHeaderData* header = m_header; + + m_header = NULL; + + return header; +} + +CAMBEData* CVoiceStore::getAMBE() +{ + DVTFR_TYPE type = m_file.read(); + if (type == DVTFR_DATA) { + CAMBEData* ambe = m_file.readData(); + if (ambe != NULL) + return ambe; + } + + for (;;) { + m_file.close(); + m_fileNumber++; + + // The end of the last file? + if (m_fileNumber == m_filenames.size()) + return NULL; + + std::string filename = m_filenames.at(m_fileNumber); + + bool ret = m_file.open(filename); + if (!ret) + return NULL; + + // This should get the header + type = m_file.read(); + + if (type == DVTFR_HEADER) { + // Throw the header away + CHeaderData* header = m_file.readHeader(); + delete header; + } else if (type == DVTFR_DATA) { + // Shouldn't usually happen + CAMBEData* ambe = m_file.readData(); + if (ambe != NULL) + return ambe; + else + continue; + } else { + // !!!! + continue; + } + + // This should get the first data record + type = m_file.read(); + + if (type == DVTFR_DATA) { + CAMBEData* ambe = m_file.readData(); + if (ambe != NULL) + return ambe; + } + } +} + +void CVoiceStore::close() +{ + m_file.close(); +} diff --git a/DGWVoiceTransmit/VoiceStore.h b/DGWVoiceTransmit/VoiceStore.h new file mode 100644 index 0000000..670c9bb --- /dev/null +++ b/DGWVoiceTransmit/VoiceStore.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef VoiceStore_H +#define VoiceStore_H + +#include +#include + +#include "DVTOOLFileReader.h" +#include "HeaderData.h" +#include "AMBEData.h" + + +class CVoiceStore { +public: + CVoiceStore(const std::vector& filenames); + ~CVoiceStore(); + + bool open(); + + CHeaderData* getHeader(); + + CAMBEData* getAMBE(); + + void close(); + +private: + std::vector m_filenames; + CHeaderData* m_header; + unsigned int m_fileNumber; + CDVTOOLFileReader m_file; +}; + +#endif diff --git a/DGWVoiceTransmit/VoiceTransmit.cpp b/DGWVoiceTransmit/VoiceTransmit.cpp new file mode 100644 index 0000000..d173241 --- /dev/null +++ b/DGWVoiceTransmit/VoiceTransmit.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +#include "DStarDefines.h" +#include "VoiceTransmit.h" + +int main(int argc, char** argv) +{ + std::string repeater; + std::vector filenames; + + if (!parseCLIArgs(argc, argv, repeater, filenames)) { + ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit ..., exiting\n"); + return 1; + } + + CHeaderData::initialise(); + + CVoiceStore store(filenames); + bool opened = store.open(); + if (!opened) { + ::fprintf(stderr, "dgwvoicetransmit: unable to open one of the files, exiting\n"); + return 1; + } + + CVoiceTransmit tt(repeater, &store); + bool ret = tt.run(); + + store.close(); + + + return ret ? 0 : 1; +} + +bool parseCLIArgs(int argc, char * argv[], std::string& repeater, std::vector& files) +{ + if(argc < 3) + return false; + + repeater.assign(argv[1]); + boost::to_upper(repeater); + boost::replace_all(repeater, "_", " "); + repeater.resize(LONG_CALLSIGN_LENGTH, ' '); + + files.clear(); + + for(int i = 2; i < argc; i++) { + if(argv[i] != nullptr) { + files.push_back(std::string(argv[i])); + } + } + + return files.size() > 0U; +} + +CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store) : +m_socket("", 0U), +m_callsign(callsign), +m_store(store) +{ + assert(store != NULL); +} + +CVoiceTransmit::~CVoiceTransmit() +{ +} + +bool CVoiceTransmit::run() +{ + bool opened = m_socket.open(); + if (!opened) + return false; + + in_addr address = CUDPReaderWriter::lookup("127.0.0.1"); + + unsigned int id = CHeaderData::createId(); + + std::string callsignG = m_callsign.substr(0U, LONG_CALLSIGN_LENGTH - 1U); + callsignG.push_back('G'); + + CHeaderData* header = m_store->getHeader(); + if (header == NULL) { + m_socket.close(); + return false; + } + + header->setId(id); + header->setRptCall1(callsignG); + header->setRptCall2(m_callsign); + header->setDestination(address, G2_DV_PORT); + + sendHeader(header); + + delete header; + + auto start = std::chrono::high_resolution_clock::now(); + + unsigned int out = 0U; + unsigned int seqNo = 0U; + + for (;;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + CAMBEData* ambe = m_store->getAMBE(); + + if (ambe == NULL) { + seqNo++; + if (seqNo >= 21U) + seqNo = 0U; + + CAMBEData data; + data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + data.setDestination(address, G2_DV_PORT); + data.setId(id); + data.setSeq(seqNo); + data.setEnd(true); + + sendData(&data); + + m_socket.close(); + + return true; + } + + seqNo = ambe->getSeq(); + + ambe->setDestination(address, G2_DV_PORT); + ambe->setEnd(false); + ambe->setId(id); + + sendData(ambe); + + delete ambe; + + out++; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10U)); + } +} + +bool CVoiceTransmit::sendHeader(CHeaderData* header) +{ + assert(header != NULL); + + unsigned char buffer[60U]; + unsigned int length = header->getG2Data(buffer, 60U, true); + + for (unsigned int i = 0U; i < 2U; i++) { + bool res = m_socket.write(buffer, length, header->getYourAddress(), header->getYourPort()); + if (!res) + return false; + } + + return true; +} + +bool CVoiceTransmit::sendData(CAMBEData* data) +{ + assert(data != NULL); + + unsigned char buffer[40U]; + unsigned int length = data->getG2Data(buffer, 40U); + + return m_socket.write(buffer, length, data->getYourAddress(), data->getYourPort()); +} diff --git a/DGWVoiceTransmit/VoiceTransmit.h b/DGWVoiceTransmit/VoiceTransmit.h new file mode 100644 index 0000000..3a72384 --- /dev/null +++ b/DGWVoiceTransmit/VoiceTransmit.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef VoiceTransmit_H +#define VoiceTransmit_H + +#include +#include + +#include "UDPReaderWriter.h" +#include "VoiceStore.h" +#include "HeaderData.h" +#include "AMBEData.h" + +bool parseCLIArgs(int argc, char * argv[], std::string& repeater, std::vector& vector); + +class CVoiceTransmit { +public: + CVoiceTransmit(const std::string& callsign, CVoiceStore* store); + ~CVoiceTransmit(); + + bool run(); + +private: + CUDPReaderWriter m_socket; + std::string m_callsign; + CVoiceStore* m_store; + + bool sendHeader(CHeaderData* header); + bool sendData(CAMBEData* data); +}; + +#endif diff --git a/Makefile b/Makefile index cb395a8..8c33b90 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif .PHONY: all -all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol #tests +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit #tests APRS/APRS.a: BaseCommon/BaseCommon.a FORCE $(MAKE) -C APRS @@ -60,6 +60,9 @@ DStarGateway/dstargateway : VersionInfo/GitVersion.h $(OBJS) APRS/APRS.a Common DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWRemoteControl +DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWVoiceTransmit + IRCDDB/IRCDDB.a: VersionInfo/GitVersion.h BaseCommon/BaseCommon.a FORCE $(MAKE) -C IRCDDB @@ -90,8 +93,10 @@ newhostfiles : .PHONY: install install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol -# install remote control +# install accessories $(MAKE) -C DGWRemoteControl install + $(MAKE) -C DGWVoiceTransmit install + # create user for daemon @useradd --user-group -M --system dstar --shell /bin/false || true From d19f24f7b3c0ba12e5838cabad707ff29cf9d191 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:33:38 +0100 Subject: [PATCH 008/112] Add test for swap_endian_le --- Tests/Utils/swap_endian_le.cpp | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Tests/Utils/swap_endian_le.cpp diff --git a/Tests/Utils/swap_endian_le.cpp b/Tests/Utils/swap_endian_le.cpp new file mode 100644 index 0000000..c84d0a9 --- /dev/null +++ b/Tests/Utils/swap_endian_le.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "Utils.h" +#include "StringUtils.h" +#include "Log.h" + +class Utils_swap_endian_le : public ::testing::Test { + +}; + +// This test will fail on big endian systems.... +TEST_F(Utils_swap_endian_le, SwapUINT32_be) { + uint32_t test = 0x12345678U; + + uint32_t res = CUtils::swap_endian_le(test); + + EXPECT_EQ(res, 0x78563412U); +} + + From 696eb4c76786549280bed73a83c0c3c3d0b1571f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:38:29 +0100 Subject: [PATCH 009/112] #18 Update readme --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 392657d..0a3a68d 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ - [4.2. Continuous Integration](#42-continuous-integration) - [5. Version History](#5-version-history) - [5.1. Version 0.6](#51-version-06) - - [5.1. Version 0.5](#51-version-05) - - [5.2. Version 0.4](#52-version-04) - - [5.3. Version 0.3](#53-version-03) - - [5.4. Version 0.2](#54-version-02) - - [5.5. Version 0.1](#55-version-01) + - [5.2. Version 0.5](#52-version-05) + - [5.3. Version 0.4](#53-version-04) + - [5.4. Version 0.3](#54-version-03) + - [5.5. Version 0.2](#55-version-02) + - [5.6. Version 0.1](#56-version-01) - [6. Future](#6-future) @@ -135,6 +135,7 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 +- [Improvement] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) ## 5.2. Version 0.5 - [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) - [Bugfix] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) From 6a6dd3b98affee2df5a99b725faab81ed46bd4fe Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 18:47:57 +0100 Subject: [PATCH 010/112] add missing include dir --- DGWRemoteControl/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWRemoteControl/Makefile b/DGWRemoteControl/Makefile index f68b716..cb7396a 100644 --- a/DGWRemoteControl/Makefile +++ b/DGWRemoteControl/Makefile @@ -6,7 +6,7 @@ dgwremotecontrol: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a . $(CC) $(CPPFLAGS) -o dgwremotecontrol $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) %.o : %.cpp - $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ + $(CC) -I../Common -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ .PHONY clean: clean: From 0e03faf5aea9413e8967948afd93a49827a40404 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 20:42:58 +0100 Subject: [PATCH 011/112] #19 move SlowDataEncoder to DStarBase --- {Common => DStarBase}/SlowDataEncoder.cpp | 0 {Common => DStarBase}/SlowDataEncoder.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {Common => DStarBase}/SlowDataEncoder.cpp (100%) rename {Common => DStarBase}/SlowDataEncoder.h (100%) diff --git a/Common/SlowDataEncoder.cpp b/DStarBase/SlowDataEncoder.cpp similarity index 100% rename from Common/SlowDataEncoder.cpp rename to DStarBase/SlowDataEncoder.cpp diff --git a/Common/SlowDataEncoder.h b/DStarBase/SlowDataEncoder.h similarity index 100% rename from Common/SlowDataEncoder.h rename to DStarBase/SlowDataEncoder.h From c76e26b0fc17c1e5cbc5c7004aa1ad2333a4205e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 20:43:57 +0100 Subject: [PATCH 012/112] #19 Add DGWTextTransmit --- .gitignore | 1 + .vscode/launch.json | 24 ++++ .vscode/tasks.json | 23 +++- DGWTextTransmit/Makefile | 22 ++++ DGWTextTransmit/TextTransmit.cpp | 206 +++++++++++++++++++++++++++++++ DGWTextTransmit/TextTransmit.h | 47 +++++++ Makefile | 6 +- README.md | 1 + 8 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 DGWTextTransmit/Makefile create mode 100644 DGWTextTransmit/TextTransmit.cpp create mode 100644 DGWTextTransmit/TextTransmit.h diff --git a/.gitignore b/.gitignore index 78fa9b1..8bf1885 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ dstargateway Tests/dstargateway_tests DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit +DGWTextTransmit/dgwtexttransmit diff --git a/.vscode/launch.json b/.vscode/launch.json index 7055ed5..cc81732 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,6 +52,30 @@ } ] }, + { + "name": "(gdb) dgwtextransmit", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWTextTransmit/dgwtexttransmit", + "args": ["F4FXL B", "-text", "test de julien a nouveau"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "(gdb) dgwvoicetransmit", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d6bee3a..6066b4b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,10 @@ "USE_GPSD=1", "all" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -42,6 +45,19 @@ "group": "build", "problemMatcher": [] }, + { + "label": "Build DGWTextTransmit", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWTextTransmit/dgwtexttransmit" + ], + "group": "build", + "problemMatcher": [] + }, { "label": "Build DGWVoiceTransmit", "type": "shell", @@ -52,10 +68,7 @@ "USE_GPSD=1", "DGWVoiceTransmit/dgwvoicetransmit" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/DGWTextTransmit/Makefile b/DGWTextTransmit/Makefile new file mode 100644 index 0000000..d209f9e --- /dev/null +++ b/DGWTextTransmit/Makefile @@ -0,0 +1,22 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dgwtexttransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a + $(CC) $(CPPFLAGS) -o dgwtexttransmit $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + +%.o : %.cpp + $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ + +.PHONY clean: +clean: + $(RM) *.o *.d dgwtexttransmit + +.PHONY install: +install: dgwtexttransmit +# copy executable + @cp -f dgwtexttransmit $(BIN_DIR) + +../BaseCommon/BaseCommon.a: +../DStarBase/DStarBase.a: +../VersionInfo/GitVersion.h: diff --git a/DGWTextTransmit/TextTransmit.cpp b/DGWTextTransmit/TextTransmit.cpp new file mode 100644 index 0000000..9862b1d --- /dev/null +++ b/DGWTextTransmit/TextTransmit.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "SlowDataEncoder.h" +#include "DStarDefines.h" +#include "TextTransmit.h" +#include "ProgramArgs.h" + + +int main(int argc, const char* argv[]) +{ + std::string repeater, text, filename; + + if (!parseCLIArgs(argc, argv, repeater, text, filename)) { + ::fprintf(stderr, "dgwtexttransmit: invalid command line usage: dgwtexttransmit -text |-file , exiting\n"); + return 1; + } + + CHeaderData::initialise(); + + if (!filename.empty()) { + std::ifstream file; + file.open(filename); + if (!file.is_open()) { + ::fprintf(stderr, "dgwtexttransmit: unable to open the file, exiting\n"); + return 1; + } + + std::getline(file, text); + + file.close(); + } + + text.resize(20U, ' '); + + CTextTransmit tt(repeater, text); + bool ret = tt.run(); + + return ret ? 0 : 1; +} + +bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::string& text, std::string& file) +{ + repeater.clear(); + text.clear(); + file.clear(); + + if(argc < 4) + return false; + + std::unordered_map namedArgs; + std::vector positionalArgs; + + CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs); + + if(namedArgs.count("text") == 0 && namedArgs.count("file") == 0) + return false; + + if(positionalArgs.size() != 1) + return false; + + repeater = boost::to_upper_copy(positionalArgs[0]); + boost::replace_all(repeater, "_", " "); + repeater.resize(LONG_CALLSIGN_LENGTH, ' '); + + if(namedArgs.count("text") == 1) { + text.assign(namedArgs["text"]); + } + else if(namedArgs.count("file") == 1){ + file.assign(namedArgs[file]); + } + + return true; +} + +CTextTransmit::CTextTransmit(const std::string& callsign, const std::string& text) : +m_socket("", 0U), +m_callsign(callsign), +m_text(text) +{ +} + +CTextTransmit::~CTextTransmit() +{ +} + +bool CTextTransmit::run() +{ + bool opened = m_socket.open(); + if (!opened) + return false; + + in_addr address = CUDPReaderWriter::lookup("127.0.0.1"); + + unsigned int id = CHeaderData::createId(); + + std::string callsignG = m_callsign.substr(0U, LONG_CALLSIGN_LENGTH - 1U); + callsignG.push_back('G'); + + CHeaderData header; + header.setId(id); + header.setMyCall1(m_callsign); + header.setMyCall2("INFO"); + header.setRptCall1(callsignG); + header.setRptCall2(m_callsign); + header.setYourCall("CQCQCQ "); + header.setDestination(address, G2_DV_PORT); + + sendHeader(header); + + CSlowDataEncoder encoder; + encoder.setHeaderData(header); + encoder.setTextData(m_text); + + CAMBEData data; + data.setDestination(address, G2_DV_PORT); + data.setId(id); + + auto start = std::chrono::high_resolution_clock::now(); + + unsigned int out = 0U; + + for (;;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + data.setSeq(out); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (out == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + encoder.sync(); + } else { + encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); + } + + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + + sendData(data); + out++; + + if (out == 21U) { + data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + data.setSeq(0U); + data.setEnd(true); + + sendData(data); + + m_socket.close(); + + return true; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10U)); + } +} + +bool CTextTransmit::sendHeader(const CHeaderData& header) +{ + unsigned char buffer[60U]; + unsigned int length = header.getG2Data(buffer, 60U, true); + + for (unsigned int i = 0U; i < 2U; i++) { + bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); + if (!res) + return false; + } + + return true; +} + +bool CTextTransmit::sendData(const CAMBEData& data) +{ + unsigned char buffer[40U]; + unsigned int length = data.getG2Data(buffer, 40U); + + return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); +} diff --git a/DGWTextTransmit/TextTransmit.h b/DGWTextTransmit/TextTransmit.h new file mode 100644 index 0000000..4b6ffd9 --- /dev/null +++ b/DGWTextTransmit/TextTransmit.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef TextTransmit_H +#define TextTransmit_H + +#include + +#include "UDPReaderWriter.h" +#include "HeaderData.h" +#include "AMBEData.h" + +bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::string& text, std::string& file); + +class CTextTransmit { +public: + CTextTransmit(const std::string& callsign, const std::string& text); + ~CTextTransmit(); + + bool run(); + +private: + CUDPReaderWriter m_socket; + std::string m_callsign; + std::string m_text; + + bool sendHeader(const CHeaderData& header); + bool sendData(const CAMBEData& data); +}; + +#endif diff --git a/Makefile b/Makefile index 8c33b90..7a131cb 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif .PHONY: all -all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit #tests +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWVoiceTransmit/dgwvoicetransmit #tests APRS/APRS.a: BaseCommon/BaseCommon.a FORCE $(MAKE) -C APRS @@ -60,6 +60,9 @@ DStarGateway/dstargateway : VersionInfo/GitVersion.h $(OBJS) APRS/APRS.a Common DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWRemoteControl +DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWTextTransmit + DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit @@ -95,6 +98,7 @@ newhostfiles : install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol # install accessories $(MAKE) -C DGWRemoteControl install + $(MAKE) -C DGWTextTransmit install $(MAKE) -C DGWVoiceTransmit install # create user for daemon diff --git a/README.md b/README.md index 0a3a68d..b60fbde 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 +- [Improvement] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) - [Improvement] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) ## 5.2. Version 0.5 - [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) From 8b1856b226c04fb4d55861a16c23b623afa62c03 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 5 Feb 2022 20:49:10 +0100 Subject: [PATCH 013/112] #19 fix file not transmitted --- .vscode/launch.json | 2 +- DGWTextTransmit/TextTransmit.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index cc81732..14711bb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -57,7 +57,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DGWTextTransmit/dgwtexttransmit", - "args": ["F4FXL B", "-text", "test de julien a nouveau"], + "args": ["F4FXL B", "-file", "${workspaceFolder}/Sandbox/text.txt"],//["F4FXL B", "-text", "test de julien a nouveau"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/DGWTextTransmit/TextTransmit.cpp b/DGWTextTransmit/TextTransmit.cpp index 9862b1d..26a7e94 100644 --- a/DGWTextTransmit/TextTransmit.cpp +++ b/DGWTextTransmit/TextTransmit.cpp @@ -90,7 +90,7 @@ bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::str text.assign(namedArgs["text"]); } else if(namedArgs.count("file") == 1){ - file.assign(namedArgs[file]); + file.assign(namedArgs["file"]); } return true; From 3f716ddfd66bc0561c0a8dc63e20e3c480e7f297 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 6 Feb 2022 14:29:47 +0100 Subject: [PATCH 014/112] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b60fbde..c064b0c 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Quite a few classes are more or less copy/paste from each other some sanitizatio - Geoffrey Merck F4FXL / KC3FRA [That's me !](https://github.com/F4FXL/) ## 2.3. Thanks - Cyrille F1MHV / DF1CHB for the testing -- Jonathan Naylor G4KLX for all the work ahead +- Jonathan Naylor G4KLX for all the work ahead and the rock solid code base ## 2.4. Features All the features found in ircddbGateway are supposed to be working. Except the ones listed below From 47e3519bba9cb4e1471113c096595f85c63907d4 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 6 Feb 2022 14:37:18 +0100 Subject: [PATCH 015/112] #20 add build/run stuff --- .vscode/launch.json | 24 ++++++++++++++++++++++++ .vscode/tasks.json | 21 +++++++++++++++++---- DGWTimeServer/Makefile | 22 ++++++++++++++++++++++ Makefile | 6 +++++- 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 DGWTimeServer/Makefile diff --git a/.vscode/launch.json b/.vscode/launch.json index 14711bb..03f8b49 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -76,6 +76,30 @@ } ] }, + { + "name": "(gdb) dgwtimeserver", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWTimeServer/dgwtimeserver", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "(gdb) dgwvoicetransmit", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6066b4b..0545c42 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -58,6 +55,22 @@ "group": "build", "problemMatcher": [] }, + { + "label": "Build DGWTimeServer", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWTimeServer/dgwtimeserver" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, { "label": "Build DGWVoiceTransmit", "type": "shell", diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile new file mode 100644 index 0000000..4ea197d --- /dev/null +++ b/DGWTimeServer/Makefile @@ -0,0 +1,22 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a + $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + +%.o : %.cpp + $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ + +.PHONY clean: +clean: + $(RM) *.o *.d dgwtimeserver + +.PHONY install: +install: dgwtimeserver +# copy executable + @cp -f dgwtimeserver $(BIN_DIR) + +../BaseCommon/BaseCommon.a: +../DStarBase/DStarBase.a: +../VersionInfo/GitVersion.h: diff --git a/Makefile b/Makefile index 7a131cb..d48596c 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif .PHONY: all -all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWVoiceTransmit/dgwvoicetransmit #tests +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests APRS/APRS.a: BaseCommon/BaseCommon.a FORCE $(MAKE) -C APRS @@ -63,6 +63,9 @@ DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DS DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWTextTransmit +DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWTimeServer + DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit @@ -99,6 +102,7 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol # install accessories $(MAKE) -C DGWRemoteControl install $(MAKE) -C DGWTextTransmit install + $(MAKE) -C DGWTimeServer install $(MAKE) -C DGWVoiceTransmit install # create user for daemon From 5144dd4d9c83813ae83dae19ac34df89fcc3d67f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Tue, 8 Feb 2022 17:12:23 +0100 Subject: [PATCH 016/112] #21 add thread name, kill all child threads --- BaseCommon/Thread.cpp | 6 +++++- BaseCommon/Thread.h | 4 +++- Common/APRSHandlerThread.cpp | 4 ++-- Common/DPlusAuthenticator.cpp | 3 ++- Common/IcomRepeaterProtocolHandler.cpp | 3 ++- DStarGateway/DStarGatewayThread.cpp | 9 ++++++++- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/BaseCommon/Thread.cpp b/BaseCommon/Thread.cpp index f2ea928..5ca7564 100644 --- a/BaseCommon/Thread.cpp +++ b/BaseCommon/Thread.cpp @@ -18,10 +18,12 @@ #include #include "Thread.h" +#include "Log.h" using namespace std; -CThread::CThread() +CThread::CThread(const std::string& name) : +m_name(name) { } @@ -53,5 +55,7 @@ void CThread::Wait() void CThread::EntryRunner(CThread * thread) { + assert(thread != nullptr); thread->Entry(); + CLog::logTrace("Exiting %s thread", thread->m_name.c_str()); } \ No newline at end of file diff --git a/BaseCommon/Thread.h b/BaseCommon/Thread.h index 4715f8d..70aa762 100644 --- a/BaseCommon/Thread.h +++ b/BaseCommon/Thread.h @@ -23,10 +23,11 @@ #define Thread_H #include +#include class CThread { public: - CThread(); + CThread(const std::string& name); virtual ~CThread(); void Create(); void Run(); @@ -39,6 +40,7 @@ protected: private: static void EntryRunner(CThread * thread); + std::string m_name; std::thread m_thread; }; diff --git a/Common/APRSHandlerThread.cpp b/Common/APRSHandlerThread.cpp index 1d6b099..e3098c6 100644 --- a/Common/APRSHandlerThread.cpp +++ b/Common/APRSHandlerThread.cpp @@ -37,7 +37,7 @@ const unsigned int APRS_READ_TIMEOUT = 1U; const unsigned int APRS_KEEP_ALIVE_TIMEOUT = 60U; CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) : -CThread(), +CThread("APRS"), m_username(callsign), m_password(password), m_ssid(callsign), @@ -65,7 +65,7 @@ m_clientName(FULL_PRODUCT_NAME) } CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter) : -CThread(), +CThread("APRS"), m_username(callsign), m_password(password), m_ssid(callsign), diff --git a/Common/DPlusAuthenticator.cpp b/Common/DPlusAuthenticator.cpp index e67ccf7..fc436a6 100644 --- a/Common/DPlusAuthenticator.cpp +++ b/Common/DPlusAuthenticator.cpp @@ -34,6 +34,7 @@ const unsigned int OPENDSTAR_PORT = 20001U; const unsigned int TCP_TIMEOUT = 10U; CDPlusAuthenticator::CDPlusAuthenticator(const std::string& loginCallsign, const std::string& gatewayCallsign, const std::string& address, CCacheManager* cache) : +CThread("DPlus"), m_loginCallsign(loginCallsign), m_gatewayCallsign(gatewayCallsign), m_address(address), @@ -106,7 +107,7 @@ void* CDPlusAuthenticator::Entry() void CDPlusAuthenticator::stop() { m_killed = true; - + CLog::logInfo("Stopping DPpus Authenticator"); Wait(); } diff --git a/Common/IcomRepeaterProtocolHandler.cpp b/Common/IcomRepeaterProtocolHandler.cpp index d6e20f8..7f3aa1e 100644 --- a/Common/IcomRepeaterProtocolHandler.cpp +++ b/Common/IcomRepeaterProtocolHandler.cpp @@ -32,6 +32,7 @@ const unsigned int LOOP_DELAY = 5UL; const unsigned int LOOP_TICKS = 200U; CIcomRepeaterProtocolHandler::CIcomRepeaterProtocolHandler(const std::string& address, unsigned int port, const std::string& icomAddress, unsigned int icomPort) : +CThread("Icom Protocol Handler"), m_socket(address, port), m_icomAddress(), m_icomPort(icomPort), @@ -527,7 +528,7 @@ CAMBEData* CIcomRepeaterProtocolHandler::readBusyAMBE() void CIcomRepeaterProtocolHandler::close() { m_killed = true; - + CLog::logInfo("Stopping Icom Repeater protocol handler thread"); Wait(); } diff --git a/DStarGateway/DStarGatewayThread.cpp b/DStarGateway/DStarGatewayThread.cpp index 693075d..a2d4bc6 100644 --- a/DStarGateway/DStarGatewayThread.cpp +++ b/DStarGateway/DStarGatewayThread.cpp @@ -60,7 +60,7 @@ const std::string LOOPBACK_ADDRESS("127.0.0.1"); const unsigned int REMOTE_DUMMY_PORT = 65016U; CDStarGatewayThread::CDStarGatewayThread(const std::string& logDir, const std::string& dataDir, const std::string& name) : -CThread(), +CThread("Gateway"), m_logDir(logDir), m_dataDir(dataDir), m_name(name), @@ -481,11 +481,18 @@ void* CDStarGatewayThread::Entry() delete m_remote; } + if(m_aprsWriter != nullptr) { + m_aprsWriter->close(); + delete m_aprsWriter; + } + if (headerLogger != NULL) { headerLogger->close(); delete headerLogger; } + CDPlusHandler::finalise(); + return NULL; } From 6137590583feaa6ea46b8252d2f0b963dd5d8291 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Tue, 8 Feb 2022 17:13:17 +0100 Subject: [PATCH 017/112] #21 handle sigterm and sigint --- DStarGateway/DStarGatewayApp.cpp | 16 ++++++++++++++-- DStarGateway/DStarGatewayApp.h | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 15de622..8acfe2e 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -46,6 +46,8 @@ #include "APRSGPSDIdFrameProvider.h" #include "APRSFixedIdFrameProvider.h" +CDStarGatewayApp * CDStarGatewayApp::g_app = nullptr; + #ifdef UNIT_TESTS int fakemain(int argc, char *argv[]) #else @@ -58,7 +60,8 @@ int main(int argc, char *argv[]) signal(SIGILL, CDStarGatewayApp::sigHandlerFatal); signal(SIGFPE, CDStarGatewayApp::sigHandlerFatal); signal(SIGABRT, CDStarGatewayApp::sigHandlerFatal); - // signal(SIGTERM, CDStarGatewayApp::sigHandler); + signal(SIGTERM, CDStarGatewayApp::sigHandler); + signal(SIGINT, CDStarGatewayApp::sigHandler); setbuf(stdout, NULL); if (2 != argc) { @@ -89,6 +92,7 @@ int main(int argc, char *argv[]) CDStarGatewayApp::CDStarGatewayApp(const std::string &configFile) : m_configFile(configFile), m_thread(NULL) { + g_app = this; } CDStarGatewayApp::~CDStarGatewayApp() @@ -276,6 +280,15 @@ bool CDStarGatewayApp::createThread() return true; } +void CDStarGatewayApp::sigHandler(int sig) +{ + CLog::logInfo("Caught signal : %s, shutting down gateway", strsignal(sig)); + + if(g_app != nullptr && g_app->m_thread != nullptr) { + g_app->m_thread->kill(); + } +} + void CDStarGatewayApp::sigHandlerFatal(int sig) { CLog::logFatal("Caught signal : %s", strsignal(sig)); @@ -289,7 +302,6 @@ void CDStarGatewayApp::sigHandlerFatal(int sig) void CDStarGatewayApp::terminateHandler() { - #ifdef DEBUG_DSTARGW std::stringstream stackTrace; stackTrace << boost::stacktrace::stacktrace(); diff --git a/DStarGateway/DStarGatewayApp.h b/DStarGateway/DStarGatewayApp.h index 08c67cb..16df95f 100644 --- a/DStarGateway/DStarGatewayApp.h +++ b/DStarGateway/DStarGatewayApp.h @@ -21,7 +21,6 @@ #include "DStarGatewayThread.h" -void __sigHandler(int sig); class CDStarGatewayApp { @@ -29,6 +28,7 @@ private: std::string m_configFile; CDStarGatewayThread *m_thread; bool createThread(); + static CDStarGatewayApp * g_app; public: CDStarGatewayApp(const std::string &configFile); @@ -38,5 +38,6 @@ public: void run(); static void sigHandlerFatal(int sig); + static void sigHandler(int sig); static void terminateHandler(); }; From 4bbb29be31adbf1658bbc65b5e7b9443a03776ce Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Tue, 8 Feb 2022 17:53:45 +0100 Subject: [PATCH 018/112] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c064b0c..25f55dd 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ This is a port of G4KLX Jonathan Naylor's [ircddbGateway](https://github.com/g4k # 2. Current State ## 2.1. Code sanity -The current code is working, yet ugly IMHO as it is a mix of C and C++ of various ages. I realised that G4KLx started programming this over a decade ago, when C++11 was not yet a thing ! +The current code is working, yet it is a mix of C and C++ of various ages and there is plenty of room for rework. I realised that G4KLx started programming this over a decade ago, when C++11 was not yet a thing ! The code has also been amended to no longer rely on compiler defines for paths like log or data. These can be set in configuration file. From 55842c567c15a26e72b6e580390f5807c2092c51 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Tue, 8 Feb 2022 17:54:58 +0100 Subject: [PATCH 019/112] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 25f55dd..091d893 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 +- [Improvement] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)) - [Improvement] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) - [Improvement] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) ## 5.2. Version 0.5 From dc989239f9080b1df456fef5c010ae2ada6f05ec Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Feb 2022 06:57:12 +0100 Subject: [PATCH 020/112] #20 Comment timeserver --- .vscode/tasks.json | 10 +++++----- Makefile | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0545c42..88ce123 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,10 @@ "USE_GPSD=1", "all" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -65,10 +68,7 @@ "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/Makefile b/Makefile index d48596c..8f8d3b5 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DSta DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWTimeServer + echo bla DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit From 77a29c6bfef883cbbb23e45d8506a6cea91eb9cd Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Feb 2022 07:01:50 +0100 Subject: [PATCH 021/112] #21 Rename --- DStarGateway/DStarGatewayDefs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DStarGateway/DStarGatewayDefs.h b/DStarGateway/DStarGatewayDefs.h index 6739ab7..b62c727 100644 --- a/DStarGateway/DStarGatewayDefs.h +++ b/DStarGateway/DStarGatewayDefs.h @@ -21,7 +21,7 @@ #include -const std::string APPLICATION_NAME("ircDDB Gateway"); +const std::string APPLICATION_NAME("dstargateway"); const std::string CONFIG_FILE_NAME("dstargateway"); From b8d80b7b41dcb67cc64df3169987bd79197c5501 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Feb 2022 21:24:06 +0100 Subject: [PATCH 022/112] #21 add daemon --- BaseCommon/Daemon.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++ BaseCommon/Daemon.h | 38 ++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 BaseCommon/Daemon.cpp create mode 100644 BaseCommon/Daemon.h diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp new file mode 100644 index 0000000..27978c9 --- /dev/null +++ b/BaseCommon/Daemon.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Losely based on https://github.com/jirihnidek/daemon/blob/master/src/daemon.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Daemon.h" +#include "Log.h" + +int CDaemon::m_pid_fd = -1; +std::string CDaemon::m_pidFileName(""); + +DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile) +{ + pid_t pid = 0; + int fd; + + /* Fork off the parent process */ + pid = fork(); + + /* An error occurred */ + if (pid < 0) { + return DR_FAILURE; + } + + /* Success: Let the parent terminate */ + if (pid > 0) { + return DR_PARENT; + } + + /* On success: The child process becomes session leader */ + if (setsid() < 0) { + return DR_FAILURE; + } + + /* Ignore signal sent from child to parent process */ + signal(SIGCHLD, SIG_IGN); + + /* Fork off for the second time*/ + pid = fork(); + + /* An error occurred */ + if (pid < 0) { + return DR_FAILURE; + } + + /* Success: Let the parent terminate */ + if (pid > 0) { + return DR_PARENT; + } + + /* Set new file permissions */ + umask(0); + + /* Change the working directory to the root directory */ + /* or another appropriated directory */ + chdir("/"); + + /* Close all open file descriptors */ + for (fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) { + close(fd); + } + + /* Reopen stdin (fd = 0), stdout (fd = 1), stderr (fd = 2) */ + stdin = fopen("/dev/null", "r"); + stdout = fopen("/dev/null", "w+"); + stderr = fopen("/dev/null", "w+"); + + /* Try to write PID of daemon to lockfile */ + if (!pidFile.empty()) + { + CLog::logInfo("pidfile"); + + m_pid_fd = open(pidFile.c_str(), O_RDWR|O_CREAT, 0640); + if (m_pid_fd < 0) { + /* Can't open lockfile */ + return DR_PIDFILE_FAILED; + } + if (lockf(m_pid_fd, F_TLOCK, 0) < 0) { + /* Can't lock file */ + return DR_PIDFILE_FAILED; + } + + m_pidFileName.assign(pidFile); + + /* Get current PID */ + char str[256]; + sprintf(str, "%d\n", getpid()); + /* Write PID to lockfile */ + write(m_pid_fd, str, strlen(str)); + } + + return DR_CHILD; +} + +void CDaemon::finalize() +{ + if(m_pid_fd != -1) { + lockf(m_pid_fd, F_ULOCK, 0); + close(m_pid_fd); + } + + if(!m_pidFileName.empty()) { + unlink(m_pidFileName.c_str()); + } +} \ No newline at end of file diff --git a/BaseCommon/Daemon.h b/BaseCommon/Daemon.h new file mode 100644 index 0000000..ae2dcf8 --- /dev/null +++ b/BaseCommon/Daemon.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +enum DAEMONIZE_RESULT +{ + DR_PARENT, + DR_CHILD, + DR_FAILURE, + DR_PIDFILE_FAILED +}; + +class CDaemon +{ +public: + static DAEMONIZE_RESULT daemonize(const std::string& pidFile); + static void finalize(); + +private: + static int m_pid_fd; + static std::string m_pidFileName; +}; \ No newline at end of file From 12095eabb4bcc0f6e12887e0a5de926e35ec9664 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Feb 2022 21:24:25 +0100 Subject: [PATCH 023/112] #21 change service type to forking --- debian/dstargateway.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/dstargateway.service b/debian/dstargateway.service index 4f8c069..beaa3c8 100644 --- a/debian/dstargateway.service +++ b/debian/dstargateway.service @@ -5,7 +5,7 @@ After=network.target,network-online.target Wants=network-online.target [Service] -Type=simple +Type=forking ExecStart=/usr/local/bin/dstargateway %CFG_DIR%/dstargateway.cfg Restart=on-failure RestartSec=5 From 88f51f75b347f81b20dd22ed910a6aae76f8faab Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 13 Feb 2022 07:51:03 +0100 Subject: [PATCH 024/112] #21 Also setsid on second fork, systemd seems to require it --- BaseCommon/Daemon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index 27978c9..f0d6335 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -75,6 +75,10 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile) return DR_PARENT; } + if (setsid() < 0) { + return DR_FAILURE; + } + /* Set new file permissions */ umask(0); From 75abc130c1baa3ad215b9a1c16c1241f2701ea1b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 13 Feb 2022 07:55:14 +0100 Subject: [PATCH 025/112] #21 make it a real daemon --- DStarGateway/DStarGatewayApp.cpp | 107 +++++++++++++++++++--------- DStarGateway/DStarGatewayApp.h | 8 +-- DStarGateway/DStarGatewayConfig.cpp | 13 ++++ DStarGateway/DStarGatewayConfig.h | 9 +++ DStarGateway/Makefile | 1 + DStarGateway/example.cfg | 7 +- 6 files changed, 104 insertions(+), 41 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 8acfe2e..f0c4e3e 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #ifdef DEBUG_DSTARGW #include #endif @@ -45,6 +47,7 @@ #include "LogConsoleTarget.h" #include "APRSGPSDIdFrameProvider.h" #include "APRSFixedIdFrameProvider.h" +#include "Daemon.h" CDStarGatewayApp * CDStarGatewayApp::g_app = nullptr; @@ -70,16 +73,52 @@ int main(int argc, char *argv[]) return 1; } + printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); + printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + if ('-' == argv[1][0]) { - printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); - printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); - printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); return 0; } - std::string cfgFile(argv[1]); + CDStarGatewayConfig * config = new CDStarGatewayConfig(std::string((argv[1]))); + if(!config->load()) { + CLog::logFatal("Invalid configuration, aborting"); + return false; + } + + TDaemon daemon; + config->getGeneral(daemon); + + if (daemon.daemon) { + CLog::logInfo("Configured as a daemon, detaching ..."); + auto res = CDaemon::daemonize(daemon.pidFile); + + switch (res) + { + case DR_PARENT: + return 0; + case DR_CHILD: + break; + case DR_PIDFILE_FAILED: + break; + case DR_FAILURE: + [[fallthrough]]; + default: + CLog::logFatal("Fork failed, exiting"); + delete config; + return 1; + } + } + + // Setup Log + TLog logConf; + config->getLog(logConf); + CLog::finalise(); + if(logConf.m_displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.m_displayLevel)); + if(logConf.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.m_fileLevel, logConf.logDir, logConf.m_fileRotate)); - CDStarGatewayApp gateway(cfgFile); + CDStarGatewayApp gateway(config); if (!gateway.init()) { return 1; @@ -87,22 +126,30 @@ int main(int argc, char *argv[]) gateway.run(); + if(daemon.daemon) { + CDaemon::finalize(); + } + return 0; } -CDStarGatewayApp::CDStarGatewayApp(const std::string &configFile) : m_configFile(configFile), m_thread(NULL) +CDStarGatewayApp::CDStarGatewayApp(CDStarGatewayConfig * config) : +m_config(config), +m_thread(NULL) { + assert(config != nullptr); g_app = this; } CDStarGatewayApp::~CDStarGatewayApp() { + delete m_config; + delete m_thread; } bool CDStarGatewayApp::init() { return createThread(); - //2021-12-31 F4FXL This is ugly, if we failed to create the thread we do not clean up ... :( } void CDStarGatewayApp::run() @@ -115,30 +162,18 @@ void CDStarGatewayApp::run() bool CDStarGatewayApp::createThread() { - printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); - printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); - printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); - - CDStarGatewayConfig config(m_configFile); - if(!config.load()) { - CLog::logFatal("Invalid configuration, aborting"); - return false; - } - - // Setup Log + // Log TLog log; - config.getLog(log); - CLog::finalise(); - if(log.m_displayLevel != LOG_NONE) CLog::addTarget(new CLogConsoleTarget(log.m_displayLevel)); - if(log.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(log.m_fileLevel, log.logDir, log.m_fileRotate)); + m_config->getLog(log); + // Paths Tpaths paths; - config.getPaths(paths); + m_config->getPaths(paths); m_thread = new CDStarGatewayThread(log.logDir, paths.dataDir, ""); // Setup the gateway TGateway gatewayConfig; - config.getGateway(gatewayConfig); + m_config->getGateway(gatewayConfig); m_thread->setGateway(gatewayConfig.type, gatewayConfig.callsign, gatewayConfig.address); m_thread->setLanguage(gatewayConfig.language); m_thread->setLocation(gatewayConfig.latitude, gatewayConfig.longitude); @@ -146,12 +181,12 @@ bool CDStarGatewayApp::createThread() #ifdef USE_GPSD // Setup GPSD TGPSD gpsdConfig; - config.getGPSD(gpsdConfig); + m_config->getGPSD(gpsdConfig); #endif // Setup APRS TAPRS aprsConfig; - config.getAPRS(aprsConfig); + m_config->getAPRS(aprsConfig); CAPRSHandler * aprsWriter = NULL; if(aprsConfig.enabled && !aprsConfig.password.empty()) { aprsWriter = new CAPRSHandler(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address); @@ -176,9 +211,9 @@ bool CDStarGatewayApp::createThread() bool ddEnabled = false; bool atLeastOneRepeater = false; CRepeaterProtocolHandlerFactory repeaterProtocolFactory; - for(unsigned int i = 0U; i < config.getRepeaterCount(); i++) { + for(unsigned int i = 0U; i < m_config->getRepeaterCount(); i++) { TRepeater rptrConfig; - config.getRepeater(i, rptrConfig); + m_config->getRepeater(i, rptrConfig); auto repeaterProtocolHandler = repeaterProtocolFactory.getRepeaterProtocolHandler(rptrConfig.hwType, gatewayConfig, rptrConfig.address, rptrConfig.port); if(repeaterProtocolHandler == nullptr) continue; @@ -221,9 +256,9 @@ bool CDStarGatewayApp::createThread() // Setup ircddb auto ircddbVersionInfo = "linux_" + PRODUCT_NAME + "-" + VERSION; std::vector clients; - for(unsigned int i=0; i < config.getIrcDDBCount(); i++) { + for(unsigned int i=0; i < m_config->getIrcDDBCount(); i++) { TircDDB ircDDBConfig; - config.getIrcDDB(i, ircDDBConfig); + m_config->getIrcDDB(i, ircDDBConfig); CLog::logInfo("ircDDB Network %d set to %s user: %s, Quadnet %d", i + 1,ircDDBConfig.hostname.c_str(), ircDDBConfig.username.c_str(), ircDDBConfig.isQuadNet); CIRCDDB * ircDDB = new CIRCDDBClient(ircDDBConfig.hostname, 9007U, ircDDBConfig.username, ircDDBConfig.password, ircddbVersionInfo, gatewayConfig.address, ircDDBConfig.isQuadNet); clients.push_back(ircDDB); @@ -240,31 +275,31 @@ bool CDStarGatewayApp::createThread() // Setup Dextra TDextra dextraConfig; - config.getDExtra(dextraConfig); + m_config->getDExtra(dextraConfig); CLog::logInfo("DExtra enabled: %d, max. dongles: %u", int(dextraConfig.enabled), dextraConfig.maxDongles); m_thread->setDExtra(dextraConfig.enabled, dextraConfig.maxDongles); // Setup DCS TDCS dcsConfig; - config.getDCS(dcsConfig); + m_config->getDCS(dcsConfig); CLog::logInfo("DCS enabled: %d", int(dcsConfig.enabled)); m_thread->setDCS(dcsConfig.enabled); // Setup DPlus TDplus dplusConfig; - config.getDPlus(dplusConfig); + m_config->getDPlus(dplusConfig); CLog::logInfo("D-Plus enabled: %d, max. dongles: %u, login: %s", int(dplusConfig.enabled), dplusConfig.maxDongles, dplusConfig.login.c_str()); m_thread->setDPlus(dplusConfig.enabled, dplusConfig.maxDongles, dplusConfig.login); // Setup XLX TXLX xlxConfig; - config.getXLX(xlxConfig); + m_config->getXLX(xlxConfig); CLog::logInfo("XLX enabled: %d, Hosts file url: %s", int(xlxConfig.enabled), xlxConfig.url.c_str()); m_thread->setXLX(xlxConfig.enabled, xlxConfig.enabled ? CXLXHostsFileDownloader::download(xlxConfig.url) : ""); // Setup Remote TRemote remoteConfig; - config.getRemote(remoteConfig); + m_config->getRemote(remoteConfig); CLog::logInfo("Remote enabled: %d, port %u", int(remoteConfig.enabled), remoteConfig.port); m_thread->setRemote(remoteConfig.enabled, remoteConfig.password, remoteConfig.port); @@ -287,6 +322,8 @@ void CDStarGatewayApp::sigHandler(int sig) if(g_app != nullptr && g_app->m_thread != nullptr) { g_app->m_thread->kill(); } + + CDaemon::finalize(); } void CDStarGatewayApp::sigHandlerFatal(int sig) diff --git a/DStarGateway/DStarGatewayApp.h b/DStarGateway/DStarGatewayApp.h index 16df95f..6e5aa21 100644 --- a/DStarGateway/DStarGatewayApp.h +++ b/DStarGateway/DStarGatewayApp.h @@ -20,18 +20,18 @@ #pragma once #include "DStarGatewayThread.h" - +#include "DStarGatewayConfig.h" class CDStarGatewayApp { private: - std::string m_configFile; - CDStarGatewayThread *m_thread; + CDStarGatewayConfig * m_config; + CDStarGatewayThread * m_thread; bool createThread(); static CDStarGatewayApp * g_app; public: - CDStarGatewayApp(const std::string &configFile); + CDStarGatewayApp(CDStarGatewayConfig * config); ~CDStarGatewayApp(); bool init(); diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index a976c1f..5b35d4d 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -55,6 +55,7 @@ bool CDStarGatewayConfig::load() #ifdef USE_GPSD ret = loadGPSD(cfg) && ret; #endif + ret = loadDaemon(cfg) && ret; } if(ret) { @@ -69,6 +70,13 @@ bool CDStarGatewayConfig::load() return ret; } +bool CDStarGatewayConfig::loadDaemon(const CConfig & cfg) +{ + bool ret = cfg.getValue("daemon", "daemon", m_general.daemon, false); + ret = cfg.getValue("daemon", "pidfile", m_general.pidFile, 0, 1024, "") && ret; + return ret; +} + bool CDStarGatewayConfig::loadXLX(const CConfig & cfg) { bool ret = cfg.getValue("xlx", "enabled", m_xlx.enabled, true); @@ -438,3 +446,8 @@ void CDStarGatewayConfig::getGPSD(TGPSD & gpsd) const gpsd = m_gpsd; } #endif + +void CDStarGatewayConfig::getGeneral(TDaemon & gen) const +{ + gen = m_general; +} \ No newline at end of file diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index b27ca44..d8152f7 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -25,6 +25,11 @@ #include "Config.h" #include "LogSeverity.h" +typedef struct { + bool daemon; + std::string pidFile; +} TDaemon; + typedef struct { GATEWAY_TYPE type; std::string callsign; @@ -150,6 +155,7 @@ public: #ifdef USE_GPSD void getGPSD(TGPSD & gpsd) const; #endif + void getGeneral(TDaemon & gen) const; private: bool open(CConfig & cfg); @@ -167,6 +173,7 @@ private: #ifdef USE_GPSD bool loadGPSD(const CConfig & cfg); #endif + bool loadDaemon(const CConfig & cfg); std::string m_fileName; TGateway m_gateway; @@ -181,6 +188,8 @@ private: #ifdef USE_GPSD TGPSD m_gpsd; #endif + TDaemon m_general; + std::vector m_repeaters; std::vector m_ircDDB; }; diff --git a/DStarGateway/Makefile b/DStarGateway/Makefile index 73318d1..d5619c5 100644 --- a/DStarGateway/Makefile +++ b/DStarGateway/Makefile @@ -21,6 +21,7 @@ install: dstargateway @cp -fn example.cfg $(CFG_DIR)/dstargateway.cfg @sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dstargateway.cfg @sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dstargateway.cfg + @sed -i "s|daemon=false|daemon=true|g" $(CFG_DIR)/dstargateway.cfg ../APRS/APRS.a: ../Common/Common.a: diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index b83f0d6..9ece63e 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -2,7 +2,6 @@ # The order of the sections or key/values pairs inside the sections does not matter nor does casing. # Boolean values can be set using true, false, 1 or 0 # Floating point values must use . (point) as decimal separator - [Gateway] type= # repeater, hotspot, dongle. Defaults to repeater callsign= @@ -22,7 +21,7 @@ language= # valid values: english_uk, deutsch, dansk, francais, ita [ircddb_1] enabled=true hostname=ircv4.openquad.net -username= # The ircDDB username default to the value defined for gateway callsign. +username= # The ircDDB username defaults to the value defined for gateway callsign. password= [ircddb_2] @@ -178,3 +177,7 @@ enabled=false port=4242 password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field +[Daemon] +daemon=false +# pid file is in our case useless when running as a daemon using systemd as systemd handles all the pid stuff for us +pidfile=#/var/run/dstargateway/dstargateway.pid \ No newline at end of file From 7e7c45ea18fa6b5557d47f53e8f51d60b2f8e34b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 19:58:25 +0100 Subject: [PATCH 026/112] #21 flush console --- BaseCommon/LogConsoleTarget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/BaseCommon/LogConsoleTarget.cpp b/BaseCommon/LogConsoleTarget.cpp index 78cdabc..f3fc385 100644 --- a/BaseCommon/LogConsoleTarget.cpp +++ b/BaseCommon/LogConsoleTarget.cpp @@ -30,4 +30,5 @@ CLogTarget(logLevel) void CLogConsoleTarget::printLogInt(const std::string& msg) { std::cout << msg; + std::cout.flush(); } \ No newline at end of file From 37298b9bfbd7302aea55ce4f1da52f0ba8e6c394 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 19:58:36 +0100 Subject: [PATCH 027/112] #21 follow fork --- .vscode/launch.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 14711bb..5dced1b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,6 +25,10 @@ "description": "Définir la version désassemblage sur Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true + }, + { + "description": "Tell GDB to follow forks child", + "text": "-gdb-set follow-fork-mode child" } ] }, From 42131c9b9703f042844e2fc89bc6a9e68d2fa5a7 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 20:00:11 +0100 Subject: [PATCH 028/112] #better pid file locking, only fork if can get everything to do so --- BaseCommon/Daemon.cpp | 120 +++++++++++++++++++++++++++++++++--------- BaseCommon/Daemon.h | 5 +- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index f0d6335..90a5c5a 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -27,8 +27,9 @@ #include #include #include -#include #include +#include +#include #include "Daemon.h" #include "Log.h" @@ -36,16 +37,63 @@ int CDaemon::m_pid_fd = -1; std::string CDaemon::m_pidFileName(""); -DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile) +DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::string& userName) { + // get user + struct passwd* user = nullptr; + if(!userName.empty()) { + user = getpwnam(userName.c_str()); + if(user == nullptr) { + CLog::logFatal("Failed to get %s user", userName.c_str()); + return DR_FAILURE; + } + } + + // Create PID file if needed + if (!pidFile.empty()) { + auto tempFd = tryGetLock(pidFile); + if (tempFd < 0) { + CLog::logFatal("Failed to acquire lock on pidfile %s : %s", pidFile.c_str(), strerror(errno)); + return DR_PIDFILE_FAILED; + } + releaseLock(tempFd, ""); + + if(user != nullptr) { + int res = chown(pidFile.c_str(), user->pw_uid, user->pw_gid); + if(res != 0) { + CLog::logFatal("Failed to set ownership of pidfile to user %s : %s", userName.c_str(), strerror(errno)); + return DR_FAILURE; + } + } + } + + // change process ownership + if(user != nullptr) { + if(setgid(user->pw_gid) != 0) { + CLog::logFatal("Failed to set %s GID : %s", userName.c_str(), strerror(errno)); + return DR_FAILURE; + } + + if(setuid(user->pw_uid) != 0) { + CLog::logFatal("Failed to set %s UID : %s", userName.c_str(), strerror(errno)); + return DR_FAILURE; + } + + // Double check it worked (AKA Paranoia) + if (::setuid(0) != -1){ + CLog::logFatal("It's possible to regain root - something is wrong!, exiting"); + return DR_FAILURE; + } + } + pid_t pid = 0; - int fd; /* Fork off the parent process */ pid = fork(); /* An error occurred */ if (pid < 0) { + CLog::logFatal("Forking failed, exiting"); return DR_FAILURE; } @@ -54,54 +102,56 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile) return DR_PARENT; } - /* On success: The child process becomes session leader */ + // On success: The child process becomes session leader if (setsid() < 0) { + CLog::logFatal("Failed to set session id, exiting"); return DR_FAILURE; } /* Ignore signal sent from child to parent process */ signal(SIGCHLD, SIG_IGN); - /* Fork off for the second time*/ +#ifdef DOUBLE FORK + // Fork off for the second time. Some litterature says it is best to fork 2 times so that the process never can open a terminal. + // However it messes up systemd, event when unit is set as forking pid = fork(); - /* An error occurred */ + // An error occurred if (pid < 0) { + CLog::logFatal("Second forking failed, exiting"); return DR_FAILURE; } - /* Success: Let the parent terminate */ + // Success: Let the parent terminate if (pid > 0) { return DR_PARENT; } +#endif - if (setsid() < 0) { - return DR_FAILURE; - } - - /* Set new file permissions */ + // Set new file permissions umask(0); /* Change the working directory to the root directory */ /* or another appropriated directory */ - chdir("/"); + if(chdir("/") != 0) { + CLog::logFatal("Faild to cd, exiting"); + return DR_FAILURE; + } - /* Close all open file descriptors */ - for (fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) { + // Close all open file descriptors + for (int fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) { close(fd); } - /* Reopen stdin (fd = 0), stdout (fd = 1), stderr (fd = 2) */ + // Reopen stdin (fd = 0), stdout (fd = 1), stderr (fd = 2) stdin = fopen("/dev/null", "r"); stdout = fopen("/dev/null", "w+"); stderr = fopen("/dev/null", "w+"); - /* Try to write PID of daemon to lockfile */ + // Try to write PID of daemon to lockfile if (!pidFile.empty()) { - CLog::logInfo("pidfile"); - - m_pid_fd = open(pidFile.c_str(), O_RDWR|O_CREAT, 0640); + m_pid_fd = tryGetLock(pidFile); if (m_pid_fd < 0) { /* Can't open lockfile */ return DR_PIDFILE_FAILED; @@ -125,12 +175,34 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile) void CDaemon::finalize() { - if(m_pid_fd != -1) { - lockf(m_pid_fd, F_ULOCK, 0); - close(m_pid_fd); - } + releaseLock(m_pid_fd, m_pidFileName); if(!m_pidFileName.empty()) { unlink(m_pidFileName.c_str()); } +} + +int CDaemon::tryGetLock( const std::string& file ) +{ + mode_t m = umask( 0 ); + int fd = open( file.c_str(), O_RDWR|O_CREAT, 0640 ); + umask( m ); + if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 ) { + close( fd ); + fd = -1; + } + return fd; +} + +/*! Release the lock obtained with tryGetLock( lockName ). + * + * @param fd File descriptor of lock returned by tryGetLock( lockName ). + * @param lockName Name of file used as lock (i.e. '/var/lock/myLock'). + */ +void CDaemon::releaseLock(int fd, const std::string& file) +{ + if( fd < 0 ) + return; + remove(file.c_str()); + close(fd); } \ No newline at end of file diff --git a/BaseCommon/Daemon.h b/BaseCommon/Daemon.h index ae2dcf8..0981896 100644 --- a/BaseCommon/Daemon.h +++ b/BaseCommon/Daemon.h @@ -29,10 +29,13 @@ enum DAEMONIZE_RESULT class CDaemon { public: - static DAEMONIZE_RESULT daemonize(const std::string& pidFile); + static DAEMONIZE_RESULT daemonize(const std::string& pidFile, const std::string& user); static void finalize(); private: + static int tryGetLock(const std::string& file ); + static void releaseLock(int fd, const std::string& file ); + static int m_pid_fd; static std::string m_pidFileName; }; \ No newline at end of file From a7d394196bbf07b787a1ae4eeadd770eb04c46c0 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 20:00:55 +0100 Subject: [PATCH 029/112] #21 start process as root, process will switch to specified user --- debian/dstargateway.service | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/dstargateway.service b/debian/dstargateway.service index beaa3c8..8b8185a 100644 --- a/debian/dstargateway.service +++ b/debian/dstargateway.service @@ -1,5 +1,4 @@ [Unit] -User=dstar Description=D-STAR Gateway Daemon After=network.target,network-online.target Wants=network-online.target From 305723d3e95f17a3fb2b8973d2301460036487f0 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 20:01:11 +0100 Subject: [PATCH 030/112] #21 exit on pid file failure --- DStarGateway/DStarGatewayApp.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index f0c4e3e..7a8628f 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -92,7 +92,7 @@ int main(int argc, char *argv[]) if (daemon.daemon) { CLog::logInfo("Configured as a daemon, detaching ..."); - auto res = CDaemon::daemonize(daemon.pidFile); + auto res = CDaemon::daemonize(daemon.pidFile, "dstar"); switch (res) { @@ -101,12 +101,11 @@ int main(int argc, char *argv[]) case DR_CHILD: break; case DR_PIDFILE_FAILED: - break; case DR_FAILURE: - [[fallthrough]]; default: - CLog::logFatal("Fork failed, exiting"); + CLog::logFatal("Failed to run as daemon"); delete config; + CLog::finalise(); return 1; } } From 027bbc1f6557cafe62f6c2c0f217d2e72c7b7ef5 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 20:01:44 +0100 Subject: [PATCH 031/112] #21 fix typo --- BaseCommon/Daemon.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index 90a5c5a..6a2976a 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -111,7 +111,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin /* Ignore signal sent from child to parent process */ signal(SIGCHLD, SIG_IGN); -#ifdef DOUBLE FORK +#ifdef DOUBLE_FORK // Fork off for the second time. Some litterature says it is best to fork 2 times so that the process never can open a terminal. // However it messes up systemd, event when unit is set as forking pid = fork(); @@ -194,11 +194,7 @@ int CDaemon::tryGetLock( const std::string& file ) return fd; } -/*! Release the lock obtained with tryGetLock( lockName ). - * - * @param fd File descriptor of lock returned by tryGetLock( lockName ). - * @param lockName Name of file used as lock (i.e. '/var/lock/myLock'). - */ + void CDaemon::releaseLock(int fd, const std::string& file) { if( fd < 0 ) From 2328c31f775b5cdc55fcca54e24047ca5ab32dc9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Mon, 14 Feb 2022 20:12:13 +0100 Subject: [PATCH 032/112] #21 add daemon user --- DStarGateway/DStarGatewayApp.cpp | 4 ++-- DStarGateway/DStarGatewayConfig.cpp | 9 +++++---- DStarGateway/DStarGatewayConfig.h | 5 +++-- DStarGateway/example.cfg | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 7a8628f..8e7299b 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -88,11 +88,11 @@ int main(int argc, char *argv[]) } TDaemon daemon; - config->getGeneral(daemon); + config->getDaemon(daemon); if (daemon.daemon) { CLog::logInfo("Configured as a daemon, detaching ..."); - auto res = CDaemon::daemonize(daemon.pidFile, "dstar"); + auto res = CDaemon::daemonize(daemon.pidFile, daemon.user); switch (res) { diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 5b35d4d..5d236ca 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -72,8 +72,9 @@ bool CDStarGatewayConfig::load() bool CDStarGatewayConfig::loadDaemon(const CConfig & cfg) { - bool ret = cfg.getValue("daemon", "daemon", m_general.daemon, false); - ret = cfg.getValue("daemon", "pidfile", m_general.pidFile, 0, 1024, "") && ret; + bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false); + ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret; + ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret; return ret; } @@ -447,7 +448,7 @@ void CDStarGatewayConfig::getGPSD(TGPSD & gpsd) const } #endif -void CDStarGatewayConfig::getGeneral(TDaemon & gen) const +void CDStarGatewayConfig::getDaemon(TDaemon & gen) const { - gen = m_general; + gen = m_daemon; } \ No newline at end of file diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index d8152f7..dec8ca5 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -28,6 +28,7 @@ typedef struct { bool daemon; std::string pidFile; + std::string user; } TDaemon; typedef struct { @@ -155,7 +156,7 @@ public: #ifdef USE_GPSD void getGPSD(TGPSD & gpsd) const; #endif - void getGeneral(TDaemon & gen) const; + void getDaemon(TDaemon & gen) const; private: bool open(CConfig & cfg); @@ -188,7 +189,7 @@ private: #ifdef USE_GPSD TGPSD m_gpsd; #endif - TDaemon m_general; + TDaemon m_daemon; std::vector m_repeaters; std::vector m_ircDDB; diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 9ece63e..2683b19 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -179,5 +179,5 @@ password=CHANGE_ME # If password is left blank, remote will be disabled regardle [Daemon] daemon=false -# pid file is in our case useless when running as a daemon using systemd as systemd handles all the pid stuff for us -pidfile=#/var/run/dstargateway/dstargateway.pid \ No newline at end of file +pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice +user=dstar # user account the daemon will run under, ideally a user with low privileges \ No newline at end of file From 00a2301ef6af3148533928dadd3c8e6c427187df Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 16 Feb 2022 09:26:00 +0100 Subject: [PATCH 033/112] #21 remove daemon finalize from sigHAndler --- DStarGateway/DStarGatewayApp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 8e7299b..325e21b 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -321,8 +321,6 @@ void CDStarGatewayApp::sigHandler(int sig) if(g_app != nullptr && g_app->m_thread != nullptr) { g_app->m_thread->kill(); } - - CDaemon::finalize(); } void CDStarGatewayApp::sigHandlerFatal(int sig) From 635e629988a762b3516f3c7988eb0a349571e9be Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 16 Feb 2022 10:57:13 +0100 Subject: [PATCH 034/112] #21 force debug info to stderr, fix segfault on exit --- DStarGateway/DStarGatewayApp.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 325e21b..b88f2d8 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -142,8 +142,6 @@ m_thread(NULL) CDStarGatewayApp::~CDStarGatewayApp() { - delete m_config; - delete m_thread; } bool CDStarGatewayApp::init() @@ -326,12 +324,14 @@ void CDStarGatewayApp::sigHandler(int sig) void CDStarGatewayApp::sigHandlerFatal(int sig) { CLog::logFatal("Caught signal : %s", strsignal(sig)); + fprintf(stderr, "Caught signal : %s\n", strsignal(sig)); #ifdef DEBUG_DSTARGW std::stringstream stackTrace; stackTrace << boost::stacktrace::stacktrace(); CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str()); + fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str()); #endif - exit(1); + exit(3); } void CDStarGatewayApp::terminateHandler() @@ -350,13 +350,15 @@ void CDStarGatewayApp::terminateHandler() } else { CLog::logFatal("Unhandled unknown exception occured"); + fprintf(stderr, "Unknown ex\n"); } } catch(const std::exception& e) { CLog::logFatal("Unhandled exception occured %s", e.what()); + fprintf(stderr, "Unhandled ex %s\n", e.what()); } #ifdef DEBUG_DSTARGW CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str()); #endif - exit(1); + exit(2); } From 4eef7b2dcf52fca0b33f4a306acdce35af9098cf Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 16 Feb 2022 10:58:27 +0100 Subject: [PATCH 035/112] #21 do not close stdin,stdout and stderr --- BaseCommon/Daemon.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index 6a2976a..5283040 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -138,6 +138,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin return DR_FAILURE; } +#ifdef CLOSE_FILE_DESC // Close all open file descriptors for (int fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) { close(fd); @@ -147,6 +148,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin stdin = fopen("/dev/null", "r"); stdout = fopen("/dev/null", "w+"); stderr = fopen("/dev/null", "w+"); +#endif // Try to write PID of daemon to lockfile if (!pidFile.empty()) From c05fcc936c7441213cec1f89505500aca451e92a Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 16 Feb 2022 13:17:39 +0100 Subject: [PATCH 036/112] #21 only change user if we are root --- BaseCommon/Daemon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index 5283040..46ad038 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -58,7 +58,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin } releaseLock(tempFd, ""); - if(user != nullptr) { + if(user != nullptr && getuid() == 0) { int res = chown(pidFile.c_str(), user->pw_uid, user->pw_gid); if(res != 0) { CLog::logFatal("Failed to set ownership of pidfile to user %s : %s", userName.c_str(), strerror(errno)); @@ -68,7 +68,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin } // change process ownership - if(user != nullptr) { + if(user != nullptr && getuid() == 0) { if(setgid(user->pw_gid) != 0) { CLog::logFatal("Failed to set %s GID : %s", userName.c_str(), strerror(errno)); return DR_FAILURE; From 721cdc130276330aae8e1cbc2eeff3f92bb72b04 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 16 Feb 2022 13:19:12 +0100 Subject: [PATCH 037/112] #21 clarify user --- DStarGateway/example.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 2683b19..a6d71c7 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -180,4 +180,4 @@ password=CHANGE_ME # If password is left blank, remote will be disabled regardle [Daemon] daemon=false pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice -user=dstar # user account the daemon will run under, ideally a user with low privileges \ No newline at end of file +user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root \ No newline at end of file From 757e62e3a3dc0a9417e10d53e180d4cb0c461845 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 16 Feb 2022 13:29:19 +0100 Subject: [PATCH 038/112] #21 sipmlify root check --- BaseCommon/Daemon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index 46ad038..d0afc21 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -41,7 +41,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin { // get user struct passwd* user = nullptr; - if(!userName.empty()) { + if(!userName.empty() && getuid() == 0) { user = getpwnam(userName.c_str()); if(user == nullptr) { CLog::logFatal("Failed to get %s user", userName.c_str()); @@ -58,7 +58,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin } releaseLock(tempFd, ""); - if(user != nullptr && getuid() == 0) { + if(user != nullptr) { int res = chown(pidFile.c_str(), user->pw_uid, user->pw_gid); if(res != 0) { CLog::logFatal("Failed to set ownership of pidfile to user %s : %s", userName.c_str(), strerror(errno)); @@ -68,7 +68,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin } // change process ownership - if(user != nullptr && getuid() == 0) { + if(user != nullptr) { if(setgid(user->pw_gid) != 0) { CLog::logFatal("Failed to set %s GID : %s", userName.c_str(), strerror(errno)); return DR_FAILURE; @@ -80,7 +80,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin } // Double check it worked (AKA Paranoia) - if (::setuid(0) != -1){ + if (setuid(0) != -1){ CLog::logFatal("It's possible to regain root - something is wrong!, exiting"); return DR_FAILURE; } From 041e0f57d9b6fe4e58e9c3cbd3c0f3a6833d20d9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 07:23:37 +0100 Subject: [PATCH 039/112] #21 finalise in Entry --- DStarGateway/DStarGatewayThread.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/DStarGateway/DStarGatewayThread.cpp b/DStarGateway/DStarGatewayThread.cpp index a2d4bc6..73ea87c 100644 --- a/DStarGateway/DStarGatewayThread.cpp +++ b/DStarGateway/DStarGatewayThread.cpp @@ -131,19 +131,7 @@ m_restrictList(NULL) CDStarGatewayThread::~CDStarGatewayThread() { - CHeaderData::finalise(); - CG2Handler::finalise(); - CDExtraHandler::finalise(); - CDPlusHandler::finalise(); - CDCSHandler::finalise(); - CRepeaterHandler::finalise(); -#ifdef USE_STARNET - CStarNetHandler::finalise(); -#endif -#ifdef USE_CCS - CCCSHandler::finalise(); -#endif - CAudioUnit::finalise(); + } void* CDStarGatewayThread::Entry() @@ -491,7 +479,19 @@ void* CDStarGatewayThread::Entry() delete headerLogger; } + CHeaderData::finalise(); + CG2Handler::finalise(); + CDExtraHandler::finalise(); CDPlusHandler::finalise(); + CDCSHandler::finalise(); + CRepeaterHandler::finalise(); +#ifdef USE_STARNET + CStarNetHandler::finalise(); +#endif +#ifdef USE_CCS + CCCSHandler::finalise(); +#endif + CAudioUnit::finalise(); return NULL; } From e7c89d81719522cad9396c95dd4766b375d12dce Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 07:25:41 +0100 Subject: [PATCH 040/112] #21 close aprs in Gateway Thread --- Common/RepeaterHandler.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Common/RepeaterHandler.cpp b/Common/RepeaterHandler.cpp index 7c486ee..35685a6 100644 --- a/Common/RepeaterHandler.cpp +++ b/Common/RepeaterHandler.cpp @@ -438,11 +438,6 @@ void CRepeaterHandler::finalise() m_repeaters[i] = NULL; } - if (m_aprsWriter != NULL) { - m_aprsWriter->close(); - delete m_aprsWriter; - } - delete[] m_repeaters; } From 8c4696ce2623722a74bb42ff5636e3f7f224b7b9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 07:26:00 +0100 Subject: [PATCH 041/112] #21 stop and then clean --- Common/APRSHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Common/APRSHandler.cpp b/Common/APRSHandler.cpp index 4d6b81a..c81a745 100644 --- a/Common/APRSHandler.cpp +++ b/Common/APRSHandler.cpp @@ -226,13 +226,13 @@ bool CAPRSHandler::isConnected() const void CAPRSHandler::close() { + m_thread->stop(); + if(m_idFrameProvider != nullptr) { m_idFrameProvider->close(); delete m_idFrameProvider; m_idFrameProvider = nullptr; } - - m_thread->stop(); } void CAPRSHandler::addReadAPRSCallback(IReadAPRSFrameCallback* cb) From 1dbdf5babb3a45eea57754b00bb4033b03ab834f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 07:51:07 +0100 Subject: [PATCH 042/112] #21 write banner do log files --- DStarGateway/DStarGatewayApp.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index b88f2d8..9483208 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -50,6 +50,9 @@ #include "Daemon.h" CDStarGatewayApp * CDStarGatewayApp::g_app = nullptr; +const std::string BANNER_1 = CStringUtils::string_format("%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); +const std::string BANNER_2 = "DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"; +const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n"; #ifdef UNIT_TESTS int fakemain(int argc, char *argv[]) @@ -73,9 +76,7 @@ int main(int argc, char *argv[]) return 1; } - printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); - printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); - printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3; if ('-' == argv[1][0]) { return 0; @@ -117,6 +118,13 @@ int main(int argc, char *argv[]) if(logConf.m_displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.m_displayLevel)); if(logConf.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.m_fileLevel, logConf.logDir, logConf.m_fileRotate)); + //write banner in log file if we are dameon + if(daemon.daemon) { + CLog::logInfo(BANNER_1); + CLog::logInfo(BANNER_2); + CLog::logInfo(BANNER_3); + } + CDStarGatewayApp gateway(config); if (!gateway.init()) { From 4eefd5c1ab7feb7b42d18fceae60ab2cf1d2578d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 07:53:48 +0100 Subject: [PATCH 043/112] #21 finalize -> finalise --- BaseCommon/Daemon.cpp | 2 +- BaseCommon/Daemon.h | 6 +++--- DStarGateway/DStarGatewayApp.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index d0afc21..6fc09b0 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -175,7 +175,7 @@ DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::strin return DR_CHILD; } -void CDaemon::finalize() +void CDaemon::finalise() { releaseLock(m_pid_fd, m_pidFileName); diff --git a/BaseCommon/Daemon.h b/BaseCommon/Daemon.h index 0981896..3fa8809 100644 --- a/BaseCommon/Daemon.h +++ b/BaseCommon/Daemon.h @@ -30,11 +30,11 @@ class CDaemon { public: static DAEMONIZE_RESULT daemonize(const std::string& pidFile, const std::string& user); - static void finalize(); + static void finalise(); private: - static int tryGetLock(const std::string& file ); - static void releaseLock(int fd, const std::string& file ); + static int tryGetLock(const std::string& file); + static void releaseLock(int fd, const std::string& file); static int m_pid_fd; static std::string m_pidFileName; diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 9483208..8604a00 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -134,7 +134,7 @@ int main(int argc, char *argv[]) gateway.run(); if(daemon.daemon) { - CDaemon::finalize(); + CDaemon::finalise(); } return 0; From e0af58fd971e32edb828857b15b3d69907e57be0 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 07:55:24 +0100 Subject: [PATCH 044/112] #21 daemonize -> daemonise --- BaseCommon/Daemon.cpp | 2 +- BaseCommon/Daemon.h | 2 +- DStarGateway/DStarGatewayApp.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BaseCommon/Daemon.cpp b/BaseCommon/Daemon.cpp index 6fc09b0..aa34ed7 100644 --- a/BaseCommon/Daemon.cpp +++ b/BaseCommon/Daemon.cpp @@ -37,7 +37,7 @@ int CDaemon::m_pid_fd = -1; std::string CDaemon::m_pidFileName(""); -DAEMONIZE_RESULT CDaemon::daemonize(const std::string& pidFile, const std::string& userName) +DAEMONIZE_RESULT CDaemon::daemonise(const std::string& pidFile, const std::string& userName) { // get user struct passwd* user = nullptr; diff --git a/BaseCommon/Daemon.h b/BaseCommon/Daemon.h index 3fa8809..7f2556f 100644 --- a/BaseCommon/Daemon.h +++ b/BaseCommon/Daemon.h @@ -29,7 +29,7 @@ enum DAEMONIZE_RESULT class CDaemon { public: - static DAEMONIZE_RESULT daemonize(const std::string& pidFile, const std::string& user); + static DAEMONIZE_RESULT daemonise(const std::string& pidFile, const std::string& user); static void finalise(); private: diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 8604a00..d0d5c52 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -93,7 +93,7 @@ int main(int argc, char *argv[]) if (daemon.daemon) { CLog::logInfo("Configured as a daemon, detaching ..."); - auto res = CDaemon::daemonize(daemon.pidFile, daemon.user); + auto res = CDaemon::daemonise(daemon.pidFile, daemon.user); switch (res) { From 48e2b492439eb668854e14ac14cf0488fa47d102 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 13:56:11 +0100 Subject: [PATCH 045/112] #21 revert back to simple systemd service --- DStarGateway/Makefile | 1 - DStarGateway/example.cfg | 3 +++ debian/dstargateway.service | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DStarGateway/Makefile b/DStarGateway/Makefile index d5619c5..73318d1 100644 --- a/DStarGateway/Makefile +++ b/DStarGateway/Makefile @@ -21,7 +21,6 @@ install: dstargateway @cp -fn example.cfg $(CFG_DIR)/dstargateway.cfg @sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dstargateway.cfg @sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dstargateway.cfg - @sed -i "s|daemon=false|daemon=true|g" $(CFG_DIR)/dstargateway.cfg ../APRS/APRS.a: ../Common/Common.a: diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index a6d71c7..36e38de 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -177,6 +177,9 @@ enabled=false port=4242 password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field +# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd +# require a pid file. Moreover system handles the user under which the program is started. This is provided as convenience for people who might +# run the program using sysv or any other old school init system. [Daemon] daemon=false pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice diff --git a/debian/dstargateway.service b/debian/dstargateway.service index 8b8185a..314a11d 100644 --- a/debian/dstargateway.service +++ b/debian/dstargateway.service @@ -1,10 +1,11 @@ [Unit] +User=dstar Description=D-STAR Gateway Daemon After=network.target,network-online.target Wants=network-online.target [Service] -Type=forking +Type=simple ExecStart=/usr/local/bin/dstargateway %CFG_DIR%/dstargateway.cfg Restart=on-failure RestartSec=5 @@ -13,5 +14,3 @@ StartLimitBurst=0 [Install] WantedBy=multi-user.target - - From 5ec67b9f8a90392e48c8699338d0db0831af8afc Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:20:09 +0100 Subject: [PATCH 046/112] #20 move install to respective makefiles --- DGWTimeServer/Makefile | 5 +++-- DStarGateway/Makefile | 9 +++++++++ Makefile | 11 +---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 4ea197d..74ef75c 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -3,7 +3,8 @@ OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a - $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) +# $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + echo bla %.o : %.cpp $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ @@ -15,7 +16,7 @@ clean: .PHONY install: install: dgwtimeserver # copy executable - @cp -f dgwtimeserver $(BIN_DIR) +# @cp -f dgwtimeserver $(BIN_DIR) ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: diff --git a/DStarGateway/Makefile b/DStarGateway/Makefile index 73318d1..03b2ea3 100644 --- a/DStarGateway/Makefile +++ b/DStarGateway/Makefile @@ -22,6 +22,15 @@ install: dstargateway @sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dstargateway.cfg @sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dstargateway.cfg +# SystemD service install + @cp -f ../debian/dstargateway.service /lib/systemd/system/ + @sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dstargateway.service + systemctl enable dstargateway.service + @systemctl daemon-reload + @echo "\n" + @echo "DStarGateway Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dstargateway.service'" + @echo "\n" + ../APRS/APRS.a: ../Common/Common.a: ../DStarBase/DStarBase.a: diff --git a/Makefile b/Makefile index 8f8d3b5..e34632e 100644 --- a/Makefile +++ b/Makefile @@ -120,18 +120,9 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol $(MAKE) -C Data install @chown -R dstar:dstar $(DATA_DIR) -#install executables +# install services executables $(MAKE) -C DStarGateway install -# SystemD service install - @cp -f debian/dstargateway.service /lib/systemd/system/ - @sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dstargateway.service - systemctl enable dstargateway.service - @systemctl daemon-reload - @echo "\n\n" - @echo "Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dstargateway.service'" - @echo "\n\n" - .PHONY: uninstall uninstall : systemctl stop dstargateway.service || true From c1ea6fa9a4a1cfc454ab04553ba05ae83b39710f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:22:03 +0100 Subject: [PATCH 047/112] #21 correct user entry --- debian/dstargateway.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/dstargateway.service b/debian/dstargateway.service index 314a11d..d3981ee 100644 --- a/debian/dstargateway.service +++ b/debian/dstargateway.service @@ -1,10 +1,10 @@ [Unit] -User=dstar Description=D-STAR Gateway Daemon After=network.target,network-online.target Wants=network-online.target [Service] +User=dstar Type=simple ExecStart=/usr/local/bin/dstargateway %CFG_DIR%/dstargateway.cfg Restart=on-failure From 874c3ab7b44e16de57f131f854870cfdf317aa33 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:37:18 +0100 Subject: [PATCH 048/112] #20 add app --- DGWTimeServer/DGWTimeServerApp.cpp | 32 ++++++++++++++++++++++++++++++ DGWTimeServer/DGWTimeServerApp.h | 21 ++++++++++++++++++++ DGWTimeServer/Makefile | 4 ++-- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 DGWTimeServer/DGWTimeServerApp.cpp create mode 100644 DGWTimeServer/DGWTimeServerApp.h diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp new file mode 100644 index 0000000..ac1c3c2 --- /dev/null +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "DGWTimeServerApp.h" + +int main(int argc, char * argv[]) +{ + if (2 != argc) { + printf("usage: %s path_to_config_file\n", argv[0]); + printf(" %s --version\n", argv[0]); + return 1; + } +} \ No newline at end of file diff --git a/DGWTimeServer/DGWTimeServerApp.h b/DGWTimeServer/DGWTimeServerApp.h new file mode 100644 index 0000000..fc87275 --- /dev/null +++ b/DGWTimeServer/DGWTimeServerApp.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 74ef75c..5400a76 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -3,7 +3,7 @@ OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a -# $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) echo bla %.o : %.cpp @@ -16,7 +16,7 @@ clean: .PHONY install: install: dgwtimeserver # copy executable -# @cp -f dgwtimeserver $(BIN_DIR) + @cp -f dgwtimeserver $(BIN_DIR) ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: From 36750136a0731efdbb442a1c2abd589c03e541a9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:37:54 +0100 Subject: [PATCH 049/112] #20 add time server to git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8bf1885..b2f773d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ Tests/dstargateway_tests DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit DGWTextTransmit/dgwtexttransmit +DGWTimeServer/dgwtimeserver From 31f41be0249d58a1391446bdcc4b42d536eb8783 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 07:46:15 +0100 Subject: [PATCH 050/112] Add new line at end of file (keep git happy) --- DStarGateway/example.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 36e38de..0d349ad 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -183,4 +183,4 @@ password=CHANGE_ME # If password is left blank, remote will be disabled regardle [Daemon] daemon=false pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice -user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root \ No newline at end of file +user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root From 5a8091c58dc3a8006a8cb5144c564e450a82a884 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 08:52:50 +0100 Subject: [PATCH 051/112] #20 clear repeater list --- DStarGateway/DStarGatewayConfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 5d236ca..0afd547 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -199,6 +199,7 @@ bool CDStarGatewayConfig::loadPaths(const CConfig & cfg) bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg) { + m_repeaters.clear(); for(unsigned int i = 0; i < 4; i++) { std::string section = CStringUtils::string_format("repeater_%d", i+ 1); bool repeaterEnabled; From 77634729d40b57a633515231b58ea6479d339309 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 09:21:05 +0100 Subject: [PATCH 052/112] #20 add config --- .vscode/launch.json | 2 +- DGWTimeServer/DGWTimeServerApp.cpp | 8 ++ DGWTimeServer/Makefile | 1 - DGWTimeServer/TimeServerConfig.cpp | 139 +++++++++++++++++++++++++++++ DGWTimeServer/TimeServerConfig.h | 65 ++++++++++++++ DGWTimeServer/TimeServerDefs.h | 49 ++++++++++ DGWTimeServer/example.cfg | 32 +++++++ Makefile | 1 - 8 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 DGWTimeServer/TimeServerConfig.cpp create mode 100644 DGWTimeServer/TimeServerConfig.h create mode 100644 DGWTimeServer/TimeServerDefs.h create mode 100644 DGWTimeServer/example.cfg diff --git a/.vscode/launch.json b/.vscode/launch.json index bd85764..f15168f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -85,7 +85,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DGWTimeServer/dgwtimeserver", - "args": [], + "args": ["${workspaceFolder}/Sandbox/__timeserver_test.cfg"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index ac1c3c2..6b894ec 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -21,6 +21,7 @@ #include #include "DGWTimeServerApp.h" +#include "TimeServerConfig.h" int main(int argc, char * argv[]) { @@ -29,4 +30,11 @@ int main(int argc, char * argv[]) printf(" %s --version\n", argv[0]); return 1; } + + std::string configfile(argv[1]); + CTimeServerConfig config(configfile); + if(!config.load()) + return 0; + + return 1; } \ No newline at end of file diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 5400a76..4ea197d 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -4,7 +4,6 @@ DEPS = $(SRCS:.cpp=.d) dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) - echo bla %.o : %.cpp $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp new file mode 100644 index 0000000..ec31444 --- /dev/null +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "TimeServerConfig.h" +#include "Log.h" +#include "StringUtils.h" + +CTimeServerConfig::CTimeServerConfig(const std::string &pathname) : +m_fileName(pathname), +m_repeaters() +{ + +} + +CTimeServerConfig::~CTimeServerConfig() +{ + for(auto repeater : m_repeaters) { + delete repeater; + } + m_repeaters.clear(); +} + +bool CTimeServerConfig::load() +{ + bool ret = false; + CLog::logInfo("Loading Configuration from %s", m_fileName.c_str()); + CConfig cfg(m_fileName); + + ret = open(cfg); + if(ret) { + ret = loadTimeServer(cfg) && ret; + ret = loadRepeaters(cfg) && ret; + ret = loadDaemon(cfg) && ret; + } + + return ret; +} + +bool CTimeServerConfig::open(CConfig & cfg) +{ + try { + return cfg.load(); + } + catch(...) { + CLog::logError("Can't read %s\n", m_fileName.c_str()); + return false; + } + return true; +} + +bool CTimeServerConfig::loadRepeaters(const CConfig & cfg) +{ + m_repeaters.clear(); + + for(unsigned int i = 0; i < 4; i++) { + std::string section = CStringUtils::string_format("repeater_%d", i+ 1); + bool repeaterEnabled; + + bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false); + if(!ret || !repeaterEnabled) + continue; + + TRepeater * repeater = new TRepeater; + ret = cfg.getValue(section, "band", repeater->band, 1, 2, "") && ret; + + bool alreadyConfigured = std::any_of(m_repeaters.begin(), m_repeaters.end(), [repeater](TRepeater * r) { return r->band == repeater->band;}); + if(alreadyConfigured) { + CLog::logWarning("%s-%s repeater already configured, ignoring", m_timeServer.callsign.c_str(), repeater->band.c_str()); + delete repeater; + continue; + } + + m_repeaters.push_back(repeater); + } + + return m_repeaters.size() > 0U; +} + +bool CTimeServerConfig::loadTimeServer(const CConfig & cfg) +{ + bool ret = cfg.getValue("timeserver", "callsign", m_timeServer.callsign, 3, 8, ""); + boost::to_upper(m_timeServer.callsign); + ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret; + + std::string format; + ret = cfg.getValue("timeserver", "format", format, "voiceandtext", {"voice", "text", "voiceandtext"}) && ret; + if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME; + else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME; + else if(format == "voiceandtext") m_timeServer.format = FORMAT_VOICE_ALL; + + std::string lang; + ret = cfg.getValue("timeserver", "language", lang, "english_uk_1", {"english_uk_1", "english_uk_2", "english_us_1", "english_us_2", "deutsch_1", "deutsch_2", "francais", "nederlands", "svenska", "espanol", "norsk", "portugues"}) && ret;; + if (lang == "english_uk_1") m_timeServer.language = LANG_ENGLISH_UK_1; + else if(lang == "english_uk_2") m_timeServer.language = LANG_ENGLISH_UK_2; + else if(lang == "english_us_1") m_timeServer.language = LANG_ENGLISH_US_1; + else if(lang == "english_us_2") m_timeServer.language = LANG_ENGLISH_US_2; + else if(lang == "deutsch_1" ) m_timeServer.language = LANG_DEUTSCH_1; + else if(lang == "detusch_2" ) m_timeServer.language = LANG_DEUTSCH_2; + else if(lang == "francais" ) m_timeServer.language = LANG_FRANCAIS; + else if(lang == "nederlands" ) m_timeServer.language = LANG_NEDERLANDS; + else if(lang == "svenska" ) m_timeServer.language = LANG_SVENSKA; + else if(lang == "espanol" ) m_timeServer.language = LANG_ESPANOL; + else if(lang == "norsk" ) m_timeServer.language = LANG_NORSK; + else if(lang == "portugues" ) m_timeServer.language = LANG_PORTUGUES; + + std::string interval; + ret = cfg.getValue("timeserver", "interval", interval, "30", {"15", "30", "60"}) && ret; + if(interval == "15") m_timeServer.interval = INTERVAL_15MINS; + else if(interval == "30") m_timeServer.interval = INTERVAL_30MINS; + else if(interval == "60") m_timeServer.interval = INTERVAL_60MINS; + + return ret; +} + +bool CTimeServerConfig::loadDaemon(const CConfig & cfg) +{ + bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false); + ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret; + ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret; + return ret; +} diff --git a/DGWTimeServer/TimeServerConfig.h b/DGWTimeServer/TimeServerConfig.h new file mode 100644 index 0000000..d17a650 --- /dev/null +++ b/DGWTimeServer/TimeServerConfig.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include + +#include "Config.h" +#include "TimeServerDefs.h" + +typedef struct { + std::string callsign; + std::string address; + FORMAT format; + LANGUAGE language; + INTERVAL interval; +} TTimeServer; + +typedef struct { + bool enabled; + std::string band; +} TRepeater; + +typedef struct { + bool daemon; + std::string pidFile; + std::string user; +} TDaemon; + + +class CTimeServerConfig +{ +public: + CTimeServerConfig(const std::string &pathname); + ~CTimeServerConfig(); + + bool load(); + +private: + bool open(CConfig & cfg); + bool loadRepeaters(const CConfig & cfg); + bool loadTimeServer(const CConfig & cfg); + bool loadDaemon(const CConfig & cfg); + + std::string m_fileName; + std::vector m_repeaters; + TTimeServer m_timeServer; + TDaemon m_daemon; +}; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h new file mode 100644 index 0000000..d82a57e --- /dev/null +++ b/DGWTimeServer/TimeServerDefs.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012,2013,2014 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +const std::string APPLICATION_NAME("Time Server"); + +enum LANGUAGE { + LANG_ENGLISH_UK_1, + LANG_ENGLISH_UK_2, + LANG_ENGLISH_US_1, + LANG_ENGLISH_US_2, + LANG_DEUTSCH_1, + LANG_DEUTSCH_2, + LANG_FRANCAIS, + LANG_NEDERLANDS, + LANG_SVENSKA, + LANG_ESPANOL, + LANG_NORSK, + LANG_PORTUGUES +}; + +enum INTERVAL { + INTERVAL_15MINS, + INTERVAL_30MINS, + INTERVAL_60MINS +}; + +enum FORMAT { + FORMAT_VOICE_TIME, + FORMAT_VOICE_ALL, + FORMAT_TEXT_TIME +}; diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg new file mode 100644 index 0000000..cc6b0cf --- /dev/null +++ b/DGWTimeServer/example.cfg @@ -0,0 +1,32 @@ +[TimeServer] +callsign= # call of the gateway to send time beacons without G letter +address= # address of the gateway, defaults to 127.0.0.1 +format= # possible values are voice, text, voiceandtext, defaults to voice and text +language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1 +interval= # valid values are 15, 30 and 60, defaults to 30 + +# Up to 4 repeaters can be enabled to transmit time beacons +[Repeater_1] +enabled=true # enable time beacons on this repeater +band=B # Module letter of the repeater + +[Repeater_2] +enabled=false # enable time beacons on this repeater +band= # Module letter of the repeater + +[Repeater_3] +enabled=false # enable time beacons on this repeater +band= # Module letter of the repeater + +[Repeater_4] +enabled=false # enable time beacons on this repeater +band= # Module letter of the repeater + + +# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd +# require a pid file. Moreover system handles the user under which the program is started. This is provided as convenience for people who might +# run the program using sysv or any other old school init system. +[Daemon] +daemon=false +pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice +user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root diff --git a/Makefile b/Makefile index e34632e..1ed1c97 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,6 @@ DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DSta DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWTimeServer - echo bla DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit From b237829b95703720aaec39726e0324b495b21df5 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 16:46:16 +0100 Subject: [PATCH 053/112] #20 first working prototype of time server --- DGWTimeServer/DGWTimeServerApp.cpp | 61 +- DGWTimeServer/DGWTimeServerApp.h | 21 + DGWTimeServer/TimeServerConfig.cpp | 32 + DGWTimeServer/TimeServerConfig.h | 11 +- DGWTimeServer/TimeServerDefs.h | 2 +- DGWTimeServer/TimeServerThread.cpp | 1458 ++++++++++++++++++++++++++++ DGWTimeServer/TimeServerThread.h | 122 +++ DGWTimeServer/example.cfg | 3 + 8 files changed, 1705 insertions(+), 5 deletions(-) create mode 100644 DGWTimeServer/TimeServerThread.cpp create mode 100644 DGWTimeServer/TimeServerThread.h diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 6b894ec..5088ee2 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -19,9 +19,9 @@ #include #include +#include #include "DGWTimeServerApp.h" -#include "TimeServerConfig.h" int main(int argc, char * argv[]) { @@ -34,7 +34,62 @@ int main(int argc, char * argv[]) std::string configfile(argv[1]); CTimeServerConfig config(configfile); if(!config.load()) + return 1; + + CDGWTimeServerApp app(&config); + + if(!app.init()) return 0; - return 1; -} \ No newline at end of file + app.run(); + + return 0; +} + +CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) : +m_config(config) +{ + assert(config != nullptr); +} + +CDGWTimeServerApp::~CDGWTimeServerApp() +{ + delete m_thread; +} + +bool CDGWTimeServerApp::init() +{ + return createThread(); +} + +void CDGWTimeServerApp::run() +{ + m_thread->Run(); + m_thread->Wait(); +} + +bool CDGWTimeServerApp::createThread() +{ + m_thread = new CTimeServerThread(); + + TTimeServer timeserver; + m_config->getTimeServer(timeserver); + + std::vector rptrs = { "", "", "", "" }; + TRepeater repeater; + for(unsigned int i = 0u; i < m_config->getRepeaterCount(); i++) { + m_config->getRepeater(i, repeater); + rptrs[i].assign(repeater.band); + } + + TPaths paths; + m_config->getPaths(paths); + + m_thread = new CTimeServerThread(); + bool ret = m_thread->setGateway(timeserver.callsign, rptrs[0], rptrs[1], rptrs[2], rptrs[3], timeserver.address, paths.data); + if(ret) { + m_thread->setAnnouncements(timeserver.language, timeserver.format, timeserver.interval); + } + + return ret; +} diff --git a/DGWTimeServer/DGWTimeServerApp.h b/DGWTimeServer/DGWTimeServerApp.h index fc87275..ffcecdf 100644 --- a/DGWTimeServer/DGWTimeServerApp.h +++ b/DGWTimeServer/DGWTimeServerApp.h @@ -19,3 +19,24 @@ #pragma once +#include "TimeServerDefs.h" +#include "TimeServerConfig.h" +#include "TimeServerThread.h" + +class CDGWTimeServerApp +{ +private: + /* data */ +public: + CDGWTimeServerApp(const CTimeServerConfig * config); + ~CDGWTimeServerApp(); + + bool init(); + void run(); + +private: + bool createThread(); + + const CTimeServerConfig * m_config; + CTimeServerThread * m_thread; +}; diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index ec31444..5d4b067 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -49,6 +49,7 @@ bool CTimeServerConfig::load() ret = loadTimeServer(cfg) && ret; ret = loadRepeaters(cfg) && ret; ret = loadDaemon(cfg) && ret; + ret = loadPaths(cfg) && ret; } return ret; @@ -137,3 +138,34 @@ bool CTimeServerConfig::loadDaemon(const CConfig & cfg) ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret; return ret; } + +bool CTimeServerConfig::loadPaths(const CConfig & cfg) +{ + bool ret = cfg.getValue("paths", "data", m_paths.data, 1, 1024, ""); + return ret; +} + +void CTimeServerConfig::getTimeServer(TTimeServer& timeserver) const +{ + timeserver = m_timeServer; +} + +void CTimeServerConfig::getDameon(TDaemon& daemon) const +{ + daemon = m_daemon; +} + +unsigned int CTimeServerConfig::getRepeaterCount() const +{ + return m_repeaters.size(); +} + +void CTimeServerConfig::getRepeater(unsigned int idx, TRepeater& repeater) const +{ + repeater = *(m_repeaters[idx]); +} + +void CTimeServerConfig::getPaths(TPaths& paths) const +{ + paths = m_paths; +} diff --git a/DGWTimeServer/TimeServerConfig.h b/DGWTimeServer/TimeServerConfig.h index d17a650..a28d36d 100644 --- a/DGWTimeServer/TimeServerConfig.h +++ b/DGWTimeServer/TimeServerConfig.h @@ -33,7 +33,6 @@ typedef struct { } TTimeServer; typedef struct { - bool enabled; std::string band; } TRepeater; @@ -43,6 +42,9 @@ typedef struct { std::string user; } TDaemon; +typedef struct { + std::string data; +} TPaths; class CTimeServerConfig { @@ -51,15 +53,22 @@ public: ~CTimeServerConfig(); bool load(); + void getTimeServer(TTimeServer& timeserver) const; + void getDameon(TDaemon& daemon) const; + unsigned int getRepeaterCount() const; + void getRepeater(unsigned int idx, TRepeater& repeater) const; + void getPaths(TPaths& paths) const; private: bool open(CConfig & cfg); bool loadRepeaters(const CConfig & cfg); bool loadTimeServer(const CConfig & cfg); bool loadDaemon(const CConfig & cfg); + bool loadPaths(const CConfig & cfg); std::string m_fileName; std::vector m_repeaters; TTimeServer m_timeServer; TDaemon m_daemon; + TPaths m_paths; }; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h index d82a57e..6b5ca4b 100644 --- a/DGWTimeServer/TimeServerDefs.h +++ b/DGWTimeServer/TimeServerDefs.h @@ -19,7 +19,7 @@ #pragma once -const std::string APPLICATION_NAME("Time Server"); +const std::string APPLICATION_NAME("DStarGateway time Server"); enum LANGUAGE { LANG_ENGLISH_UK_1, diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp new file mode 100644 index 0000000..2bfcccc --- /dev/null +++ b/DGWTimeServer/TimeServerThread.cpp @@ -0,0 +1,1458 @@ +/* + * Copyright (C) 2012,2013 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "TimeServerThread.h" +#include "DStarDefines.h" +#include "Utils.h" +#include "NetUtils.h" +#include "StringUtils.h" +#include "Log.h" + +const unsigned int MAX_FRAMES = 60U * DSTAR_FRAMES_PER_SEC; + +const unsigned int SILENCE_LENGTH = 10U; + +enum SLOW_DATA { + SD_HEADER, + SD_TEXT +}; + +CTimeServerThread::CTimeServerThread() : +CThread("Time Server"), +m_socket("", 0U), +m_callsign(), +m_callsignA(), +m_callsignB(), +m_callsignC(), +m_callsignD(), +m_callsignE(), +m_callsignG(), +m_address(), +m_language(LANG_ENGLISH_UK_1), +m_format(FORMAT_VOICE_TIME), +m_interval(INTERVAL_15MINS), +m_ambe(NULL), +m_ambeLength(0U), +m_index(), +m_seqNo(0U), +m_in(0U), +m_encoder(), +m_data(NULL), +m_killed(false), +m_dataPath("") +{ + CHeaderData::initialise(); + m_address.s_addr = INADDR_NONE; + + m_data = new CAMBEData*[MAX_FRAMES]; + + for (unsigned int i = 0U; i < MAX_FRAMES; i++) + m_data[i] = nullptr; +} + +CTimeServerThread::~CTimeServerThread() +{ + for (auto it = m_index.begin(); it != m_index.end(); it++) + delete it->second; + + delete[] m_ambe; + delete[] m_data; +} + +void * CTimeServerThread::Entry() +{ + // Wait here until we have the essentials to run + while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty() && m_callsignE.empty()) + Sleep(500UL); // 1/2 sec + + if (m_killed) + return nullptr; + + if (m_format != FORMAT_TEXT_TIME) { + bool ret = loadAMBE(); + if (!ret) { + CLog::logWarning(("Cannot load the AMBE data, using text only time")); + m_format = FORMAT_TEXT_TIME; + } + } + + CLog::logInfo(("Starting the Time Server thread")); + + unsigned int lastMin = 0U; + + while (!m_killed) { + time_t now; + ::time(&now); + + struct tm* tm = ::localtime(&now); + + unsigned int hour = tm->tm_hour; + unsigned int min = tm->tm_min; + + if (min != lastMin) { + if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) + sendTime(hour, min); + else if (m_interval == INTERVAL_30MINS && (min == 0U || min == 30U)) + sendTime(hour, min); + else if (m_interval == INTERVAL_60MINS && min == 0U) + sendTime(hour, min); + } + + lastMin = min; + + Sleep(450UL); + } + + CLog::logInfo(("Stopping the Time Server thread")); + + m_socket.close(); + + return nullptr; +} + +void CTimeServerThread::kill() +{ + m_killed = true; +} + +bool CTimeServerThread::setGateway(const std::string& callsign, const std::string& rpt1, const std::string& rpt2, const std::string& rpt3, const std::string& rpt4, const std::string& address, const std::string& dataPath) +{ + m_callsign = callsign; + m_callsign.resize(LONG_CALLSIGN_LENGTH - 1U, (' ')); + + m_callsignG = m_callsign; + m_callsignG.push_back('G'); + + if (!rpt1.empty()) { + m_callsignA = m_callsign + rpt1; + } + + if (!rpt2.empty()) { + m_callsignB = m_callsign + rpt2; + } + + if (!rpt3.empty()) { + m_callsignC = m_callsign + rpt3; + } + + if (!rpt4.empty()) { + m_callsignD = m_callsign + rpt4; + } + + m_callsign.push_back(' '); + + m_address = CUDPReaderWriter::lookup(address); + m_dataPath.assign(dataPath); + + bool ret = m_socket.open(); + if (!ret) + return false; + + return true; +} + +void CTimeServerThread::setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval) +{ + m_language = language; + m_format = format; + m_interval = interval; +} + +void CTimeServerThread::sendTime(unsigned int hour, unsigned int min) +{ + std::vector words; + + switch (m_language) { + case LANG_ENGLISH_UK_1: + words = sendTimeEnGB1(hour, min); + break; + case LANG_ENGLISH_UK_2: + words = sendTimeEnGB2(hour, min); + break; + case LANG_ENGLISH_US_1: + words = sendTimeEnUS1(hour, min); + break; + case LANG_ENGLISH_US_2: + words = sendTimeEnUS2(hour, min); + break; + case LANG_DEUTSCH_1: + words = sendTimeDeDE1(hour, min); + break; + case LANG_DEUTSCH_2: + words = sendTimeDeDE2(hour, min); + break; + case LANG_FRANCAIS: + words = sendTimeFrFR(hour, min); + break; + case LANG_NEDERLANDS: + words = sendTimeNlNL(hour, min); + break; + case LANG_SVENSKA: + words = sendTimeSeSE(hour, min); + break; + case LANG_ESPANOL: + words = sendTimeEsES(hour, min); + break; + case LANG_NORSK: + words = sendTimeNoNO(hour, min); + break; + case LANG_PORTUGUES: + words = sendTimePtPT(hour, min); + break; + default: + break; + } + + send(words, hour, min); +} + +std::vector CTimeServerThread::sendTimeEnGB1(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("It_is")); + + switch (hour) { + case 0U: + case 12U: + words.push_back(("twelve")); + break; + case 1U: + case 13U: + words.push_back(("one")); + break; + case 2U: + case 14U: + words.push_back(("two")); + break; + case 3U: + case 15U: + words.push_back(("three")); + break; + case 4U: + case 16U: + words.push_back(("four")); + break; + case 5U: + case 17U: + words.push_back(("five")); + break; + case 6U: + case 18U: + words.push_back(("six")); + break; + case 7U: + case 19U: + words.push_back(("seven")); + break; + case 8U: + case 20U: + words.push_back(("eight")); + break; + case 9U: + case 21U: + words.push_back(("nine")); + break; + case 10U: + case 22U: + words.push_back(("ten")); + break; + case 11U: + case 23U: + words.push_back(("eleven")); + break; + default: + break; + } + + switch (min) { + case 15U: + words.push_back(("fifteen")); + break; + case 30U: + words.push_back(("thirty")); + break; + case 45U: + words.push_back(("forty-five")); + break; + default: + break; + } + + if (hour >= 12U) + words.push_back(("PM")); + else + words.push_back(("AM")); + + return words; +} + +std::vector CTimeServerThread::sendTimeEnGB2(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back("It_is"); + + if (min == 15U) { + words.push_back(("a_quarter_past")); + } else if (min == 30U) { + words.push_back(("half_past")); + } else if (min == 45U) { + words.push_back(("a_quarter_to")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U && min == 0U) { + words.push_back(("midnight")); + } else if (hour == 12U && min == 0U) { + words.push_back(("twelve")); + words.push_back(("noon")); + } else if (hour == 0U || hour == 12U) { + words.push_back(("twelve")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("one")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("two")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("three")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("four")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("five")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("six")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("seven")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("eight")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nine")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("ten")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("eleven")); + } + + if (hour != 0U && hour != 12U && min == 0U) + words.push_back(("O_Clock")); + + return words; +} + +std::vector CTimeServerThread::sendTimeEnUS1(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("It_is")); + + switch (hour) { + case 0U: + case 12U: + words.push_back(("twelve")); + break; + case 1U: + case 13U: + words.push_back(("one")); + break; + case 2U: + case 14U: + words.push_back(("two")); + break; + case 3U: + case 15U: + words.push_back(("three")); + break; + case 4U: + case 16U: + words.push_back(("four")); + break; + case 5U: + case 17U: + words.push_back(("five")); + break; + case 6U: + case 18U: + words.push_back(("six")); + break; + case 7U: + case 19U: + words.push_back(("seven")); + break; + case 8U: + case 20U: + words.push_back(("eight")); + break; + case 9U: + case 21U: + words.push_back(("nine")); + break; + case 10U: + case 22U: + words.push_back(("ten")); + break; + case 11U: + case 23U: + words.push_back(("eleven")); + break; + default: + break; + } + + switch (min) { + case 15U: + words.push_back(("fifteen")); + break; + case 30U: + words.push_back(("thirty")); + break; + case 45U: + words.push_back(("forty-five")); + break; + default: + break; + } + + if (hour >= 12U) + words.push_back(("PM")); + else + words.push_back(("AM")); + + return words; +} + +std::vector CTimeServerThread::sendTimeEnUS2(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("It_is")); + + if (min == 15U) { + words.push_back(("a_quarter_past")); + } else if (min == 30U) { + words.push_back(("half_past")); + } else if (min == 45U) { + words.push_back(("a_quarter_to")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U && min == 0U) { + words.push_back(("midnight")); + } else if (hour == 12U && min == 0U) { + words.push_back(("twelve")); + words.push_back(("noon")); + } else if (hour == 0U || hour == 12U) { + words.push_back(("twelve")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("one")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("two")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("three")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("four")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("five")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("six")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("seven")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("eight")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nine")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("ten")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("eleven")); + } + + if (hour != 0U && hour != 12U && min == 0U) + words.push_back(("O_Clock")); + + return words; +} + +std::vector CTimeServerThread::sendTimeDeDE1(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Es_ist")); + + switch (hour) { + case 0U: words.push_back(("null")); break; + case 1U: words.push_back(("ein")); break; + case 2U: words.push_back(("zwei")); break; + case 3U: words.push_back(("drei")); break; + case 4U: words.push_back(("vier")); break; + case 5U: words.push_back(("fuenf")); break; + case 6U: words.push_back(("sechs")); break; + case 7U: words.push_back(("sieben")); break; + case 8U: words.push_back(("acht")); break; + case 9U: words.push_back(("neun")); break; + case 10U: words.push_back(("zehn")); break; + case 11U: words.push_back(("elf")); break; + case 12U: words.push_back(("zwoelf")); break; + case 13U: words.push_back(("dreizehn")); break; + case 14U: words.push_back(("vierzehn")); break; + case 15U: words.push_back(("fuenfzehn")); break; + case 16U: words.push_back(("sechzehn")); break; + case 17U: words.push_back(("siebzehn")); break; + case 18U: words.push_back(("achtzehn")); break; + case 19U: words.push_back(("neunzehn")); break; + case 20U: words.push_back(("zwanzig")); break; + case 21U: words.push_back(("einundzwanzig")); break; + case 22U: words.push_back(("zweiundzwanzig")); break; + case 23U: words.push_back(("dreiundzwanzig")); break; + default: break; + } + + words.push_back(("Uhr")); + + switch (min) { + case 15U: + words.push_back(("fuenfzehn")); + break; + case 30U: + words.push_back(("dreissig")); + break; + case 45U: + words.push_back(("fuenfundvierzig")); + break; + default: + break; + } + + return words; +} + +std::vector CTimeServerThread::sendTimeDeDE2(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Es_ist")); + + if (min == 15U) { + words.push_back(("viertel_nach")); + } else if (min == 30U) { + words.push_back(("halb")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("viertel_vor")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U) { + words.push_back(("null")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("ein")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("zwei")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("drei")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("vier")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("fuenf")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("sechs")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sieben")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("acht")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("neun")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("zehn")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elf")); + } else if (hour == 12U) { + words.push_back(("zwoelf")); + } + + if (min == 0U) + words.push_back(("Uhr")); + + return words; +} + +std::vector CTimeServerThread::sendTimeFrFR(unsigned int hour, unsigned int min) +{ + std::vector words; + + // if (hour > 17U) + // words.push_back(("bonsoir")); + // else + // words.push_back(("bonjour")); + + words.push_back(("il_est")); + + if (min == 45U) + hour++; + + if (hour == 0U) { + words.push_back(("minuit")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("une")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("deux")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("trois")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("quatre")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("cinq")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("six")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sept")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("huit")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("neuf")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("dix")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("onze")); + } else if (hour == 12U) { + words.push_back(("midi")); + } + + if (hour == 1U || hour == 13U) + words.push_back(("heure")); + else if (hour != 12U && hour != 0U) + words.push_back(("heures")); + + if (min == 15U) { + words.push_back(("et_quart")); + } else if (min == 30U) { + words.push_back(("et_demie")); + } else if (min == 45U) { + words.push_back(("moins_le_quart")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + return words; +} + +std::vector CTimeServerThread::sendTimeNlNL(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Het_is")); + + if (min == 15U) { + words.push_back(("kwart_over")); + } else if (min == 30U) { + words.push_back(("half")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("kwart_voor")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U || hour == 12U) { + words.push_back(("twaalf")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("een")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("twee")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("drie")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("vier")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("vijf")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("zes")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("zeven")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("acht")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("negen")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("tien")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elf")); + } + + if (min == 0U) + words.push_back(("uur")); + + return words; +} + +std::vector CTimeServerThread::sendTimeSeSE(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Klockan_ar")); + + if (min == 15U) { + words.push_back(("kvart_over")); + } else if (min == 30U) { + words.push_back(("halv")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("kvart_i")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U || hour == 12U) { + words.push_back(("tolv")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("ett")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("tva")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tre")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("fyra")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("fem")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("sex")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sju")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("atta")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nio")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("tio")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elva")); + } + + return words; +} + +std::vector CTimeServerThread::sendTimeEsES(unsigned int hour, unsigned int min) +{ + std::vector words; + + if (min == 45U) { + hour++; + if (hour == 24U) + hour = 0U; + } + + if (hour == 1U) + words.push_back(("Es_la")); + else if (hour == 0U || hour == 12U) + words.push_back(("Es")); + else + words.push_back(("Son_las")); + + if (hour == 0U) { + words.push_back(("medianoche")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("una")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("dos")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tres")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("cuarto")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("cinco")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("seis")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("siete")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("ocho")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nueve")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("diez")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("once")); + } else { + words.push_back(("mediodia")); + } + + if (min == 15U) + words.push_back(("y_cuarto")); + else if (min == 30U) + words.push_back(("y_media")); + else if (min == 45U) + words.push_back(("menos_cuarto")); + + if (hour > 0U && hour < 12U) + words.push_back(("de_la_manana")); + else if (hour > 12U && hour < 19U) + words.push_back(("de_la_tarde")); + else if (hour >= 19U && hour <= 23U) + words.push_back(("de_la_noche")); + + return words; +} + +std::vector CTimeServerThread::sendTimeNoNO(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Klokken_er")); + + if (min == 15U) { + words.push_back(("kvart_over")); + } else if (min == 30U) { + words.push_back(("halv")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("kvart_pa")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U || hour == 12U) { + words.push_back(("tolv")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("ett")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("to")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tre")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("fire")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("fem")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("seks")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sju")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("atte")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("ni")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("ti")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elleve")); + } + + return words; +} + +std::vector CTimeServerThread::sendTimePtPT(unsigned int hour, unsigned int min) +{ + std::vector words; + + if (min == 45U) { + hour++; + if (hour == 24U) + hour = 0U; + } + + if (hour == 1U || hour == 13U) + words.push_back(("E")); + else if (hour == 0U || hour == 12U) + words.push_back(("Es")); + else + words.push_back(("Sao")); + + if (min == 45U) { + if (hour == 0U || hour == 12U || hour == 1U || hour == 13U) + words.push_back(("quinze_para")); + else + words.push_back(("quinze_para_as")); + } + + if (hour == 0U) { + words.push_back(("meia-noite")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("uma")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("duas")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tres")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("quatro")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("cinco")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("seis")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sete")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("oito")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nove")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("dez")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("onze")); + } else { + words.push_back(("meio-dia")); + } + + if (min == 0U) + words.push_back(("hora")); + else if (min == 15U) + words.push_back(("e_quinze")); + else if (min == 30U) + words.push_back(("e_meia")); + + return words; +} + +bool CTimeServerThread::loadAMBE() +{ + std::string ambeFileName; + std::string indxFileName; + + switch (m_language) { + case LANG_ENGLISH_US_1: + case LANG_ENGLISH_US_2: + ambeFileName = "TIME_en_US.ambe"; + indxFileName = "TIME_en_US.indx"; + break; + case LANG_DEUTSCH_1: + case LANG_DEUTSCH_2: + ambeFileName = "TIME_de_DE.ambe"; + indxFileName = "TIME_de_DE.indx"; + break; + case LANG_FRANCAIS: + ambeFileName = "TIME_fr_FR.ambe"; + indxFileName = "TIME_fr_FR.indx"; + break; + case LANG_NEDERLANDS: + ambeFileName = "TIME_nl_NL.ambe"; + indxFileName = "TIME_nl_NL.indx"; + break; + case LANG_SVENSKA: + ambeFileName = "TIME_se_SE.ambe"; + indxFileName = "TIME_se_SE.indx"; + break; + case LANG_ESPANOL: + ambeFileName = "TIME_es_ES.ambe"; + indxFileName = "TIME_es_ES.indx"; + break; + case LANG_NORSK: + ambeFileName = "TIME_no_NO.ambe"; + indxFileName = "TIME_no_NO.indx"; + break; + case LANG_PORTUGUES: + ambeFileName = "TIME_pt_PT.ambe"; + indxFileName = "TIME_pt_PT.indx"; + break; + default: + ambeFileName = "TIME_en_GB.ambe"; + indxFileName = "TIME_en_GB.indx"; + break; + } + + bool ret = readAMBE(m_dataPath, ambeFileName); + if (!ret) { + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + ret = readIndex(m_dataPath, indxFileName); + if (!ret) { + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + return true; +} + +bool CTimeServerThread::readAMBE(const std::string& dir, const std::string& name) +{ + std::string fileName = dir + "/" + name; + struct stat sbuf; + + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + fileName.append("/data/"); + fileName += name; + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + return false; + } + } + unsigned int fsize = sbuf.st_size; + + FILE *file = fopen(fileName.c_str(), "rb"); + if (NULL == file) { + CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); + return false; + } + + CLog::logInfo("Reading %s\n", fileName.c_str()); + + unsigned char buffer[VOICE_FRAME_LENGTH_BYTES]; + + size_t n = fread(buffer, sizeof(unsigned char), 4, file); + if (n != 4) { + CLog::logError("Unable to read the header from %s\n", fileName.c_str()); + fclose(file); + return false; + } + + if (memcmp(buffer, "AMBE", 4)) { + CLog::logError("Invalid header from %s\n", fileName.c_str()); + fclose(file); + return false; + } + + // Length of the file minus the header + unsigned int length = fsize - 4U; + + // Hold the file data plus silence at the end + m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES]; + m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES; + + // Add silence to the beginning of the buffer + unsigned char* p = m_ambe; + for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES) + memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + n = fread(p, 1, length, file); + if (n != length) { + CLog::logError("Unable to read the AMBE data from %s\n", fileName.c_str()); + fclose(file); + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + fclose(file); + + return true; +} + +bool CTimeServerThread::readIndex(const std::string& dir, const std::string& name) +{ + std::string fileName = dir + "/" + name; + struct stat sbuf; + + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + fileName.append("/data/"); + fileName += name; + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + return false; + } + } + + FILE *file = fopen(fileName.c_str(), "r"); + if (NULL == file) { + CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); + return false; + } + + // Add a silence entry at the beginning + m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); + + CLog::logInfo("Reading %s\n", fileName.c_str()); + + char line[128]; + while (fgets(line, 128, file)) { + + if (strlen(line) && '#'!=line[0]) { + const std::string space(" \t\r\n"); + std::string name(strtok(line, space.c_str())); + std::string strt(strtok(NULL, space.c_str())); + std::string leng(strtok(NULL, space.c_str())); + + if (name.size() && strt.size() && leng.size()) { + unsigned long start = std::stoul(strt); + unsigned long length = std::stoul(leng); + + if (start >= m_ambeLength || (start + length) >= m_ambeLength) + CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length); + else + m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length); + } + } + } + + fclose(file); + + return true; +} + +bool CTimeServerThread::lookup(const std::string &id) +{ + CIndexRecord* info = m_index[id]; + if (info == NULL) { + // wxLogError(("Cannot find the AMBE index for *%s*"), id.c_str()); + return false; + } + + unsigned int start = info->getStart(); + unsigned int length = info->getLength(); + + SLOW_DATA slowData = SD_TEXT; + + for (unsigned int i = 0U; i < length; i++) { + unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; + + CAMBEData* dataOut = new CAMBEData; + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(m_seqNo); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (m_seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_encoder.sync(); + + switch (slowData) { + case SD_HEADER: + slowData = SD_TEXT; + break; + case SD_TEXT: + slowData = SD_HEADER; + break; + } + } else { + switch (slowData) { + case SD_HEADER: + m_encoder.getHeaderData(buffer + VOICE_FRAME_LENGTH_BYTES); + break; + case SD_TEXT: + m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); + break; + } + } + + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); + + m_seqNo++; + if (m_seqNo == 21U) + m_seqNo = 0U; + + m_data[m_in] = dataOut; + m_in++; + } + + return true; +} + +void CTimeServerThread::end() +{ + CAMBEData* dataOut = new CAMBEData; + dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(m_seqNo); + dataOut->setEnd(true); + + m_data[m_in] = dataOut; + m_in++; +} + +bool CTimeServerThread::send(const std::vector &words, unsigned int hour, unsigned int min) +{ + unsigned int idA = CHeaderData::createId(); + unsigned int idB = CHeaderData::createId(); + unsigned int idC = CHeaderData::createId(); + unsigned int idD = CHeaderData::createId(); + unsigned int idE = CHeaderData::createId(); + + CHeaderData header; + header.setMyCall1(m_callsign); + header.setRptCall1(m_callsignG); + header.setRptCall2(m_callsign); // Just for the slow data header + header.setYourCall("CQCQCQ "); + header.setDestination(m_address, G2_DV_PORT); + + std::string slowData; + switch (m_language) { + case LANG_DEUTSCH_1: + case LANG_DEUTSCH_2: + header.setMyCall2(("ZEIT")); + slowData = CStringUtils::string_format(("Es ist %02u:%02u Uhr"), hour, min); + break; + case LANG_FRANCAIS: + header.setMyCall2(("TIME")); + slowData = CStringUtils::string_format(("Il est %02u:%02u"), hour, min); + break; + case LANG_NEDERLANDS: + header.setMyCall2(("TIJD")); + slowData = CStringUtils::string_format(("Het is %02u:%02u"), hour, min); + break; + case LANG_SVENSKA: + header.setMyCall2(("TID ")); + slowData = CStringUtils::string_format(("Klockan ar %02u:%02u"), hour, min); + break; + case LANG_ENGLISH_US_1: + case LANG_ENGLISH_UK_1: + header.setMyCall2(("TIME")); + if (hour == 0U) + slowData = CStringUtils::string_format(("It is 12:%02u AM"), min); + else if (hour == 12U) + slowData = CStringUtils::string_format(("It is 12:%02u PM"), min); + else if (hour > 12U) + slowData = CStringUtils::string_format(("It is %02u:%02u PM"), hour - 12U, min); + else + slowData = CStringUtils::string_format(("It is %02u:%02u AM"), hour, min); + break; + case LANG_ESPANOL: + header.setMyCall2(("HORA")); + if (hour == 1U) + slowData = CStringUtils::string_format(("Es la %02u:%02u"), hour, min); + else + slowData = CStringUtils::string_format(("Son las %02u:%02u"), hour, min); + break; + case LANG_NORSK: + header.setMyCall2(("TID ")); + slowData = CStringUtils::string_format(("Klokken er %02u:%02u"), hour, min); + break; + case LANG_PORTUGUES: + header.setMyCall2(("HORA")); + if (hour == 1U) + slowData = CStringUtils::string_format(("E %02u:%02u"), hour, min); + else + slowData = CStringUtils::string_format(("Sao %02u:%02u"), hour, min); + break; + default: + header.setMyCall2(("TIME")); + slowData = CStringUtils::string_format(("It is %02u:%02u"), hour, min); + break; + } + + m_encoder.setHeaderData(header); + m_encoder.setTextData(slowData); + + m_in = 0U; + + if (m_format != FORMAT_TEXT_TIME) { + std::string text = words.at(0U); + for (unsigned int i = 1U; i < words.size(); i++) { + text.push_back(' '); + text += words.at(i); + } + + boost::replace_all(text, "_", " "); + CLog::logInfo(("Sending voice \"%s\", sending text \"%s\""), text.c_str(), slowData.c_str()); + + m_seqNo = 0U; + + // Build the audio + lookup((" ")); + lookup((" ")); + lookup((" ")); + lookup((" ")); + + for (unsigned int i = 0U; i < words.size(); i++) + lookup(words.at(i)); + + lookup((" ")); + lookup((" ")); + lookup((" ")); + lookup((" ")); + + end(); + } else { + CLog::logInfo(("Sending text \"%s\""), slowData.c_str()); + + for (unsigned int i = 0U; i < 21U; i++) { + CAMBEData* dataOut = new CAMBEData; + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(i); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (i == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_encoder.sync(); + } else { + m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); + } + + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); + + m_data[m_in] = dataOut; + m_in++; + } + + CAMBEData* dataOut = new CAMBEData; + dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(0U); + dataOut->setEnd(true); + + m_data[m_in] = dataOut; + m_in++; + } + + if (m_in == 0U) { + CLog::logWarning(("Not sending, no audio files loaded")); + return false; + } + + if (!m_callsignA.empty()) { + header.setRptCall2(m_callsignA); + header.setId(idA); + sendHeader(header); + } + + if (!m_callsignB.empty()) { + header.setRptCall2(m_callsignB); + header.setId(idB); + sendHeader(header); + } + + if (!m_callsignC.empty()) { + header.setRptCall2(m_callsignC); + header.setId(idC); + sendHeader(header); + } + + if (!m_callsignD.empty()) { + header.setRptCall2(m_callsignD); + header.setId(idD); + sendHeader(header); + } + + if (!m_callsignE.empty()) { + header.setRptCall2(m_callsignE); + header.setId(idE); + sendHeader(header); + } + + unsigned int out = 0U; + + auto start = std::chrono::high_resolution_clock::now(); + + for (;;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + CAMBEData* data = m_data[out]; + m_data[out] = NULL; + out++; + + if (!m_callsignA.empty()) { + data->setId(idA); + sendData(*data); + } + + if (!m_callsignB.empty()) { + data->setId(idB); + sendData(*data); + } + + if (!m_callsignC.empty()) { + data->setId(idC); + sendData(*data); + } + + if (!m_callsignD.empty()) { + data->setId(idD); + sendData(*data); + } + + if (!m_callsignE.empty()) { + data->setId(idE); + sendData(*data); + } + + delete data; + + if (m_in == out) + return true; + } + + Sleep(10UL); + } +} + +bool CTimeServerThread::sendHeader(const CHeaderData &header) +{ + unsigned char buffer[60U]; + unsigned int length = header.getG2Data(buffer, 60U, true); + +#if defined(DUMP_TX) + CUtils::dump(("Sending Header"), buffer, length); + return true; +#else + for (unsigned int i = 0U; i < 5U; i++) { + bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); + if (!res) + return false; + } + + return true; +#endif +} + +bool CTimeServerThread::sendData(const CAMBEData& data) +{ + unsigned char buffer[40U]; + unsigned int length = data.getG2Data(buffer, 40U); + +#if defined(DUMP_TX) + CUtils::dump(("Sending Data"), buffer, length); + return true; +#else + return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); +#endif +} diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h new file mode 100644 index 0000000..ee3f967 --- /dev/null +++ b/DGWTimeServer/TimeServerThread.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012,2013 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include + +#include "SlowDataEncoder.h" +#include "UDPReaderWriter.h" +#include "TimeServerDefs.h" +#include "HeaderData.h" +#include "AMBEData.h" +#include "Thread.h" + +class CIndexRecord { +public: + CIndexRecord(const std::string& name, unsigned int start, unsigned int length) : + m_name(name), + m_start(start), + m_length(length) + { + } + + std::string getName() const + { + return m_name; + } + + unsigned int getStart() const + { + return m_start; + } + + unsigned int getLength() const + { + return m_length; + } + +private: + std::string m_name; + unsigned int m_start; + unsigned int m_length; +}; + + +class CTimeServerThread : public CThread +{ +public: + CTimeServerThread(); + ~CTimeServerThread(); + + bool setGateway(const std::string& callsign, const std::string& rpt1, const std::string& rpt2, const std::string& rpt3, const std::string& rpt4, const std::string& address, const std::string& dataPath); + void setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval); + + void * Entry(); + void kill(); + +private: + CUDPReaderWriter m_socket; + std::string m_callsign; + std::string m_callsignA; + std::string m_callsignB; + std::string m_callsignC; + std::string m_callsignD; + std::string m_callsignE; + std::string m_callsignG; + in_addr m_address; + LANGUAGE m_language; + FORMAT m_format; + INTERVAL m_interval; + unsigned char* m_ambe; + unsigned int m_ambeLength; + std::unordered_map m_index; + unsigned int m_seqNo; + unsigned int m_in; + CSlowDataEncoder m_encoder; + CAMBEData** m_data; + bool m_killed; + std::string m_dataPath; + + void sendTime(unsigned int hour, unsigned int min); + + std::vector sendTimeEnGB1(unsigned int hour, unsigned int min); + std::vector sendTimeEnGB2(unsigned int hour, unsigned int min); + std::vector sendTimeEnUS1(unsigned int hour, unsigned int min); + std::vector sendTimeEnUS2(unsigned int hour, unsigned int min); + std::vector sendTimeDeDE1(unsigned int hour, unsigned int min); + std::vector sendTimeDeDE2(unsigned int hour, unsigned int min); + std::vector sendTimeFrFR(unsigned int hour, unsigned int min); + std::vector sendTimeNlNL(unsigned int hour, unsigned int min); + std::vector sendTimeSeSE(unsigned int hour, unsigned int min); + std::vector sendTimeEsES(unsigned int hour, unsigned int min); + std::vector sendTimeNoNO(unsigned int hour, unsigned int min); + std::vector sendTimePtPT(unsigned int hour, unsigned int min); + + bool send(const std::vector& words, unsigned int hour, unsigned int min); + bool sendHeader(const CHeaderData& header); + bool sendData(const CAMBEData& data); + + bool loadAMBE(); + bool readAMBE(const std::string& dir, const std::string& name); + bool readIndex(const std::string& dir, const std::string& name); + + bool lookup(const std::string& id); + void end(); +}; diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index cc6b0cf..27a40df 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -5,6 +5,9 @@ format= # possible values are voice, text, voiceandtext, defaults to voice a language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1 interval= # valid values are 15, 30 and 60, defaults to 30 +[Paths] +data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found + # Up to 4 repeaters can be enabled to transmit time beacons [Repeater_1] enabled=true # enable time beacons on this repeater From 0161d4879f4dcb7d2e271e5e5f5438043a3e56f2 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 16:58:20 +0100 Subject: [PATCH 054/112] #20 add systemd stuff for time server --- DGWTimeServer/Makefile | 14 ++++++++++++++ debian/dgwtimeserver.service | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 debian/dgwtimeserver.service diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 4ea197d..1212731 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -17,6 +17,20 @@ install: dgwtimeserver # copy executable @cp -f dgwtimeserver $(BIN_DIR) +# copy and adjust config + @cp -fn example.cfg $(CFG_DIR)/dgwtimeserver.cfg + @sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg + @sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg + +# SystemD service install + @cp -f ../debian/dgwtimeserver.service /lib/systemd/system/ + @sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dgwtimeserver.service + systemctl enable dgwtimeserver.service + @systemctl daemon-reload + @echo "\n" + @echo "DGWTimeserver Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dgwtimeserver.service'" + @echo "\n" + ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: ../VersionInfo/GitVersion.h: diff --git a/debian/dgwtimeserver.service b/debian/dgwtimeserver.service new file mode 100644 index 0000000..5c00484 --- /dev/null +++ b/debian/dgwtimeserver.service @@ -0,0 +1,13 @@ +[Unit] +Description=D-STAR Gateway Daemon +After=network.target,network-online.target +Wants=network-online.target + +[Service] +User=dstar +Type=simple +ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dstargateway.cfg +Restart=on-failure + +[Install] +WantedBy=multi-user.target From c290a0f50bbf44e2138729b0b816c03536b1646d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 17:30:27 +0100 Subject: [PATCH 055/112] #20 fix copy paste type --- debian/dgwtimeserver.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/dgwtimeserver.service b/debian/dgwtimeserver.service index 5c00484..6767c9c 100644 --- a/debian/dgwtimeserver.service +++ b/debian/dgwtimeserver.service @@ -1,12 +1,12 @@ [Unit] -Description=D-STAR Gateway Daemon +Description=D-STAR Time Server Daemon After=network.target,network-online.target Wants=network-online.target [Service] User=dstar Type=simple -ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dstargateway.cfg +ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dgwtimeserver.cfg Restart=on-failure [Install] From 8f5f878d23c251e0266a90098cbb3776cc8ffe12 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:04:20 +0100 Subject: [PATCH 056/112] #20 simplify slow data --- DGWTimeServer/TimeServerThread.cpp | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 2bfcccc..8150858 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -36,11 +36,6 @@ const unsigned int MAX_FRAMES = 60U * DSTAR_FRAMES_PER_SEC; const unsigned int SILENCE_LENGTH = 10U; -enum SLOW_DATA { - SD_HEADER, - SD_TEXT -}; - CTimeServerThread::CTimeServerThread() : CThread("Time Server"), m_socket("", 0U), @@ -113,6 +108,9 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; + if (min != lastMin) + sendTime(15, 45); + if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) sendTime(hour, min); @@ -1144,8 +1142,6 @@ bool CTimeServerThread::lookup(const std::string &id) unsigned int start = info->getStart(); unsigned int length = info->getLength(); - SLOW_DATA slowData = SD_TEXT; - for (unsigned int i = 0U; i < length; i++) { unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; @@ -1161,23 +1157,8 @@ bool CTimeServerThread::lookup(const std::string &id) ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); m_encoder.sync(); - switch (slowData) { - case SD_HEADER: - slowData = SD_TEXT; - break; - case SD_TEXT: - slowData = SD_HEADER; - break; - } } else { - switch (slowData) { - case SD_HEADER: - m_encoder.getHeaderData(buffer + VOICE_FRAME_LENGTH_BYTES); - break; - case SD_TEXT: - m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); - break; - } + m_encoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); } dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); From 318d1b7c621b297c7b5f942852600543bfc5f121 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:10:15 +0100 Subject: [PATCH 057/112] #20 add banner and signal handlers --- DGWTimeServer/DGWTimeServerApp.cpp | 76 ++++++++++++++++++++++++++++++ DGWTimeServer/DGWTimeServerApp.h | 8 +++- DGWTimeServer/TimeServerDefs.h | 2 +- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 5088ee2..64fe964 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -20,17 +20,42 @@ #include #include #include +#include +#ifdef DEBUG_DSTARGW +#include +#endif #include "DGWTimeServerApp.h" +#include "Version.h" +#include "Log.h" + +CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr; +const std::string BANNER_1 = CStringUtils::string_format("%s %s Copyright (C) %s\n", APPLICATION_NAME.c_str(), VENDOR_NAME.c_str()); +const std::string BANNER_2 = "DGWTimeServer comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"; +const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n"; int main(int argc, char * argv[]) { + std::set_terminate(CDGWTimeServerApp::terminateHandler); + + signal(SIGSEGV, CDGWTimeServerApp::sigHandlerFatal); + signal(SIGILL, CDGWTimeServerApp::sigHandlerFatal); + signal(SIGFPE, CDGWTimeServerApp::sigHandlerFatal); + signal(SIGABRT, CDGWTimeServerApp::sigHandlerFatal); + signal(SIGTERM, CDGWTimeServerApp::sigHandler); + signal(SIGINT, CDGWTimeServerApp::sigHandler); + if (2 != argc) { printf("usage: %s path_to_config_file\n", argv[0]); printf(" %s --version\n", argv[0]); return 1; } + std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3; + if(argv[1][0] == '-') { + return 0; + } + std::string configfile(argv[1]); CTimeServerConfig config(configfile); if(!config.load()) @@ -93,3 +118,54 @@ bool CDGWTimeServerApp::createThread() return ret; } + +void CDGWTimeServerApp::sigHandler(int sig) +{ + CLog::logInfo("Caught signal : %s, shutting down gateway", strsignal(sig)); + + if(g_app != nullptr && g_app->m_thread != nullptr) { + g_app->m_thread->kill(); + } +} + +void CDGWTimeServerApp::sigHandlerFatal(int sig) +{ + CLog::logFatal("Caught signal : %s", strsignal(sig)); + fprintf(stderr, "Caught signal : %s\n", strsignal(sig)); +#ifdef DEBUG_DSTARGW + std::stringstream stackTrace; + stackTrace << boost::stacktrace::stacktrace(); + CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str()); + fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str()); +#endif + exit(3); +} + +void CDGWTimeServerApp::terminateHandler() +{ +#ifdef DEBUG_DSTARGW + std::stringstream stackTrace; + stackTrace << boost::stacktrace::stacktrace(); +#endif + + std::exception_ptr eptr; + eptr = std::current_exception(); + + try { + if (eptr != nullptr) { + std::rethrow_exception(eptr); + } + else { + CLog::logFatal("Unhandled unknown exception occured"); + fprintf(stderr, "Unknown ex\n"); + } + } catch(const std::exception& e) { + CLog::logFatal("Unhandled exception occured %s", e.what()); + fprintf(stderr, "Unhandled ex %s\n", e.what()); + } + +#ifdef DEBUG_DSTARGW + CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str()); +#endif + exit(2); +} \ No newline at end of file diff --git a/DGWTimeServer/DGWTimeServerApp.h b/DGWTimeServer/DGWTimeServerApp.h index ffcecdf..a25cdaf 100644 --- a/DGWTimeServer/DGWTimeServerApp.h +++ b/DGWTimeServer/DGWTimeServerApp.h @@ -25,8 +25,6 @@ class CDGWTimeServerApp { -private: - /* data */ public: CDGWTimeServerApp(const CTimeServerConfig * config); ~CDGWTimeServerApp(); @@ -34,9 +32,15 @@ public: bool init(); void run(); + static void sigHandler(int sig); + static void sigHandlerFatal(int sig); + static void terminateHandler(); + private: bool createThread(); + static CDGWTimeServerApp * g_app; + const CTimeServerConfig * m_config; CTimeServerThread * m_thread; }; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h index 6b5ca4b..8917675 100644 --- a/DGWTimeServer/TimeServerDefs.h +++ b/DGWTimeServer/TimeServerDefs.h @@ -19,7 +19,7 @@ #pragma once -const std::string APPLICATION_NAME("DStarGateway time Server"); +const std::string APPLICATION_NAME("DStarGateway Time Server"); enum LANGUAGE { LANG_ENGLISH_UK_1, From d22ae5f02eb33baa7e73a99c7eecd48065ee2183 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:13:16 +0100 Subject: [PATCH 058/112] #20 remove test code --- DGWTimeServer/TimeServerThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 8150858..253edfb 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -108,8 +108,8 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; - if (min != lastMin) - sendTime(15, 45); + // if (min != lastMin) + // sendTime(15, 45); if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) From 31d11136ec0e4414612d4d5a78388b1235deb031 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:14:56 +0100 Subject: [PATCH 059/112] #20 fix copy paste typo --- DGWTimeServer/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 1212731..87b80ab 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -28,7 +28,7 @@ install: dgwtimeserver systemctl enable dgwtimeserver.service @systemctl daemon-reload @echo "\n" - @echo "DGWTimeserver Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dgwtimeserver.service'" + @echo "DGWTimeserver Install complete, edit $(CFG_DIR)dgwtimeserver.cfg and start the daemon with 'systemctl start dgwtimeserver.service'" @echo "\n" ../BaseCommon/BaseCommon.a: From 5798c187dc534e5d36ad6ecbf07a3703d6a9286d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:20:36 +0100 Subject: [PATCH 060/112] #20 set g_app to allow proper terminating --- DGWTimeServer/DGWTimeServerApp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 64fe964..245c79a 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -74,6 +74,7 @@ int main(int argc, char * argv[]) CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) : m_config(config) { + g_app = this; assert(config != nullptr); } From b3e0b16e6cb4d5bd47b24da3d3a6bd7d313d43f2 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:22:38 +0100 Subject: [PATCH 061/112] #20 add version number in banner --- DGWTimeServer/DGWTimeServerApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 245c79a..9b26922 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -30,7 +30,7 @@ #include "Log.h" CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr; -const std::string BANNER_1 = CStringUtils::string_format("%s %s Copyright (C) %s\n", APPLICATION_NAME.c_str(), VENDOR_NAME.c_str()); +const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str()); const std::string BANNER_2 = "DGWTimeServer comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"; const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n"; From c84275802ed092bd936f9c60e4ad7d18e98355e1 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:29:26 +0100 Subject: [PATCH 062/112] #20 check band --- DGWTimeServer/TimeServerConfig.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index 5d4b067..dd03332 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -81,6 +81,11 @@ bool CTimeServerConfig::loadRepeaters(const CConfig & cfg) TRepeater * repeater = new TRepeater; ret = cfg.getValue(section, "band", repeater->band, 1, 2, "") && ret; + if(!ret) { + delete repeater; + continue; + } + boost::to_upper(repeater->band); bool alreadyConfigured = std::any_of(m_repeaters.begin(), m_repeaters.end(), [repeater](TRepeater * r) { return r->band == repeater->band;}); if(alreadyConfigured) { From 20b2d8b912ef674b176333adfaf2bcc78495052d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:40:19 +0100 Subject: [PATCH 063/112] #20 make it a daemon --- DGWTimeServer/DGWTimeServerApp.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 9b26922..7fb39d2 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -28,6 +28,7 @@ #include "DGWTimeServerApp.h" #include "Version.h" #include "Log.h" +#include "Daemon.h" CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr; const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str()); @@ -61,6 +62,27 @@ int main(int argc, char * argv[]) if(!config.load()) return 1; + TDaemon daemon; + config.getDameon(daemon); + if (daemon.daemon) { + CLog::logInfo("Configured as a daemon, detaching ..."); + auto res = CDaemon::daemonise(daemon.pidFile, daemon.user); + + switch (res) + { + case DR_PARENT: + return 0; + case DR_CHILD: + break; + case DR_PIDFILE_FAILED: + case DR_FAILURE: + default: + CLog::logFatal("Failed to run as daemon"); + CLog::finalise(); + return 1; + } + } + CDGWTimeServerApp app(&config); if(!app.init()) From c0a49a4cf3036303af0b7bc22cd39a140353717d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:42:07 +0100 Subject: [PATCH 064/112] #20 typo --- DGWTimeServer/example.cfg | 2 +- DStarGateway/example.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index 27a40df..a643d60 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -27,7 +27,7 @@ band= # Module letter of the repeater # Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd -# require a pid file. Moreover system handles the user under which the program is started. This is provided as convenience for people who might +# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might # run the program using sysv or any other old school init system. [Daemon] daemon=false diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 0d349ad..f79e4df 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -178,7 +178,7 @@ port=4242 password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field # Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd -# require a pid file. Moreover system handles the user under which the program is started. This is provided as convenience for people who might +# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might # run the program using sysv or any other old school init system. [Daemon] daemon=false From 7d12e567ac4dbd02bf6a4bb959724603fa28a295 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 19:10:09 +0100 Subject: [PATCH 065/112] #20 remove hard coded file root --- BaseCommon/LogFileTarget.cpp | 12 ++++++------ BaseCommon/LogFileTarget.h | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/BaseCommon/LogFileTarget.cpp b/BaseCommon/LogFileTarget.cpp index d99d09e..081222d 100644 --- a/BaseCommon/LogFileTarget.cpp +++ b/BaseCommon/LogFileTarget.cpp @@ -21,19 +21,19 @@ #include #include #include +#include #include "LogFileTarget.h" -#define LOG_FILE_ROOT "dstargateway" - -CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) : +CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, const std::string& fileRoot, bool rotate) : CLogTarget(logLevel), m_dir(dir), +m_fileRoot(fileRoot), m_rotate(rotate), m_file(), m_day(0) { - + assert(!fileRoot.empty()); } CLogFileTarget::~CLogFileTarget() @@ -53,7 +53,7 @@ void CLogFileTarget::printLogIntFixed(const std::string& msg) std::string filename(m_dir); if(filename[filename.length() - 1U] != '/') filename.push_back('/'); - filename.append(LOG_FILE_ROOT).append(".log"); + filename.append(m_fileRoot).append(".log"); m_file.open(filename, std::ios::app); if(m_file.is_open()) { @@ -80,7 +80,7 @@ void CLogFileTarget::printLogIntRotate(const std::string& msg) if(filename[filename.length() - 1U] != '/') filename.push_back('/'); char buf[64]; std::strftime(buf, 42, "-%Y-%m-%d", now_tm); - filename.append(LOG_FILE_ROOT).append(buf).append(".log"); + filename.append(m_fileRoot).append(buf).append(".log"); m_file.open(filename, std::ios::app); if(!m_file.is_open()) { std::cerr << "FAILED TO OPEN LOG FILE :" << filename; diff --git a/BaseCommon/LogFileTarget.h b/BaseCommon/LogFileTarget.h index 8df8c8b..10dc77d 100644 --- a/BaseCommon/LogFileTarget.h +++ b/BaseCommon/LogFileTarget.h @@ -26,7 +26,7 @@ class CLogFileTarget : public CLogTarget { public: - CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, bool rotate); + CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, const std::string& fileRoot, bool rotate); ~CLogFileTarget(); protected: @@ -37,6 +37,7 @@ private: void printLogIntFixed(const std::string& msg); std::string buildFileName(); std::string m_dir; + std::string m_fileRoot; bool m_rotate; std::fstream m_file; int m_day; From 263d5e83bfe2415e4b0d906a74ae0429b80b0baf Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 19:10:18 +0100 Subject: [PATCH 066/112] #20 add logging --- DGWTimeServer/DGWTimeServerApp.cpp | 21 ++++++++++---- DGWTimeServer/TimeServerConfig.cpp | 43 ++++++++++++++++++++++++++++- DGWTimeServer/TimeServerConfig.h | 12 ++++++++ DGWTimeServer/example.cfg | 6 ++++ DStarGateway/DStarGatewayApp.cpp | 4 +-- DStarGateway/DStarGatewayConfig.cpp | 34 +++++++++++------------ DStarGateway/DStarGatewayConfig.h | 8 +++--- 7 files changed, 98 insertions(+), 30 deletions(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 7fb39d2..eebb8d2 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -29,6 +29,8 @@ #include "Version.h" #include "Log.h" #include "Daemon.h" +#include "LogConsoleTarget.h" +#include "LogFileTarget.h" CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr; const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str()); @@ -57,11 +59,13 @@ int main(int argc, char * argv[]) return 0; } + // Load config std::string configfile(argv[1]); CTimeServerConfig config(configfile); if(!config.load()) return 1; + // Do daemon stuff TDaemon daemon; config.getDameon(daemon); if (daemon.daemon) { @@ -83,14 +87,21 @@ int main(int argc, char * argv[]) } } - CDGWTimeServerApp app(&config); + // Setup Log + TLog logConf; + config.getLog(logConf); + CLog::finalise(); + if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel)); + if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate)); - if(!app.init()) + // Start the app + CDGWTimeServerApp app(&config); + if(app.init()) { + app.run(); return 0; + } - app.run(); - - return 0; + return 1; } CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) : diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index dd03332..d2c4fab 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -20,7 +20,6 @@ #include #include "TimeServerConfig.h" -#include "Log.h" #include "StringUtils.h" CTimeServerConfig::CTimeServerConfig(const std::string &pathname) : @@ -50,6 +49,7 @@ bool CTimeServerConfig::load() ret = loadRepeaters(cfg) && ret; ret = loadDaemon(cfg) && ret; ret = loadPaths(cfg) && ret; + ret = loadLog(cfg) && ret; } return ret; @@ -150,6 +150,42 @@ bool CTimeServerConfig::loadPaths(const CConfig & cfg) return ret; } +bool CTimeServerConfig::loadLog(const CConfig & cfg) +{ + bool ret = cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/"); + if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') { + m_log.logDir.push_back('/'); + } + + ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dgwtimeserver") && ret; + ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret; + + std::string levelStr; + ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; + if(ret) { + if(levelStr == "trace") m_log.fileLevel = LOG_TRACE; + else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG; + else if(levelStr == "info") m_log.fileLevel = LOG_INFO; + else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING; + else if(levelStr == "error") m_log.fileLevel = LOG_ERROR; + else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL; + else if(levelStr == "none") m_log.fileLevel = LOG_NONE; + } + + ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; + if(ret) { + if(levelStr == "trace") m_log.displayLevel = LOG_TRACE; + else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG; + else if(levelStr == "info") m_log.displayLevel = LOG_INFO; + else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING; + else if(levelStr == "error") m_log.displayLevel = LOG_ERROR; + else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL; + else if(levelStr == "none") m_log.displayLevel = LOG_NONE; + } + + return ret; +} + void CTimeServerConfig::getTimeServer(TTimeServer& timeserver) const { timeserver = m_timeServer; @@ -174,3 +210,8 @@ void CTimeServerConfig::getPaths(TPaths& paths) const { paths = m_paths; } + +void CTimeServerConfig::getLog(TLog& log) const +{ + log = m_log; +} diff --git a/DGWTimeServer/TimeServerConfig.h b/DGWTimeServer/TimeServerConfig.h index a28d36d..7eeaacf 100644 --- a/DGWTimeServer/TimeServerConfig.h +++ b/DGWTimeServer/TimeServerConfig.h @@ -23,6 +23,7 @@ #include "Config.h" #include "TimeServerDefs.h" +#include "Log.h" typedef struct { std::string callsign; @@ -46,6 +47,14 @@ typedef struct { std::string data; } TPaths; +typedef struct { + std::string logDir; + LOG_SEVERITY displayLevel; + LOG_SEVERITY fileLevel; + std::string fileRoot; + bool fileRotate; +} TLog; + class CTimeServerConfig { public: @@ -58,6 +67,7 @@ public: unsigned int getRepeaterCount() const; void getRepeater(unsigned int idx, TRepeater& repeater) const; void getPaths(TPaths& paths) const; + void getLog(TLog& log) const; private: bool open(CConfig & cfg); @@ -65,10 +75,12 @@ private: bool loadTimeServer(const CConfig & cfg); bool loadDaemon(const CConfig & cfg); bool loadPaths(const CConfig & cfg); + bool loadLog(const CConfig & cfg); std::string m_fileName; std::vector m_repeaters; TTimeServer m_timeServer; TDaemon m_daemon; TPaths m_paths; + TLog m_log; }; diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index a643d60..94a823a 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -25,6 +25,12 @@ band= # Module letter of the repeater enabled=false # enable time beacons on this repeater band= # Module letter of the repeater +[Log] +path=/var/log/dstargateway/ +fileRoot= # defaults to dgwtimeserver +fileRotate= # rotate log files daily, defaults to true +fileLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none +displayLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none # Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd # require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index d0d5c52..634dd41 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -115,8 +115,8 @@ int main(int argc, char *argv[]) TLog logConf; config->getLog(logConf); CLog::finalise(); - if(logConf.m_displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.m_displayLevel)); - if(logConf.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.m_fileLevel, logConf.logDir, logConf.m_fileRotate)); + if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel)); + if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate)); //write banner in log file if we are dameon if(daemon.daemon) { diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 0afd547..77f8086 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -153,34 +153,32 @@ bool CDStarGatewayConfig::loadLog(const CConfig & cfg) m_log.logDir.push_back('/'); } - ret = cfg.getValue("log", "fileRoot", m_log.m_fileRoot, 0, 64, "dstargateway") && ret; - ret = cfg.getValue("log", "fileRotate", m_log.m_fileRotate, true) && ret; + ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dstargateway") && ret; + ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret; std::string levelStr; ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; if(ret) { - if(levelStr == "trace") m_log.m_fileLevel = LOG_TRACE; - else if(levelStr == "debug") m_log.m_fileLevel = LOG_DEBUG; - else if(levelStr == "info") m_log.m_fileLevel = LOG_INFO; - else if(levelStr == "warning") m_log.m_fileLevel = LOG_WARNING; - else if(levelStr == "error") m_log.m_fileLevel = LOG_ERROR; - else if(levelStr == "fatal") m_log.m_fileLevel = LOG_FATAL; - else if(levelStr == "none") m_log.m_fileLevel = LOG_NONE; + if(levelStr == "trace") m_log.fileLevel = LOG_TRACE; + else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG; + else if(levelStr == "info") m_log.fileLevel = LOG_INFO; + else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING; + else if(levelStr == "error") m_log.fileLevel = LOG_ERROR; + else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL; + else if(levelStr == "none") m_log.fileLevel = LOG_NONE; } ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; if(ret) { - if(levelStr == "trace") m_log.m_displayLevel = LOG_TRACE; - else if(levelStr == "debug") m_log.m_displayLevel = LOG_DEBUG; - else if(levelStr == "info") m_log.m_displayLevel = LOG_INFO; - else if(levelStr == "warning") m_log.m_displayLevel = LOG_WARNING; - else if(levelStr == "error") m_log.m_displayLevel = LOG_ERROR; - else if(levelStr == "fatal") m_log.m_displayLevel = LOG_FATAL; - else if(levelStr == "none") m_log.m_displayLevel = LOG_NONE; + if(levelStr == "trace") m_log.displayLevel = LOG_TRACE; + else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG; + else if(levelStr == "info") m_log.displayLevel = LOG_INFO; + else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING; + else if(levelStr == "error") m_log.displayLevel = LOG_ERROR; + else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL; + else if(levelStr == "none") m_log.displayLevel = LOG_NONE; } - //TODO 20211226 check if directories are accessible - return ret; } diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index dec8ca5..1bfce6c 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -86,10 +86,10 @@ typedef struct { typedef struct { std::string logDir; - LOG_SEVERITY m_displayLevel; - LOG_SEVERITY m_fileLevel; - std::string m_fileRoot; - bool m_fileRotate; + LOG_SEVERITY displayLevel; + LOG_SEVERITY fileLevel; + std::string fileRoot; + bool fileRotate; } TLog; typedef struct { From a009e094ab6d55170a2f52ccc7f46ecdaf2f0bfe Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 20:45:46 +0100 Subject: [PATCH 067/112] #20 one socket per repetaer (fixes time not sent on multi band repeaters) --- DGWTimeServer/TimeServerThread.cpp | 90 ++++++++++++++++++------------ DGWTimeServer/TimeServerThread.h | 7 +-- 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 253edfb..cfcfc47 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -38,15 +38,14 @@ const unsigned int SILENCE_LENGTH = 10U; CTimeServerThread::CTimeServerThread() : CThread("Time Server"), -m_socket("", 0U), m_callsign(), m_callsignA(), m_callsignB(), m_callsignC(), m_callsignD(), -m_callsignE(), m_callsignG(), m_address(), +m_addressStr(), m_language(LANG_ENGLISH_UK_1), m_format(FORMAT_VOICE_TIME), m_interval(INTERVAL_15MINS), @@ -81,7 +80,7 @@ CTimeServerThread::~CTimeServerThread() void * CTimeServerThread::Entry() { // Wait here until we have the essentials to run - while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty() && m_callsignE.empty()) + while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty()) Sleep(500UL); // 1/2 sec if (m_killed) @@ -127,8 +126,6 @@ void * CTimeServerThread::Entry() CLog::logInfo(("Stopping the Time Server thread")); - m_socket.close(); - return nullptr; } @@ -163,13 +160,10 @@ bool CTimeServerThread::setGateway(const std::string& callsign, const std::strin m_callsign.push_back(' '); + m_addressStr.assign(address); m_address = CUDPReaderWriter::lookup(address); m_dataPath.assign(dataPath); - bool ret = m_socket.open(); - if (!ret) - return false; - return true; } @@ -1192,7 +1186,6 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int unsigned int idB = CHeaderData::createId(); unsigned int idC = CHeaderData::createId(); unsigned int idD = CHeaderData::createId(); - unsigned int idE = CHeaderData::createId(); CHeaderData header; header.setMyCall1(m_callsign); @@ -1328,41 +1321,48 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int return false; } + CUDPReaderWriter * socketA = nullptr; + CUDPReaderWriter * socketB = nullptr; + CUDPReaderWriter * socketC = nullptr; + CUDPReaderWriter * socketD = nullptr; + if (!m_callsignA.empty()) { + socketA = new CUDPReaderWriter("", 0U); + socketA->open(); header.setRptCall2(m_callsignA); header.setId(idA); - sendHeader(header); + sendHeader(*socketA, header); } if (!m_callsignB.empty()) { + socketB = new CUDPReaderWriter("", 0U); + socketB->open(); header.setRptCall2(m_callsignB); header.setId(idB); - sendHeader(header); + sendHeader(*socketB, header); } if (!m_callsignC.empty()) { + socketC = new CUDPReaderWriter("", 0U); + socketC->open(); header.setRptCall2(m_callsignC); header.setId(idC); - sendHeader(header); + sendHeader(*socketC, header); } if (!m_callsignD.empty()) { + socketD = new CUDPReaderWriter("", 0U); + socketD->open(); header.setRptCall2(m_callsignD); header.setId(idD); - sendHeader(header); - } - - if (!m_callsignE.empty()) { - header.setRptCall2(m_callsignE); - header.setId(idE); - sendHeader(header); + sendHeader(*socketD, header); } + bool loop = true; unsigned int out = 0U; - auto start = std::chrono::high_resolution_clock::now(); - for (;;) { + for (;loop;) { unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); needed /= DSTAR_FRAME_TIME_MS; @@ -1373,40 +1373,56 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int if (!m_callsignA.empty()) { data->setId(idA); - sendData(*data); + sendData(*socketA, *data); } if (!m_callsignB.empty()) { data->setId(idB); - sendData(*data); + sendData(*socketB, *data); } if (!m_callsignC.empty()) { data->setId(idC); - sendData(*data); + sendData(*socketC, *data); } if (!m_callsignD.empty()) { data->setId(idD); - sendData(*data); - } - - if (!m_callsignE.empty()) { - data->setId(idE); - sendData(*data); + sendData(*socketD, *data); } delete data; - if (m_in == out) - return true; + if (m_in == out) { + loop = false; + break; + } } Sleep(10UL); } + + if(socketA != nullptr) { + socketA->close(); + delete socketA; + } + if(socketB != nullptr) { + socketB->close(); + delete socketB; + } + if(socketC != nullptr) { + socketC->close(); + delete socketC; + } + if(socketD != nullptr) { + socketD->close(); + delete socketD; + } + + return true; } -bool CTimeServerThread::sendHeader(const CHeaderData &header) +bool CTimeServerThread::sendHeader(CUDPReaderWriter& socket, const CHeaderData &header) { unsigned char buffer[60U]; unsigned int length = header.getG2Data(buffer, 60U, true); @@ -1416,7 +1432,7 @@ bool CTimeServerThread::sendHeader(const CHeaderData &header) return true; #else for (unsigned int i = 0U; i < 5U; i++) { - bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); + bool res = socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); if (!res) return false; } @@ -1425,7 +1441,7 @@ bool CTimeServerThread::sendHeader(const CHeaderData &header) #endif } -bool CTimeServerThread::sendData(const CAMBEData& data) +bool CTimeServerThread::sendData(CUDPReaderWriter& socket, const CAMBEData& data) { unsigned char buffer[40U]; unsigned int length = data.getG2Data(buffer, 40U); @@ -1434,6 +1450,6 @@ bool CTimeServerThread::sendData(const CAMBEData& data) CUtils::dump(("Sending Data"), buffer, length); return true; #else - return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); + return socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); #endif } diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index ee3f967..a1dffd8 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -72,15 +72,14 @@ public: void kill(); private: - CUDPReaderWriter m_socket; std::string m_callsign; std::string m_callsignA; std::string m_callsignB; std::string m_callsignC; std::string m_callsignD; - std::string m_callsignE; std::string m_callsignG; in_addr m_address; + std::string m_addressStr; LANGUAGE m_language; FORMAT m_format; INTERVAL m_interval; @@ -110,8 +109,8 @@ private: std::vector sendTimePtPT(unsigned int hour, unsigned int min); bool send(const std::vector& words, unsigned int hour, unsigned int min); - bool sendHeader(const CHeaderData& header); - bool sendData(const CAMBEData& data); + bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header); + bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); bool loadAMBE(); bool readAMBE(const std::string& dir, const std::string& name); From 9a691eb1903d2dd74d704d621572b0fce568bf8b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 09:02:23 +0100 Subject: [PATCH 068/112] #20 start simplifying things --- DGWTimeServer/TimeServerThread.cpp | 132 ++++++++++------------------- DGWTimeServer/TimeServerThread.h | 6 +- 2 files changed, 49 insertions(+), 89 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index cfcfc47..575f220 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "TimeServerThread.h" #include "DStarDefines.h" @@ -39,10 +41,7 @@ const unsigned int SILENCE_LENGTH = 10U; CTimeServerThread::CTimeServerThread() : CThread("Time Server"), m_callsign(), -m_callsignA(), -m_callsignB(), -m_callsignC(), -m_callsignD(), +m_repeaters(), m_callsignG(), m_address(), m_addressStr(), @@ -80,7 +79,7 @@ CTimeServerThread::~CTimeServerThread() void * CTimeServerThread::Entry() { // Wait here until we have the essentials to run - while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty()) + while (!m_killed && m_address.s_addr == INADDR_NONE && m_repeaters.size() == 0U) Sleep(500UL); // 1/2 sec if (m_killed) @@ -143,19 +142,19 @@ bool CTimeServerThread::setGateway(const std::string& callsign, const std::strin m_callsignG.push_back('G'); if (!rpt1.empty()) { - m_callsignA = m_callsign + rpt1; + m_repeaters.push_back(m_callsign + rpt1); } if (!rpt2.empty()) { - m_callsignB = m_callsign + rpt2; + m_repeaters.push_back(m_callsign + rpt2); } if (!rpt3.empty()) { - m_callsignC = m_callsign + rpt3; + m_repeaters.push_back(m_callsign + rpt3); } if (!rpt4.empty()) { - m_callsignD = m_callsign + rpt4; + m_repeaters.push_back(m_callsign + rpt4); } m_callsign.push_back(' '); @@ -1182,11 +1181,6 @@ void CTimeServerThread::end() bool CTimeServerThread::send(const std::vector &words, unsigned int hour, unsigned int min) { - unsigned int idA = CHeaderData::createId(); - unsigned int idB = CHeaderData::createId(); - unsigned int idC = CHeaderData::createId(); - unsigned int idD = CHeaderData::createId(); - CHeaderData header; header.setMyCall1(m_callsign); header.setRptCall1(m_callsignG); @@ -1321,43 +1315,47 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int return false; } - CUDPReaderWriter * socketA = nullptr; - CUDPReaderWriter * socketB = nullptr; - CUDPReaderWriter * socketC = nullptr; - CUDPReaderWriter * socketD = nullptr; - - if (!m_callsignA.empty()) { - socketA = new CUDPReaderWriter("", 0U); - socketA->open(); - header.setRptCall2(m_callsignA); - header.setId(idA); - sendHeader(*socketA, header); + bool res = true; + for(auto rpt : m_repeaters) { + res = sendToRepeater(header, rpt) && res; } - if (!m_callsignB.empty()) { - socketB = new CUDPReaderWriter("", 0U); - socketB->open(); - header.setRptCall2(m_callsignB); - header.setId(idB); - sendHeader(*socketB, header); - } + // std::vector *> tasks; - if (!m_callsignC.empty()) { - socketC = new CUDPReaderWriter("", 0U); - socketC->open(); - header.setRptCall2(m_callsignC); - header.setId(idC); - sendHeader(*socketC, header); - } + // for(auto rpt : m_repeaters) { + // std::packaged_task * task = new std::packaged_task([header, rpt, this] { return sendToRepeater(header, rpt);} ); + // std::thread t(std::move(*task)); + // } + + // bool res = true; + // for(auto task : tasks) { + // auto future = task->get_future(); + // future.wait(); + // res = future.get() && res; + // delete task; + // } - if (!m_callsignD.empty()) { - socketD = new CUDPReaderWriter("", 0U); - socketD->open(); - header.setRptCall2(m_callsignD); - header.setId(idD); - sendHeader(*socketD, header); + for(unsigned int i = 0U; i < MAX_FRAMES; i++) { + delete m_data[i]; + m_data[i] = nullptr; } + return res; +} + +bool CTimeServerThread::sendToRepeater(const CHeaderData& h, const std::string& rptCall2) +{ + CUDPReaderWriter socket("", 0U); + if(!socket.open()) + return false; + + auto id = CHeaderData::createId(); + CHeaderData header(h); + header.setId(id); + header.setRptCall2(rptCall2); + + sendHeader(socket, header); + bool loop = true; unsigned int out = 0U; auto start = std::chrono::high_resolution_clock::now(); @@ -1367,31 +1365,10 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int needed /= DSTAR_FRAME_TIME_MS; while (out < needed) { - CAMBEData* data = m_data[out]; - m_data[out] = NULL; + CAMBEData data(*(m_data[out])); out++; - - if (!m_callsignA.empty()) { - data->setId(idA); - sendData(*socketA, *data); - } - - if (!m_callsignB.empty()) { - data->setId(idB); - sendData(*socketB, *data); - } - - if (!m_callsignC.empty()) { - data->setId(idC); - sendData(*socketC, *data); - } - - if (!m_callsignD.empty()) { - data->setId(idD); - sendData(*socketD, *data); - } - - delete data; + data.setId(id); + sendData(socket, data); if (m_in == out) { loop = false; @@ -1402,22 +1379,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int Sleep(10UL); } - if(socketA != nullptr) { - socketA->close(); - delete socketA; - } - if(socketB != nullptr) { - socketB->close(); - delete socketB; - } - if(socketC != nullptr) { - socketC->close(); - delete socketC; - } - if(socketD != nullptr) { - socketD->close(); - delete socketD; - } + socket.close(); return true; } diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index a1dffd8..ef501fc 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -73,10 +73,7 @@ public: private: std::string m_callsign; - std::string m_callsignA; - std::string m_callsignB; - std::string m_callsignC; - std::string m_callsignD; + std::vector m_repeaters; std::string m_callsignG; in_addr m_address; std::string m_addressStr; @@ -111,6 +108,7 @@ private: bool send(const std::vector& words, unsigned int hour, unsigned int min); bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header); bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); + bool sendToRepeater(const CHeaderData& header, const std::string& rptCall2); bool loadAMBE(); bool readAMBE(const std::string& dir, const std::string& name); From 663dffdb1934ebc2d17d412a2814dc171cb133c3 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 16:54:59 +0100 Subject: [PATCH 069/112] #20 Add some delay when firing headers --- DGWTimeServer/TimeServerThread.cpp | 71 ++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 575f220..8018255 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -106,8 +106,8 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; - // if (min != lastMin) - // sendTime(15, 45); + if (min != lastMin) + sendTime(15, 45); if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) @@ -1315,11 +1315,64 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int return false; } - bool res = true; + std::vector ids; + std::vector sockets; for(auto rpt : m_repeaters) { - res = sendToRepeater(header, rpt) && res; + auto socket = new CUDPReaderWriter("", 0U); + sockets.push_back(socket); + ids.push_back(CHeaderData::createId()); } + bool allOpen = std::all_of(sockets.begin(), sockets.end(), [](CUDPReaderWriter* s) { return s->open(); }); + if(allOpen) { + //send headers + for(unsigned int i = 0; i < m_repeaters.size(); i++) { + CHeaderData headerCopy(header); + headerCopy.setId(ids[i]); + headerCopy.setRptCall2(m_repeaters[i]); + sendHeader(*(sockets[i]), headerCopy); + Sleep(5); + } + + // send audio + bool loop = true; + unsigned int out = 0U; + auto start = std::chrono::high_resolution_clock::now(); + + for (;loop;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + for(unsigned int i = 0; i < m_repeaters.size(); i++) { + CAMBEData data(*(m_data[out])); + data.setId(ids[i]); + sendData(*(sockets[i]), data); + Sleep(5); + } + + delete m_data[out]; + m_data[out] = nullptr; + out++; + + if (m_in == out) { + loop = false; + break; + } + } + } + } + + for(auto socket : sockets) { + socket->close(); + delete socket; + } + + // bool res = true; + // for(auto rpt : m_repeaters) { + // res = sendToRepeater(header, rpt) && res; + // } + // std::vector *> tasks; // for(auto rpt : m_repeaters) { @@ -1335,12 +1388,12 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int // delete task; // } - for(unsigned int i = 0U; i < MAX_FRAMES; i++) { - delete m_data[i]; - m_data[i] = nullptr; - } + // for(unsigned int i = 0U; i < MAX_FRAMES; i++) { + // delete m_data[i]; + // m_data[i] = nullptr; + // } - return res; + return true; } bool CTimeServerThread::sendToRepeater(const CHeaderData& h, const std::string& rptCall2) From 8d3c716c5f9423aa7df32742acec721f3ca94cd9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 17:03:00 +0100 Subject: [PATCH 070/112] #20 clean up --- DGWTimeServer/TimeServerThread.cpp | 71 ++---------------------------- DGWTimeServer/TimeServerThread.h | 1 - 2 files changed, 3 insertions(+), 69 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 8018255..3efea8d 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -106,8 +106,8 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; - if (min != lastMin) - sendTime(15, 45); + // if (min != lastMin) + // sendTime(15, 45); if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) @@ -1339,7 +1339,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int unsigned int out = 0U; auto start = std::chrono::high_resolution_clock::now(); - for (;loop;) { + while(loop) { unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); needed /= DSTAR_FRAME_TIME_MS; @@ -1368,74 +1368,9 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int delete socket; } - // bool res = true; - // for(auto rpt : m_repeaters) { - // res = sendToRepeater(header, rpt) && res; - // } - - // std::vector *> tasks; - - // for(auto rpt : m_repeaters) { - // std::packaged_task * task = new std::packaged_task([header, rpt, this] { return sendToRepeater(header, rpt);} ); - // std::thread t(std::move(*task)); - // } - - // bool res = true; - // for(auto task : tasks) { - // auto future = task->get_future(); - // future.wait(); - // res = future.get() && res; - // delete task; - // } - - // for(unsigned int i = 0U; i < MAX_FRAMES; i++) { - // delete m_data[i]; - // m_data[i] = nullptr; - // } - return true; } -bool CTimeServerThread::sendToRepeater(const CHeaderData& h, const std::string& rptCall2) -{ - CUDPReaderWriter socket("", 0U); - if(!socket.open()) - return false; - - auto id = CHeaderData::createId(); - CHeaderData header(h); - header.setId(id); - header.setRptCall2(rptCall2); - - sendHeader(socket, header); - - bool loop = true; - unsigned int out = 0U; - auto start = std::chrono::high_resolution_clock::now(); - - for (;loop;) { - unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); - needed /= DSTAR_FRAME_TIME_MS; - - while (out < needed) { - CAMBEData data(*(m_data[out])); - out++; - data.setId(id); - sendData(socket, data); - - if (m_in == out) { - loop = false; - break; - } - } - - Sleep(10UL); - } - - socket.close(); - - return true; -} bool CTimeServerThread::sendHeader(CUDPReaderWriter& socket, const CHeaderData &header) { diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index ef501fc..3abe883 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -108,7 +108,6 @@ private: bool send(const std::vector& words, unsigned int hour, unsigned int min); bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header); bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); - bool sendToRepeater(const CHeaderData& header, const std::string& rptCall2); bool loadAMBE(); bool readAMBE(const std::string& dir, const std::string& name); From 4511d88e0f84d60ceed3a1a89c69099148c8b8cb Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 17:08:06 +0100 Subject: [PATCH 071/112] #20 remove unused includes --- DGWTimeServer/TimeServerThread.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 3efea8d..696099f 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -24,8 +24,6 @@ #include #include #include -#include -#include #include "TimeServerThread.h" #include "DStarDefines.h" From 05e3aa7fa9764dfc6590502807f77919a7509af7 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 17:12:34 +0100 Subject: [PATCH 072/112] #20 fix copy paste typo --- DGWTimeServer/DGWTimeServerApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index eebb8d2..623f610 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -155,7 +155,7 @@ bool CDGWTimeServerApp::createThread() void CDGWTimeServerApp::sigHandler(int sig) { - CLog::logInfo("Caught signal : %s, shutting down gateway", strsignal(sig)); + CLog::logInfo("Caught signal : %s, shutting down time server", strsignal(sig)); if(g_app != nullptr && g_app->m_thread != nullptr) { g_app->m_thread->kill(); From 1bb29f31a96e55fd97c7535c4b1372539492e548 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 18:13:00 +0100 Subject: [PATCH 073/112] #20 add missing include --- DGWTimeServer/DGWTimeServerApp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 623f610..847b327 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef DEBUG_DSTARGW #include #endif From 1ff39d65d8ad1ba6527ceba9eeecda91750b9cd6 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 18:21:51 +0100 Subject: [PATCH 074/112] #20 Udpate readme --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 091d893..03649c1 100644 --- a/README.md +++ b/README.md @@ -135,26 +135,27 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 -- [Improvement] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)) -- [Improvement] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) -- [Improvement] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) +- [**Improvement**] Add time server +- [**Improvement**] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)). DStarGateway can also be run as a "forking" daemon. This might be required for distros still using sysv. Systemd can live without it. +- [**Improvement**] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) +- [**Improvement**] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) ## 5.2. Version 0.5 -- [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) -- [Bugfix] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) -- [Improvement] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5)) -- [Improvement] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9)) -- [Bugfix] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14)) -- [Bugfix] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13)) -- [Bugfix] Trying to connect to ghost ircDDB when no ircDDB is configured +- [**Improvement**] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) +- [**Bugfix**] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) +- [**Improvement**] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5)) +- [**Improvement**] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9)) +- [**Bugfix**] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14)) +- [**Bugfix**] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13)) +- [**Bugfix**] Trying to connect to ghost ircDDB when no ircDDB is configured ## 5.3. Version 0.4 -- [Improvement] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) -- [Bugfix] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7)) -- [Improvement] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6)) -- [Improvement] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) +- [**Improvement**] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) +- [**Bugfix**] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7)) +- [**Improvement**] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6)) +- [**Improvement**] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) ## 5.4. Version 0.3 -- [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. +- [**Improvement**] Get ride of libcongig++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. ## 5.5. Version 0.2 -- [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) +- [**Bugfix**] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization ## 5.6. Version 0.1 First working version @@ -163,10 +164,9 @@ I started this during my 2021 seasons holiday. It took me almost 8 days to get t - ☑ Better NatTraversal - No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer - Support for all protocols (G2, DExtra, DPlus) DCS does nto make sense as it was historically never used as protocol for linking repeaters - - A [branch](https://github.com/F4FXL/DStarGateway/tree/feature/NatTraversal) already exists for this - ☑ Send the connection status to APRS-IS as a status frame - ☒ Reinstantiate DRATS -- ☒ Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) +- ☑ Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) - ☒ Automatic refresh of host files - ☒ Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ? - ☒ Forward messages from RS-MS1A to APRS and vice versa From bb652f5265b307d4c45f6642e6aefaf8aa0a64d4 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 26 Feb 2022 11:52:45 +0100 Subject: [PATCH 075/112] #20 fix test clean up --- Tests/Makefile | 4 +++- Tests/{ => ProgramArgs}/ProgramArgs.cpp | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename Tests/{ => ProgramArgs}/ProgramArgs.cpp (100%) diff --git a/Tests/Makefile b/Tests/Makefile index 2e29d24..f338acc 100644 --- a/Tests/Makefile +++ b/Tests/Makefile @@ -15,7 +15,9 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB .PHONY clean : clean : - @$(RM) *.o *.d dstargateway_tests + find . -name "*.o" -type f -delete + find . -name "*.d" -type f -delete + $(RM) *.o *.d dstargateway_tests ../APRS/APRS.a: ../Common/Common.a: diff --git a/Tests/ProgramArgs.cpp b/Tests/ProgramArgs/ProgramArgs.cpp similarity index 100% rename from Tests/ProgramArgs.cpp rename to Tests/ProgramArgs/ProgramArgs.cpp From d31ecf63c07927541773b4f7063fb10b4d15a149 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 26 Feb 2022 16:13:48 +0100 Subject: [PATCH 076/112] #20 add AMBEFileReader and corresponding tests --- .vscode/tasks.json | 10 +- DStarBase/AMBEFileReader.cpp | 179 ++++++++++++++++++++++++++++++++ DStarBase/AMBEFileReader.h | 74 +++++++++++++ Tests/AMBEFileReader/fr_FR.ambe | Bin 0 -> 20137 bytes Tests/AMBEFileReader/fr_FR.indx | 44 ++++++++ Tests/AMBEFileReader/lookup.cpp | 115 ++++++++++++++++++++ Tests/AMBEFileReader/read.cpp | 62 +++++++++++ 7 files changed, 479 insertions(+), 5 deletions(-) create mode 100644 DStarBase/AMBEFileReader.cpp create mode 100644 DStarBase/AMBEFileReader.h create mode 100644 Tests/AMBEFileReader/fr_FR.ambe create mode 100644 Tests/AMBEFileReader/fr_FR.indx create mode 100644 Tests/AMBEFileReader/lookup.cpp create mode 100644 Tests/AMBEFileReader/read.cpp diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 88ce123..995a9f1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -94,7 +91,10 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] } ] diff --git a/DStarBase/AMBEFileReader.cpp b/DStarBase/AMBEFileReader.cpp new file mode 100644 index 0000000..119ea4a --- /dev/null +++ b/DStarBase/AMBEFileReader.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "AMBEFileReader.h" +#include "DStarDefines.h" +#include "Log.h" + +const unsigned int SILENCE_LENGTH = 10U; + +CAMBEFileReader::CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile) : +m_indexFile(indexFile), +m_ambeFile(ambeFile), +m_ambe(nullptr), +m_ambeLength(0U), +m_index() +{ + +} + +CAMBEFileReader::~CAMBEFileReader() +{ + if(m_ambe != nullptr) { + delete[] m_ambe; + } +} + +bool CAMBEFileReader::read() +{ + bool ret = readAmbe() && readIndex(); + return ret; +} + +bool CAMBEFileReader::readAmbe() +{ + struct stat sbuf; + if (stat(m_ambeFile.c_str(), &sbuf)) { + CLog::logWarning("File %s not readable\n", m_ambeFile.c_str()); + return false; + } + + unsigned int fsize = sbuf.st_size; + + FILE *file = fopen(m_ambeFile.c_str(), "rb"); + if (NULL == file) { + CLog::logError("Cannot open %s for reading\n", m_ambeFile.c_str()); + return false; + } + + CLog::logInfo("Reading %s\n", m_ambeFile.c_str()); + + unsigned char buffer[VOICE_FRAME_LENGTH_BYTES]; + + size_t n = fread(buffer, sizeof(unsigned char), 4, file); + if (n != 4) { + CLog::logError("Unable to read the header from %s\n", m_ambeFile.c_str()); + fclose(file); + return false; + } + + if (memcmp(buffer, "AMBE", 4)) { + CLog::logError("Invalid header from %s\n", m_ambeFile.c_str()); + fclose(file); + return false; + } + + // Length of the file minus the header + unsigned int length = fsize - 4U; + + // Hold the file data plus silence at the end + m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES]; + m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES; + + // Add silence to the beginning of the buffer + unsigned char* p = m_ambe; + for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES) + memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + n = fread(p, 1, length, file); + if (n != length) { + CLog::logError("Unable to read the AMBE data from %s\n", m_ambeFile.c_str()); + fclose(file); + delete[] m_ambe; + m_ambeLength = 0U; + m_ambe = nullptr; + return false; + } + + fclose(file); + + return true; +} + +bool CAMBEFileReader::readIndex() +{ + struct stat sbuf; + + if (stat(m_indexFile.c_str(), &sbuf)) { + CLog::logError("File %s not readable\n", m_indexFile.c_str()); + return false; + } + + FILE *file = fopen(m_indexFile.c_str(), "r"); + if (file == nullptr) { + CLog::logError("Cannot open %s for reading\n", m_indexFile.c_str()); + return false; + } + + // Add a silence entry at the beginning + m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); + + CLog::logInfo("Reading %s\n", m_indexFile.c_str()); + + char line[128]; + while (fgets(line, 128, file)) { + if (strlen(line) && '#'!=line[0]) { + const std::string space(" \t\r\n"); + std::string name(strtok(line, space.c_str())); + std::string strt(strtok(NULL, space.c_str())); + std::string leng(strtok(NULL, space.c_str())); + + if (name.size() && strt.size() && leng.size()) { + unsigned long start = std::stoul(strt); + unsigned long length = std::stoul(leng); + + if (start >= m_ambeLength || (start + length) >= m_ambeLength) + CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length); + else + m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length); + } + } + } + + fclose(file); + + return true; +} + +bool CAMBEFileReader::lookup(const std::string &id, std::vector& data) +{ + if(m_index.count(id) == 0U) { + CLog::logError("Cannot find the AMBE index for *%s*", id.c_str()); + return false; + } + + CIndexRecord* info = m_index[id]; + unsigned int start = info->getStart(); + unsigned int length = info->getLength(); + + for (unsigned int i = 0U; i < length; i++) { + unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); + + CAMBEData* dataOut = new CAMBEData; + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); + data.push_back(dataOut); + } + + return true; +} \ No newline at end of file diff --git a/DStarBase/AMBEFileReader.h b/DStarBase/AMBEFileReader.h new file mode 100644 index 0000000..cd66e6c --- /dev/null +++ b/DStarBase/AMBEFileReader.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include + +#include "AMBEData.h" + +class CIndexRecord { +public: + CIndexRecord(const std::string& name, unsigned int start, unsigned int length) : + m_name(name), + m_start(start), + m_length(length) + { + } + + std::string getName() const + { + return m_name; + } + + unsigned int getStart() const + { + return m_start; + } + + unsigned int getLength() const + { + return m_length; + } + +private: + std::string m_name; + unsigned int m_start; + unsigned int m_length; +}; + +class CAMBEFileReader +{ +public: + CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile); + ~CAMBEFileReader(); + bool read(); + bool lookup(const std::string &id, std::vector& data); + +private: + bool readAmbe(); + bool readIndex(); + + std::string m_indexFile; + std::string m_ambeFile; + unsigned char* m_ambe; + unsigned int m_ambeLength; + std::unordered_map m_index; +}; \ No newline at end of file diff --git a/Tests/AMBEFileReader/fr_FR.ambe b/Tests/AMBEFileReader/fr_FR.ambe new file mode 100644 index 0000000000000000000000000000000000000000..c81d0630940a7fa373c5b4882ae9a765679c95d3 GIT binary patch literal 20137 zcmaHScQ{;a_q9&+-iheZTl5Ig5+zL%M1-i(MTsbh(c54!7=857JEQjmQG)11kKTKW zZw8U)ect!?{WsUK?{hBa+2hKnm0VqU`IFa2xJV2GeKt(HoVY_^b>FARm|P%Re*~t$e2)UNmvuen5QAav>4PB_SOwp zfn5kd+=sX2&$Bw?lnX&kWM*!_KhFY&V#A?`V4j91nr+J6gL}lay)F=~b@j%-Px7Z=j{@~V`BrZM=6TpO`&K%bscE)U zmiZ_Rg7H2*4l`{U9jL&57#dorf#of-og5k}s@mc$VmKcfQmUa06iH1F3N4ktymA@S z_m7yKLJh>zg%MW}%dwoz{$DZM$=CO(v?xdYMJ#tRTWU&- zX8?EJNQ3ptZrPr#x`{%bc8;LBwybWhMRz|-*&DCQnm=f9dAr#{oeF${BXvqzjP@`= z1S0=vQ8O5lAoSg3@lZPC2w3(K^<(n=?;e{X?QE7GuyY&HVwTE7nBo;AU1>JTHa2?K zV+e=ep99Y-i8?iP-&Rx^Xq?O^l*nS>zHiY8YbnEulc;H->I8;odvw+XJH*3=N}EQF zNyoqwMa5&Qc_XEG0Z-VQ+bRLBM9J~wf;UZA8S~o{T6Cz}&n32lb$@n1Fl$uag)uxk z<_b=GXXronV2oynOY%JI-pl1S4^SNc;;Vh8v-{}l76TtkpT#}oV>-%m;cDjUkoykp zv87-9!kt~#95zYa`!39k*S7K|v+XysX&wLE`nm~-7uw$R_j}n1Scltn8pAGdI1}wR z+;2teG9D&$I28#Sd@`i>u^b#CS2{&;r$db9CTG6+yR?$1AJ85Lp{cZnJ@kuw_K+t| zAJ=&P-u)Y_IcR||#LnaJ;fCpW#H35z{CudbVNag9^r>-4a6n!+mU{8P4Y9n5h4}+h z$zrf+9xD9a9y}^&j^}iO?Hd)wG11dU&IP8DCM!glm6vuHquqW>WF_`6qM*hV6UY={ z6R3bI+vb(is+h!hc zC0CD6;P)HD!_}DnG*Z;X)lXIJP3;WF5len_#)-D{xI3(fl%a|?@l_m8nZ5X2;;Acj z^&`F4YoX3ru3Q!%vQG7hDe^-ynA30#Pwf-Jp%|(;LT*r^|5iX;8z&ACjVk;H+|#0A zwreo6!5J$jO$Qb!+09Hn3}qCQQ}2)`KV$~o4wqX>&Fx?ZJ+16%on<{%qJICe^&q2o z$%u~Rz+l85jaUED->Lz@M=9cQ0`Ri8et<+#t2$0YqP-jU@tqE|5eT zP{LYQ=tXX&-sXoyBqX+6pbg0JE-(&>V@Qa*SVdx{c8Nb^W(d?mVjVIj^ba29EBMQ@ zqY^x=4O&CvEm`DYA#LS=(5Q<446*NY(0H71`n8Cu>}9XQYx`K-u7Jo9%bTaMqqxt0 zT6F84mAoyer5rJ0unV;4Oc)q5x(Ba1!uBVV|8RG&k-&dEY^crswySt&=g>Hy+9SzBdUXq zf#F#YtuEirsXi<}?yxCR_Y z+3Z1QTo_WF;$Nfn4x`cH=$uZ6n!HK+6IG6X`{(9rEJLYF)8$W3ixj=CI$0yM08 zV3Ocv|B=}Cytr~!t3lWpI};woD^l1Gi40uKln>ZPF-f8UEsUIOq)R|pkvyuLHkr|RkWtFZXiE`QB>*)^UQmIk>Kul&{QT05TvUC4z~08{LvN6ux8uwb z$c(TNJrEVF_j{d-CL`tv>CxadYigS7-%H-|>&$bX+fIbH(BouO-OyJzI))5N! z^7SA9M2EM-aQPhQFdxrkiICEmxIE~ow<&(5>%Xi`(%)G_CxMZum)S5Pl=7}?)raGU zS;{OY=~2SKydzjPK(BVEa)kUD?s5u87RP*)>QTPa^z!hb-Pb6!^qkT;$oIGOF1eZF zt*f*T)dP>3B%2q<_LPVU(~ZhQw}?C1e$)wpwr#yv-tQ-VUpYzhZVDeaiNc$;65@j5 zKfroA%a@`$P2#Awk;=|K4fR*}wOfH*jAR9Oz5j54&YVlWVcFuxQ3q{ea83XwVbwrU zD6K62a)}Feah=Nu-qPuHnWvvF83;0|>oIByy}-s+{@1+a0uxBBr2oeJC)*)$W&nQ0 z;DMwAccI_XJN?M= zW4;|Ndz5MVuu~^-7M|sjh`+Bg>0TeN?@KovtU2)f>^xlS+qmu6Lc)n>?gS|EY%k`l ze>}K$WP-zjKSToD_;I48eTu*#Z`%p27xSL@56zenE?B<P&C zVRJ{&EI%HB`$++PK?xD6FyCGQRlJte*-pC5;fya@NFObxrAX&%Zy%`Vd710DQEoBBZ3`pFy*gD?VA1XDKv}C3HgeR55i9hl{8$y=M-9s*eJKh z3>0~v#Z4(1)k4T7a^jv*I*T4^b*r`90!1p78r9b3{86zG8k&O=Rdr9fst-VbC7q`Tr*-QbpthMplId}H5UrYaRu{axXiPR_AE5*9TsRn3Y99_+*mpkqKJ;+b-$^L zOGOQaazDu{J74HwgLesbc)ld}M+#ij$l^@sXhpiHrs=T}=W&DYjqcuW>O2-u3g=JR zlWU?}Ea zjd?&5l)yPUwn3lO4DS=eD956$ICXJX4gzuEtaXaLXmk*UF*M|hc*n1sZi0~LokQAS~(tGb`H!0atV()XY zeq&H<&W%bZykk^|eh7a1z{;w1;)u?T3ES3PYw{z>$4jKD3HVqMuO=f?wS|-3s1ez# zFt|%4bny|@lov;syQ`OKWwk5K;?nQgFjnrqKWytAQF_P?>_zW7CA@vOr%S@S`ik$q z+v5%S(84ulNxhK<5vsCxw9lM98o}dDOlikG6*^Kw;fCDaeWQR*@*V_+c^y(!ape_9$%&5Q;`v?Yj#9WSZp!+f2dmkQjE zOV;DJ9V>q~uI$KE8G03vz0z2&B+Y&{WFokK&LfE?r)Z1uG&;Y~aNbke&@5J;G>bjb zu}rEsg+5@OK`y$a+t>F7Dp1#?=RA#?G@V$k$)KNuP=^DkkOPi0CbwXrrWv5yu@>P% z!Bi_Bh|aIE#|pq{T-sC)X_mkF5cT+{C2@%y)=Nh;Ha7C}^T zXNUb+M3dLQo-|G6d?SU7ylzBW2oEhqj~T=&((KO0>8N@TCRfCA7Yl@IZ)`@VoG=>? z`CPRjO{aGj|J!&hMJ)Ko#PiRN*^+n(5&cv25<9GgTYb)-xY8uMGSeSi;}^`gx>T8;yYF4|1GddF#tS1K%*KA+oP4Xy*5AIvOIH&7ktSk4{T# zQwj>nzG=~Zj~HB$rt7l+p-SX*SJ`9Aau(Zs&j9qJL_0zSj(p&BzX%A{ag%>ly+R>$ zJl;=buYv-|tq4LYSTAbICx0*Os!O4I!9_uiyud#k@{)^^nf;^6{HGQsF?$X4`=fI! zkn9v$r2cZzOC`?m4=(zL(-vG2V4ttXJkS|H1SP;GWjPo1|$wjRkJ{w{CRF-`V8DhB}+y= z2k(wUK+r}mjgiWsdv^|2?ozXdg3~8+bdg&a!MCS#RZxp>WB}Pv`MSR3+qJ}tlsdtD z1UMIn)J$>%TESr~XtyJBvalJm06>O*E2X%U5;>rA`tc)#d}F?$E9SO}ML3p1sx1re zY=)qrqTP-xY=F{pr3Boi+QwQU)-sBhP34FCml^`V`k7*IyefImFo&q4Eh55KXnq8Z z?Zv6&mF$pDLqh9l;HgFmwoR3UmG~JST3$hTf<^qy9GHNlR4N4xng*gdUSZVI%U2P^ zQYX=iZ9UHttr*dCr5W=LF!Uw1eoQ@4&b?GFZ=k0&QNFUAa%)j)c-AVRG|m`KRlk?} zdH(C$^B?8yl=sZLRj7xFClB82WsK9p5ml!8C~8N_|M^nsSnBqEHGJYRgU(X#DLC?X z=QK_lzj)J;MA|bNX<4kUy^pLa)kl_M!5bW;+C1;rK)}_umu-_TvkeVBEc~q@h*W>dA`k@h+aJfuAC>I9t4ZP^C6=sQM4|3M|ydbD7HbCDtA$yg{7KK58 zLUy|#Um`R1@Q-6@IbDNj%)(Rj6C4QUe@mh9&;#A z{|WTBS}utq>1!d0{72cgO<}~D->z+z@2Om{h6qa&8fXgB(LBAhKtV)-fDSgkG;v;N zzL07+2eyz3`BnZl1qJ@o<50P0w;b`1t0;*(|$@)SGkfx|N;2 z1M9IJF+J;9pQSgW7N37Q&D^CDra!CsoZ9^4p5&-uVmuV?y}dOQY=2vVed`Bcr|wnI zFCDgx78-me7n0yIK-ya116rKy`{1fag)UaXNK05-3z=cnOzjK zUV%*Y>Jtf&uSz(3CAX$xjv8N5!$a-n$4^JR+g3Ax-fi){r8r!PdBK(ABhej;8w(2o zqi2qv%Qj^+D|L@b~`q0F2E z?tBZ+Z73v#?6$8L?01rZRqZTPZi}6LhnP=jahH0Wen5d|<=!d=O_h%wRaO$W^ZRJ> zxZ%C!xs?X{uGTQ>&PXYL`b?2UvNBRzJbggZZM`Z{tEZBxNAS^n=%zi8tkq8MXh{aA zHEf>v#umC#-65IVp|)~F%g~tySM%NHL1^N&cZz53=?1J1`8+K=sRoRGzL&LCGpnV% z9_pHP+#;28vyC^Sv##3rfhWM=;Oj3bNDk}QQU#)9LBp_qw08I!4gNkZso zA78jAs1hhz#1@Y~uRWwMV8#x9QsH|-SVP(v;9%UZtHLF$d{cJ)`TI5@^93w3MU#bJ zYUkkx_QO1B+>&#*afe3teFejo^)l;)u5#IJm0jz$7!%a216a0_H?+3EME zf?UZH24KM-;giXpF%1Zi{k3}`g(yF{U5X=w(Saxme{5A`nLx@Mzdyp~mr3?a^9>rJ zkvc5#zLt15rilYfN!kYQk4}*VHrev!4hC3yJ4CSiFzO_~30PxVtjw*~)8FEWcp9~3 z-fe+$ALWl63BKNe?6r9_S*OdS29E`b+VUFOV=^|cNNeF++P{o_M*VWfz23T-*?gpb z_oi|6&rcR(jgyCGC92^TujDsIB;v**q(&{&L(2*<@9{&-)E`%eV%CU_&VRD`m|8CK zIKhaZz}h=`%0~Q4i+1iSpg;Ec>yZb%Ms~C2lfP(3f@h}TMNe_av{NbAu;O1*j`Di$ z`{D(IlDYYOw!Xz|3P_%Oj4~nfKXZZ|KR|7J+h3_osXcsCT28*Rvj z&xdX3Na`!!H_RyUarq&>Y>ftLN8YtKC2H4X2*G^ZN@poEAi&5eCW)ZN-rFCeFz+&D z1peU(Cm|B#0_(2Q3Tfe9JHQv|fXu-^Df54&)xTr>$;tkIsdt^-7kP?woA(!i2ANd4 zrqMyX;;zO4Oesq>Sd?_%?bY3vw)VH%giJl?x_<2ZMjN#0eK!T6axtU0EoGK5z2|A~ zdWa^c>8hg>b;#){Yr-x)#BgYm=qE)-JRK4@*LNdKG*MMe%dX;62kG5jHk{Q&rAl|H zE_23|;PMvk*EDN5oU+f>WD)~Y6NiOg0hB&c&y?4cCJ*#kNvj$smDY|Vh2*2X7`Pn= zs#Y(TSHj{cUN3pbL@Ox^ZoP4sd8{T?a4zq&UZ!jM zw}a#ut)mwc+o$fj?c=}E2;8~cNn7Z^Q}K}SXS|E`kk}`pSvxCJxqK(j8`LOz+H9Db zS<&2vu;Ub9y^S~z*h{;b>QdRT^{4B5p>?U4A#R10C6-YjtQ!h#g_)*YTge1!zNLSd zRa+ePMW)=nBwa{z@8VBLOE7{%5`JMMh9Z*@(XBxEhlrL$*jq~ki$h!?F~dg$Us1k) zBR{dU~4O6P~of*zN7%(4Qd6{r{oItC}IKr1> zJevCzF7OL+H50z0q8c~z z26OnoWrl$)qF|{vnSTCi{#7{LN`3obX@zliqlb^thtew#mbdfb^&;`3*yxge`5H@i~94zuU@Y-AOh*`B3n^O%81Sp;y#_39EBjn zeR$d?H^gaKyk9$x^vX+eHO5$t@5SM}9Jk?N;DM$CK33lR>?c`r#_j)s zMDYN(oSl|ol6=;hN+Va_8>>__kFY^Vzrp8ylwPez)l36654-@u8+Qg*;_hjVP!x#Kxtz=c&rTeTRj1*Q&eezSt??)k&6g5VYv%FTr0+MX1^tX5nZ9U6?#M?AyXn-yUoQ zdOvYB<~#@_+Zy@4z<~qn5J+G8K8+oe`}+w?%}a}TuJpl!Qn;95Zd+cH$?gP+so07^ z$1PYwxL#-1@wxY=4)s`KJ5P#!zH8ak@7t$m<3x`rR=GwC@2P64YAhS8z|=EY^n}G} z3Xh6^-AosrC;InYmvNk1jFXgN7-oK1#@+2s9p$~#C{rC1xVf`$lpfe_!zBJDT$^HG>7M-Ine za$e1|Ek&HuZ2i+I*wgWc5em&~I&RC4 zCnR7_;f0I1va#NK9*hUbFjOLcx>>z!C9Y+Hp$);#CV-Gvm;cc3YtnvQ->)gef0Op0>s5b=#}(gLZ-Ch~0*p43 zi}u5b#Rh~EiW+IU!{SajR}<8xU)!W##W!!(PYaWYq*YQw|ZF%a?%Z~wGk zQAIW2^ZB5XZ-kBBy-q)5c>L8fho2r4#lpUSOc;N1kla4Mcr?;Laux=e-qfBvE8hCR zRnt>B0wFy&j@|VYC=MG?6Zux8vHNSHAY!rD&eO>%+?p+JIs)S8Y89 z2MeC4tf&k;;d0d$6aKP5c@l0~dup0JoI!pr)RsuKTh%dEAQkpVS<9AL1b@b$mY^xC zFsUhQpnPbF*t7`bM{e4B>{37z#JI-)s%OXh$LGJAPe2ymzmyoc3Q@ZeR}j6we-wIM z|BL?Mb-&=cT7|tul$$@#x|nJ};JLB($BehGyZs{zZw@o+$Z9JZNf=h@i=*`Mj%d0h5#pw=7orsCCt=-yoIrTcbVM8dU6qu*}IG7;eL+P|-^FVpMT5 zz03C2LMAOK8~AB6Z09RIGX|+6$!gN$8Vz3{D|6mPXQ5C-un}7%G7G!`BDa?Q?&ts7 znb%VBFKzxG+Zd54$h`QsVnxvJzuNOEo363|DQ5pD*1r$EiWyad31UN@Pw%F`$Le4w z3*Sij&X}m*IdR|yikFC2Ns@Th@TBFl_O03~2xtiUD#?uZmfybLL=3)EZt0lfc<=%?Nk{WfhrE$}oi}Ch% z9SjkOUtT^fN5bvw-Ua>)dk=DBb^c6Kcp;)|X4O`Wap@nnUDOMtLi9t{0^|VC1-V7G zurF!>nKQD@jUczkw7Q_FAY*C3HM9D&>XLmSTk2O81kq&w!_Y2PU8WVHS|N0<|8*t0 z;B1JrLU8P>+T>>jzb`Z>|NdKUHX*u9md?Yem{C2vj|O$^;L~AM#gp^j7VT#PC;Q@u z#Zh@M@J_4b*<}1z;B!;(k-_?lqqV0_*0G<*xO@FUE3B{93bX=NHF^>mrh>}SvR*V| zexA!QtTxpdNUWMqOj)}x)t!7M5{}vU$^ykB*ftzn-g7yZ^rs`biHc`CAMPiCd%J)y zreM!WVJsA%juucj!5$h}=+_5TPw-7l6=MS*fdZ)Ac^%N|)Mx{o?NGtE%c0Qw^%)q? zA_CECWPY2*)HN#D$uM&AsUPf0Gw6ERc@ zVr5=R@`bgh61{Gz-7tZ|FoESwl7L`Yv55;O$AgJwI_{6a{JXI)s|-Rj{@vjh zdtM%qDgQOI^dD)3v~>QFO;>qwm8aKf<@L`2rz=ZmBM++(N-Oc@bjX_7)q~c|E$`Gf z@$J{O@g$G1Sk3gcIbA>;FhN(ZYK>${sz|5x#aOz_ZgFqqNox$u61gd-r|{`PszyAU1Te4W zeZFY?02f}RJIT7z^ADgi&kO~A$-zZj_W|lB8$|Y{t{79?jw%{WJY1U$5+)t9)@X2a zYJ#CVCqN-d6rh_aLqSNPs!|}iXEI{U#vYS^r=*~qBfr`d^%G5UrcuT;I)Rq&ZXC8` zVJedCTvQO`(Au?OlFCsGLTqDvggimi3*@ZY)%1uPLY`dCiU6*q;&n~Ao(#G6@UNZ! zD-ZuJa1w(FmIk0VXsPGtqWyAHqB3TY7*0Y_L><2m8;ehF)9Wjv$~pl@p5A_8+h3^7 z_#xNT1)$Ld0dSr#k+PunR(mr|m@>+C2lNZXuCUgkxQ>i@GbJwKq5~@F2%W1;tbMlg zXmQ_)FS<}}SGxMj#aS9q3FIQQ+OR#I*KB*4%*gP9J(EP^i!{in%(H5r-XG#i%ckyy zM|+W<+i{@o==f2FCd5HmR!aPe34UBV6mto{W% zNU(}Vs&ynZ{Q;|n)JtLb54!sY;w}Xy60EKoG{{MwD_|T`jaj_QoC&o zjuJw=GB9a~qpArdbufcDnREQ|iZ!rsZ~)w+c}#Rfa*q>kRuPoH)n*2&1o`4anQ)I2 zn2%}&>V!kTaT6^&jz)3L;PTC(H8a8 z$(g4y?NQTy)XCM-I%hvVtyjC{4^53kxLdpK_vrLxJyET_Y5A)F+d6LT{^ag&HxfO) zR-5C`%jpZG-<=a4osQmo|nLC3nY5C<>|Y;OwS&4?+H6&hBUGv2Zop@>Bbv_v;w zO3>8TId+_ADV+Fj!+pcxH`x@t*GYTyIK@y5Kp<%L*sS9~qJI-$>lr|gT&^8Ep0o9r?IDzb?;KBacBNT{^ZKQ_N ze2^I_lW*W1*4B;HQf6j+%TkS^cZ|y|H&MTzHZ}C_SEfUmXBc2 z-_YJoWtW5jD9w0*nV8wVjW=FL!LKXzmEVFKenTikBwxMQ*d)&Ch>CxCo8= zY2Wo_9>hPlh(B6yqL5zev;R!{aiuC6rg3ImJ5}j+#aU`A$d4x6|*pn)joP zt|TVSg45ikXpZpncmhN3ot8wU@U5^=#`5q^ z)3rT2=ar^?()uog>g%cym!a2_msIC-iD*iUW-004cp*nn0(|;TaB^CED+X$^WE;%Y_laozh9VySAd4J4X+r{zuDJ6tVM)Th6J<=*7OIOt^kdv z?Sg$Fjk5p6zAm2?FlodG^6^EH3-Zk3dT-?I{%bDaDc2aaBRJ;now-NI-@+GIg?mm;l1H1{MtOx zQdDX{M?i^1-AODZo36Y`RegneP=dP*9}=YCEqjcc5#PqXxM{NQ>LEGmKrF+w5}jM_ zsu&{8Y|+yxL)u7SsYw>9R%(b9N-sNqk_QCHfo@Tjf6W6RTIzSocV`J}XqkM}_O(+t z1XQt5SW|PH+HpxUe0jnvO#?#Ej8O0eN-FXTbvSTQN~|(B3gxi?oT)iE-6E={xSTV2 zk=XPxxA1A~vkpH0(t8_e?8ZITEh3~3(83Cfwl0=8L6dIEi7OD&Q6xkOzg4nYsG$(R zc*H@YS<)~Kd5tcaW3z1Ex-UGoPis4TfG z5;DZMCx}-K;9lvWzx zosx0%9%dUZyHkCi8(%tiKe9J4$o0aekg*wR^Ng;^jm~tiAi55Y62F($Y!h)|d#?<0iJxTILumix68d>MmaUl-yU zsk{G^V*Nv;tNULQs%wh%N0MKzx>PP#W4+ggz{P+r!utX=XW`;B6RW8-j2UM`=KSNyirpZMU#jm<=X&;Gn zT(Ge;!a_k*2P6UhtJ*I0AaWe#+EKhhq`0f*YpKFvE}0_vNh z($O$~aQNLh^d?9@Cddsh*TZ~k-Pua_i$>PH5BSv&o|~0TTR>@3w<)tC%Fb7%Y2Tu` zRgeYslv-c~koPSe*rZt8B73i!<;PJ4Xav9DZA)d9gnNP<`7VL_QePlmFVc^?uy8Nc z#N~_nE5QFWi2n-bKP8~^ukm~NQT|~LUzHNXheMVLGUrd1vK^^hu7o^Nxm-NU4d}%s zU`{&j>KV{ZM5!9%;8RDKHKsxLiYY{Aays&CpV~H%oPX`RDL?J>pzy{(|1`&m#c@OO zNcc3-@%$HqysijCs^h7qh^(%@S!^w%<&><6@C6Q6i*=H>VB#akk4NRr=M5z~3-?Y_ zen(AFj+iEFecfXEkllE_BCauzJl)S#TZ=NG|db@~iq|bss z&gdt4!BWJ5lFsJXtLVaKJBrk&DYEVe0&J}19PU**MWt(V>e>S^c~(99A<1$1UX0}H zAqp4cI;VR#3*A?A?tBx5;eyOolAMGJL6S#_!bwab-?8OG7X50o?rG^lAL?78$KoxB;5;S1OJB_|WX-_;I_mPT&{bq0sC=Tqxfg zeB1AX{DB}uNhaN`uqNaZc9}nD?CC8perDU?)%F#Isks39KoOmrP0AKe67=GLP=+)$;K4x>_akr-AkC;wrp{M#P+d&57U^2(@3j=o$OFMnhHJoNlZF3oo~3$-@} zKdny#%w|k`hchujzxhCLO-PJOIN~sgRlP=Ol%rRJv5vKQ%{B67QDS9v98~O*+xucz zElq3HG?z46)dEr%z^We4fUn&w18ALoOD|-1A6Sy8HXP+l3&+rCTvQy2$ zcm!5k8?yc6?lKXglNW`Wqxz}UJ!tF8{uJ)>18?fNyj4=y$X88zYp&kYjJr*$9|xkh z>-C?l-*yIwBbVMsIb9xN+6VxtqFQ3W?pI`Pq*I@s2flHU28&8A1O z4h=&bo+(sP|LH?+`21vAK!|@SY+6J|0^MgjlB7LK-40s?v`YTsf!-`3`IZ>t#Fw~F zd5UN3H+w0}*kW5ZRkzQGv@8>rybML|M-+$~JnoZx58~uip>{tY>t&R%7k>x-vj1KCh8A>qk5)it=_VnqDkq$^xCUI^^y(dM%)s!IJs;Y+)qto~ zkP~@shW-3||lP53wDK z(ZgQsQcjX4cDru-D|pk)h7M$k+eMHrhyV(mE)z|0uD4e+goV4AI5s|-=i z>Jw=Ru`=R`qRPnaEMwL##?o<(8njI2vv+y$-fTD+uDk|I2;-WWl6Z8#EX+u{#B#e2 zol2e#{;(~5`{M}*y<8{`e~o1q7rjow?>XnXlY0!ulIBbpi^uY>cC$YNK3kmwsXP~> z7D69CgFR(`8z4J}iGCzZ60SP+s3opF>P@a?!4HH3ia2BVD7|y2Xm~Tp<&XuYfzuZK zOjEl0o9uD|X!3{d$*fUbLa)w`yS3k1D%<+?CW(XseorZFpgD`VwW zEpy#2)!IP!ttx%58Yi1l82{*(TG-2#G+gFx#d`S5d2kl{?{bmW2t$sCSw969Ac9-E z-^!wFYv^KaUhp2}h}xRGddTx?5wMhG;5bhRFBDsec(u7Mmp9TU%&z+W;|+uJ;xecn z6CVvT?s#E*96`CgF(yVIe>gV)4;p(*Aa5B5-#{VeMa5(i;#i!wx5!l9HSzdI#{D5D ze`?D0pRVdoAHvoD&wRgvf`83$Ud3Fe@IU%!R~>|ZH|7vs=|2x$;D@6bdh7oR^UR#ze!XE~{~SVK%JJ=Xl=m+U>EP&MdyZwZ=%A(7UcAgEe{4 zjiLmnk2n^c#!c^7&lL)6=#1;uCkT%i0@3UH*PJ1o3wt#R|k zU*o?6%Nu-S7gv*)&1g(n6<3uL@uM<5R_8%0C8}gA$XqM7zzpP>x{WxLXRm-OV8{*) zX7`*5CPuBBvn;PNqR@U*6KzrjYKTWj*K2wE-?RExJq2Xb4KdMytY}xK&pustu91zo zi_E>qWn>IeGK4A%`6GUO0$k-g!rA_hP6YC3d=B>I?9=6akSE=)Po(DY33*^qaft-IN_*rNzF&Bk3?agqJh*Z4~h1_5`ehIA(6Bn)*HQS4hl~5r` zS%TMMB&(g!e&tXHT(c|xk)pP~rqRIj==B8rS1hUd7n82Wcyz=j!pm)<#mq{d!+~NN| zn17uhH7Ye4C=dnqy&7D&d^6bSLs5tV?gM228}nW)2hi3hh%LEt?gt&#h88$}G5v=O zJj9fP!nbk|4qwX`_VrNJ5*Yi;iuqJbcyzXNgelqFilxS4FYn_oxbOX>wO^BhNrqf7 z4P_-uc;jdP_Trx6v3~mL)W!SM;p)__Pw0;EnP0gXQ=`#UaSNEgnBAoOI;kcptOH2w zSqtjwRzHSBek>ZZG801M6mS)6Duo&!Q z5$=YN#@f^oz60&b1UM-&bS;<6){Q*-YBg~V#+oe~k{2a7vc{NO6d8kM^{Wn~ACd4> zmu(6uV1aGQ&A^(!Jk+ak$twjI=*Hqg@`<;Y5&+-rn~5@>agX|e<0RYjJ>pLl(O90v zTl@|#_1j^6IbqR_UmDk+k5=t?a9WzT)*usWQ?9pIQ&DPiwy@tV?5DwBOVQ3oXj%Pz z???Fy_ciBplGNd+69S>*;6ttU7Z1+5EjlM^MwGRW zBY7MBX(vv#N_`H;^4=(o(3s+N1OOR2(K-&vC47K^;dGXe+dSHSX5!}4(aNZ;2fQMZ zQ}{-j8ne0Lw8;c+4w2D#<>t>`I|+LHT!GsUH_^OoZ<5WPNs0nwV67-|jXf5XC0%Z` z3E#4GH5mYeW((cIVdqA(S;*sL$}+sm>*M)I#K2!heRA^jh5v2qI-;9jKGRp_5G99v ze3ATPOf6}*&Rk`cLc=EM{cK;(?h|Blp#sYcKxS`z@~Ns&2K;;}-gW8AVEHPfRZ&ZF zn??aXqP@ypSauv<2|wry`#cbw3xwnzKR+f8Cf!>p-c}gt!x*lV6G(1zQ!7n98Av<~ zx<{t_s+ekJb3%%DMfsSKQA$#0K%@dpC^N8#%9sYNUWq;2v3Fn#TUf?VX7zgHt-Zig zgDdN$Z%PtqTGND;V1x<|XO-?SCD8|rrb27PXBjc{Kl$X=1YmKS(W1foeAgDYAH}%NN(Y+sZo>a7BAIA1Nb^NVq+v&wE0LKIO z;*|NR-?qz@GLFlc!e>5rJpCMhAY!*39h+LYhf(zGu6y}#OeP~p3+G(t&6iMSgEc5I zQ?Ff3f%1*%u-XHYlZBVOXU8EEnv+L+Bg;tx!K6uV>#N@neR+BoK0=Pqo30iqmSpy` zhMk{Rlb_MGexi&K118LHRG;T4^qd{7#v(L8bVai_K(vt|xFFiFI=8o`z@j?1O0AvKZvEam9V#3>&xu)2*um@oRD^taP{_w%L`6nVvq z6b18YC%+1X0IF@;*Jw$FH##GW(iVUE;_?vRWBH7r&uuJ*ThkQ-GAYzADD=u@W6U|9 zd<~L)|2n@ZP0G|u{0CWb4xv{IK7Nd6I*xxeCZTk-XA-`sA=V864bE}dY^Y(9?EYjY z5w#gRHHoIdI!>UuQM4lE)6ft}!%Pnm;)3XIAq&<3&K>@hN0PLt*bY&V^_GPiegG7? zji0`R5)FQeQ3_LPJSQ!f4w1@zhWQ5azy}5!d346F(+JeI1`?>^E13h?>}~~MQwQjy zlgwJIzBWE@B~Z&~NMys;%0S`mlSop?*g^+d&J{-(u`Zbi zCSVf0i0VfIstZ`G4GXb0#V+pzt$u2x_-9D|Ji%rUash+mvUjVkU{0m-Y2#dR5&@(X zk261+eIe$19C0E>W0WV+=t^Sx2+tirBH=1LuS6$UDqo_fX{y|{FqGQ+NO&VXAT`I} z)I?~-cck{EW2R}Ge=#S;xD!dR2tF>Oor&){*baCo`3bE>emKJ$qN-1_H3aDnw0-c! zx8xNNAOC@iyl?W$U=AS*Db*Zd%rp+T{aUd=WRK>U%rn>>{-i}ay)>Pbyo9cDQ~LMM z@!i7$C|$>U_G_~@+m6W?6+>y+fj~4CTB{euw%AonqSyNa^{tMxnwjPX2sn7JSF z$(An#5JeTf`$#)gHQ6Dfub$^5Lbs61!?b8|*Rna#8iArU>KjSJcl;%+;Z&a=KYCqP zO$ZHf(TxO;J@~ml9?BlRa&pTqqtk0{xLvvI=6E13+3kG z6^gcGjjMM_AO`B%J0&&%-=Sr-)iuMgWvVJxf*)HeLdQm0KShQo03wIoj^g^0ITYbx zkHws-nZsYMOaWbPsbI#bCPH7m*bC3zdnjqLSUhHe0Po`)QL+(DMNRt# zVuZ(hRc54BqIq$jZ2pKv5s_Bpcd7mv4vYxr?gf;yIELw{1ocv*y(Y*j}uVqA%oqCm-Jz zqILLZTio8YVXhwJV=@T2MO`@HL^H^wtFJ4?`6jW4_6-YrPY!Lu@dLF?29bQy@jTD| zVobt36$aKs$Jgv4#Gmn*1AV_!?hYp<7v`)*3mO15UY}Uryc4a-nm^z;cYQL8Di#`@OvfuYc!%v2-|Pe_`st|uh_R>CEO~-8#pVpX+Qv7P!`<#) zwhPH^z4DOFVtPq|2EpA@KMQJ< zQHfU7L$^!A5O%R!tl+V~gN^sG{xNpRW~H+31GnQtltW$ZdUJbI+VI-S;=Tsafz?F_ zJolKYkn@Z%W3%bXG zc^IJ(7Pl^gLQdkZaxTMyyN&4i=&Zs~jpU_IXL&_&?+4prktA8kxcXqR%GvZ2)uFL? zyxt#MK%8VZ47S}LFo#5;Hb(O7kK3uss5Da3*a^{E7qa*;K4kL_Y=(p>CRnkG^cfKf z>IcgieFF_KcxxM#@63=s5 zSFG4oS5&R+0;Kf=Uj3(^d@e>Jdl~DcujD3L+a(da6mju zHP920g|!;d8=$nr+3gmt8jte5lMnUgk(>7}9LH^rF{ zVGlYNII!p(NLx%gc&_@W+RE6AjPw4ju-U@%d*3?SeyCea+q=>-E5DfA%){DTDoHo! zdiE&YKo8F8;d!(|m`*kTI{3IUf%uRKp}Y4t0txpz{z8q7yBL5rtg9(&h}s7KnpPGF zaIC&M>G;GF)S~G4s>}$1Ec!$BTe_GfZ)~mwrLQodC7B@C8~TnGzeWcU;F`+MoWF+% z2_jauKrSofi~x5Q^DeM50}oV()!~x7qh*R +#include +#include +#include + +#include "AMBEFileReader.h" +#include "AMBEData.h" + +namespace AMBEFileReaderTests +{ + class AMBEFileReader_lookup : public ::testing::Test { + + }; + + TEST_F(AMBEFileReader_lookup, nonExistentFiles) + { + CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file"); + bool res = reader.read(); + EXPECT_FALSE(res) << "read shall return false on non existent files"; + + std::vector data; + res = reader.lookup("0", data); + EXPECT_FALSE(res) << "read shall return false on non existent files"; + + for(auto d : data) { + delete d; + } + } + + TEST_F(AMBEFileReader_lookup, onlyIndexFileExists) + { + std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + CAMBEFileReader reader(indexFile, "/this/file/does/not/exist"); + bool res = reader.read(); + EXPECT_FALSE(res) << "read shall return false on non existent file"; + + std::vector data; + res = reader.lookup("0", data); + EXPECT_FALSE(res) << "read shall return false on non existent file"; + + for(auto d : data) { + delete d; + } + } + + TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists) + { + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader("/this/file/does/not/exist", ambeFile); + bool res = reader.read(); + EXPECT_FALSE(res) << "read shall return false on non existent file"; + + std::vector data; + res = reader.lookup("0", data); + EXPECT_FALSE(res) << "read shall return false on non existent file"; + + for(auto d : data) { + delete d; + } + } + + TEST_F(AMBEFileReader_lookup, validId) + { + std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader(indexFile, ambeFile); + bool res = reader.read(); + EXPECT_TRUE(res) << "read shall return true on existent files"; + + std::vector data; + res = reader.lookup("0", data); + EXPECT_TRUE(res) << "read shall return true on existent files and valid Id"; + EXPECT_NE(data.size(), 0U) << "Vector shall contain data"; + + for(auto d : data) { + delete d; + } + } + + TEST_F(AMBEFileReader_lookup, invalidId) + { + std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader(indexFile, ambeFile); + bool res = reader.read(); + EXPECT_TRUE(res) << "read shall return true on existent files"; + + std::vector data; + res = reader.lookup("This Id does not exist", data); + EXPECT_FALSE(res) << "read shall return false on existent files and invalid Id"; + EXPECT_EQ(data.size(), 0U) << "Vector shall not contain data"; + + for(auto d : data) { + delete d; + } + } +} \ No newline at end of file diff --git a/Tests/AMBEFileReader/read.cpp b/Tests/AMBEFileReader/read.cpp new file mode 100644 index 0000000..99f7bf4 --- /dev/null +++ b/Tests/AMBEFileReader/read.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include + +#include "AMBEFileReader.h" + +namespace AMBEFileReaderTests +{ + class AMBEFileReader_read : public ::testing::Test { + + }; + + TEST_F(AMBEFileReader_read, nonExistentFiles) + { + CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file"); + bool res = reader.read(); + EXPECT_FALSE(res) << "read shall return false on non existent files"; + } + + TEST_F(AMBEFileReader_read, onlyIndexFileExists) + { + std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + CAMBEFileReader reader(indexFile, "/this/file/does/not/exist"); + bool res = reader.read(); + EXPECT_FALSE(res) << "read shall return false on non existent file"; + } + + TEST_F(AMBEFileReader_read, onlyAmbeFileExists) + { + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader("/this/file/does/not/exist", ambeFile); + bool res = reader.read(); + EXPECT_FALSE(res) << "read shall return false on non existent file"; + } + + TEST_F(AMBEFileReader_read, bothFileExist) + { + std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader(indexFile, ambeFile); + bool res = reader.read(); + EXPECT_TRUE(res) << "read shall return true on existent files"; + } +} \ No newline at end of file From c8ec37bd8595788a415c682b9eede01f1557036b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 26 Feb 2022 19:37:46 +0100 Subject: [PATCH 077/112] #20 using AmbeFilReader --- .vscode/tasks.json | 10 +- DGWTimeServer/TimeServerConfig.cpp | 3 +- DGWTimeServer/TimeServerDefs.h | 1 - DGWTimeServer/TimeServerThread.cpp | 324 ++++++++--------------------- DGWTimeServer/TimeServerThread.h | 65 ++---- 5 files changed, 107 insertions(+), 296 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 995a9f1..0545c42 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -65,7 +65,10 @@ "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -91,10 +94,7 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] } ] diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index d2c4fab..0a2f170 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -107,10 +107,9 @@ bool CTimeServerConfig::loadTimeServer(const CConfig & cfg) ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret; std::string format; - ret = cfg.getValue("timeserver", "format", format, "voiceandtext", {"voice", "text", "voiceandtext"}) && ret; + ret = cfg.getValue("timeserver", "format", format, "voice", {"voice", "text" }) && ret; if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME; else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME; - else if(format == "voiceandtext") m_timeServer.format = FORMAT_VOICE_ALL; std::string lang; ret = cfg.getValue("timeserver", "language", lang, "english_uk_1", {"english_uk_1", "english_uk_2", "english_us_1", "english_us_2", "deutsch_1", "deutsch_2", "francais", "nederlands", "svenska", "espanol", "norsk", "portugues"}) && ret;; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h index 8917675..b6910c2 100644 --- a/DGWTimeServer/TimeServerDefs.h +++ b/DGWTimeServer/TimeServerDefs.h @@ -44,6 +44,5 @@ enum INTERVAL { enum FORMAT { FORMAT_VOICE_TIME, - FORMAT_VOICE_ALL, FORMAT_TEXT_TIME }; diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 696099f..be8d20e 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -46,32 +46,23 @@ m_addressStr(), m_language(LANG_ENGLISH_UK_1), m_format(FORMAT_VOICE_TIME), m_interval(INTERVAL_15MINS), -m_ambe(NULL), -m_ambeLength(0U), -m_index(), -m_seqNo(0U), -m_in(0U), -m_encoder(), -m_data(NULL), +m_data(), m_killed(false), -m_dataPath("") +m_dataPath(""), +m_ambeFileReader(nullptr) { CHeaderData::initialise(); - m_address.s_addr = INADDR_NONE; - - m_data = new CAMBEData*[MAX_FRAMES]; - - for (unsigned int i = 0U; i < MAX_FRAMES; i++) - m_data[i] = nullptr; + m_address.s_addr = INADDR_NONE; } CTimeServerThread::~CTimeServerThread() { - for (auto it = m_index.begin(); it != m_index.end(); it++) - delete it->second; + for(auto d : m_data) + delete d; - delete[] m_ambe; - delete[] m_data; + m_data.clear(); + + delete[] m_ambeFileReader; } void * CTimeServerThread::Entry() @@ -158,8 +149,8 @@ bool CTimeServerThread::setGateway(const std::string& callsign, const std::strin m_callsign.push_back(' '); m_addressStr.assign(address); - m_address = CUDPReaderWriter::lookup(address); m_dataPath.assign(dataPath); + m_address = CUDPReaderWriter::lookup(address); return true; } @@ -988,197 +979,103 @@ bool CTimeServerThread::loadAMBE() break; } - bool ret = readAMBE(m_dataPath, ambeFileName); - if (!ret) { - delete[] m_ambe; - m_ambe = NULL; - return false; - } + m_ambeFileReader = new CAMBEFileReader(m_dataPath + "/" + indxFileName, m_dataPath + "/" + ambeFileName); + bool ret = m_ambeFileReader->read(); - ret = readIndex(m_dataPath, indxFileName); if (!ret) { - delete[] m_ambe; - m_ambe = NULL; + delete[] m_ambeFileReader; + m_ambeFileReader = nullptr; return false; } return true; } -bool CTimeServerThread::readAMBE(const std::string& dir, const std::string& name) +void CTimeServerThread::buildAudio(const std::vector& words, CSlowDataEncoder& slowDataEncoder) { - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; - } - } - unsigned int fsize = sbuf.st_size; - - FILE *file = fopen(fileName.c_str(), "rb"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; - } - - CLog::logInfo("Reading %s\n", fileName.c_str()); + unsigned int seqNo = 0U; - unsigned char buffer[VOICE_FRAME_LENGTH_BYTES]; + m_data.clear(); - size_t n = fread(buffer, sizeof(unsigned char), 4, file); - if (n != 4) { - CLog::logError("Unable to read the header from %s\n", fileName.c_str()); - fclose(file); - return false; - } + if(words.size() == 0U || m_ambeFileReader == nullptr) + CLog::logWarning("No words, falling back to text only"); - if (memcmp(buffer, "AMBE", 4)) { - CLog::logError("Invalid header from %s\n", fileName.c_str()); - fclose(file); - return false; - } + if(m_format == FORMAT_VOICE_TIME && words.size() != 0U) { + // Build the audio + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); - // Length of the file minus the header - unsigned int length = fsize - 4U; + for (unsigned int i = 0U; i < words.size(); i++) + m_ambeFileReader->lookup(words.at(i), m_data); - // Hold the file data plus silence at the end - m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES]; - m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES; + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); - // Add silence to the beginning of the buffer - unsigned char* p = m_ambe; - for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES) - memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + for(unsigned int i = 0U; i < m_data.size(); i++) { + m_data[i]->setDestination(m_address, G2_DV_PORT); + m_data[i]->setSeq(seqNo); - n = fread(p, 1, length, file); - if (n != length) { - CLog::logError("Unable to read the AMBE data from %s\n", fileName.c_str()); - fclose(file); - delete[] m_ambe; - m_ambe = NULL; - return false; - } + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + m_data[i]->getData(buffer, DV_FRAME_LENGTH_BYTES); - fclose(file); + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowDataEncoder.sync(); + } else { + slowDataEncoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); + } - return true; -} + m_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES); -bool CTimeServerThread::readIndex(const std::string& dir, const std::string& name) -{ - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; + seqNo++; + if(seqNo >= 21U) seqNo = 0U; } } + else { + for (unsigned int i = 0U; i < 21U; i++, seqNo++) { + CAMBEData* dataOut = new CAMBEData; + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(i); - FILE *file = fopen(fileName.c_str(), "r"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; - } - - // Add a silence entry at the beginning - m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); - - CLog::logInfo("Reading %s\n", fileName.c_str()); - - char line[128]; - while (fgets(line, 128, file)) { - - if (strlen(line) && '#'!=line[0]) { - const std::string space(" \t\r\n"); - std::string name(strtok(line, space.c_str())); - std::string strt(strtok(NULL, space.c_str())); - std::string leng(strtok(NULL, space.c_str())); - - if (name.size() && strt.size() && leng.size()) { - unsigned long start = std::stoul(strt); - unsigned long length = std::stoul(leng); + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); - if (start >= m_ambeLength || (start + length) >= m_ambeLength) - CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length); - else - m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length); + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (i == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowDataEncoder.sync(); + } else { + slowDataEncoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); } - } - } - fclose(file); - - return true; -} - -bool CTimeServerThread::lookup(const std::string &id) -{ - CIndexRecord* info = m_index[id]; - if (info == NULL) { - // wxLogError(("Cannot find the AMBE index for *%s*"), id.c_str()); - return false; - } - - unsigned int start = info->getStart(); - unsigned int length = info->getLength(); - - for (unsigned int i = 0U; i < length; i++) { - unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; - - CAMBEData* dataOut = new CAMBEData; - dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(m_seqNo); - - unsigned char buffer[DV_FRAME_LENGTH_BYTES]; - ::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); - - // Insert sync bytes when the sequence number is zero, slow data otherwise - if (m_seqNo == 0U) { - ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - m_encoder.sync(); + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - } else { - m_encoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); + m_data.push_back(dataOut); } - - dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - - m_seqNo++; - if (m_seqNo == 21U) - m_seqNo = 0U; - - m_data[m_in] = dataOut; - m_in++; } - return true; -} + if(seqNo >= 21U) { + seqNo = 0U; + } -void CTimeServerThread::end() -{ CAMBEData* dataOut = new CAMBEData; dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(m_seqNo); + dataOut->setSeq(seqNo); dataOut->setEnd(true); - m_data[m_in] = dataOut; - m_in++; + m_data.push_back(dataOut); } bool CTimeServerThread::send(const std::vector &words, unsigned int hour, unsigned int min) { + CSlowDataEncoder encoder; + CHeaderData header; header.setMyCall1(m_callsign); header.setRptCall1(m_callsignG); @@ -1241,78 +1138,26 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int break; } - m_encoder.setHeaderData(header); - m_encoder.setTextData(slowData); + encoder.setHeaderData(header); + encoder.setTextData(slowData); - m_in = 0U; + buildAudio(words, encoder); - if (m_format != FORMAT_TEXT_TIME) { - std::string text = words.at(0U); - for (unsigned int i = 1U; i < words.size(); i++) { - text.push_back(' '); - text += words.at(i); - } + if (m_data.size() == 0U) { + CLog::logWarning(("Not sending, no audio files loaded")); + return false; + } + if(m_format == FORMAT_VOICE_TIME) { + std::string text = boost::algorithm::join(words, " "); boost::replace_all(text, "_", " "); - CLog::logInfo(("Sending voice \"%s\", sending text \"%s\""), text.c_str(), slowData.c_str()); - - m_seqNo = 0U; - - // Build the audio - lookup((" ")); - lookup((" ")); - lookup((" ")); - lookup((" ")); - - for (unsigned int i = 0U; i < words.size(); i++) - lookup(words.at(i)); - - lookup((" ")); - lookup((" ")); - lookup((" ")); - lookup((" ")); - - end(); - } else { - CLog::logInfo(("Sending text \"%s\""), slowData.c_str()); - - for (unsigned int i = 0U; i < 21U; i++) { - CAMBEData* dataOut = new CAMBEData; - dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(i); - - unsigned char buffer[DV_FRAME_LENGTH_BYTES]; - ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); - - // Insert sync bytes when the sequence number is zero, slow data otherwise - if (i == 0U) { - ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - m_encoder.sync(); - } else { - m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); - } - - dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - - m_data[m_in] = dataOut; - m_in++; - } - - CAMBEData* dataOut = new CAMBEData; - dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); - dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(0U); - dataOut->setEnd(true); - - m_data[m_in] = dataOut; - m_in++; + CLog::logInfo("Sending voice \"%s\", sending text \"%s\"", text.c_str(), slowData.c_str()); } - - if (m_in == 0U) { - CLog::logWarning(("Not sending, no audio files loaded")); - return false; + else { + CLog::logInfo("Sending text \"%s\"", slowData.c_str()); } + // Build id and socket lists std::vector ids; std::vector sockets; for(auto rpt : m_repeaters) { @@ -1321,6 +1166,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int ids.push_back(CHeaderData::createId()); } + // open them all bool allOpen = std::all_of(sockets.begin(), sockets.end(), [](CUDPReaderWriter* s) { return s->open(); }); if(allOpen) { //send headers @@ -1332,7 +1178,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int Sleep(5); } - // send audio + // send audio bool loop = true; unsigned int out = 0U; auto start = std::chrono::high_resolution_clock::now(); @@ -1353,7 +1199,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int m_data[out] = nullptr; out++; - if (m_in == out) { + if (out >= m_data.size()) { loop = false; break; } @@ -1361,6 +1207,8 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int } } + m_data.clear(); + for(auto socket : sockets) { socket->close(); delete socket; diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index 3abe883..36bb9c7 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -27,37 +27,7 @@ #include "HeaderData.h" #include "AMBEData.h" #include "Thread.h" - -class CIndexRecord { -public: - CIndexRecord(const std::string& name, unsigned int start, unsigned int length) : - m_name(name), - m_start(start), - m_length(length) - { - } - - std::string getName() const - { - return m_name; - } - - unsigned int getStart() const - { - return m_start; - } - - unsigned int getLength() const - { - return m_length; - } - -private: - std::string m_name; - unsigned int m_start; - unsigned int m_length; -}; - +#include "AMBEFileReader.h" class CTimeServerThread : public CThread { @@ -72,23 +42,19 @@ public: void kill(); private: - std::string m_callsign; + std::string m_callsign; std::vector m_repeaters; - std::string m_callsignG; - in_addr m_address; - std::string m_addressStr; - LANGUAGE m_language; - FORMAT m_format; - INTERVAL m_interval; - unsigned char* m_ambe; - unsigned int m_ambeLength; - std::unordered_map m_index; - unsigned int m_seqNo; - unsigned int m_in; - CSlowDataEncoder m_encoder; - CAMBEData** m_data; - bool m_killed; - std::string m_dataPath; + std::string m_callsignG; + in_addr m_address; + std::string m_addressStr; + LANGUAGE m_language; + FORMAT m_format; + INTERVAL m_interval; + CSlowDataEncoder m_encoder; + std::vector m_data; + bool m_killed; + std::string m_dataPath; + CAMBEFileReader * m_ambeFileReader; void sendTime(unsigned int hour, unsigned int min); @@ -110,9 +76,8 @@ private: bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); bool loadAMBE(); - bool readAMBE(const std::string& dir, const std::string& name); - bool readIndex(const std::string& dir, const std::string& name); bool lookup(const std::string& id); - void end(); + + void buildAudio(const std::vector& words, CSlowDataEncoder& slowDataEncoder); }; From e05493633e561348531039d2dc089fedcb63ec3f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Thu, 10 Mar 2022 20:40:24 +0100 Subject: [PATCH 078/112] #20 add some comment --- DGWTimeServer/TimeServerThread.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index be8d20e..e3f90ff 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -1015,6 +1015,8 @@ void CTimeServerThread::buildAudio(const std::vector& words, CSlowD m_ambeFileReader->lookup(" ", m_data); m_ambeFileReader->lookup(" ", m_data); + + // add the slow data for(unsigned int i = 0U; i < m_data.size(); i++) { m_data[i]->setDestination(m_address, G2_DV_PORT); m_data[i]->setSeq(seqNo); From 0e940f117e0a66e91213519917960f684719bf9e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 11 Mar 2022 17:42:58 +0100 Subject: [PATCH 079/112] #20 audio Unit now also using ambe fiel reader --- .vscode/tasks.json | 10 +- Common/AudioUnit.cpp | 368 +++++++++++++------------------------------ Common/AudioUnit.h | 47 +----- 3 files changed, 118 insertions(+), 307 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0545c42..587d251 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,7 +26,10 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -65,10 +68,7 @@ "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/Common/AudioUnit.cpp b/Common/AudioUnit.cpp index 66684a0..997acc9 100644 --- a/Common/AudioUnit.cpp +++ b/Common/AudioUnit.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "DStarDefines.h" #include "HeaderData.h" @@ -30,9 +31,8 @@ #include "Utils.h" #include "Log.h" -unsigned char* CAudioUnit::m_ambe = NULL; -unsigned int CAudioUnit::m_ambeLength = 0U; -std::map CAudioUnit::m_index; + +CAMBEFileReader * CAudioUnit::m_ambeFilereader = nullptr; TEXT_LANG CAudioUnit::m_language = TL_ENGLISH_UK; @@ -46,6 +46,11 @@ void CAudioUnit::initialise() void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language) { + if(m_ambeFilereader != nullptr) { + delete m_ambeFilereader; + m_ambeFilereader = nullptr; + } + m_language = language; std::string ambeFileName; @@ -94,32 +99,22 @@ void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language) break; } - bool ret = readAMBE(dir, ambeFileName); - if (!ret) { - delete[] m_ambe; - m_ambe = NULL; - return; - } - - ret = readIndex(dir, indxFileName); - if (!ret) { - delete[] m_ambe; - m_ambe = NULL; + m_ambeFilereader = new CAMBEFileReader(dir + "/" + indxFileName, dir + "/" + ambeFileName); + bool ret = m_ambeFilereader->read(); + if(!ret) { + delete m_ambeFilereader; + m_ambeFilereader = nullptr; } } void CAudioUnit::finalise() { - for (std::map::iterator it = m_index.begin(); it != m_index.end(); ++it) - delete it->second; - - delete[] m_ambe; + delete m_ambeFilereader; } CAudioUnit::CAudioUnit(IRepeaterCallback* handler, const std::string& callsign) : m_handler(handler), m_callsign(callsign), -m_encoder(), m_status(AS_IDLE), m_linkStatus(LS_NONE), m_tempLinkStatus(LS_NONE), @@ -129,28 +124,24 @@ m_reflector(), m_tempReflector(), m_hasTemporary(false), m_timer(1000U, REPLY_TIME), -m_data(NULL), -m_in(0U), -m_out(0U), -m_seqNo(0U) //, +m_data(), +m_out(0U) //m_time() { assert(handler != NULL); - - m_data = new CAMBEData*[MAX_FRAMES]; - - for (unsigned int i = 0U; i < MAX_FRAMES; i++) - m_data[i] = NULL; } CAudioUnit::~CAudioUnit() { - delete[] m_data; + for (auto item : m_data) { + delete item; + } + m_data.clear(); } void CAudioUnit::sendStatus() { - if (m_ambe == NULL) + if (m_ambeFilereader == nullptr) return; if (m_status != AS_IDLE) @@ -190,7 +181,6 @@ void CAudioUnit::clock(unsigned int ms) m_timer.stop(); m_out = 0U; - m_seqNo = 0U; m_status = AS_TRANSMIT; m_time = std::chrono::high_resolution_clock::now(); @@ -199,29 +189,21 @@ void CAudioUnit::clock(unsigned int ms) } if (m_status == AS_TRANSMIT) { - std::chrono::high_resolution_clock::time_point hrctp = std::chrono::high_resolution_clock::now(); - auto elapse = std::chrono::duration_cast(hrctp - m_time); - unsigned int needed = elapse.count() / DSTAR_FRAME_TIME_MS; + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_time).count(); + needed /= DSTAR_FRAME_TIME_MS; - while (m_out < needed) { + while (m_out < needed && m_out < m_data.size()) { CAMBEData* data = m_data[m_out]; - m_data[m_out] = NULL; m_out++; - - if (m_in == m_out) - data->setEnd(true); - + CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size()); m_handler->process(*data, DIR_INCOMING, AS_INFO); + } - delete data; - - if (m_in == m_out) { - m_in = 0U; - m_out = 0U; - m_status = AS_IDLE; - m_timer.stop(); - return; - } + if (m_out >= m_data.size()) { + m_out = 0U; + m_status = AS_IDLE; + m_timer.stop(); } return; @@ -230,63 +212,14 @@ void CAudioUnit::clock(unsigned int ms) void CAudioUnit::cancel() { - for (unsigned int i = 0U; i < MAX_FRAMES; i++) { - if (m_data[i] != NULL) { - delete m_data[i]; - m_data[i] = NULL; - } - } - + CLog::logTrace("Audio Unit Cancel"); m_status = AS_IDLE; m_out = 0U; - m_in = 0U; m_timer.stop(); } -bool CAudioUnit::lookup(unsigned int id, const std::string &name) -{ - CIndexRecord* info = m_index[name]; - if (info == NULL) { - // CLog::logError("Cannot find the AMBE index for *%s*", name.c_str()); - return false; - } - - unsigned int start = info->getStart(); - unsigned int length = info->getLength(); - - for (unsigned int i = 0U; i < length; i++) { - unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; - - CAMBEData* dataOut = new CAMBEData; - dataOut->setSeq(m_seqNo); - dataOut->setId(id); - - unsigned char buffer[DV_FRAME_LENGTH_BYTES]; - memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); - - // Insert sync bytes when the sequence number is zero, slow data otherwise - if (m_seqNo == 0U) { - memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - m_encoder.sync(); - } else { - m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); - } - - dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - - m_seqNo++; - if (m_seqNo == 21) - m_seqNo = 0; - - m_data[m_in] = dataOut; - m_in++; - } - - return true; -} - -void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector) +void CAudioUnit::spellReflector(const std::string &reflector) { unsigned int length = reflector.size(); @@ -294,11 +227,10 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector) std::string c = reflector.substr(i, 1); if (c.compare(" ")) - lookup(id, c); + m_ambeFilereader->lookup(c, m_data); } char c = reflector.at(length - 1); - if (c == ' ') return; @@ -306,197 +238,113 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector) cstr.push_back(c); if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) { - lookup(id, cstr); + m_ambeFilereader->lookup(cstr, m_data); return; } switch (c) { case 'A': - lookup(id, "alpha"); + m_ambeFilereader->lookup("alpha", m_data); break; case 'B': - lookup(id, "bravo"); + m_ambeFilereader->lookup("bravo", m_data); break; case 'C': - lookup(id, "charlie"); + m_ambeFilereader->lookup("charlie", m_data); break; case 'D': - lookup(id, "delta"); + m_ambeFilereader->lookup("delta", m_data); break; default: - lookup(id, cstr); + m_ambeFilereader->lookup(cstr, m_data); break; } } -bool CAudioUnit::readAMBE(const std::string& dir, const std::string& name) +void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text) { - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; - } - } - unsigned int fsize = sbuf.st_size; - - FILE *file = fopen(fileName.c_str(), "rb"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; - } - - CLog::logInfo("Reading %s\n", fileName.c_str()); - - unsigned char buffer[VOICE_FRAME_LENGTH_BYTES]; - - size_t n = fread(buffer, sizeof(unsigned char), 4, file); - if (n != 4) { - CLog::logError("Unable to read the header from %s\n", fileName.c_str()); - fclose(file); - return false; - } + CLog::logTrace("Audio Unit sendStatus"); - if (memcmp(buffer, "AMBE", 4)) { - CLog::logError("Invalid header from %s\n", fileName.c_str()); - fclose(file); - return false; + // do some clean up, delete old message + for (auto item : m_data) { + delete item; } + m_data.clear(); - // Length of the file minus the header - unsigned int length = fsize - 4U; - - // Hold the file data plus silence at the end - m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES]; - m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES; - - // Add silence to the beginning of the buffer - unsigned char* p = m_ambe; - for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES) - memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); - - n = fread(p, 1, length, file); - if (n != length) { - CLog::logError("Unable to read the AMBE data from %s\n", fileName.c_str()); - fclose(file); - delete[] m_ambe; - m_ambe = NULL; - return false; - } + // Create the message + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); - fclose(file); + bool found; - return true; -} - -bool CAudioUnit::readIndex(const std::string& dir, const std::string& name) -{ - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; - } - } - - FILE *file = fopen(fileName.c_str(), "r"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; + switch (status) { + case LS_NONE: + m_ambeFilereader->lookup("notlinked", m_data); + break; + case LS_LINKED_CCS: + case LS_LINKED_DCS: + case LS_LINKED_DPLUS: + case LS_LINKED_DEXTRA: + case LS_LINKED_LOOPBACK: + found = m_ambeFilereader->lookup("linkedto", m_data); + if (!found) { + m_ambeFilereader->lookup("linked", m_data); + m_ambeFilereader->lookup("2", m_data); + } + spellReflector(reflector); + break; + default: + found = m_ambeFilereader->lookup("linkingto", m_data); + if (!found) { + m_ambeFilereader->lookup("linking", m_data); + m_ambeFilereader->lookup("2", m_data); + } + spellReflector(reflector); + break; } - // Add a silence entry at the beginning - m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); - CLog::logInfo("Reading %s\n", fileName.c_str()); + unsigned int id = CHeaderData::createId(); + // RPT1 and RPT2 will be filled in later + CHeaderData header; + header.setMyCall1(m_callsign); + header.setMyCall2("INFO"); + header.setYourCall("CQCQCQ "); + header.setId(id); - char line[128]; - while (fgets(line, 128, file)) { + CSlowDataEncoder slowDataEncoder; + slowDataEncoder.setTextData(text); + unsigned int seqNo = 0U; - if (strlen(line) && '#'!=line[0]) { - const std::string space(" \t\r\n"); - std::string name(strtok(line, space.c_str())); - std::string strt(strtok(NULL, space.c_str())); - std::string leng(strtok(NULL, space.c_str())); + // add the slow data, id, seq num etc ... + for(unsigned int i = 0U; i < m_data.size(); i++) { + m_data[i]->setId(id); + m_data[i]->setSeq(seqNo); - if (name.size() && strt.size() && leng.size()) { - unsigned long start = std::stoul(strt); - unsigned long length = std::stoul(leng); + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + m_data[i]->getData(buffer, DV_FRAME_LENGTH_BYTES); - if (start >= m_ambeLength || (start + length) >= m_ambeLength) - CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length); - else - m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length); - } + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowDataEncoder.sync(); + } else { + slowDataEncoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); } - } - - fclose(file); - - return true; -} -void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text) -{ - m_encoder.setTextData(text); - - // Create the message - unsigned int id = CHeaderData::createId(); - - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); - - bool found; - - switch (status) { - case LS_NONE: - lookup(id, "notlinked"); - break; - case LS_LINKED_CCS: - case LS_LINKED_DCS: - case LS_LINKED_DPLUS: - case LS_LINKED_DEXTRA: - case LS_LINKED_LOOPBACK: - found = lookup(id, "linkedto"); - if (!found) { - lookup(id, "linked"); - lookup(id, "2"); - } - spellReflector(id, reflector); - break; - default: - found = lookup(id, "linkingto"); - if (!found) { - lookup(id, "linking"); - lookup(id, "2"); - } - spellReflector(id, reflector); - break; - } + m_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES); - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); + seqNo++; + if(seqNo >= 21U) seqNo = 0U; + } - // RPT1 and RPT2 will be filled in later - CHeaderData header; - header.setMyCall1(m_callsign); - header.setMyCall2("INFO"); - header.setYourCall("CQCQCQ "); - header.setId(id); + m_data[m_data.size() - 1]->setEnd(true); - m_handler->process(header, DIR_INCOMING, AS_INFO); + m_handler->process(header, DIR_INCOMING, AS_INFO); } diff --git a/Common/AudioUnit.h b/Common/AudioUnit.h index 6cdd862..7b27ba8 100644 --- a/Common/AudioUnit.h +++ b/Common/AudioUnit.h @@ -23,42 +23,14 @@ #include #include #include +#include #include "RepeaterCallback.h" #include "SlowDataEncoder.h" #include "AMBEData.h" #include "Timer.h" #include "Defs.h" - -class CIndexRecord { -public: - CIndexRecord(const std::string& name, unsigned int start, unsigned int length) : - m_name(name), - m_start(start), - m_length(length) - { - } - - std::string getName() const - { - return m_name; - } - - unsigned int getStart() const - { - return m_start; - } - - unsigned int getLength() const - { - return m_length; - } - -private: - std::string m_name; - unsigned int m_start; - unsigned int m_length; -}; +#include "AMBEFileReader.h" enum AUDIO_STATUS { AS_IDLE, @@ -87,14 +59,10 @@ public: static void finalise(); private: - static std::map m_index; - static unsigned char* m_ambe; - static unsigned int m_ambeLength; static TEXT_LANG m_language; IRepeaterCallback* m_handler; std::string m_callsign; - CSlowDataEncoder m_encoder; AUDIO_STATUS m_status; LINK_STATUS m_linkStatus; LINK_STATUS m_tempLinkStatus; @@ -104,17 +72,12 @@ private: std::string m_tempReflector; bool m_hasTemporary; CTimer m_timer; - CAMBEData** m_data; - unsigned int m_in; + std::vector m_data; + static CAMBEFileReader* m_ambeFilereader; unsigned int m_out; - unsigned int m_seqNo; std::chrono::high_resolution_clock::time_point m_time; - bool lookup(unsigned int id, const std::string& name); - void spellReflector(unsigned int id, const std::string& reflector); + void spellReflector(const std::string& reflector); void sendStatus(LINK_STATUS status, const std::string& reflector, const std::string& text); - - static bool readAMBE(const std::string& dir, const std::string& name); - static bool readIndex(const std::string& dir, const std::string& name); }; From 7864f33c3c847bbde7b559840059371b332219c8 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 11 Mar 2022 17:51:55 +0100 Subject: [PATCH 080/112] #20 reduce verbosity --- Common/AudioUnit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/AudioUnit.cpp b/Common/AudioUnit.cpp index 997acc9..db704a6 100644 --- a/Common/AudioUnit.cpp +++ b/Common/AudioUnit.cpp @@ -196,7 +196,7 @@ void CAudioUnit::clock(unsigned int ms) while (m_out < needed && m_out < m_data.size()) { CAMBEData* data = m_data[m_out]; m_out++; - CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size()); + // CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size()); m_handler->process(*data, DIR_INCOMING, AS_INFO); } From 45584655f86ff93844fc448133652aa70b141668 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 04:42:47 +0100 Subject: [PATCH 081/112] #20 remove sleep --- Common/AudioUnit.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Common/AudioUnit.cpp b/Common/AudioUnit.cpp index db704a6..0adeac4 100644 --- a/Common/AudioUnit.cpp +++ b/Common/AudioUnit.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include "DStarDefines.h" #include "HeaderData.h" @@ -189,7 +188,6 @@ void CAudioUnit::clock(unsigned int ms) } if (m_status == AS_TRANSMIT) { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_time).count(); needed /= DSTAR_FRAME_TIME_MS; From 7fad5bfbc33b321e30e1673aa79b7a2547c66fb9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 04:59:14 +0100 Subject: [PATCH 082/112] #20 Add Readme --- DGWTimeServer/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 DGWTimeServer/README.md diff --git a/DGWTimeServer/README.md b/DGWTimeServer/README.md new file mode 100644 index 0000000..1a48592 --- /dev/null +++ b/DGWTimeServer/README.md @@ -0,0 +1,11 @@ +Time Server for DStarGateway. Installs as a systemd service. + +If you did not change build settings, the configuration can be found in +``` +/usr/local/etc/dgwtimeserver.cfg +``` +After configuring enable and start the dgwtimeserver systemd service using : +``` +sudo systemctl enable dgwtimeserver +sudo systemctl start dgwtimeserver +``` \ No newline at end of file From ee4270940dfb00dbce43b99d3358c2518a409d55 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 07:44:29 +0100 Subject: [PATCH 083/112] #20 add soem debug info --- .vscode/tasks.json | 10 +++++----- Tests/AMBEFileReader/read.cpp | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 587d251..995a9f1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,10 +26,7 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -94,7 +91,10 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] } ] diff --git a/Tests/AMBEFileReader/read.cpp b/Tests/AMBEFileReader/read.cpp index 99f7bf4..a60b8e6 100644 --- a/Tests/AMBEFileReader/read.cpp +++ b/Tests/AMBEFileReader/read.cpp @@ -21,6 +21,7 @@ #include #include "AMBEFileReader.h" +#include "Log.h" namespace AMBEFileReaderTests { @@ -55,6 +56,10 @@ namespace AMBEFileReaderTests { std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + + CLog::logInfo("Reading file: %s", indexFile.c_str()); + CLog::logInfo("Reading file: %s", ambeFile.c_str()); + CAMBEFileReader reader(indexFile, ambeFile); bool res = reader.read(); EXPECT_TRUE(res) << "read shall return true on existent files"; From d5b78104c633d78d2c174d4ba1bca9bd89be19e8 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 07:53:52 +0100 Subject: [PATCH 084/112] #20 fix broken test on Circle CI --- .vscode/launch.json | 2 +- Tests/AMBEFileReader/lookup.cpp | 12 ++++++------ Tests/AMBEFileReader/read.cpp | 11 ++++------- Tests/Makefile | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f15168f..55284ba 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -135,7 +135,7 @@ "program": "${workspaceFolder}/Tests/dstargateway_tests", "args": [ ], "stopAtEntry": false, - "cwd": "${workspaceFolder}", + "cwd": "${workspaceFolder}/Tests/", "environment": [], "externalConsole": false, "MIMode": "gdb", diff --git a/Tests/AMBEFileReader/lookup.cpp b/Tests/AMBEFileReader/lookup.cpp index f239eae..9f75475 100644 --- a/Tests/AMBEFileReader/lookup.cpp +++ b/Tests/AMBEFileReader/lookup.cpp @@ -47,7 +47,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, onlyIndexFileExists) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx"; CAMBEFileReader reader(indexFile, "/this/file/does/not/exist"); bool res = reader.read(); EXPECT_FALSE(res) << "read shall return false on non existent file"; @@ -63,7 +63,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists) { - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe"; CAMBEFileReader reader("/this/file/does/not/exist", ambeFile); bool res = reader.read(); EXPECT_FALSE(res) << "read shall return false on non existent file"; @@ -79,8 +79,8 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, validId) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe"; CAMBEFileReader reader(indexFile, ambeFile); bool res = reader.read(); EXPECT_TRUE(res) << "read shall return true on existent files"; @@ -97,8 +97,8 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, invalidId) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe"; CAMBEFileReader reader(indexFile, ambeFile); bool res = reader.read(); EXPECT_TRUE(res) << "read shall return true on existent files"; diff --git a/Tests/AMBEFileReader/read.cpp b/Tests/AMBEFileReader/read.cpp index a60b8e6..f7b1753 100644 --- a/Tests/AMBEFileReader/read.cpp +++ b/Tests/AMBEFileReader/read.cpp @@ -38,7 +38,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_read, onlyIndexFileExists) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx"; CAMBEFileReader reader(indexFile, "/this/file/does/not/exist"); bool res = reader.read(); EXPECT_FALSE(res) << "read shall return false on non existent file"; @@ -46,7 +46,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_read, onlyAmbeFileExists) { - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe"; CAMBEFileReader reader("/this/file/does/not/exist", ambeFile); bool res = reader.read(); EXPECT_FALSE(res) << "read shall return false on non existent file"; @@ -54,11 +54,8 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_read, bothFileExist) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; - - CLog::logInfo("Reading file: %s", indexFile.c_str()); - CLog::logInfo("Reading file: %s", ambeFile.c_str()); + std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe"; CAMBEFileReader reader(indexFile, ambeFile); bool res = reader.read(); diff --git a/Tests/Makefile b/Tests/Makefile index f338acc..d3b30bb 100644 --- a/Tests/Makefile +++ b/Tests/Makefile @@ -10,7 +10,7 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB -include $(DEPS) -.PHONY run-tests: dstargateway_tests +.PHONY run-tests: dstargateway_tests ./dstargateway_tests .PHONY clean : From c2a940bee7af88b36a7f0b1985cf7f52815371e0 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Thu, 24 Mar 2022 18:57:12 +0100 Subject: [PATCH 085/112] #23 add possibility to override text --- .vscode/launch.json | 2 +- .vscode/tasks.json | 10 ++--- DGWVoiceTransmit/VoiceTransmit.cpp | 72 +++++++++++++++++++++--------- DGWVoiceTransmit/VoiceTransmit.h | 5 ++- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 55284ba..94b71b0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -109,7 +109,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DGWVoiceTransmit/dgwvoicetransmit", - "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool"], + "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool", "-text", "www.F5KAV.fr"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 995a9f1..df6906d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -78,7 +78,10 @@ "USE_GPSD=1", "DGWVoiceTransmit/dgwvoicetransmit" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -91,10 +94,7 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] } ] diff --git a/DGWVoiceTransmit/VoiceTransmit.cpp b/DGWVoiceTransmit/VoiceTransmit.cpp index d173241..67b76ca 100644 --- a/DGWVoiceTransmit/VoiceTransmit.cpp +++ b/DGWVoiceTransmit/VoiceTransmit.cpp @@ -22,16 +22,18 @@ #include #include +#include "ProgramArgs.h" #include "DStarDefines.h" #include "VoiceTransmit.h" +#include "SlowDataEncoder.h" -int main(int argc, char** argv) +int main(int argc, const char * argv[]) { - std::string repeater; + std::string repeater, text; std::vector filenames; - if (!parseCLIArgs(argc, argv, repeater, filenames)) { - ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit ..., exiting\n"); + if (!parseCLIArgs(argc, argv, repeater, filenames, text)) { + ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit [-text text] ..., exiting\n"); return 1; } @@ -44,7 +46,7 @@ int main(int argc, char** argv) return 1; } - CVoiceTransmit tt(repeater, &store); + CVoiceTransmit tt(repeater, &store, text); bool ret = tt.run(); store.close(); @@ -53,30 +55,36 @@ int main(int argc, char** argv) return ret ? 0 : 1; } -bool parseCLIArgs(int argc, char * argv[], std::string& repeater, std::vector& files) +bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector& files, std::string& text) { if(argc < 3) return false; - repeater.assign(argv[1]); - boost::to_upper(repeater); - boost::replace_all(repeater, "_", " "); - repeater.resize(LONG_CALLSIGN_LENGTH, ' '); + std::unordered_map namedArgs; + std::vector positionalArgs; - files.clear(); + CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs); - for(int i = 2; i < argc; i++) { - if(argv[i] != nullptr) { - files.push_back(std::string(argv[i])); - } + if(positionalArgs.size() < 2U) + return false; + + repeater.assign(positionalArgs[0]); + files.assign(positionalArgs.begin() + 1, positionalArgs.end()); + + if(namedArgs.count("text") > 0U) { + text.assign(namedArgs["text"]); + } + else { + text.assign(""); } - return files.size() > 0U; + return true; } -CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store) : +CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text) : m_socket("", 0U), m_callsign(callsign), +m_text(text), m_store(store) { assert(store != NULL); @@ -88,6 +96,7 @@ CVoiceTransmit::~CVoiceTransmit() bool CVoiceTransmit::run() { + CSlowDataEncoder * slowData = nullptr; bool opened = m_socket.open(); if (!opened) return false; @@ -110,6 +119,12 @@ bool CVoiceTransmit::run() header->setRptCall2(m_callsign); header->setDestination(address, G2_DV_PORT); + if(!m_text.empty()) { + slowData = new CSlowDataEncoder(); + slowData->setHeaderData(*header); + slowData->setTextData(m_text); + } + sendHeader(header); delete header; @@ -127,10 +142,6 @@ bool CVoiceTransmit::run() CAMBEData* ambe = m_store->getAMBE(); if (ambe == NULL) { - seqNo++; - if (seqNo >= 21U) - seqNo = 0U; - CAMBEData data; data.setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); data.setDestination(address, G2_DV_PORT); @@ -145,8 +156,22 @@ bool CVoiceTransmit::run() return true; } - seqNo = ambe->getSeq(); + if(slowData != nullptr) { // Override slowdata if specified so + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ambe->getData(buffer, DV_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowData->sync(); + } else { + slowData->getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); + } + ambe->setData(buffer, DV_FRAME_LENGTH_BYTES); + } + + ambe->setSeq(seqNo); ambe->setDestination(address, G2_DV_PORT); ambe->setEnd(false); ambe->setId(id); @@ -156,6 +181,9 @@ bool CVoiceTransmit::run() delete ambe; out++; + + seqNo++; + if(seqNo >= 21U) seqNo = 0U; } std::this_thread::sleep_for(std::chrono::milliseconds(10U)); diff --git a/DGWVoiceTransmit/VoiceTransmit.h b/DGWVoiceTransmit/VoiceTransmit.h index 3a72384..2bc0fb8 100644 --- a/DGWVoiceTransmit/VoiceTransmit.h +++ b/DGWVoiceTransmit/VoiceTransmit.h @@ -28,11 +28,11 @@ #include "HeaderData.h" #include "AMBEData.h" -bool parseCLIArgs(int argc, char * argv[], std::string& repeater, std::vector& vector); +bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector& vector, std::string& text); class CVoiceTransmit { public: - CVoiceTransmit(const std::string& callsign, CVoiceStore* store); + CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text); ~CVoiceTransmit(); bool run(); @@ -40,6 +40,7 @@ public: private: CUDPReaderWriter m_socket; std::string m_callsign; + std::string m_text; CVoiceStore* m_store; bool sendHeader(CHeaderData* header); From b3522c5750c4d204e655e983eca3d38f1a750e2e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Thu, 24 Mar 2022 20:31:45 +0100 Subject: [PATCH 086/112] #23 detect CRC header in calc CRC, add test --- .vscode/launch.json | 2 +- APRS/APRSUtils.cpp | 6 ++++- Tests/APRSUtils/calcGPSAIcomCRC.cpp | 41 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 Tests/APRSUtils/calcGPSAIcomCRC.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 94b71b0..d6dd4ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -109,7 +109,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DGWVoiceTransmit/dgwvoicetransmit", - "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool", "-text", "www.F5KAV.fr"], + "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool", "-text", "www.F5KAV.fr", "-dprs", "!4858.72ND00736.91E&RNG0037/A=000098 440 Voice 439.80000MHz -9.4000MHz"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/APRS/APRSUtils.cpp b/APRS/APRSUtils.cpp index 2309344..5684088 100644 --- a/APRS/APRSUtils.cpp +++ b/APRS/APRSUtils.cpp @@ -37,8 +37,12 @@ unsigned int CAPRSUtils::calcGPSAIcomCRC(const std::string& gpsa) { unsigned int icomcrc = 0xFFFFU; + unsigned int dataBegin = 0U; + if(boost::starts_with(gpsa, "$$CRC") && gpsa.length() >= 10 && gpsa[9] == ',') + dataBegin = 10U; + auto length = gpsa.length(); - for (unsigned int j = 10U; j < length; j++) { + for (unsigned int j = dataBegin; j < length; j++) { unsigned char ch = (unsigned char)gpsa[j]; for (unsigned int i = 0U; i < 8U; i++) { diff --git a/Tests/APRSUtils/calcGPSAIcomCRC.cpp b/Tests/APRSUtils/calcGPSAIcomCRC.cpp new file mode 100644 index 0000000..9d5850c --- /dev/null +++ b/Tests/APRSUtils/calcGPSAIcomCRC.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "APRSUtils.h" + +namespace APRStoDPRSTests +{ + class APRSUtils_calcGPSAIcomCRC : public ::testing::Test { + }; + + TEST_F(APRSUtils_calcGPSAIcomCRC, withCRCHeader) + { + auto crc = CAPRSUtils::calcGPSAIcomCRC("$$CRC6F5E,ABCDEF"); + + EXPECT_EQ(crc, 0x6f5e) << "CRC shall be valid"; + } + + TEST_F(APRSUtils_calcGPSAIcomCRC, withoutCRCHeader) + { + auto crc = CAPRSUtils::calcGPSAIcomCRC("ABCDEF"); + + EXPECT_EQ(crc, 0x6f5e) << "CRC shall be valid"; + } +} \ No newline at end of file From c10c7151d4d79034c763989f1f1acf0cfeec363e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 25 Mar 2022 16:28:59 +0100 Subject: [PATCH 087/112] #23 add possibility to override dprs --- .vscode/launch.json | 2 +- DGWVoiceTransmit/Makefile | 7 ++--- DGWVoiceTransmit/VoiceTransmit.cpp | 43 +++++++++++++++++++++--------- DGWVoiceTransmit/VoiceTransmit.h | 7 ++--- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d6dd4ae..ae32c96 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -109,7 +109,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DGWVoiceTransmit/dgwvoicetransmit", - "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool", "-text", "www.F5KAV.fr", "-dprs", "!4858.72ND00736.91E&RNG0037/A=000098 440 Voice 439.80000MHz -9.4000MHz"], + "args": ["F4FXL B", "${workspaceFolder}/Sandbox/Announce_F5ZEE__B.dvtool", "-text", "www.F5KAV.fr", "-dprs", "!4858.72N/00736.91Er/"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/DGWVoiceTransmit/Makefile b/DGWVoiceTransmit/Makefile index 4579621..d214adb 100644 --- a/DGWVoiceTransmit/Makefile +++ b/DGWVoiceTransmit/Makefile @@ -2,11 +2,11 @@ SRCS = $(wildcard *.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -dgwvoicetransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a - $(CC) $(CPPFLAGS) -o dgwvoicetransmit $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) +dgwvoicetransmit: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../APRS/APRS.a ../BaseCommon/BaseCommon.a + $(CC) $(CPPFLAGS) -o dgwvoicetransmit $(OBJS) ../DStarBase/DStarBase.a ../APRS/APRS.a ../BaseCommon/BaseCommon.a $(LDFLAGS) %.o : %.cpp - $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ + $(CC) -I../BaseCommon -I../APRS -I../DStarBase -I../VersionInfo -DCFG_DIR='"$(CFG_DIR)"' $(CPPFLAGS) -MMD -MD -c $< -o $@ .PHONY clean: clean: @@ -17,6 +17,7 @@ install: dgwvoicetransmit # copy executable @cp -f dgwvoicetransmit $(BIN_DIR) +../APRS/APRS.a: ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: ../VersionInfo/GitVersion.h: diff --git a/DGWVoiceTransmit/VoiceTransmit.cpp b/DGWVoiceTransmit/VoiceTransmit.cpp index 67b76ca..655f5c7 100644 --- a/DGWVoiceTransmit/VoiceTransmit.cpp +++ b/DGWVoiceTransmit/VoiceTransmit.cpp @@ -26,13 +26,15 @@ #include "DStarDefines.h" #include "VoiceTransmit.h" #include "SlowDataEncoder.h" +#include "APRSUtils.h" +#include "StringUtils.h" int main(int argc, const char * argv[]) { - std::string repeater, text; + std::string repeater, text, dprs; std::vector filenames; - if (!parseCLIArgs(argc, argv, repeater, filenames, text)) { + if (!parseCLIArgs(argc, argv, repeater, filenames, text, dprs)) { ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit [-text text] ..., exiting\n"); return 1; } @@ -46,7 +48,7 @@ int main(int argc, const char * argv[]) return 1; } - CVoiceTransmit tt(repeater, &store, text); + CVoiceTransmit tt(repeater, &store, text, dprs); bool ret = tt.run(); store.close(); @@ -55,7 +57,7 @@ int main(int argc, const char * argv[]) return ret ? 0 : 1; } -bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector& files, std::string& text) +bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector& files, std::string& text, std::string& dprs) { if(argc < 3) return false; @@ -68,7 +70,7 @@ bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vec if(positionalArgs.size() < 2U) return false; - repeater.assign(positionalArgs[0]); + repeater.assign(boost::replace_all_copy(positionalArgs[0], "_", " ")); files.assign(positionalArgs.begin() + 1, positionalArgs.end()); if(namedArgs.count("text") > 0U) { @@ -78,13 +80,24 @@ bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vec text.assign(""); } + if(namedArgs.count("dprs") > 0U) { + std::string dprsRepeater(repeater); + CAPRSUtils::dstarCallsignToAPRS(dprsRepeater); + std::string dprsnoCrc = CStringUtils::string_format("%s>DPRS:%s\r", dprsRepeater.c_str(), namedArgs["dprs"].c_str()); + dprs = CStringUtils::string_format("$$CRC%04X,%s", CAPRSUtils::calcGPSAIcomCRC(dprsnoCrc), dprsnoCrc.c_str()); + } + else { + dprs.assign(""); + } + return true; } -CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text) : +CVoiceTransmit::CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text, const std::string& dprs) : m_socket("", 0U), m_callsign(callsign), m_text(text), +m_dprs(dprs), m_store(store) { assert(store != NULL); @@ -121,8 +134,9 @@ bool CVoiceTransmit::run() if(!m_text.empty()) { slowData = new CSlowDataEncoder(); - slowData->setHeaderData(*header); - slowData->setTextData(m_text); + // slowData->setHeaderData(*header); + if(!m_text.empty()) slowData->setTextData(m_text); + if(!m_dprs.empty()) slowData->setGPSData(m_dprs); } sendHeader(header); @@ -133,8 +147,9 @@ bool CVoiceTransmit::run() unsigned int out = 0U; unsigned int seqNo = 0U; + bool loop = true; - for (;;) { + while (loop) { unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); needed /= DSTAR_FRAME_TIME_MS; @@ -153,7 +168,8 @@ bool CVoiceTransmit::run() m_socket.close(); - return true; + loop = false; + break; } if(slowData != nullptr) { // Override slowdata if specified so @@ -163,7 +179,6 @@ bool CVoiceTransmit::run() // Insert sync bytes when the sequence number is zero, slow data otherwise if (seqNo == 0U) { ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - slowData->sync(); } else { slowData->getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); } @@ -177,17 +192,19 @@ bool CVoiceTransmit::run() ambe->setId(id); sendData(ambe); - delete ambe; out++; - seqNo++; if(seqNo >= 21U) seqNo = 0U; } std::this_thread::sleep_for(std::chrono::milliseconds(10U)); } + + if(slowData != nullptr) delete slowData; + + return true; } bool CVoiceTransmit::sendHeader(CHeaderData* header) diff --git a/DGWVoiceTransmit/VoiceTransmit.h b/DGWVoiceTransmit/VoiceTransmit.h index 2bc0fb8..38838cc 100644 --- a/DGWVoiceTransmit/VoiceTransmit.h +++ b/DGWVoiceTransmit/VoiceTransmit.h @@ -28,11 +28,11 @@ #include "HeaderData.h" #include "AMBEData.h" -bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector& vector, std::string& text); +bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vector& vector, std::string& text, std::string& dprs); class CVoiceTransmit { public: - CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text); + CVoiceTransmit(const std::string& callsign, CVoiceStore* store, const std::string& text, const std::string& dprs); ~CVoiceTransmit(); bool run(); @@ -41,7 +41,8 @@ private: CUDPReaderWriter m_socket; std::string m_callsign; std::string m_text; - CVoiceStore* m_store; + std::string m_dprs; + CVoiceStore* m_store; bool sendHeader(CHeaderData* header); bool sendData(CAMBEData* data); From 4715ca404179cb1cd92f6a3f734eeb3d83320d5f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 25 Mar 2022 16:49:58 +0100 Subject: [PATCH 088/112] #25 make callsing upper --- DGWVoiceTransmit/VoiceTransmit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWVoiceTransmit/VoiceTransmit.cpp b/DGWVoiceTransmit/VoiceTransmit.cpp index 655f5c7..9403756 100644 --- a/DGWVoiceTransmit/VoiceTransmit.cpp +++ b/DGWVoiceTransmit/VoiceTransmit.cpp @@ -70,7 +70,7 @@ bool parseCLIArgs(int argc, const char * argv[], std::string& repeater, std::vec if(positionalArgs.size() < 2U) return false; - repeater.assign(boost::replace_all_copy(positionalArgs[0], "_", " ")); + repeater.assign(boost::replace_all_copy(boost::to_upper_copy(positionalArgs[0]), "_", " ")); files.assign(positionalArgs.begin() + 1, positionalArgs.end()); if(namedArgs.count("text") > 0U) { From d3cc307c653556e48299e4945afc45933a79e986 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 10 Apr 2022 07:03:15 +0200 Subject: [PATCH 089/112] #22 Add access control --- .vscode/tasks.json | 10 +++++----- DStarGateway/DStarGatewayApp.cpp | 28 ++++++++++++++++++++++++++++ DStarGateway/DStarGatewayConfig.cpp | 15 +++++++++++++++ DStarGateway/DStarGatewayConfig.h | 9 +++++++++ DStarGateway/example.cfg | 8 +++++++- 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index df6906d..587d251 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,7 +26,10 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -78,10 +81,7 @@ "USE_GPSD=1", "DGWVoiceTransmit/dgwvoicetransmit" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 634dd41..328e8fc 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -212,6 +212,34 @@ bool CDStarGatewayApp::createThread() } } + // Setup access control + TAccessControl accessControl; + m_config->getAccessControl(accessControl); + + CCallsignList * whiteList = new CCallsignList(accessControl.whiteList); + if(whiteList->load() && whiteList->getCount() > 0U) { + m_thread->setWhiteList(whiteList); + } + else { + delete whiteList; + } + + CCallsignList * blackList = new CCallsignList(accessControl.blackList); + if(blackList->load() && blackList->getCount() > 0U) { + m_thread->setBlackList(blackList); + } + else { + delete blackList; + } + + CCallsignList * restrictList = new CCallsignList(accessControl.restrictList); + if(restrictList->load() && restrictList->getCount() > 0U) { + m_thread->setRestrictList(restrictList); + } + else { + delete restrictList; + } + // Setup the repeaters bool ddEnabled = false; bool atLeastOneRepeater = false; diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 77f8086..9fd97c0 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -56,6 +56,7 @@ bool CDStarGatewayConfig::load() ret = loadGPSD(cfg) && ret; #endif ret = loadDaemon(cfg) && ret; + ret = loadAccessControl(cfg) && ret; } if(ret) { @@ -350,6 +351,15 @@ bool CDStarGatewayConfig::loadGPSD(const CConfig & cfg) } #endif +bool CDStarGatewayConfig::loadAccessControl(const CConfig & cfg) +{ + bool ret = cfg.getValue("AccessControl", "whiteList", m_accessControl.whiteList, 0U, 2048U, ""); + ret = cfg.getValue("AccessControl", "blackList", m_accessControl.blackList, 0U, 2048U, "") && ret; + ret = cfg.getValue("AccessControl", "restrictList", m_accessControl.restrictList, 0U, 2048U, "") && ret; + + return ret; +} + bool CDStarGatewayConfig::open(CConfig & cfg) { try { @@ -450,4 +460,9 @@ void CDStarGatewayConfig::getGPSD(TGPSD & gpsd) const void CDStarGatewayConfig::getDaemon(TDaemon & gen) const { gen = m_daemon; +} + +void CDStarGatewayConfig::getAccessControl(TAccessControl & accessControl) const +{ + accessControl = m_accessControl; } \ No newline at end of file diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index 1bfce6c..b39dd84 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -134,6 +134,12 @@ typedef struct { } TGPSD; #endif +typedef struct { + std::string whiteList; + std::string blackList; + std::string restrictList; +} TAccessControl; + class CDStarGatewayConfig { public: CDStarGatewayConfig(const std::string &pathname); @@ -157,6 +163,7 @@ public: void getGPSD(TGPSD & gpsd) const; #endif void getDaemon(TDaemon & gen) const; + void getAccessControl(TAccessControl & accessControl) const; private: bool open(CConfig & cfg); @@ -175,6 +182,7 @@ private: bool loadGPSD(const CConfig & cfg); #endif bool loadDaemon(const CConfig & cfg); + bool loadAccessControl(const CConfig & cfg); std::string m_fileName; TGateway m_gateway; @@ -190,6 +198,7 @@ private: TGPSD m_gpsd; #endif TDaemon m_daemon; + TAccessControl m_accessControl; std::vector m_repeaters; std::vector m_ircDDB; diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index f79e4df..5d117bb 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -177,7 +177,13 @@ enabled=false port=4242 password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field -# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd +# Should only be used with respect to your local regulation! Many countries prohibit setting up private repeaters ! +[AccessControl] +whiteList= # Only affects network +blackList= # Only affects network +restrictList= # Only affects RF, call signs present in this list are now allowed to change reflector or unlink the repeater + +# The Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd # require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might # run the program using sysv or any other old school init system. [Daemon] From 2e840764825a26fdda5cd2f725fc2a7cc0eb34b0 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 10 Apr 2022 07:44:11 +0200 Subject: [PATCH 090/112] Add missing clean call --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 1ed1c97..0383f23 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,7 @@ clean: $(MAKE) -C APRS clean $(MAKE) -C Common clean $(MAKE) -C BaseCommon clean + $(MAKE) -C DGWRemoteControl clean $(MAKE) -C DStarBase clean $(MAKE) -C DStarGateway clean $(MAKE) -C IRCDDB clean From e0856466c7cfab550ff8ec4389ea7f6fb2c81d93 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 10 Apr 2022 08:03:03 +0200 Subject: [PATCH 091/112] Add missing cleans --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 0383f23..ea4c59d 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,8 @@ clean: $(MAKE) -C Common clean $(MAKE) -C BaseCommon clean $(MAKE) -C DGWRemoteControl clean + $(MAKE) -C DGWTextTransmit clean + $(MAKE) -C DGWTimeServer clean $(MAKE) -C DStarBase clean $(MAKE) -C DStarGateway clean $(MAKE) -C IRCDDB clean From 6b418e9966634a795c2dfb43a7f327f14d12b999 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 10 Apr 2022 08:07:20 +0200 Subject: [PATCH 092/112] Add more missing cleans --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ea4c59d..ce115f6 100644 --- a/Makefile +++ b/Makefile @@ -79,11 +79,12 @@ VersionInfo/GitVersion.h: FORCE clean: $(MAKE) -C Tests clean $(MAKE) -C APRS clean - $(MAKE) -C Common clean $(MAKE) -C BaseCommon clean + $(MAKE) -C Common clean $(MAKE) -C DGWRemoteControl clean $(MAKE) -C DGWTextTransmit clean $(MAKE) -C DGWTimeServer clean + $(MAKE) -C DGWVoiceTransmit clean $(MAKE) -C DStarBase clean $(MAKE) -C DStarGateway clean $(MAKE) -C IRCDDB clean From 697e84e48c66001365eab7291168de640dd1764a Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Thu, 14 Apr 2022 16:30:29 +0200 Subject: [PATCH 093/112] #22 #23 Udpate Readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 03649c1..f300bcf 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,8 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 +- [**Improvement**] Add call sign lists ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) +- [**Improvement**] Add a way to override Slow Data in VoiceTransmit ([#23](https://github.com/F4FXL/DStarGateway/issues/23)) - [**Improvement**] Add time server - [**Improvement**] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)). DStarGateway can also be run as a "forking" daemon. This might be required for distros still using sysv. Systemd can live without it. - [**Improvement**] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) From c11065855e0d2eb4cd73b802691d5649985f4d7c Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 15 Apr 2022 07:35:04 +0200 Subject: [PATCH 094/112] Clean Up --- BaseCommon/UDPReaderWriter.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/BaseCommon/UDPReaderWriter.cpp b/BaseCommon/UDPReaderWriter.cpp index d0b826f..7047eeb 100644 --- a/BaseCommon/UDPReaderWriter.cpp +++ b/BaseCommon/UDPReaderWriter.cpp @@ -157,23 +157,6 @@ bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, c TOIPV4(addr)->sin_port = htons(port); return write(buffer, length, addr); - // sockaddr_in addr; - // ::memset(&addr, 0x00, sizeof(sockaddr_in)); - - // addr.sin_family = AF_INET; - // addr.sin_addr = address; - // addr.sin_port = htons(port); - - // ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); - // if (ret < 0) { - // CLog::logError("Error returned from sendto (port: %u), err: %s\n", m_port, strerror(errno)); - // return false; - // } - - // if (ret != ssize_t(length)) - // return false; - - // return true; } bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, const struct sockaddr_storage& addr) From 0ddbe62b18b7ef1c7ca73358c8e785c8f529a65f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 15 Apr 2022 08:55:07 +0200 Subject: [PATCH 095/112] #24 Port DRats --- BaseCommon/TCPReaderWriterServer.cpp | 267 +++++++++++++++++++ BaseCommon/TCPReaderWriterServer.h | 61 +++++ Common/DRATSServer.cpp | 384 +++++++++++++++++++++++++++ Common/DRATSServer.h | 66 +++++ Common/RepeaterHandler.cpp | 2 +- DStarGateway/DStarGatewayApp.cpp | 3 + Makefile | 1 + 7 files changed, 783 insertions(+), 1 deletion(-) create mode 100644 BaseCommon/TCPReaderWriterServer.cpp create mode 100644 BaseCommon/TCPReaderWriterServer.h create mode 100644 Common/DRATSServer.cpp create mode 100644 Common/DRATSServer.h diff --git a/BaseCommon/TCPReaderWriterServer.cpp b/BaseCommon/TCPReaderWriterServer.cpp new file mode 100644 index 0000000..181a3bb --- /dev/null +++ b/BaseCommon/TCPReaderWriterServer.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2011 by Jonathan Naylor G4KLX + * Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "TCPReaderWriterServer.h" +#include "Log.h" +#include "NetUtils.h" + +#include +#include + + +CTCPReaderWriterServer::CTCPReaderWriterServer(const std::string& address, unsigned int port) : +CThread("TCP server"), +m_address(address), +m_port(port), +m_fd(-1), +m_client(NULL), +m_stopped(false) +{ + assert(port > 0U); +} + +CTCPReaderWriterServer::~CTCPReaderWriterServer() +{ +} + +bool CTCPReaderWriterServer::start() +{ + bool ret = open(); + if (!ret) { + close(); + return false; + } + + Create(); + Run(); + + return true; +} + +int CTCPReaderWriterServer::read(unsigned char* buffer, unsigned int length, unsigned int secs) +{ + assert(buffer != NULL); + assert(length > 0U); + + if (m_client != NULL) { + int ret = m_client->read(buffer, length, secs); + if (ret < 0) { + CLog::logInfo("Lost TCP connection to port %u", m_port); + + m_client->close(); + delete m_client; + m_client = NULL; + + open(); + + return 0; + } + + return ret; + } + + return 0; +} + +bool CTCPReaderWriterServer::write(const unsigned char* buffer, unsigned int length) +{ + assert(buffer != NULL); + assert(length > 0U); + + if (m_client != NULL) { + bool ret = m_client->write(buffer, length); + if (!ret) { + CLog::logInfo("Lost TCP connection to port %u", m_port); + + m_client->close(); + delete m_client; + m_client = NULL; + + open(); + + return false; + } + + return true; + } + + return true; +} + +void* CTCPReaderWriterServer::Entry() +{ +#ifndef DEBUG_DSTARGW + try { +#endif + while (!m_stopped) { + int ret = accept(); + switch (ret) { + case -2: + break; + case -1: + break; + default: + CLog::logInfo("Incoming TCP connection to port %u", m_port); + m_client = new CTCPReaderWriterClient(ret); + close(); + break; + } + + Sleep(1000UL); + } + + if (m_client != NULL) { + m_client->close(); + delete m_client; + } + + close(); + +#ifndef DEBUG_DSTARGW + } + catch (std::exception& e) { + std::string message(e.what()); + CLog::logError("Exception raised in the TCP Reader-Writer Server thread - \"%s\"", message.c_str()); + } + catch (...) { + CLog::logError("Unknown exception raised in the TCP Reader-Writer Server thread"); + } +#endif + + return NULL; +} + +void CTCPReaderWriterServer::stop() +{ + m_stopped = true; + + Wait(); +} + +bool CTCPReaderWriterServer::open() +{ + m_fd = ::socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + CLog::logError("Cannot create the TCP server socket, err=%d", errno); + return false; + } + + struct sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + if (m_address.empty()) + addr.sin_addr.s_addr = htonl(INADDR_ANY); + else + addr.sin_addr = lookup(m_address); + + if (addr.sin_addr.s_addr == INADDR_NONE) { + CLog::logError("The address is invalid - %s", m_address.c_str()); + close(); + return false; + } + + int reuse = 1; + if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + CLog::logError("Cannot set the TCP server socket option, err=%d", errno); + close(); + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) { + CLog::logError("Cannot bind the TCP server address, err=%d", errno); + close(); + return false; + } + + ::listen(m_fd, 5); + + return true; +} + +int CTCPReaderWriterServer::accept() +{ + if (m_fd == -1) + return -1; + + // Check that the accept() won't block + fd_set readFds; + FD_ZERO(&readFds); +#if defined(__WINDOWS__) + FD_SET((unsigned int)m_fd, &readFds); +#else + FD_SET(m_fd, &readFds); +#endif + + // Return after timeout + timeval tv; + tv.tv_sec = 0L; + tv.tv_usec = 0L; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { + CLog::logError("Error returned from TCP server select, err=%d", errno); + return -2; + } + +#if defined(__WINDOWS__) + if (!FD_ISSET((unsigned int)m_fd, &readFds)) + return -1; +#else + if (!FD_ISSET(m_fd, &readFds)) + return -1; +#endif + + struct sockaddr_in addr; +#if defined(__WINDOWS__) + int len = sizeof(struct sockaddr_in); +#else + socklen_t len = sizeof(struct sockaddr_in); +#endif + + ret = ::accept(m_fd, (sockaddr*)&addr, &len); + if (ret < 0) { + CLog::logError("Error returned from TCP server accept, err=%d", errno); + } + + return ret; +} + +void CTCPReaderWriterServer::close() +{ + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +} + +in_addr CTCPReaderWriterServer::lookup(const std::string& hostname) const +{ + in_addr addrv4; + addrv4.s_addr = INADDR_NONE; + + sockaddr_storage addr; + auto res = CNetUtils::lookupV4(hostname, addr); + + if(res) { + addrv4 = TOIPV4(addr)->sin_addr; + } + + return addrv4; +} diff --git a/BaseCommon/TCPReaderWriterServer.h b/BaseCommon/TCPReaderWriterServer.h new file mode 100644 index 0000000..25a6deb --- /dev/null +++ b/BaseCommon/TCPReaderWriterServer.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011 by Jonathan Naylor G4KLX + * Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include "TCPReaderWriterClient.h" +#include "Thread.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CTCPReaderWriterServer : public CThread { +public: + CTCPReaderWriterServer(const std::string& address, unsigned int port); + virtual ~CTCPReaderWriterServer(); + + virtual bool start(); + + virtual bool write(const unsigned char* buffer, unsigned int length); + virtual int read(unsigned char* buffer, unsigned int length, unsigned int secs); + + virtual void stop(); + + virtual void* Entry(); + +private: + std::string m_address; + unsigned short m_port; + int m_fd; + CTCPReaderWriterClient* m_client; + bool m_stopped; + + bool open(); + int accept(); + void close(); + in_addr lookup(const std::string& hostname) const; +}; diff --git a/Common/DRATSServer.cpp b/Common/DRATSServer.cpp new file mode 100644 index 0000000..d83bbc4 --- /dev/null +++ b/Common/DRATSServer.cpp @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarDefines.h" +#include "DRATSServer.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include + +// #define LOOPBACK + +const unsigned int BUFFER_LENGTH = 30000U; + +CDRATSServer::CDRATSServer(const std::string& address, unsigned int port, const std::string& callsign, IRepeaterCallback* handler) : +CThread("DRats"), +m_address(address), +m_port(port), +m_callsign(callsign), +m_handler(handler), +m_socket(NULL), +m_stopped(false), +m_readState(SS_FIRST), +m_readBuffer(NULL), +m_readLength(0U), +m_readPos(0U), +m_readEnd(false), +m_writeText(NULL), +m_writeState(SS_FIRST), +m_writeBuffer(NULL), +m_writeLength(0U) +{ + assert(handler != NULL); + assert(port > 0U); + + m_readBuffer = new unsigned char[BUFFER_LENGTH]; + m_writeBuffer = new unsigned char[BUFFER_LENGTH]; + m_writeText = new unsigned char[6U]; +} + +CDRATSServer::~CDRATSServer() +{ + delete[] m_readBuffer; + delete[] m_writeBuffer; + delete[] m_writeText; +} + +bool CDRATSServer::open() +{ + m_socket = new CTCPReaderWriterServer(m_address, m_port); + bool ret = m_socket->start(); + if (!ret) { + delete m_socket; + m_socket = NULL; + return false; + } + + Create(); + Run(); + + return true; +} + +void CDRATSServer::writeHeader(const CHeaderData&) +{ + m_writeState = SS_FIRST; + + if (m_writeLength > 0U && m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; +} + +void CDRATSServer::writeData(const CAMBEData& data) +{ + // Sync data isn't sent on + if (data.isSync()) { + m_writeState = SS_FIRST; + return; + } + + if (data.isEnd()) { + if (m_writeLength > 0U && m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; + return; + } + + unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES]; + unsigned int length = data.getData(buffer, DV_FRAME_MAX_LENGTH_BYTES); + + if (length != DV_FRAME_LENGTH_BYTES) + return; + + unsigned char byte1 = buffer[VOICE_FRAME_LENGTH_BYTES + 0U] ^ SCRAMBLER_BYTE1; + unsigned char byte2 = buffer[VOICE_FRAME_LENGTH_BYTES + 1U] ^ SCRAMBLER_BYTE2; + unsigned char byte3 = buffer[VOICE_FRAME_LENGTH_BYTES + 2U] ^ SCRAMBLER_BYTE3; + + switch (m_writeState) { + case SS_FIRST: + m_writeText[0U] = byte1; + m_writeText[1U] = byte2; + m_writeText[2U] = byte3; + m_writeState = SS_SECOND; + return; + + case SS_SECOND: + m_writeText[3U] = byte1; + m_writeText[4U] = byte2; + m_writeText[5U] = byte3; + m_writeState = SS_FIRST; + break; + } + + if ((m_writeText[0U] & SLOW_DATA_TYPE_MASK) != SLOW_DATA_TYPE_GPS) + return; + + length = m_writeText[0U] & 0x07; // Maximum value of 5 + if (length > 5U) + length = 5U; + + for (unsigned int i = 0U; i < length; i++) { + m_writeBuffer[m_writeLength++] = m_writeText[i + 1U]; + + // Check for [EOB] in the buffer to signal the end of the D-RATS data. + // To allow strstr() to run correctly + m_writeBuffer[m_writeLength] = 0x00U; + + if (::strstr((char*)m_writeBuffer, "[EOB]") != NULL) { + if (m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; + } + } +} + +void CDRATSServer::writeEnd() +{ + if (m_writeLength > 0U && m_socket != NULL) { + CUtils::dump("From RF", m_writeBuffer, m_writeLength); + m_socket->write(m_writeBuffer, m_writeLength); + } + + m_writeLength = 0U; +} + +void* CDRATSServer::Entry() +{ + CLog::logInfo("Starting the D-RATS Server thread for %s", m_callsign.c_str()); + + bool sending = false; + unsigned int id = 0U; + + unsigned char seqNo = 0U; + unsigned int sent = 0U; + + std::chrono::high_resolution_clock::time_point time; + +#ifndef DEBUG_DSTARGW + try { +#endif + while (!m_stopped) { + serviceSocket(); + + if (m_readEnd && !sending) { + id = CHeaderData::createId(); + + // Write header + CHeaderData header; + header.setMyCall1(m_callsign); + header.setMyCall2("DATA"); + header.setYourCall("CQCQCQ "); + header.setId(id); + +#if defined(LOOPBACK) + writeHeader(header); +#else + m_handler->process(header, DIR_INCOMING, AS_DRATS); +#endif + + m_readState = SS_FIRST; + m_readPos = 0U; + sending = true; + seqNo = 0U; + sent = 0U; + + time = std::chrono::high_resolution_clock::now(); + } + + if (m_readEnd && sending) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - time).count() / DSTAR_FRAME_TIME_MS; + + while (sent < needed && sending) { + // Write AMBE data + CAMBEData data; + data.setId(id); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_readState = SS_FIRST; + } else { + if (m_readState == SS_FIRST) { + unsigned char readText[3U]; + ::memset(readText, 'f', 3U); + + unsigned int length = m_readLength - m_readPos; + unsigned char bytes = 5U; + if (length < 5U) + bytes = length; + + readText[0U] = SLOW_DATA_TYPE_GPS | bytes; + + for (unsigned int i = 0U; i < 2U && m_readPos < m_readLength; i++) + readText[i + 1U] = m_readBuffer[m_readPos++]; + + readText[0U] ^= SCRAMBLER_BYTE1; + readText[1U] ^= SCRAMBLER_BYTE2; + readText[2U] ^= SCRAMBLER_BYTE3; + + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES); + + m_readState = SS_SECOND; + } else { + unsigned char readText[3U]; + ::memset(readText, 'f', 3U); + + for (unsigned int i = 0U; i < 3U && m_readPos < m_readLength; i++) + readText[i] = m_readBuffer[m_readPos++]; + + readText[0U] ^= SCRAMBLER_BYTE1; + readText[1U] ^= SCRAMBLER_BYTE2; + readText[2U] ^= SCRAMBLER_BYTE3; + + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES); + + m_readState = SS_FIRST; + } + } + + data.setSeq(seqNo); + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + sent++; + +#if defined(LOOPBACK) + writeData(data); +#else + m_handler->process(data, DIR_INCOMING, AS_DRATS); +#endif + if (m_readPos == m_readLength) { + if (m_readState == SS_SECOND) { + seqNo++; + if (seqNo == 21U) + seqNo = 0U; + + unsigned char readText[3U]; + readText[0U] = 'f' ^ SCRAMBLER_BYTE1; + readText[1U] = 'f' ^ SCRAMBLER_BYTE2; + readText[2U] = 'f' ^ SCRAMBLER_BYTE3; + + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, readText, DATA_FRAME_LENGTH_BYTES); + + data.setSeq(seqNo); + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + sent++; +#if defined(LOOPBACK) + writeData(data); +#else + m_handler->process(data, DIR_INCOMING, AS_DRATS); +#endif + } + + seqNo++; + if (seqNo == 21U) + seqNo = 0U; + + if (seqNo == 0U) + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + else + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, NULL_SLOW_DATA_BYTES, DATA_FRAME_LENGTH_BYTES); + + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + data.setSeq(seqNo); + data.setEnd(true); + sent++; +#if defined(LOOPBACK) + writeData(data); +#else + m_handler->process(data, DIR_INCOMING, AS_DRATS); +#endif + m_readLength = 0U; + m_readPos = 0U; + m_readEnd = false; + sending = false; + sent = 0U; + } + + seqNo++; + if (seqNo == 21U) + seqNo = 0U; + } + } + + // 50ms + Sleep(50UL); + } + + if (m_socket != NULL) + m_socket->stop(); +#ifndef DEBUG_DSTARGW + } + catch (std::exception& e) { + std::string message(e.what()); + CLog::logError("Exception raised in the D-RATS Server thread - \"%s\""), message.c_str(); + } + catch (...) { + CLog::logError("Unknown exception raised in the D-RATS Server thread"); + } +#endif + + CLog::logInfo("Stopping the D-RATS Server thread for %s", m_callsign.c_str()); + + return NULL; +} + +void CDRATSServer::close() +{ + m_stopped = true; + + Wait(); +} + +void CDRATSServer::serviceSocket() +{ + if (m_socket == NULL) { + m_readLength = 0U; + m_readPos = 0U; + m_readEnd = false; + return; + } + + int len = m_socket->read(m_readBuffer + m_readLength, BUFFER_LENGTH - m_readLength, 0U); + if (len > 0) { + m_readLength += len; + + if (!m_readEnd) { + // To allow strstr() to run correctly + m_readBuffer[m_readLength] = 0x00U; + + if (::strstr((char*)m_readBuffer, "[EOB]") != NULL) { + CUtils::dump("To RF", m_readBuffer, m_readLength); + m_readEnd = true; + } + } + } +} diff --git a/Common/DRATSServer.h b/Common/DRATSServer.h new file mode 100644 index 0000000..0436bcf --- /dev/null +++ b/Common/DRATSServer.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011,2012 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DRATSServer_H +#define DRATSServer_H + +#include "TCPReaderWriterServer.h" +#include "RepeaterCallback.h" +#include "HeaderData.h" +#include "AMBEData.h" +#include "Defs.h" +#include "Thread.h" + +#include + +class CDRATSServer : public CThread { +public: + CDRATSServer(const std::string& address, unsigned int port, const std::string& callsign, IRepeaterCallback* handler); + virtual ~CDRATSServer(); + + virtual bool open(); + + virtual void writeHeader(const CHeaderData& header); + virtual void writeData(const CAMBEData& data); + virtual void writeEnd(); + + virtual void close(); + + virtual void* Entry(); + +private: + std::string m_address; + unsigned int m_port; + std::string m_callsign; + IRepeaterCallback* m_handler; + CTCPReaderWriterServer* m_socket; + bool m_stopped; + SLOWDATA_STATE m_readState; + unsigned char* m_readBuffer; + unsigned int m_readLength; + unsigned int m_readPos; + bool m_readEnd; + unsigned char* m_writeText; + SLOWDATA_STATE m_writeState; + unsigned char* m_writeBuffer; + unsigned int m_writeLength; + + void serviceSocket(); +}; + +#endif diff --git a/Common/RepeaterHandler.cpp b/Common/RepeaterHandler.cpp index 35685a6..9f817ca 100644 --- a/Common/RepeaterHandler.cpp +++ b/Common/RepeaterHandler.cpp @@ -64,7 +64,7 @@ CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL; CCallsignList* CRepeaterHandler::m_restrictList = NULL; #ifdef USE_DRATS - CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unigned char band3) : + CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : #else CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : #endif diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 328e8fc..b29c2c0 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -260,6 +260,9 @@ bool CDStarGatewayApp::createThread() rptrConfig.reflectorAtStartup, rptrConfig.reflectorReconnect, rptrConfig.frequency, + #ifdef USE_DRATS + true, + #endif rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, diff --git a/Makefile b/Makefile index ce115f6..d9f7720 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ export CPPFLAGS+= -DUSE_GPSD export LDFLAGS+= -lgps endif +export CPPFLAGS+= -DUSE_DRATS .PHONY: all all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests From b5cef98c0082a06a8052a705fbbf1104aa1ceec8 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 16 Apr 2022 07:49:07 +0200 Subject: [PATCH 096/112] #24 add DRats Config --- DStarGateway/DStarGatewayApp.cpp | 6 +++++- DStarGateway/DStarGatewayConfig.cpp | 21 ++++++++++++++++++++- DStarGateway/DStarGatewayConfig.h | 15 +++++++++++++++ DStarGateway/example.cfg | 3 +++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index b29c2c0..940fc8f 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -240,6 +240,10 @@ bool CDStarGatewayApp::createThread() delete restrictList; } + // Drats + TDRats drats; + m_config->getDRats(drats); + // Setup the repeaters bool ddEnabled = false; bool atLeastOneRepeater = false; @@ -261,7 +265,7 @@ bool CDStarGatewayApp::createThread() rptrConfig.reflectorReconnect, rptrConfig.frequency, #ifdef USE_DRATS - true, + drats.enabled, #endif rptrConfig.offset, rptrConfig.range, diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 9fd97c0..635a647 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -57,6 +57,9 @@ bool CDStarGatewayConfig::load() #endif ret = loadDaemon(cfg) && ret; ret = loadAccessControl(cfg) && ret; +#ifdef USE_DRATS + ret = loadDRats(cfg) && ret; +#endif } if(ret) { @@ -360,6 +363,15 @@ bool CDStarGatewayConfig::loadAccessControl(const CConfig & cfg) return ret; } +#ifdef USE_DRATS +bool CDStarGatewayConfig::loadDRats(const CConfig & cfg) +{ + bool ret = cfg.getValue("DRats", "enabled", m_drats.enabled, false); + + return ret; +} +#endif + bool CDStarGatewayConfig::open(CConfig & cfg) { try { @@ -465,4 +477,11 @@ void CDStarGatewayConfig::getDaemon(TDaemon & gen) const void CDStarGatewayConfig::getAccessControl(TAccessControl & accessControl) const { accessControl = m_accessControl; -} \ No newline at end of file +} + +#if USE_DRATS +void CDStarGatewayConfig::getDRats(TDRats & drats) const +{ + drats = m_drats; +} +#endif \ No newline at end of file diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index b39dd84..fdb5339 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -115,6 +115,12 @@ typedef struct { bool enabled; } TDCS; +#ifdef USE_DRATS +typedef struct { + bool enabled; +} TDRats; +#endif + typedef struct { bool enabled; std::string url; @@ -164,6 +170,9 @@ public: #endif void getDaemon(TDaemon & gen) const; void getAccessControl(TAccessControl & accessControl) const; +#ifdef USE_DRATS + void getDRats(TDRats & drats) const; +#endif private: bool open(CConfig & cfg); @@ -183,6 +192,9 @@ private: #endif bool loadDaemon(const CConfig & cfg); bool loadAccessControl(const CConfig & cfg); +#ifdef USE_DRATS + bool loadDRats(const CConfig & cfg); +#endif std::string m_fileName; TGateway m_gateway; @@ -199,6 +211,9 @@ private: #endif TDaemon m_daemon; TAccessControl m_accessControl; +#ifdef USE_DRATS + TDRats m_drats; +#endif std::vector m_repeaters; std::vector m_ircDDB; diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 5d117bb..731da4e 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -172,6 +172,9 @@ enabled=true # There is no reason to disable this enabled=true hostfileUrl=http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster +[DRats] +enabled=false # Defaults to false. The program need to be compiled with DRats support for DRats to be actually enabled + [Remote] enabled=false port=4242 From 2fb6f04d7ff6669dbd5a49aed0ac4d72aeacda12 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 15 Jun 2022 17:10:29 +0200 Subject: [PATCH 097/112] #25 try to clarify comment in example config --- DGWTimeServer/example.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index 94a823a..1274b63 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -1,7 +1,7 @@ [TimeServer] callsign= # call of the gateway to send time beacons without G letter address= # address of the gateway, defaults to 127.0.0.1 -format= # possible values are voice, text, voiceandtext, defaults to voice and text +format= # possible values are voice, text, defaults to voice. note that voice also sends text along. language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1 interval= # valid values are 15, 30 and 60, defaults to 30 From e442acadc70b2154f934d49fc27e7dc2598263ff Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 15 Jun 2022 17:20:16 +0200 Subject: [PATCH 098/112] #24 Clean up --- Common/RepeaterHandler.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Common/RepeaterHandler.h b/Common/RepeaterHandler.h index 0b29b85..6b7e10d 100644 --- a/Common/RepeaterHandler.h +++ b/Common/RepeaterHandler.h @@ -20,10 +20,6 @@ #ifndef RepeaterHandler_H #define RepeaterHandler_H -//#define USE_CCS -//#define USE_STARNET -//#define USE_DRATS - #include "RepeaterProtocolHandler.h" #include "DExtraProtocolHandler.h" #include "DPlusProtocolHandler.h" From 948e0889ed457ba01982b4ff4fd3ec24f98595cd Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 15 Jun 2022 17:56:02 +0200 Subject: [PATCH 099/112] #24 Make Drats a build option --- .vscode/tasks.json | 1 + DStarGateway/DStarGatewayApp.cpp | 2 ++ Makefile | 2 ++ 3 files changed, 5 insertions(+) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 587d251..e1ed666 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,6 +11,7 @@ "-j3", "ENABLE_DEBUG=1", "USE_GPSD=1", + "USE_DRATS=1", "all" ], "group": "build", diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 940fc8f..16c7f52 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -240,9 +240,11 @@ bool CDStarGatewayApp::createThread() delete restrictList; } +#ifdef USE_DRATS // Drats TDRats drats; m_config->getDRats(drats); +#endif // Setup the repeaters bool ddEnabled = false; diff --git a/Makefile b/Makefile index d9f7720..28e98c0 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,9 @@ export CPPFLAGS+= -DUSE_GPSD export LDFLAGS+= -lgps endif +ifeq ($(USE_DRATS), 1) export CPPFLAGS+= -DUSE_DRATS +endif .PHONY: all all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests From 7a79e5c5c0b7ea291437188aebcd3697110630ee Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 15 Jun 2022 17:58:24 +0200 Subject: [PATCH 100/112] #24 Update readme --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f300bcf..d444ef7 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ - [3.4. Prerequisites and dependencies](#34-prerequisites-and-dependencies) - [3.5. Building](#35-building) - [3.5.0.1. Build With GPSD Support](#3501-build-with-gpsd-support) - - [3.5.0.2. Debug Build](#3502-debug-build) + - [3.5.0.2. Build With DRats Support](#3502-build-with-drats-support) + - [3.5.0.3. Debug Build](#3503-debug-build) - [3.6. Installing](#36-installing) - [3.7. Configuring](#37-configuring) - [4. Contributing](#4-contributing) @@ -100,7 +101,12 @@ make ``` make USE_GPS=1 ``` -#### 3.5.0.2. Debug Build +#### 3.5.0.2. Build With DRats Support +IMHO Drats is a dying thing, therefore it is included as a build option. +``` +make USE_DRATS=1 +``` +#### 3.5.0.3. Debug Build ``` make ENABLE_DEBUG=1 ``` @@ -135,6 +141,7 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 +- [**Improvement**] Add DRats Support (conditional build) ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) - [**Improvement**] Add call sign lists ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) - [**Improvement**] Add a way to override Slow Data in VoiceTransmit ([#23](https://github.com/F4FXL/DStarGateway/issues/23)) - [**Improvement**] Add time server From 856d481b3fc5beaa1fc77026e64cfb1d185b8182 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 15 Jun 2022 18:00:32 +0200 Subject: [PATCH 101/112] #24 include DRats build option in CI --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f858990..6960b90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,10 +21,10 @@ jobs: sudo apt-get -y install libgtest-dev libcurl4-openssl-dev libboost-dev libgps-dev - run: name: "Build" - command: "make -j 3 ENABLE_DEBUG=1 USE_GPSD=1" + command: "make -j 3 ENABLE_DEBUG=1 USE_GPSD=1 USE_DRATS=1" - run: name: "Run Tests" - command: "make run-tests ENABLE_DEBUG=1 USE_GPSD=1" + command: "make run-tests ENABLE_DEBUG=1 USE_GPSD=1 USE_DRATS=1" # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: From c951ddae6f220505b7404f152ee482becc43c458 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 3 Sep 2022 06:53:06 +0200 Subject: [PATCH 102/112] #24 update readme --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d444ef7..bc530e6 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ - [3.5.0.3. Debug Build](#3503-debug-build) - [3.6. Installing](#36-installing) - [3.7. Configuring](#37-configuring) + - [3.8. Updating host files](#38-updating-host-files) - [4. Contributing](#4-contributing) - [4.1. Work Flow](#41-work-flow) - [4.2. Continuous Integration](#42-continuous-integration) @@ -102,7 +103,7 @@ make make USE_GPS=1 ``` #### 3.5.0.2. Build With DRats Support -IMHO Drats is a dying thing, therefore it is included as a build option. +IMHO Drats is a decaying thing, therefore it is included as a build option. ``` make USE_DRATS=1 ``` @@ -110,7 +111,7 @@ make USE_DRATS=1 ``` make ENABLE_DEBUG=1 ``` -Note that this will link with libl +Note that this will will add libl dependency. Building this way will output the stack trace in case of a crash. ## 3.6. Installing The program is meant to run as a systemd service. All bits an pieces are provided. ``` @@ -122,13 +123,21 @@ After installing you have to edit the configuration file. If you went with defau The configuration format is quite straight forward. It is organised in sections and key/value pairs. The order of the sections or key/values pairs inside the sections does not matter nor does casing. Boolean values can be set using true, false, 1 or 0 -Floating point values must use . (point) as decimal separatorsensitive. +Floating point values must use . (point) as decimal separator. -When done with configuration, the daemon will be started automatically on boot. To manual start and stop it use the usual systemd commands +When done with configuration, the daemon will be started automatically on next boot. To manual start and stop it, use the usual systemd commands ``` sudo systemctl start dstargateway.service sudo systemctl stop dstargateway.service ``` + +## 3.8. Updating host files +To update host files, from within the source code directory, run +``` +sudo make newhostfiles +sudo systemctl restart dstargateway.service +``` + # 4. Contributing ## 4.1. Work Flow I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my workflow. PR are welcome but pleasee observe following rules : @@ -137,7 +146,7 @@ I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my work - Code formating rules are observed (these are very lousy though) ## 4.2. Continuous Integration I have added some basic CI using CircleCI [![F4FXL](https://circleci.com/gh/F4FXL/DStarGateway.svg?style=svg)](https://app.circleci.com/pipelines/github/F4FXL/DStarGateway?filter=all) I am trying to rewrite the code so that it can be put into some Behavior Driven Development scheme. This is a long haul task and I'll try do do it on the go while changing/adding stuff. -the testing framwework used is Google Test. +The testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 From cae6aea472ebadadc98e165b8707f9869a0707d2 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Tue, 20 Sep 2022 20:48:15 +0200 Subject: [PATCH 103/112] Do not make DRats a compile option --- .circleci/config.yml | 4 ++-- .vscode/tasks.json | 1 - Common/RepeaterHandler.cpp | 32 +++++++---------------------- Common/RepeaterHandler.h | 13 +----------- DStarGateway/DStarGatewayApp.cpp | 4 ---- DStarGateway/DStarGatewayConfig.cpp | 8 +------- DStarGateway/DStarGatewayConfig.h | 10 --------- DStarGateway/DStarGatewayThread.cpp | 7 +------ DStarGateway/DStarGatewayThread.h | 7 ++----- Makefile | 4 ---- README.md | 10 ++------- 11 files changed, 16 insertions(+), 84 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6960b90..f858990 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,10 +21,10 @@ jobs: sudo apt-get -y install libgtest-dev libcurl4-openssl-dev libboost-dev libgps-dev - run: name: "Build" - command: "make -j 3 ENABLE_DEBUG=1 USE_GPSD=1 USE_DRATS=1" + command: "make -j 3 ENABLE_DEBUG=1 USE_GPSD=1" - run: name: "Run Tests" - command: "make run-tests ENABLE_DEBUG=1 USE_GPSD=1 USE_DRATS=1" + command: "make run-tests ENABLE_DEBUG=1 USE_GPSD=1" # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e1ed666..587d251 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -11,7 +11,6 @@ "-j3", "ENABLE_DEBUG=1", "USE_GPSD=1", - "USE_DRATS=1", "all" ], "group": "build", diff --git a/Common/RepeaterHandler.cpp b/Common/RepeaterHandler.cpp index 9f817ca..c66e95f 100644 --- a/Common/RepeaterHandler.cpp +++ b/Common/RepeaterHandler.cpp @@ -63,11 +63,8 @@ CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL; CCallsignList* CRepeaterHandler::m_restrictList = NULL; -#ifdef USE_DRATS - CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : -#else - CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : -#endif +CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : + m_index(0x00U), m_rptCallsign(), m_gwyCallsign(), @@ -137,9 +134,7 @@ m_wxAudio(NULL), m_wxNeeded(false), #endif m_version(NULL), -#ifdef USE_DRATS m_drats(NULL), -#endif m_dtmf(), m_pollTimer(1000U, 900U), // 15 minutes #ifdef USE_CSS @@ -228,7 +223,7 @@ m_heardTimer(1000U, 0U, 100U) // 100ms m_version = new CVersionUnit(this, callsign); m_aprsUnit = new CAPRSUnit(this); -#ifdef USE_DRATS + if (dratsEnabled) { m_drats = new CDRATSServer(m_localAddress, port, callsign, this); bool ret = m_drats->open(); @@ -237,7 +232,6 @@ m_heardTimer(1000U, 0U, 100U) // 100ms m_drats = NULL; } } -#endif } CRepeaterHandler::~CRepeaterHandler() @@ -250,10 +244,8 @@ CRepeaterHandler::~CRepeaterHandler() #endif delete m_version; -#ifdef USE_DRATS if (m_drats != NULL) m_drats->close(); -#endif } void CRepeaterHandler::initialise(unsigned int maxRepeaters) @@ -272,21 +264,14 @@ void CRepeaterHandler::setIndex(unsigned int index) m_index = index; } -#ifdef USE_DRATS void CRepeaterHandler::add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) -#else -void CRepeaterHandler::add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) -#endif { assert(!callsign.empty()); assert(port > 0U); assert(handler != NULL); -#ifdef USE_DRATS + CRepeaterHandler* repeater = new CRepeaterHandler(callsign, band, address, port, hwType, reflector, atStartup, reconnect, dratsEnabled, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3); -#else - CRepeaterHandler* repeater = new CRepeaterHandler(callsign, band, address, port, hwType, reflector, atStartup, reconnect, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3); -#endif for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] == NULL) { @@ -606,10 +591,8 @@ void CRepeaterHandler::processRepeater(CHeaderData& header) // The Icom heard timer m_heardTimer.stop(); -#ifdef USE_DRATS if (m_drats != NULL) m_drats->writeHeader(header); -#endif // Reset the statistics m_frames = 0U; @@ -836,10 +819,9 @@ void CRepeaterHandler::processRepeater(CAMBEData& data) // CCS gets everything m_ccsHandler->writeAMBE(data); #endif -#ifdef USE_DRATS + if (m_drats != NULL) m_drats->writeData(data); -#endif if (m_aprsWriter != NULL) m_aprsWriter->writeData(m_rptCallsign, data); @@ -1572,10 +1554,10 @@ void CRepeaterHandler::clockInt(unsigned int ms) if (m_repeaterId != 0x00U) { if (m_text.empty()) sendHeard(); -#ifdef USE_DRATS + if (m_drats != NULL) m_drats->writeEnd(); -#endif + sendStats(); diff --git a/Common/RepeaterHandler.h b/Common/RepeaterHandler.h index 6b7e10d..e0422c4 100644 --- a/Common/RepeaterHandler.h +++ b/Common/RepeaterHandler.h @@ -35,9 +35,7 @@ #include "CacheManager.h" #include "HeaderLogger.h" #include "CallsignList.h" -#ifdef USE_DRATS #include "DRATSServer.h" -#endif #include "CCSCallback.h" #include "VersionUnit.h" #ifdef USE_CCS @@ -64,11 +62,7 @@ class CRepeaterHandler : public IRepeaterCallback, public IReflectorCallback, pu public: static void initialise(unsigned int maxRepeaters); -#ifdef USE_DRATS static void add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3); -#else - static void add(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3); -#endif static void setLocalAddress(const std::string& address); static void setG2HandlerPool(CG2ProtocolHandlerPool* handler); @@ -143,11 +137,8 @@ public: virtual void readAPRSFrame(CAPRSFrame& frame); protected: -#ifdef USE_DRATS CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3); -#else - CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3); -#endif + virtual ~CRepeaterHandler(); void resolveUserInt(const std::string& user, const std::string& repeater, const std::string& gateway, const std::string& address); @@ -278,10 +269,8 @@ private: // APRS to DPRS CAPRSUnit* m_aprsUnit; -#ifdef USE_DRATS // D-RATS handler CDRATSServer* m_drats; -#endif // DTMF commands CDTMF m_dtmf; diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 16c7f52..792a6bf 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -240,11 +240,9 @@ bool CDStarGatewayApp::createThread() delete restrictList; } -#ifdef USE_DRATS // Drats TDRats drats; m_config->getDRats(drats); -#endif // Setup the repeaters bool ddEnabled = false; @@ -266,9 +264,7 @@ bool CDStarGatewayApp::createThread() rptrConfig.reflectorAtStartup, rptrConfig.reflectorReconnect, rptrConfig.frequency, - #ifdef USE_DRATS drats.enabled, - #endif rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 635a647..2cfd90a 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -57,9 +57,7 @@ bool CDStarGatewayConfig::load() #endif ret = loadDaemon(cfg) && ret; ret = loadAccessControl(cfg) && ret; -#ifdef USE_DRATS ret = loadDRats(cfg) && ret; -#endif } if(ret) { @@ -363,14 +361,12 @@ bool CDStarGatewayConfig::loadAccessControl(const CConfig & cfg) return ret; } -#ifdef USE_DRATS bool CDStarGatewayConfig::loadDRats(const CConfig & cfg) { bool ret = cfg.getValue("DRats", "enabled", m_drats.enabled, false); return ret; } -#endif bool CDStarGatewayConfig::open(CConfig & cfg) { @@ -479,9 +475,7 @@ void CDStarGatewayConfig::getAccessControl(TAccessControl & accessControl) const accessControl = m_accessControl; } -#if USE_DRATS void CDStarGatewayConfig::getDRats(TDRats & drats) const { drats = m_drats; -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index fdb5339..d148987 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -56,9 +56,7 @@ typedef struct { HW_TYPE hwType; bool reflectorAtStartup; RECONNECT reflectorReconnect; -#ifdef USE_DRATS bool dRatsEnabled; -#endif double frequency; double offset; double range; @@ -115,11 +113,9 @@ typedef struct { bool enabled; } TDCS; -#ifdef USE_DRATS typedef struct { bool enabled; } TDRats; -#endif typedef struct { bool enabled; @@ -170,9 +166,7 @@ public: #endif void getDaemon(TDaemon & gen) const; void getAccessControl(TAccessControl & accessControl) const; -#ifdef USE_DRATS void getDRats(TDRats & drats) const; -#endif private: bool open(CConfig & cfg); @@ -192,9 +186,7 @@ private: #endif bool loadDaemon(const CConfig & cfg); bool loadAccessControl(const CConfig & cfg); -#ifdef USE_DRATS bool loadDRats(const CConfig & cfg); -#endif std::string m_fileName; TGateway m_gateway; @@ -211,9 +203,7 @@ private: #endif TDaemon m_daemon; TAccessControl m_accessControl; -#ifdef USE_DRATS TDRats m_drats; -#endif std::vector m_repeaters; std::vector m_ircDDB; diff --git a/DStarGateway/DStarGatewayThread.cpp b/DStarGateway/DStarGatewayThread.cpp index 73ea87c..1d7d074 100644 --- a/DStarGateway/DStarGatewayThread.cpp +++ b/DStarGateway/DStarGatewayThread.cpp @@ -511,15 +511,10 @@ void CDStarGatewayThread::setGateway(GATEWAY_TYPE gatewayType, const std::string m_gatewayAddress = gatewayAddress; } -#ifdef USE_DRATS + void CDStarGatewayThread::addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) { CRepeaterHandler::add(callsign, band, address, port, hwType, reflector, atStartup, reconnect, dratsEnabled, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3); -#else -void CDStarGatewayThread::addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) -{ - CRepeaterHandler::add(callsign, band, address, port, hwType, reflector, atStartup, reconnect, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3); -#endif std::string repeater = callsign; repeater.resize(LONG_CALLSIGN_LENGTH - 1U); diff --git a/DStarGateway/DStarGatewayThread.h b/DStarGateway/DStarGatewayThread.h index 5b4fe3c..767a81d 100644 --- a/DStarGateway/DStarGatewayThread.h +++ b/DStarGateway/DStarGatewayThread.h @@ -44,11 +44,8 @@ public: virtual ~CDStarGatewayThread(); virtual void setGateway(GATEWAY_TYPE type, const std::string& callsign, const std::string& address); -#ifdef USE_DRATS -virtual void addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1 = 0x00U, unsigned char band2 = 0x00U, unsigned char band3 = 0x00U); -#else - virtual void addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1 = 0x00U, unsigned char band2 = 0x00U, unsigned char band3 = 0x00U); -#endif + virtual void addRepeater(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1 = 0x00U, unsigned char band2 = 0x00U, unsigned char band3 = 0x00U); + #ifdef USE_STARNET #if defined(DEXTRA_LINK) || defined(DCS_LINK) virtual void addStarNet(const std::string& callsign, const std::string& logoff, const std::string& repeater, const std::string& infoText, const std::string& permanent, unsigned int userTimeout, unsigned int groupTimeout, STARNET_CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch, const std::string& reflector); diff --git a/Makefile b/Makefile index 28e98c0..d7daf6d 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,6 @@ export CPPFLAGS+= -DUSE_GPSD export LDFLAGS+= -lgps endif -ifeq ($(USE_DRATS), 1) -export CPPFLAGS+= -DUSE_DRATS -endif - .PHONY: all all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests diff --git a/README.md b/README.md index bc530e6..afda621 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ - [3.4. Prerequisites and dependencies](#34-prerequisites-and-dependencies) - [3.5. Building](#35-building) - [3.5.0.1. Build With GPSD Support](#3501-build-with-gpsd-support) - - [3.5.0.2. Build With DRats Support](#3502-build-with-drats-support) - - [3.5.0.3. Debug Build](#3503-debug-build) + - [3.5.0.2. Debug Build](#3502-debug-build) - [3.6. Installing](#36-installing) - [3.7. Configuring](#37-configuring) - [3.8. Updating host files](#38-updating-host-files) @@ -102,12 +101,7 @@ make ``` make USE_GPS=1 ``` -#### 3.5.0.2. Build With DRats Support -IMHO Drats is a decaying thing, therefore it is included as a build option. -``` -make USE_DRATS=1 -``` -#### 3.5.0.3. Debug Build +#### 3.5.0.2. Debug Build ``` make ENABLE_DEBUG=1 ``` From 464b6e32db74451fece304255d4110080c0e5847 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Tue, 20 Sep 2022 21:23:22 +0200 Subject: [PATCH 104/112] #22 Update Readme --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index afda621..c0cf48c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - [2.3. Thanks](#23-thanks) - [2.4. Features](#24-features) - [2.4.1. Features that where left out :](#241-features-that-where-left-out-) - - [2.4.2. Additional Features :](#242-additional-features-) + - [2.4.2. Additional Features compared to ircddbGateway:](#242-additional-features-compared-to-ircddbgateway) - [3. Building and installing](#3-building-and-installing) - [3.1. Initial setup](#31-initial-setup) - [3.2. Get latest stable version (recommended)](#32-get-latest-stable-version-recommended) @@ -55,12 +55,11 @@ All the features found in ircddbGateway are supposed to be working. Except the o - Starnet: You might consider running [Smart Group Server XL](https://github.com/F4FXL/smart-group-server-xl) from a dedicated computer instead. - Announcement: same can be achieved using VoiceTransmit. - APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not, depending on the system owner wish. I run mostly DStar Only repeaters, having an additional program to maintain is unnecessary burden. -- DRats : Is on the to-do list see [#6](#6) - CallSign Server : this is a legacy from the dead project xreflector.net, I will most probably drop it for good. -### 2.4.2. Additional Features : -- DPlus, DExtra and G2 NAT Traversal using ircddb network as rendez-vous server -- Forward RSMS1A app messages from/to APRS-IS Network, yes you can send/receive messages to and from aprs. +### 2.4.2. Additional Features compared to ircddbGateway: +- DPlus, DExtra and G2 NAT Traversal using ircddb network as rendez-vous server. I.e. it is not required to open firewall ports for Callsign Routing or Gateway calls. however it is still recommended to do so. But NAT Traversal will bring more flexibility when operating on CGNAT (Mobile) Networks. +- Forward RSMS1A app messages from/to APRS-IS Network, yes you can send/receive messages to and from aprs. Yes, you can send messages to APRS stations and Vice Versa. Additionnally part of the message is sent as Text Dat in the slow data. This allows you to read the message dirdclty on your radio screen. - Repeater Link status is sent to APRS-IS as a status frame # 3. Building and installing @@ -144,7 +143,7 @@ The testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 -- [**Improvement**] Add DRats Support (conditional build) ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) +- [**Improvement**] Add DRats Support ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) - [**Improvement**] Add call sign lists ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) - [**Improvement**] Add a way to override Slow Data in VoiceTransmit ([#23](https://github.com/F4FXL/DStarGateway/issues/23)) - [**Improvement**] Add time server @@ -177,9 +176,9 @@ I started this during my 2021 seasons holiday. It took me almost 8 days to get t - No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer - Support for all protocols (G2, DExtra, DPlus) DCS does nto make sense as it was historically never used as protocol for linking repeaters - ☑ Send the connection status to APRS-IS as a status frame -- ☒ Reinstantiate DRATS +- ☑ Reinstantiate DRATS - ☑ Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) - ☒ Automatic refresh of host files - ☒ Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ? -- ☒ Forward messages from RS-MS1A to APRS and vice versa +- ☑ Forward messages from RS-MS1A to APRS and vice versa - Everything that might come handy to make dstar the most powerful system ever :) From ae81a8855e89ebd509e62a988df639a9ee177616 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 21 Sep 2022 11:41:07 +0200 Subject: [PATCH 105/112] Correct some Typos in Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0cf48c..5003389 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ Quite a few classes are more or less copy/paste from each other some sanitizatio All the features found in ircddbGateway are supposed to be working. Except the ones listed below ### 2.4.1. Features that where left out : -- CCS: is still being used? I always considered this as trojan horse to push some DMR Agenda into DStar an more or les a burdain to use. Call sign routing is by far more flexible and superior. +- CCS: is still being used? I always considered this as trojan horse to push some DMR Agenda into DStar and a burdain to use. Call sign routing is by far more flexible and superior. - Starnet: You might consider running [Smart Group Server XL](https://github.com/F4FXL/smart-group-server-xl) from a dedicated computer instead. - Announcement: same can be achieved using VoiceTransmit. -- APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not, depending on the system owner wish. I run mostly DStar Only repeaters, having an additional program to maintain is unnecessary burden. +- APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not, depending on the system owner wish. I run mostly DStar Only repeaters, having an additional program to maintain is unnecessary overkill. - CallSign Server : this is a legacy from the dead project xreflector.net, I will most probably drop it for good. ### 2.4.2. Additional Features compared to ircddbGateway: From 0172c15b4b3952094565f7d683f000f669e6c11e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 5 Oct 2022 21:30:19 +0200 Subject: [PATCH 106/112] #27 fix frequency not being reported correctly --- DStarGateway/DStarGatewayApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 792a6bf..8ce9864 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -263,8 +263,8 @@ bool CDStarGatewayApp::createThread() rptrConfig.reflector, rptrConfig.reflectorAtStartup, rptrConfig.reflectorReconnect, - rptrConfig.frequency, drats.enabled, + rptrConfig.frequency, rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, From 08c7b2816b8c239c5c1894a4798ea4a405736be7 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 5 Oct 2022 21:35:49 +0200 Subject: [PATCH 107/112] #27 fix repeater description not loaded --- DStarGateway/DStarGatewayConfig.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 2cfd90a..b3321d5 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -247,8 +247,8 @@ bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg) ret = cfg.getValue(section, "latitude", repeater->latitude, -90.0, 90.0, m_gateway.latitude) && ret; ret = cfg.getValue(section, "longitude", repeater->longitude, -180.0, 180.0, m_gateway.longitude) && ret; ret = cfg.getValue(section, "agl", repeater->agl, 0, 1000.0, 0.0) && ret; - ret = cfg.getValue(section, "description1", m_gateway.description1, 0, 1024, "") && ret; - ret = cfg.getValue(section, "description2", m_gateway.description2, 0, 1024, "") && ret; + ret = cfg.getValue(section, "description1", repeater->description1, 0, 1024, "") && ret; + ret = cfg.getValue(section, "description2", repeater->description2, 0, 1024, "") && ret; ret = cfg.getValue(section, "url", m_gateway.url, 0, 1024, "") && ret;; ret = cfg.getValue(section, "band1", repeater->band1, 0, 255, 0) && ret; ret = cfg.getValue(section, "band2", repeater->band2, 0, 255, 0) && ret; From 40819ac01498baab562426a59697d19a061cd8d3 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 5 Oct 2022 22:17:24 +0200 Subject: [PATCH 108/112] #27 descriptions default to gw description, fix url not loaded --- DStarGateway/DStarGatewayConfig.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index b3321d5..f50b7fb 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -247,9 +247,9 @@ bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg) ret = cfg.getValue(section, "latitude", repeater->latitude, -90.0, 90.0, m_gateway.latitude) && ret; ret = cfg.getValue(section, "longitude", repeater->longitude, -180.0, 180.0, m_gateway.longitude) && ret; ret = cfg.getValue(section, "agl", repeater->agl, 0, 1000.0, 0.0) && ret; - ret = cfg.getValue(section, "description1", repeater->description1, 0, 1024, "") && ret; - ret = cfg.getValue(section, "description2", repeater->description2, 0, 1024, "") && ret; - ret = cfg.getValue(section, "url", m_gateway.url, 0, 1024, "") && ret;; + ret = cfg.getValue(section, "description1", repeater->description1, 0, 1024, m_gateway.description1) && ret; + ret = cfg.getValue(section, "description2", repeater->description2, 0, 1024, m_gateway.description2) && ret; + ret = cfg.getValue(section, "url", repeater->url, 0, 1024, m_gateway.url) && ret;; ret = cfg.getValue(section, "band1", repeater->band1, 0, 255, 0) && ret; ret = cfg.getValue(section, "band2", repeater->band2, 0, 255, 0) && ret; ret = cfg.getValue(section, "band3", repeater->band3, 0, 255, 0) && ret; From 4849b7a188027b27ad3c522ef284938ccae41d86 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 30 Oct 2022 06:33:39 +0100 Subject: [PATCH 109/112] Crank build jobs to 9 --- .vscode/tasks.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 587d251..bf6c999 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,7 +8,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "ENABLE_DEBUG=1", "USE_GPSD=1", "all" @@ -21,7 +21,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "ENABLE_DEBUG=1", "USE_GPSD=1", "DStarGateway/dstargateway" @@ -37,7 +37,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "ENABLE_DEBUG=1", "USE_GPSD=1", "DGWRemoteControl/dgwremotecontrol" @@ -50,7 +50,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "ENABLE_DEBUG=1", "USE_GPSD=1", "DGWTextTransmit/dgwtexttransmit" @@ -63,7 +63,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "ENABLE_DEBUG=1", "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" @@ -76,7 +76,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "ENABLE_DEBUG=1", "USE_GPSD=1", "DGWVoiceTransmit/dgwvoicetransmit" @@ -89,7 +89,7 @@ "type": "shell", "command": "make", "args": [ - "-j3", + "-j9", "tests", "ENABLE_DEBUG=1", "USE_GPSD=1" From 4e6f3077821d091980e194152c2bd606b347b6c4 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 30 Oct 2022 06:38:32 +0100 Subject: [PATCH 110/112] Add DPRS usage infor --- DGWVoiceTransmit/VoiceTransmit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWVoiceTransmit/VoiceTransmit.cpp b/DGWVoiceTransmit/VoiceTransmit.cpp index 9403756..eb7f6b3 100644 --- a/DGWVoiceTransmit/VoiceTransmit.cpp +++ b/DGWVoiceTransmit/VoiceTransmit.cpp @@ -35,7 +35,7 @@ int main(int argc, const char * argv[]) std::vector filenames; if (!parseCLIArgs(argc, argv, repeater, filenames, text, dprs)) { - ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit [-text text] ..., exiting\n"); + ::fprintf(stderr, "dgwvoicetransmit: invalid command line usage: dgwvoicetransmit [-text text] [-dprs dprs] ..., exiting\n"); return 1; } From 4c1e558b79453f29d1eac40548e61d163d6ec176 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 30 Oct 2022 07:03:40 +0100 Subject: [PATCH 111/112] #26 Add readme for DGW Voice transmit --- DGWVoiceTransmit/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 DGWVoiceTransmit/README.md diff --git a/DGWVoiceTransmit/README.md b/DGWVoiceTransmit/README.md new file mode 100644 index 0000000..8d3d2da --- /dev/null +++ b/DGWVoiceTransmit/README.md @@ -0,0 +1,31 @@ + +DGWVoiceTransmit allow you to transmit .dvtool files through your repeater. +It must be run on the same machine where DStarGateway is running. + +- [1. Usage Examples](#1-usage-examples) + - [1.1. Transmit one or more files](#11-transmit-one-or-more-files) + - [1.2. Override or insert Text Data](#12-override-or-insert-text-data) + - [1.3. Override or insert DPRS Data](#13-override-or-insert-dprs-data) + +# 1. Usage Examples +Here are a few usage examples and explanation +## 1.1. Transmit one or more files +This wil transmit one file after the other tthoug repeater N0CALL B. You can either specify the call sign using spaces or underscores. When using spaces, make sure you put it into quotes. +``` +dgwvoicetransmit N0CALL_B file1.dvtool file2.dvtool +dgwvoicetransmit "N0CALL B" file1.dvtool file2.dvtool +``` +## 1.2. Override or insert Text Data +It is possible to ovveride the text slow data contained in the .dvtool file. For this, use -text flag. If no text data is present, the specified text data will be inserted into the transmsssion +``` +dgwvoicetransmit N0CALL_B -text "Override text data" file1.dvtool +``` + +## 1.3. Override or insert DPRS Data +You cann also override or insert DPRS data. This will cause modern DStar Radio to display a popup with distance, bearing and other DPRS information etc. + +You have to insert an "Icom compatible" DPRS frame. No validatin is made wether the frame is valid or not. +``` +dgwvoicetransmit N0CALL_B -dprs "!4898.03N/00844.64Er/DPRS Comment" file1.dvtool +This can be combined with -text flag. +``` \ No newline at end of file From bc59e3a9c1c7937dfc7a8e08829bbff30b9fa889 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 30 Oct 2022 07:08:35 +0100 Subject: [PATCH 112/112] Changed default task --- .vscode/tasks.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bf6c999..6d6532b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,10 @@ "USE_GPSD=1", "all" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -26,10 +29,7 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, {