diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..f858990 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,33 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/2.0/configuration-reference/#jobs +jobs: + build-dstargateway: + # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor + docker: + - image: cimg/base:stable + # Add steps to the job + # See: https://circleci.com/docs/2.0/configuration-reference/#steps + steps: + - checkout + - run: + name: Install dependencies + command: | + sudo apt-get update + 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" + - run: + name: "Run Tests" + 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: + dstar-gateway-workflow: + jobs: + - build-dstargateway diff --git a/.gitignore b/.gitignore index 522c725..08410df 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ Sandbox/* *.out *.app dstargateway - +Tests/dstargateway_tests +DGWRemoteControl/dgwremotecontrol diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..f243b89 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,25 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/BaseCommon/", + "${workspaceFolder}/APRS/", + "${workspaceFolder}/Common/", + "${workspaceFolder}/DStarBase/", + "${workspaceFolder}/IRCDDB/", + "${workspaceFolder}/VersionInfo/", + "${workspaceFolder}/BaseCommon/", + "${workspaceFolder}/BaseCommon" + ], + "defines": [], + "compilerPath": "/bin/g++", + "cStandard": "gnu17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64", + "configurationProvider": "ms-vscode.makefile-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index b2c785b..5cb32b0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,11 +5,59 @@ "version": "0.2.0", "configurations": [ { - "name": "(gdb) Lancer", + "name": "(gdb) dstargateway", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/dstargateway", - "args": ["${workspaceFolder}/___test.cfg"], + "program": "${workspaceFolder}/DStarGateway/dstargateway", + "args": ["${workspaceFolder}/Sandbox/___test.cfg"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, + { + "name": "(gdb) dgwremotecontrol", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWRemoteControl/dgwremotecontrol", + "args": ["--name", "city_center", "F4FXL B", "link", "never", "DCS208_C"], + "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", + "request": "launch", + "program": "${workspaceFolder}/Tests/dstargateway_tests", + "args": [ ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/.vscode/settings.json b/.vscode/settings.json index d6cf341..42e5956 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,100 @@ "binaryPath": "/home/geoffrey/Documents/Dev/DStarGateway/dstargateway", "binaryArgs": [] } - ] + ], + "files.associations": { + "new": "cpp", + "type_traits": "cpp", + "array": "cpp", + "*.tcc": "cpp", + "chrono": "cpp", + "deque": "cpp", + "list": "cpp", + "vector": "cpp", + "optional": "cpp", + "stop_token": "cpp", + "any": "cpp", + "bitset": "cpp", + "memory": "cpp", + "random": "cpp", + "future": "cpp", + "memory_resource": "cpp", + "ranges": "cpp", + "mutex": "cpp", + "condition_variable": "cpp", + "forward_list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "algorithm": "cpp", + "cctype": "cpp", + "cstddef": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "atomic": "cpp", + "bit": "cpp", + "compare": "cpp", + "cstdint": "cpp", + "exception": "cpp", + "functional": "cpp", + "iterator": "cpp", + "string_view": "cpp", + "tuple": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "typeinfo": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "complex": "cpp", + "concepts": "cpp", + "cstdio": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwctype": "cpp", + "istream": "cpp", + "numbers": "cpp", + "ratio": "cpp", + "semaphore": "cpp", + "streambuf": "cpp", + "system_error": "cpp", + "thread": "cpp", + "typeindex": "cpp", + "variant": "cpp", + "iostream": "cpp", + "fstream": "cpp" + }, + "editor.tokenColorCustomizations": { + "textMateRules": [ + { + "scope": "googletest.failed", + "settings": { + "foreground": "#f00" + } + }, + { + "scope": "googletest.passed", + "settings": { + "foreground": "#0f0" + } + }, + { + "scope": "googletest.run", + "settings": { + "foreground": "#0f0" + } + } + ] + }, + "gtest-adapter.debugConfig": [ + "Tests" + ], + "gtest-adapter.supportLocation": true } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..46b9b3b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,62 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build All", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "all" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Build DStarGateway", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DStarGateway/dstargateway" + ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build DGWRemoteControl", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWRemoteControl/dgwremotecontrol" + ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build Tests", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "tests", + "ENABLE_DEBUG=1", + "USE_GPSD=1" + ], + "group": "build", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/APRS/APRSFormater.cpp b/APRS/APRSFormater.cpp new file mode 100644 index 0000000..359ca3e --- /dev/null +++ b/APRS/APRSFormater.cpp @@ -0,0 +1,47 @@ +/* + * 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 "APRSFormater.h" +#include "Log.h" +#include "StringUtils.h" + +bool CAPRSFormater::frameToString(std::string& output, CAPRSFrame& frame) +{ + // make sur we have the minimal stuff to build a correct aprs string + if(frame.getSource().empty() + || frame.getDestination().empty() + || frame.getBody().empty()) { + CLog::logWarning("Invalid APRS frame, missing source, destination or body"); + return false; + } + + auto path = boost::join_if(frame.getPath(), ",", [](std::string s) { return !string_is_blank_or_empty(s); }); + + CStringUtils::string_format_in_place(output, "%s>%s%s%s:%s", + frame.getSource().c_str(), + frame.getDestination().c_str(), + path.empty() ? "" : ",", + path.c_str(), + frame.getBody().c_str()); + + return true; +} + diff --git a/APRS/APRSFormater.h b/APRS/APRSFormater.h new file mode 100644 index 0000000..c42bf0c --- /dev/null +++ b/APRS/APRSFormater.h @@ -0,0 +1,27 @@ +/* + * 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 "APRSFrame.h" + +class CAPRSFormater +{ +public: + static bool frameToString(std::string& output, CAPRSFrame& frame); +}; \ No newline at end of file diff --git a/APRS/APRSFrame.cpp b/APRS/APRSFrame.cpp new file mode 100644 index 0000000..a28ba4c --- /dev/null +++ b/APRS/APRSFrame.cpp @@ -0,0 +1,53 @@ +/* + * 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 "APRSFrame.h" + +CAPRSFrame::CAPRSFrame() : +m_source(), +m_destination(), +m_path(), +m_body(), +m_type(APFT_UNKNOWN) +{ + +} + +CAPRSFrame::CAPRSFrame(const std::string& source, const std::string& destination, const std::vector& path, const std::string& body, APRS_FRAME_TYPE type) : +m_source(source), +m_destination(destination), +m_path(), +m_body(body), +m_type(type) +{ + m_path.assign(path.begin(), path.end()); +} + +CAPRSFrame::~CAPRSFrame() +{ + m_path.clear(); +} + +void CAPRSFrame::clear() +{ + m_source.clear(); + m_destination.clear(); + m_path.clear(); + m_body.clear(); + m_type = APFT_UNKNOWN; +} \ No newline at end of file diff --git a/APRS/APRSFrame.h b/APRS/APRSFrame.h new file mode 100644 index 0000000..ca6ec85 --- /dev/null +++ b/APRS/APRSFrame.h @@ -0,0 +1,55 @@ +/* + * 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 +#include + +// We only support these types for now +enum APRS_FRAME_TYPE { + APFT_UNKNOWN = 0, + APFT_MESSAGE, + APFT_POSITION, + APFT_NMEA, + APFT_STATUS, + APFT_OBJECT, + APFT_WX, + APFT_TELEMETRY +}; + +class CAPRSFrame { +public: + CAPRSFrame(); + CAPRSFrame(const std::string& source, const std::string& destination, const std::vector& path, const std::string& body, APRS_FRAME_TYPE type); + ~CAPRSFrame(); + + void clear(); + std::string& getSource() { return m_source; } + std::string& getDestination() { return m_destination; } + std::vector& getPath() { return m_path; } + std::string& getBody() { return m_body; } + APRS_FRAME_TYPE& getType() { return m_type; } + +private: + std::string m_source; + std::string m_destination; + std::vector m_path; + std::string m_body; + APRS_FRAME_TYPE m_type; +}; \ No newline at end of file diff --git a/APRS/APRSParser.cpp b/APRS/APRSParser.cpp new file mode 100644 index 0000000..0eeaabd --- /dev/null +++ b/APRS/APRSParser.cpp @@ -0,0 +1,160 @@ +/* + * 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 "APRSParser.h" +#include "Log.h" + +bool CAPRSParser::parseFrame(const std::string& frameStr, CAPRSFrame& frame) +{ + frame.clear(); + bool ret = false; + + if(!frameStr.empty()) { + auto pos = frameStr.find_first_of(':'); + if(pos != std::string::npos && pos != frameStr.length() - 1) { + auto header = frameStr.substr(0, pos); // contains source, dest and path + auto body = frameStr.substr(pos +1); + + std::vector headerSplits; + boost::split(headerSplits, header, [](char c) { return c == ',' || c == '>';}); + + //we need at least source and dest to form a valid frame, also headers shall not contain empty strings + if(headerSplits.size() >= 2 && std::none_of(headerSplits.begin(), headerSplits.end(), [](std::string s){ return s.empty(); })) { + frame.getSource().assign(headerSplits[0]); + frame.getDestination().assign(headerSplits[1]); + + for(unsigned int i = 2; i < headerSplits.size(); i++) { + frame.getPath().push_back(headerSplits[i]); + } + + frame.getBody().assign(body); + + ret = parseInt(frame); + if(!ret) { + frame.clear(); + } + } + } + } + + return ret; +} + +bool CAPRSParser::parseInt(CAPRSFrame& frame) +{ + APRS_FRAME_TYPE type = APFT_UNKNOWN; + unsigned char typeChar = frame.getBody()[0]; + std::string body(frame.getBody().substr(1));//strip the type char for processing purposes + + if(body.empty()) + return false; + + switch (typeChar) + { + case '!': + if(body[0] == '!') { + // This is ultimeter 200 weather station + return false; + } + [[fallthrough]]; + case '=': + case '/': + case '@': + { + if(body.length() < 10) return false;//enough chars to have a chance to parse it ? + /* Normal or compressed location packet, with or without + * timestamp, with or without messaging capability + * + * ! and / have messaging, / and @ have a prepended timestamp + */ + type = APFT_POSITION; + if(typeChar == '/' || typeChar== '@')//With a prepended timestamp, jump over it. + body = body.substr(7U); + + auto posChar = body[0]; + if(valid_sym_table_compressed(posChar)//Compressed format + && body.length() >= 13){//we need at least 13 char + //icom unsupported, ignore for now + return false;//parse_aprs_compressed(pb, body, body_end); + } + else if(posChar >= '0' && posChar <= '9' //Normal uncompressed format + && body.length() >=19){//we need at least 19 chars for it to be valid + + // if(ensureIsIcomCompatible(packet)) + // return Parse(packet.Raw(), packet); + } + } + break; + case '$' : + if(body.length() > 10) { + type = APFT_NMEA; + } + break; + case ':': + // we have either message or telemetry labels or telemetry EQNS + if(body[9] == ':' + && std::all_of(body.begin(), body.begin() + 9, [](char c){ return c == ' ' || c == '-' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); })) { + type = APFT_MESSAGE; + + //If reciepient is same as source and we donot have a sequence number at the end of message, Then it is telemetry + if(body.find(frame.getSource()) == 0U) { + auto eqnsPos = body.find("EQNS."); + auto parmPos = body.find("PARM."); + auto seqNumPos = body.find_last_of('{'); + if((eqnsPos == 10U || parmPos == 10U) && seqNumPos == std::string::npos) { + type = APFT_TELEMETRY; + } + } + } + break; + case '>': + type = APFT_STATUS; + break; + case '#': /* Peet Bros U-II Weather Station */ + case '*': /* Peet Bros U-I Weather Station */ + case '_': /* Weather report without position */ + type = APFT_WX; + break; + case '{': + type = APFT_UNKNOWN; // + break; + case 'T': + if(body[0] == '#') { + type = APFT_TELEMETRY; + } + break; + default: + type = APFT_UNKNOWN; + break; + } + + frame.getType() = type; + return type != APFT_UNKNOWN; +} + +bool CAPRSParser::valid_sym_table_compressed(unsigned char c) +{ + return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A) + || (c >= 0x61 && c <= 0x6A)); /* [\/\\A-Za-j] */ +} + +bool CAPRSParser::valid_sym_table_uncompressed(unsigned char c) +{ + return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A) + || (c >= 0x30 && c <= 0x39)); /* [\/\\A-Z0-9] */ +} \ No newline at end of file diff --git a/APRS/APRSParser.h b/APRS/APRSParser.h new file mode 100644 index 0000000..6414ffb --- /dev/null +++ b/APRS/APRSParser.h @@ -0,0 +1,35 @@ +/* + * 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 +#include + +#include "APRSFrame.h" + +class CAPRSParser +{ +public: + static bool parseFrame(const std::string& frameStr, CAPRSFrame& frame); + +private: + static bool parseInt(CAPRSFrame& frame); + static bool valid_sym_table_compressed(unsigned char c); + static bool valid_sym_table_uncompressed(unsigned char c); +}; \ No newline at end of file diff --git a/APRS/APRSUtils.cpp b/APRS/APRSUtils.cpp new file mode 100644 index 0000000..2309344 --- /dev/null +++ b/APRS/APRSUtils.cpp @@ -0,0 +1,57 @@ +/* + * 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" + +void CAPRSUtils::dstarCallsignToAPRS(std::string& dstarCallsign) +{ + if(dstarCallsign[dstarCallsign.length() - 1] == ' ') { + boost::trim(dstarCallsign); + } else { + //loop until got rid of all double blanks + while(dstarCallsign.find(" ") != std::string::npos) { + boost::replace_all(dstarCallsign, " ", " "); + } + boost::replace_all(dstarCallsign, " ", "-");//replace remaining blank with a - + } +} + +unsigned int CAPRSUtils::calcGPSAIcomCRC(const std::string& gpsa) +{ + unsigned int icomcrc = 0xFFFFU; + + auto length = gpsa.length(); + for (unsigned int j = 10U; j < length; j++) { + unsigned char ch = (unsigned char)gpsa[j]; + + for (unsigned int i = 0U; i < 8U; i++) { + bool xorflag = (((icomcrc ^ ch) & 0x01U) == 0x01U); + + icomcrc >>= 1; + + if (xorflag) + icomcrc ^= 0x8408U; + + ch >>= 1; + } + } + + return ~icomcrc & 0xFFFFU; +} diff --git a/APRS/APRSUtils.h b/APRS/APRSUtils.h new file mode 100644 index 0000000..9455ac9 --- /dev/null +++ b/APRS/APRSUtils.h @@ -0,0 +1,28 @@ +/* + * 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 + +class CAPRSUtils +{ +public: + static void dstarCallsignToAPRS(std::string& dstarCallsign); + static unsigned int calcGPSAIcomCRC(const std::string& gpsa); +}; \ No newline at end of file diff --git a/APRS/Makefile b/APRS/Makefile new file mode 100644 index 0000000..d72b824 --- /dev/null +++ b/APRS/Makefile @@ -0,0 +1,12 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +APRS.a: $(OBJS) + $(AR) rcs APRS.a $(OBJS) + +%.o : %.cpp + $(CC) -I../BaseCommon $(CPPFLAGS) -MMD -MD -c $< -o $@ + +clean: + $(RM) *.o *.d APRS.a \ No newline at end of file diff --git a/APRSCollector.cpp b/APRSCollector.cpp deleted file mode 100644 index 8af6d24..0000000 --- a/APRSCollector.cpp +++ /dev/null @@ -1,561 +0,0 @@ -/* - * Copyright (C) 2010,2012,2018 by Jonathan Naylor G4KLX - * 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 - -#include "APRSCollector.h" -#include "DStarDefines.h" -#include "Utils.h" - -const unsigned int APRS_CSUM_LENGTH = 4U; -const unsigned int APRS_DATA_LENGTH = 300U; -const unsigned int SLOW_DATA_BLOCK_LENGTH = 6U; - -const char APRS_OVERLAY = '\\'; -const char APRS_SYMBOL = 'K'; - -CAPRSCollector::CAPRSCollector() : -m_state(AS_NONE), -m_ggaData(NULL), -m_ggaLength(0U), -m_ggaValid(false), -m_rmcData(NULL), -m_rmcLength(0U), -m_rmcValid(false), -m_crcData(NULL), -m_crcLength(0U), -m_crcValid(false), -m_buffer(NULL), -m_slowData(SS_FIRST), -m_collector(), -m_callsign() -{ - m_ggaData = new unsigned char[APRS_DATA_LENGTH]; - m_rmcData = new unsigned char[APRS_DATA_LENGTH]; - m_crcData = new unsigned char[APRS_DATA_LENGTH]; - m_buffer = new unsigned char[SLOW_DATA_BLOCK_LENGTH]; -} - -CAPRSCollector::~CAPRSCollector() -{ - delete[] m_ggaData; - delete[] m_rmcData; - delete[] m_crcData; - delete[] m_buffer; -} - -void CAPRSCollector::writeHeader(const std::string& callsign) -{ - m_callsign = callsign; -} - -bool CAPRSCollector::writeData(const unsigned char* data) -{ - assert(data != NULL); - - switch (m_slowData) { - case SS_FIRST: - m_buffer[0U] = data[0U] ^ SCRAMBLER_BYTE1; - m_buffer[1U] = data[1U] ^ SCRAMBLER_BYTE2; - m_buffer[2U] = data[2U] ^ SCRAMBLER_BYTE3; - m_slowData = SS_SECOND; - return false; - - case SS_SECOND: - m_buffer[3U] = data[0U] ^ SCRAMBLER_BYTE1; - m_buffer[4U] = data[1U] ^ SCRAMBLER_BYTE2; - m_buffer[5U] = data[2U] ^ SCRAMBLER_BYTE3; - m_slowData = SS_FIRST; - break; - } - - // Is it GPS data? - if ((m_buffer[0U] & SLOW_DATA_TYPE_MASK) == SLOW_DATA_TYPE_GPS) - return addGPSData(m_buffer + 1U); - - return false; -} - -void CAPRSCollector::reset() -{ - m_state = AS_NONE; - m_ggaLength = 0U; - m_ggaValid = false; - m_rmcLength = 0U; - m_rmcValid = false; - m_crcLength = 0U; - m_crcValid = false; - m_slowData = SS_FIRST; - m_collector.clear(); - m_callsign.clear(); -} - -void CAPRSCollector::sync() -{ - m_slowData = SS_FIRST; -} - -bool CAPRSCollector::addGPSData(const unsigned char* data) -{ - assert(data != NULL); - - m_collector += std::string((char*)data, 5U); - - if (m_state == AS_GGA) { - addGGAData(); - return false; - } else if (m_state == AS_RMC) { - return addRMCData(); - } else if (m_state == AS_CRC) { - return addCRCData(); - } - - if (m_state != AS_GGA && m_collector.find("$GPGGA") != std::string::npos) { - m_state = AS_GGA; - m_ggaLength = 0U; - m_ggaValid = false; - m_rmcLength = 0U; - m_rmcValid = false; - return false; - } else if (m_state != AS_RMC && m_collector.find("$GPRMC") != std::string::npos) { - m_state = AS_RMC; - m_rmcLength = 0U; - m_rmcValid = false; - return false; - } else if (m_state != AS_CRC && m_collector.find("$$CRC") != std::string::npos) { - m_state = AS_CRC; - m_crcLength = 0U; - m_crcValid = false; - } - - return false; -} - -void CAPRSCollector::addGGAData() -{ - std::string::size_type n2 = m_collector.find_last_of('\x0A'); - if (n2 == std::string::npos) - return; - - std::string::size_type n1 = m_collector.find("$GPGGA"); - if (n1 == std::string::npos) - return; - - if (n2 < n1) - return; - - std::string::size_type len = n2 - n1; - - if (len >= APRS_DATA_LENGTH) { - m_ggaLength = 0U; - m_ggaValid = false; - m_state = AS_NONE; - return; - } - - m_ggaLength = 0U; - for (unsigned int i = n1; i <= n2; i++) { - m_ggaData[m_ggaLength] = m_collector[i]; - m_ggaData[m_ggaLength] &= 0x7FU; - m_ggaLength++; - } - - bool ret = checkXOR(m_ggaData + 1U, m_ggaLength - 1U); - if (ret) { - // CUtils::dump("$GPGGA Valid", m_ggaData, m_ggaLength); - m_ggaValid = true; - m_state = AS_RMC; - } else { - // CUtils::dump("$GPGGA Bad checksum", m_ggaData, m_ggaLength); - m_ggaLength = 0U; - m_ggaValid = false; - m_state = AS_RMC; - } - - m_collector = m_collector.substr(n2); -} - -bool CAPRSCollector::addRMCData() -{ - std::string::size_type n2 = m_collector.find_last_of('\x0A'); - if (n2 == std::string::npos) - return false; - - std::string::size_type n1 = m_collector.find("$GPRMC"); - if (n1 == std::string::npos) - return false; - - if (n2 < n1) - return false; - - unsigned int len = n2 - n1; - - if (len >= APRS_DATA_LENGTH) { - m_rmcLength = 0U; - m_rmcValid = false; - m_state = AS_NONE; - return false; - } - - m_rmcLength = 0U; - for (unsigned int i = n1; i <= n2; i++) { - m_rmcData[m_rmcLength] = m_collector[i]; - m_rmcData[m_rmcLength] &= 0x7FU; - m_rmcLength++; - } - - bool ret = checkXOR(m_rmcData + 1U, m_rmcLength - 1U); - if (ret) { - // CUtils::dump("$GPRMC Valid", m_rmcData, m_rmcLength); - m_rmcValid = true; - } else { - // CUtils::dump("$GPRMC Bad checksum", m_rmcData, m_rmcLength); - m_rmcLength = 0U; - m_rmcValid = false; - } - - m_collector = m_collector.substr(n2); - - m_state = AS_NONE; - - return true; -} - -bool CAPRSCollector::addCRCData() -{ - std::string::size_type n2 = m_collector.find_last_of('\x0D'); - if (n2 == std::string::npos) - return false; - - std::string::size_type n1 = m_collector.find("$$CRC"); - if (n1 == std::string::npos) - return false; - - if (n2 < n1) - return false; - - unsigned int len = n2 - n1; - - if (len >= APRS_DATA_LENGTH) { - m_crcLength = 0U; - m_crcValid = false; - m_state = AS_NONE; - return false; - } - - m_crcLength = 0U; - for (unsigned int i = n1; i <= n2; i++) { - m_crcData[m_crcLength] = m_collector[i]; - m_crcLength++; - } - - bool ret = checkCRC(m_crcData, m_crcLength); - if (ret) { - // CUtils::dump("$$CRC Valid", m_crcData, m_crcLength); - m_crcValid = true; - m_state = AS_NONE; - m_collector = m_collector.substr(n2); - return true; - } else { - // CUtils::dump("$$CRC Bad checksum", m_crcData, m_crcLength); - m_crcLength = 0U; - m_crcValid = false; - m_state = AS_NONE; - m_collector = m_collector.substr(n2); - return false; - } -} - -unsigned int CAPRSCollector::getData(unsigned char* data, unsigned int length) -{ - assert(data != NULL); - - // Have we got GPS-A data? - if (m_crcValid) { - unsigned int len = m_crcLength - 10U; - if (len > length) - len = length; - - ::memcpy(data, m_crcData + 10U, len); - - m_crcLength = 0U; - m_crcValid = false; - - return len; - } - - // Have we got GGA data? - if (m_ggaValid) { - unsigned int len = convertNMEA1(data, length); - - m_ggaLength = 0U; - m_rmcLength = 0U; - m_ggaValid = false; - m_rmcValid = false; - - return len; - } - - // Have we got RMC data? - if (m_rmcValid) { - unsigned int len = convertNMEA2(data, length); - - m_ggaLength = 0U; - m_rmcLength = 0U; - m_ggaValid = false; - m_rmcValid = false; - - return len; - } - - return 0U; -} - -bool CAPRSCollector::checkXOR(const unsigned char* data, unsigned int length) const -{ - unsigned int posStar = 0U; - for (unsigned int i = length - 1U; i > 0U; i--) { - if (data[i] == '*') { - posStar = i; - break; - } - } - - if (posStar == 0U) - return false; - - unsigned char csum = calcXOR(data, posStar); - - char buffer[10U]; - ::sprintf(buffer, "%02X", csum); - - return ::memcmp(buffer, data + posStar + 1U, 2U) == 0; -} - -unsigned char CAPRSCollector::calcXOR(const unsigned char* buffer, unsigned int length) const -{ - assert(buffer != NULL); - assert(length > 0U); - - unsigned char res = 0U; - - for (unsigned int i = 0U; i < length; i++) - res ^= buffer[i]; - - return res; -} - -bool CAPRSCollector::checkCRC(const unsigned char* data, unsigned int length) const -{ - unsigned int csum = calcCRC(data + 10U, length - 10U); - - char buffer[10U]; - ::sprintf(buffer, "%04X", csum); - - return ::memcmp(buffer, data + 5U, APRS_CSUM_LENGTH) == 0; -} - -unsigned int CAPRSCollector::calcCRC(const unsigned char* buffer, unsigned int length) const -{ - assert(buffer != NULL); - assert(length > 0U); - - unsigned int icomcrc = 0xFFFFU; - - for (unsigned int j = 0U; j < length; j++) { - unsigned char ch = buffer[j]; - - for (unsigned int i = 0U; i < 8U; i++) { - bool xorflag = (((icomcrc ^ ch) & 0x01U) == 0x01U); - - icomcrc >>= 1; - - if (xorflag) - icomcrc ^= 0x8408U; - - ch >>= 1; - } - } - - return ~icomcrc & 0xFFFFU; -} - -unsigned int CAPRSCollector::convertNMEA1(unsigned char* data, unsigned int) -{ - // Parse the $GPGGA string into tokens - char* pGGA[20U]; - ::memset(pGGA, 0x00U, 20U * sizeof(char*)); - unsigned int nGGA = 0U; - - char* str = (char*)m_ggaData; - while (nGGA < 20U) { - char* p = mystrsep(&str, ",\r\n"); - - pGGA[nGGA++] = p; - if (p == NULL) - break; - } - - // Is there any position data? - if (pGGA[2U] == NULL || pGGA[3U] == NULL || pGGA[4U] == NULL || pGGA[5U] == NULL || ::strlen(pGGA[2U]) == 0U || ::strlen(pGGA[3U]) == 0U || ::strlen(pGGA[4U]) == 0 || ::strlen(pGGA[5U]) == 0) - return 0U; - - // Is it a valid GPS fix? - if (::strcmp(pGGA[6U], "0") == 0) - return 0U; - - char callsign[10U]; - dstarCallsignToAPRS(m_callsign, callsign); - - ::sprintf((char*)data, "%s>APDPRS,DSTAR*:!%.7s%s%c%.8s%s%c", callsign, pGGA[2U], pGGA[3U], APRS_OVERLAY, pGGA[4U], pGGA[5U], APRS_SYMBOL); - - // Get the bearing and speed from the RMC data - if (m_rmcValid) { - // Parse the $GPRMC string into tokens - char* pRMC[20U]; - ::memset(pRMC, 0x00U, 20U * sizeof(char*)); - unsigned int nRMC = 0U; - - str = (char*)m_rmcData; - for (;;) { - char* p = mystrsep(&str, ",\r\n"); - - pRMC[nRMC++] = p; - if (p == NULL) - break; - } - - // Check that we have a bearing and speed - if (pRMC[7U] != NULL && pRMC[8U] != NULL && ::strlen(pRMC[7U]) > 0U && ::strlen(pRMC[8U]) > 0U) { - int bearing = ::atoi(pRMC[8U]); - int speed = ::atoi(pRMC[7U]); - - ::sprintf((char*)data + ::strlen((char*)data), "%03d/%03d", bearing, speed); - } - } - - if (pGGA[9U] != NULL && ::strlen(pGGA[9U]) > 0U) { - // Convert altitude from metres to feet - int altitude = ::atoi(pGGA[9U]); - ::sprintf((char*)data + ::strlen((char*)data), "/A=%06.0f", float(altitude) * 3.28F); - } - - return ::strlen((char*)data); -} - -unsigned int CAPRSCollector::convertNMEA2(unsigned char* data, unsigned int) -{ - // Parse the $GPRMC string into tokens - char* pRMC[20U]; - ::memset(pRMC, 0x00U, 20U * sizeof(char*)); - unsigned int nRMC = 0U; - - char* str = (char*)m_rmcData; - while (nRMC < 20U) { - char* p = mystrsep(&str, ",\r\n"); - - pRMC[nRMC++] = p; - if (p == NULL) - break; - } - - // Is there any position data? - if (pRMC[3U] == NULL || pRMC[4U] == NULL || pRMC[5U] == NULL || pRMC[6U] == NULL || ::strlen(pRMC[3U]) == 0U || ::strlen(pRMC[4U]) == 0U || ::strlen(pRMC[5U]) == 0 || ::strlen(pRMC[6U]) == 0) - return 0U; - - // Is it a valid GPS fix? - if (::strcmp(pRMC[2U], "A") != 0) - return 0U; - - char callsign[10U]; - dstarCallsignToAPRS(m_callsign, callsign); - - ::sprintf((char*)data, "%s>APDPRS,DSTAR*:!%.7s%s%c%.8s%s%c", callsign, pRMC[3U], pRMC[4U], APRS_OVERLAY, pRMC[5U], pRMC[6U], APRS_SYMBOL); - - if (pRMC[7U] != NULL && pRMC[8U] != NULL && ::strlen(pRMC[7U]) > 0U && ::strlen(pRMC[8U]) > 0U) { - int bearing = ::atoi(pRMC[8U]); - int speed = ::atoi(pRMC[7U]); - - ::sprintf((char*)data + ::strlen((char*)data), "%03d/%03d", bearing, speed); - } - - return ::strlen((char*)data); -} - -void CAPRSCollector::dstarCallsignToAPRS(const std::string& dstarCallsign, char* aprsCallsign) const -{ - assert(aprsCallsign != NULL); - - std::string dstarcallsignTmp(dstarCallsign); - - if(dstarcallsignTmp[dstarcallsignTmp.length() - 1] == ' ') { - boost::trim(dstarcallsignTmp); - } else { - //loop until got rid of all double blanks - while(dstarcallsignTmp.find(" ") != std::string::npos) { - boost::replace_all(dstarcallsignTmp, " ", " "); - } - boost::replace_all(dstarcallsignTmp, " ", "-");//replace remaining blank with a - - } - - unsigned int i = 0U; - for (; i < dstarcallsignTmp.length(); i++) { - aprsCallsign[i] = dstarcallsignTmp[i]; - } - aprsCallsign[i] = '\0'; - - - // std::string first = dstarCallsign.BeforeFirst(wxT(' ')); - // std::string last = dstarCallsign.AfterLast(wxT(' ')); - - // if (last.empty() || first == last) { - // unsigned int n = 0U; - // for (unsigned int i = 0U; i < first.length(); i++) - // aprsCallsign[n++] = first[i]; - // aprsCallsign[n++] = '\0'; - // } else { - // unsigned int n = 0U; - // for (unsigned int i = 0U; i < first.length(); i++) - // aprsCallsign[n++] = first[i]; - // aprsCallsign[n++] = '-'; - // for (unsigned int i = 0U; i < last.length(); i++) - // aprsCallsign[n++] = last[i]; - // aprsCallsign[n++] = '\0'; - // } -} - -// Source found at -char* CAPRSCollector::mystrsep(char** sp, const char* sep) const -{ - if (sp == NULL || *sp == NULL || **sp == '\0') - return NULL; - - char* s = *sp; - char* p = s + ::strcspn(s, sep); - - if (*p != '\0') - *p++ = '\0'; - - *sp = p; - - return s; -} diff --git a/APRSCollector.h b/APRSCollector.h deleted file mode 100644 index ec44543..0000000 --- a/APRSCollector.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2010,2012,2018 by Jonathan Naylor G4KLX - * 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. - */ - -#ifndef APRSCollector_H -#define APRSCollector_H - -#include "Defs.h" - -enum APRS_STATE { - AS_NONE, - AS_GGA, - AS_RMC, - AS_CRC -}; - -class CAPRSCollector { -public: - CAPRSCollector(); - ~CAPRSCollector(); - - void writeHeader(const std::string& callsign); - - bool writeData(const unsigned char* data); - - void reset(); - - void sync(); - - unsigned int getData(unsigned char* data, unsigned int length); - -private: - APRS_STATE m_state; - unsigned char* m_ggaData; - unsigned int m_ggaLength; - bool m_ggaValid; - unsigned char* m_rmcData; - unsigned int m_rmcLength; - bool m_rmcValid; - unsigned char* m_crcData; - unsigned int m_crcLength; - bool m_crcValid; - unsigned char* m_buffer; - SLOWDATA_STATE m_slowData; - std::string m_collector; - std::string m_callsign; - - bool addGPSData(const unsigned char* data); - - void addGGAData(); - bool addRMCData(); - bool addCRCData(); - - bool checkXOR(const unsigned char* data, unsigned int length) const; - unsigned char calcXOR(const unsigned char* buffer, unsigned int length) const; - - bool checkCRC(const unsigned char* data, unsigned int length) const; - unsigned int calcCRC(const unsigned char* buffer, unsigned int length) const; - - unsigned int convertNMEA1(unsigned char* data, unsigned int length); - unsigned int convertNMEA2(unsigned char* data, unsigned int length); - - void dstarCallsignToAPRS(const std::string& dstarCallsign, char* aprsCallsign) const; - - char* mystrsep(char** sp, const char* sep) const; -}; - -#endif diff --git a/CCITTChecksum.cpp b/BaseCommon/CCITTChecksum.cpp similarity index 100% rename from CCITTChecksum.cpp rename to BaseCommon/CCITTChecksum.cpp diff --git a/CCITTChecksum.h b/BaseCommon/CCITTChecksum.h similarity index 100% rename from CCITTChecksum.h rename to BaseCommon/CCITTChecksum.h diff --git a/Config.cpp b/BaseCommon/Config.cpp similarity index 94% rename from Config.cpp rename to BaseCommon/Config.cpp index 210d26a..ffe8468 100644 --- a/Config.cpp +++ b/BaseCommon/Config.cpp @@ -43,7 +43,7 @@ bool CConfig::load() std::ifstream file; file.open(m_filename, std::ios::in); if(!file.is_open()) { - CLog::logError("Failed to open configuration file %s", m_filename); + CLog::logError("Failed to open configuration file %s", m_filename.c_str()); return false; } @@ -93,33 +93,22 @@ void CConfig::stripComment(std::string& s) const free(sdup);// could we use delete sdup here? } -TConfigValue * CConfig::readKeyAndValue(const std::string s) const +TConfigValue * CConfig::readKeyAndValue(const std::string& s) const { TConfigValue* res = nullptr; - - char * sdup = strdup(boost::trim_copy(s).c_str()); - - char * keyPtr = strtok(sdup, "="); - std::string key(keyPtr != nullptr ? keyPtr : ""); - - boost::trim(key); - - if(!key.empty()) { - char * valuePtr = strtok(nullptr, "="); - std::string value(valuePtr != nullptr? valuePtr : ""); - + auto sCopy = boost::trim_copy(s); + auto equalPos = sCopy.find_first_of('='); + if(equalPos != std::string::npos) { res = new TConfigValue; - res->m_key = key; - res->m_value = boost::trim_copy(value); + res->m_key.assign(sCopy.substr(0, equalPos)); + res->m_value.assign(sCopy.substr(equalPos + 1)); } - - free(sdup);// could we use delete sdup here? - return res; } bool CConfig::getValue(const std::string §ion, const std::string& key, bool &value, bool defaultValue) const { + value = defaultValue; std::string valueTemp; std::string dafaultValueStr = defaultValue ? "true" : "false"; bool ret = getValue(section, key, valueTemp, dafaultValueStr, {"true", "1", "false", "0"}); diff --git a/Config.h b/BaseCommon/Config.h similarity index 98% rename from Config.h rename to BaseCommon/Config.h index d25bca6..aabf432 100644 --- a/Config.h +++ b/BaseCommon/Config.h @@ -106,7 +106,7 @@ public: private: void stripComment(std::string& s) const; - TConfigValue * readKeyAndValue(const std::string s) const; + TConfigValue * readKeyAndValue(const std::string& s) const; TConfigValue * lookupValue(const std::string& section, const std::string& key) const; std::string m_filename; diff --git a/Log.cpp b/BaseCommon/Log.cpp similarity index 100% rename from Log.cpp rename to BaseCommon/Log.cpp diff --git a/Log.h b/BaseCommon/Log.h similarity index 73% rename from Log.h rename to BaseCommon/Log.h index 26414c8..e2afcc1 100644 --- a/Log.h +++ b/BaseCommon/Log.h @@ -19,11 +19,11 @@ #pragma once +#include #include #include #include #include -#include #include #include "StringUtils.h" @@ -36,45 +36,46 @@ private: static bool m_addedTargets; static std::recursive_mutex m_targetsMutex; - static void getTimeStamp(std::string & s); + static void getTimeStamp(std::string& s); - template static void formatLogMessage(std::string& output, LOG_SEVERITY severity, const std::string & f, Args... args) + template + static void formatLogMessage(std::string& output, LOG_SEVERITY severity, const std::string & f, Args... args) { assert(severity != LOG_NONE); - std::string severityStr; + std::string severityStr(" "); switch (severity) { case LOG_DEBUG: - severityStr = "DEBUG "; + severityStr.assign("DEBUG "); break; case LOG_ERROR: - severityStr = "ERROR "; + severityStr.assign("ERROR "); break; case LOG_FATAL: - severityStr = "FATAL "; + severityStr.assign("FATAL "); break; case LOG_INFO : - severityStr = "INFO "; + severityStr.assign("INFO "); break; case LOG_WARNING: - severityStr = "WARNING"; + severityStr.assign("WARNING"); break; case LOG_TRACE: - severityStr = "TRACE "; + severityStr.assign("TRACE "); break; default: break; } - std::string message = CStringUtils::string_format(f, args...); - boost::trim(message); - std::string timeUtc; - getTimeStamp(timeUtc); - std::stringstream s; - s << "[" << timeUtc << "] [" << severityStr << "] " << message << std::endl; + std::string timestamp; + getTimeStamp(timestamp); - output = s.str(); + std::string f2("[%s] [%s] "); + f2.append(f); + CStringUtils::string_format_in_place(output, f2, timestamp.c_str(), severityStr.c_str(), args...); + boost::trim_if(output, [](char c){ return c == '\n' || c == '\r' || c == ' ' || c == '\t'; }); + output.push_back('\n'); } public: @@ -82,6 +83,11 @@ public: static void addTarget(CLogTarget * target); static void finalise(); + template static void logTrace(const std::string & f, Args... args) + { + log(LOG_TRACE, f, args...); + } + template static void logDebug(const std::string & f, Args... args) { log(LOG_DEBUG, f, args...); diff --git a/LogConsoleTarget.cpp b/BaseCommon/LogConsoleTarget.cpp similarity index 100% rename from LogConsoleTarget.cpp rename to BaseCommon/LogConsoleTarget.cpp diff --git a/LogConsoleTarget.h b/BaseCommon/LogConsoleTarget.h similarity index 100% rename from LogConsoleTarget.h rename to BaseCommon/LogConsoleTarget.h diff --git a/BaseCommon/LogFileTarget.cpp b/BaseCommon/LogFileTarget.cpp new file mode 100644 index 0000000..d99d09e --- /dev/null +++ b/BaseCommon/LogFileTarget.cpp @@ -0,0 +1,103 @@ +/* + * 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 +#include +#include + +#include "LogFileTarget.h" + +#define LOG_FILE_ROOT "dstargateway" + +CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) : +CLogTarget(logLevel), +m_dir(dir), +m_rotate(rotate), +m_file(), +m_day(0) +{ + +} + +CLogFileTarget::~CLogFileTarget() +{ + if(m_file.is_open()) { + m_file.close(); + } +} + +void CLogFileTarget::printLogIntFixed(const std::string& msg) +{ + if(m_file.is_open()) { + m_file << msg; + m_file.flush(); + return; + } + + std::string filename(m_dir); + if(filename[filename.length() - 1U] != '/') filename.push_back('/'); + filename.append(LOG_FILE_ROOT).append(".log"); + m_file.open(filename, std::ios::app); + + if(m_file.is_open()) { + printLogIntFixed(msg); + } + else { + std::cerr << "FAILED TO OPEN LOG FILE :" << filename; + } +} + +void CLogFileTarget::printLogIntRotate(const std::string& msg) +{ + std::time_t now = std::time(0); + std::tm* now_tm = std::gmtime(&now); + if(now_tm->tm_yday != m_day) { + m_day = now_tm->tm_yday; + if(m_file.is_open()) { + m_file.close(); + } + } + + if(!m_file.is_open()) { + std::string filename(m_dir); + 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"); + m_file.open(filename, std::ios::app); + if(!m_file.is_open()) { + std::cerr << "FAILED TO OPEN LOG FILE :" << filename; + } + } + + if(m_file.is_open()) { + m_file << msg; + m_file.flush(); + return; + } +} +void CLogFileTarget::printLogInt(const std::string& msg) +{ + m_file.seekp(0, std::ios::end); + if(m_rotate) + printLogIntRotate(msg); + else + printLogIntFixed(msg); +} diff --git a/LogFileTarget.h b/BaseCommon/LogFileTarget.h similarity index 83% rename from LogFileTarget.h rename to BaseCommon/LogFileTarget.h index 2a8ad1d..8df8c8b 100644 --- a/LogFileTarget.h +++ b/BaseCommon/LogFileTarget.h @@ -19,6 +19,7 @@ #pragma once #include +#include #include "LogTarget.h" @@ -26,11 +27,17 @@ class CLogFileTarget : public CLogTarget { public: CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, bool rotate); + ~CLogFileTarget(); protected: virtual void printLogInt(const std::string& msg); private: + void printLogIntRotate(const std::string& msg); + void printLogIntFixed(const std::string& msg); + std::string buildFileName(); std::string m_dir; bool m_rotate; + std::fstream m_file; + int m_day; }; \ No newline at end of file diff --git a/LogSeverity.h b/BaseCommon/LogSeverity.h similarity index 100% rename from LogSeverity.h rename to BaseCommon/LogSeverity.h diff --git a/LogTarget.cpp b/BaseCommon/LogTarget.cpp similarity index 100% rename from LogTarget.cpp rename to BaseCommon/LogTarget.cpp diff --git a/LogTarget.h b/BaseCommon/LogTarget.h similarity index 100% rename from LogTarget.h rename to BaseCommon/LogTarget.h diff --git a/BaseCommon/Makefile b/BaseCommon/Makefile new file mode 100644 index 0000000..dc769f0 --- /dev/null +++ b/BaseCommon/Makefile @@ -0,0 +1,12 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +BaseCommon.a: $(OBJS) + $(AR) rcs BaseCommon.a $(OBJS) + +%.o : %.cpp + $(CC) $(CPPFLAGS) -MMD -MD -c $< -o $@ + +clean: + $(RM) *.o *.d BaseCommon.a \ No newline at end of file diff --git a/BaseCommon/NetUtils.cpp b/BaseCommon/NetUtils.cpp new file mode 100644 index 0000000..e0d3ac9 --- /dev/null +++ b/BaseCommon/NetUtils.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * Copyright (C) 2009-2011,2013,2015,2016,2020 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 "NetUtils.h" + +bool CNetUtils::lookupV4(const std::string& hostname, sockaddr_storage& addr) +{ + struct addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + + return lookup(hostname, addr, hints); +} + +bool CNetUtils::lookupV6(const std::string& hostname, sockaddr_storage& addr) +{ + struct addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + + return lookup(hostname, addr, hints); +} + +bool CNetUtils::lookup(const std::string& hostname, sockaddr_storage& addr) +{ + struct addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + return lookup(hostname, addr, hints); +} + +bool CNetUtils::lookup(const std::string& hostname, sockaddr_storage& addr, struct addrinfo& hints) +{ + struct addrinfo *res; + + int err = getaddrinfo(hostname.c_str(), nullptr, &hints, &res); + if(err != 0) { + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + lookup("255.255.255.255", addr, hints); + return false; + } + + ::memcpy(&addr, res->ai_addr, res->ai_addrlen); + + ::freeaddrinfo(res); + + return true; +} + +bool CNetUtils::match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type) +{ + if (addr1.ss_family != addr2.ss_family) + return false; + + if (type == IMT_ADDRESS_AND_PORT) { + switch (addr1.ss_family) { + case AF_INET: + struct sockaddr_in *in_1, *in_2; + in_1 = (struct sockaddr_in*)&addr1; + in_2 = (struct sockaddr_in*)&addr2; + return (in_1->sin_addr.s_addr == in_2->sin_addr.s_addr) && (in_1->sin_port == in_2->sin_port); + case AF_INET6: + struct sockaddr_in6 *in6_1, *in6_2; + in6_1 = (struct sockaddr_in6*)&addr1; + in6_2 = (struct sockaddr_in6*)&addr2; + return IN6_ARE_ADDR_EQUAL(&in6_1->sin6_addr, &in6_2->sin6_addr) && (in6_1->sin6_port == in6_2->sin6_port); + default: + return false; + } + } else if (type == IMT_ADDRESS_ONLY) { + switch (addr1.ss_family) { + case AF_INET: + struct sockaddr_in *in_1, *in_2; + in_1 = (struct sockaddr_in*)&addr1; + in_2 = (struct sockaddr_in*)&addr2; + return in_1->sin_addr.s_addr == in_2->sin_addr.s_addr; + case AF_INET6: + struct sockaddr_in6 *in6_1, *in6_2; + in6_1 = (struct sockaddr_in6*)&addr1; + in6_2 = (struct sockaddr_in6*)&addr2; + return IN6_ARE_ADDR_EQUAL(&in6_1->sin6_addr, &in6_2->sin6_addr); + default: + return false; + } + } else { + return false; + } +} + +void CNetUtils::setPort(struct sockaddr_storage& addr, in_port_t port) +{ + switch (addr.ss_family) + { + case AF_INET: + TOIPV4(addr)->sin_port = port; + break; + case AF_INET6: + TOIPV6(addr)->sin6_port = port; + default: + break; + } +} diff --git a/BaseCommon/NetUtils.h b/BaseCommon/NetUtils.h new file mode 100644 index 0000000..b5c795b --- /dev/null +++ b/BaseCommon/NetUtils.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * Copyright (C) 2009-2011,2013,2015,2016,2020 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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 + +#define TOIPV6(s) ((struct sockaddr_in6*)&s) +#define TOIPV4(s) (((struct sockaddr_in*)&s)) +#define GETPORT(s) (s.ss_family == AF_INET6 ? TOIPV6(s)->sin6_port : TOIPV4(s)->sin_port) +#define SETPORT(s, p) (if(s.ss_family == AF_INET6)TOIPV6(s)->sin6_port = p;else TOIPV4(s)->sin_port = p;) + +enum IPMATCHTYPE { + IMT_ADDRESS_AND_PORT, + IMT_ADDRESS_ONLY +}; + +class CNetUtils +{ +public: + static bool lookupV6(const std::string& hostname, sockaddr_storage& addr); + static bool lookupV4(const std::string& hostname, sockaddr_storage& addr); + static bool lookup(const std::string& hostname, sockaddr_storage& addr); + static bool lookup(const std::string& hostname, sockaddr_storage& addr, struct addrinfo& hints); + static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type); + static void setPort(struct sockaddr_storage& addr, in_port_t port); +}; \ No newline at end of file diff --git a/BaseCommon/ProgramArgs.cpp b/BaseCommon/ProgramArgs.cpp new file mode 100644 index 0000000..7370c36 --- /dev/null +++ b/BaseCommon/ProgramArgs.cpp @@ -0,0 +1,60 @@ +/* + * 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 "ProgramArgs.h" + +void CProgramArgs::eatArguments(int argc, const char *argv[], std::unordered_map& namedArgs, std::vector& positionalArgs) +{ + assert(argv != nullptr); + + namedArgs.clear(); + positionalArgs.clear(); + + std::vector programArgs; + + // Copy to a vector for easier handling, also skip program name + for(int i = 1;i < argc; i++) { + if(argv[i] != nullptr) { + programArgs.push_back(std::string(argv[i])); + } + } + + // Consume Named args first + for(auto it = programArgs.begin(); it != programArgs.end();) { + if(boost::starts_with(*it, "-")) { + std::string argName = boost::trim_left_copy_if(*it, [] (char c) { return c == '-'; }); + if(!argName.empty()) { + namedArgs[argName] = ""; + it = programArgs.erase(it); + if(it != programArgs.end()) { + namedArgs[argName] = *it; + it = programArgs.erase(it); + } + } + } + else { + it++; + } + } + + //ProgramArgs now only contains pôsitional Args + positionalArgs.assign(programArgs.begin(), programArgs.end()); +} diff --git a/BaseCommon/ProgramArgs.h b/BaseCommon/ProgramArgs.h new file mode 100644 index 0000000..78d47b0 --- /dev/null +++ b/BaseCommon/ProgramArgs.h @@ -0,0 +1,29 @@ +/* + * 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 + +class CProgramArgs +{ +public: + static void eatArguments(int argc, const char *argv[], std::unordered_map& namedArgs, std::vector& positionalArgs); +}; \ No newline at end of file diff --git a/RingBuffer.h b/BaseCommon/RingBuffer.h similarity index 97% rename from RingBuffer.h rename to BaseCommon/RingBuffer.h index 3182382..1ff2ab4 100644 --- a/RingBuffer.h +++ b/BaseCommon/RingBuffer.h @@ -36,8 +36,6 @@ public: assert(length > 0U); m_buffer = new T[length]; - - ::memset(m_buffer, 0x00, length * sizeof(T)); } ~CRingBuffer() diff --git a/SHA256.cpp b/BaseCommon/SHA256.cpp similarity index 100% rename from SHA256.cpp rename to BaseCommon/SHA256.cpp diff --git a/SHA256.h b/BaseCommon/SHA256.h similarity index 100% rename from SHA256.h rename to BaseCommon/SHA256.h diff --git a/BaseCommon/StringUtils.cpp b/BaseCommon/StringUtils.cpp new file mode 100644 index 0000000..a20a6ec --- /dev/null +++ b/BaseCommon/StringUtils.cpp @@ -0,0 +1,52 @@ +/* + * 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 + +#include "StringUtils.h" + +size_t CStringUtils::find_nth(const std::string& haystack, char needle, size_t nth) +{ + size_t matches = 0U; + auto haystackLength = haystack.length(); + + for(size_t i = 0; i < haystackLength; i++) { + if(haystack[i] == needle) { + matches++; + if(matches == nth) + return i; + } + } + + return std::string::npos; +} + +unsigned int CStringUtils::stringToPort(const std::string& s) +{ + unsigned int port = 0U; + std::string ls = boost::trim_copy(s); + + if(!ls.empty() && std::all_of(ls.begin(), ls.end(), [](char c){ return c >= '0' && c <= '9'; })) { + auto portTemp = boost::lexical_cast(ls); + if(portTemp > 0U && portTemp <= 65535U) + port = portTemp; + } + + return port; +} \ No newline at end of file diff --git a/StringUtils.h b/BaseCommon/StringUtils.h similarity index 63% rename from StringUtils.h rename to BaseCommon/StringUtils.h index 2dadc82..5df05ab 100644 --- a/StringUtils.h +++ b/BaseCommon/StringUtils.h @@ -1,7 +1,5 @@ /* - * Copyright (C) 2009-2011,2013 by Jonathan Naylor G4KLX - * Copyright (c) 2017 by Thomas A. Early N7TAE - * Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA + * 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 @@ -23,19 +21,37 @@ #include #include #include +#include #define string_right(s,l) (s.substr(s.length() - l, l)) +#define string_is_blank(s) (std::all_of(s.begin(), s.end(), [](char c) { return c == ' '; })) +#define string_is_blank_or_empty(s) (s.empty() || std::all_of(s.begin(), s.end(), [](char c) { return c == ' '; })) class CStringUtils { public: template static std::string string_format( const std::string& format, Args ... args ) + { + std::string ret; + string_format_in_place(ret, format, args...); + return ret; + } + + template + static void string_format_in_place(std::string& output, const std::string& format, Args ... args ) { int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); } + auto size = static_cast( size_s ); auto buf = std::make_unique( size ); std::snprintf( buf.get(), size, format.c_str(), args ... ); - return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside + + output.reserve(size); + output.assign(buf.get(), size - 1); // -1 because we do not need trailing '\0' } + + static size_t find_nth(const std::string& haystack, char needle, size_t nth); + + static unsigned int stringToPort(const std::string& s); }; diff --git a/TCPReaderWriterClient.cpp b/BaseCommon/TCPReaderWriterClient.cpp similarity index 93% rename from TCPReaderWriterClient.cpp rename to BaseCommon/TCPReaderWriterClient.cpp index e21da7a..624cb51 100644 --- a/TCPReaderWriterClient.cpp +++ b/BaseCommon/TCPReaderWriterClient.cpp @@ -201,19 +201,15 @@ bool CTCPReaderWriterClient::write(const unsigned char* buffer, unsigned int len bool CTCPReaderWriterClient::writeLine(const std::string& line) { - std::string lineCopy(line); - if(lineCopy.size() > 0 && lineCopy.at(lineCopy.size() - 1) != '\n') - lineCopy.push_back('\n'); - - //stupidly write one char after the other - size_t len = lineCopy.size(); - bool result = true; - for(size_t i = 0; i < len && result; i++){ - unsigned char c = lineCopy.at(i); - result = write(&c , 1); - } + if(line.empty()) + return true; + + bool ret = write((unsigned char *)line.c_str(), line.length()); + + if(line[line.length() - 1] != '\n')//make sure we send a newline + ret = writeLine("\n") && ret; - return result; + return ret; } void CTCPReaderWriterClient::close() diff --git a/TCPReaderWriterClient.h b/BaseCommon/TCPReaderWriterClient.h similarity index 100% rename from TCPReaderWriterClient.h rename to BaseCommon/TCPReaderWriterClient.h diff --git a/Thread.cpp b/BaseCommon/Thread.cpp similarity index 100% rename from Thread.cpp rename to BaseCommon/Thread.cpp diff --git a/Thread.h b/BaseCommon/Thread.h similarity index 100% rename from Thread.h rename to BaseCommon/Thread.h diff --git a/Timer.cpp b/BaseCommon/Timer.cpp similarity index 100% rename from Timer.cpp rename to BaseCommon/Timer.cpp diff --git a/Timer.h b/BaseCommon/Timer.h similarity index 100% rename from Timer.h rename to BaseCommon/Timer.h diff --git a/UDPReaderWriter.cpp b/BaseCommon/UDPReaderWriter.cpp similarity index 76% rename from UDPReaderWriter.cpp rename to BaseCommon/UDPReaderWriter.cpp index d89beea..d0b826f 100644 --- a/UDPReaderWriter.cpp +++ b/BaseCommon/UDPReaderWriter.cpp @@ -21,6 +21,7 @@ #include #include "UDPReaderWriter.h" #include "Log.h" +#include "NetUtils.h" CUDPReaderWriter::CUDPReaderWriter(const std::string& address, unsigned int port) : m_address(address), @@ -101,7 +102,7 @@ bool CUDPReaderWriter::open() return true; } -int CUDPReaderWriter::read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port) +int CUDPReaderWriter::read(unsigned char* buffer, unsigned int length, struct sockaddr_storage& addr) { // Check that the readfrom() won't block fd_set readFds; @@ -122,8 +123,7 @@ int CUDPReaderWriter::read(unsigned char* buffer, unsigned int length, in_addr& if (ret == 0) return 0; - sockaddr_in addr; - socklen_t size = sizeof(sockaddr_in); + socklen_t size = sizeof(addr); ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size); if (len <= 0) { @@ -131,22 +131,54 @@ int CUDPReaderWriter::read(unsigned char* buffer, unsigned int length, in_addr& return -1; } - address = addr.sin_addr; - port = ntohs(addr.sin_port); - return len; } +int CUDPReaderWriter::read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port) +{ + struct sockaddr_storage addr; + auto res = read(buffer, length, addr); + + if(res >= 0 && addr.ss_family == AF_INET) { + address = TOIPV4(addr)->sin_addr; + port = ntohs(TOIPV4(addr)->sin_port); + } + + return res; +} + bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port) { - sockaddr_in addr; - ::memset(&addr, 0x00, sizeof(sockaddr_in)); + struct sockaddr_storage addr; + ::memset(&addr, 0, sizeof(sockaddr_storage)); + + addr.ss_family = AF_INET; + TOIPV4(addr)->sin_addr = address; + TOIPV4(addr)->sin_port = htons(port); - addr.sin_family = AF_INET; - addr.sin_addr = address; - addr.sin_port = htons(port); + return write(buffer, length, addr); + // sockaddr_in addr; + // ::memset(&addr, 0x00, sizeof(sockaddr_in)); - ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, 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) +{ + ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(addr)); if (ret < 0) { CLog::logError("Error returned from sendto (port: %u), err: %s\n", m_port, strerror(errno)); return false; @@ -158,6 +190,8 @@ bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, c return true; } + + void CUDPReaderWriter::close() { ::close(m_fd); diff --git a/UDPReaderWriter.h b/BaseCommon/UDPReaderWriter.h similarity index 85% rename from UDPReaderWriter.h rename to BaseCommon/UDPReaderWriter.h index 25cfb08..0cddfac 100644 --- a/UDPReaderWriter.h +++ b/BaseCommon/UDPReaderWriter.h @@ -41,8 +41,10 @@ public: bool open(); - int read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port); + int read(unsigned char* buffer, unsigned int length, struct sockaddr_storage& addr); + int read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port); bool write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port); + bool write(const unsigned char* buffer, unsigned int length, const struct sockaddr_storage& addr); void close(); diff --git a/Utils.cpp b/BaseCommon/Utils.cpp similarity index 100% rename from Utils.cpp rename to BaseCommon/Utils.cpp diff --git a/Utils.h b/BaseCommon/Utils.h similarity index 85% rename from Utils.h rename to BaseCommon/Utils.h index 61fa0fb..0e60527 100644 --- a/Utils.h +++ b/BaseCommon/Utils.h @@ -66,4 +66,25 @@ public: return dest.u; } + + // https://stackoverflow.com/questions/1001307/detecting-endianness-programmatically-in-a-c-program + static bool is_big_endian() + { + union { + uint32_t i; + char c[4]; + } bint = {0x01020304}; + + return bint.c[0] == 1; + } + + // Ersatz for macro wxINT32_SWAP_ON_BE + template + static T swap_endian_be(T u) + { + if(is_big_endian()) + return swap_endian(u); + + return u; + } }; diff --git a/Common/APRSCollector.cpp b/Common/APRSCollector.cpp new file mode 100644 index 0000000..afba4d0 --- /dev/null +++ b/Common/APRSCollector.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2010,2012,2018 by Jonathan Naylor G4KLX + * 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 + +#include "APRSCollector.h" +#include "DStarDefines.h" +#include "Utils.h" +#include "NMEASentenceCollector.h" +#include "GPSACollector.h" +#include "RSMS1AMessageCollector.h" +#include "SlowDataCollectorThrottle.h" + +CAPRSCollector::CAPRSCollector() : +m_collectors() +{ + m_collectors.push_back(new CRSMS1AMessageCollector()); // we do not throttle messages, they have highest priority ! + m_collectors.push_back(new CSlowDataCollectorThrottle(new CGPSACollector(), 10U)); + m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGGA"), 10U)); + m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGLL"), 10U)); + m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPVTG"), 10U)); + m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPRMC"), 10U)); + m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGSA"), 10U)); + m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGSV"), 10U)); +} + +CAPRSCollector::~CAPRSCollector() +{ + for(auto collector : m_collectors) { + delete collector; + } + m_collectors.clear(); +} + +void CAPRSCollector::writeHeader(const std::string& callsign) +{ + for(auto collector : m_collectors) { + collector->setMyCall(callsign); + } +} + +bool CAPRSCollector::writeData(const unsigned char* data) +{ + bool ret = false; + + for(auto collector : m_collectors) { + bool ret2 = collector->writeData(data); + ret = ret || ret2; + } + return ret; +} + +void CAPRSCollector::reset() +{ + for(auto collector : m_collectors) { + collector->reset(); + } +} + +void CAPRSCollector::sync() +{ + for(auto collector : m_collectors) { + collector->sync(); + } +} + +unsigned int CAPRSCollector::getData(unsigned char dataType, unsigned char* data, unsigned int length) +{ + for(auto collector : m_collectors) { + if(collector->getDataType() == dataType) { + unsigned int res = collector->getData(data, length); + if(res > 0U) + return res; + } + } + return 0U; +} + +void CAPRSCollector::getData(std::function dataHandler) +{ + for(auto collector : m_collectors) { + std::string data; + if(collector->getData(data)) { + dataHandler(data); + collector->reset(); + } + } +} + +void CAPRSCollector::clock(unsigned int ms) +{ + for(auto collector : m_collectors) { + collector->clock(ms); + } +} \ No newline at end of file diff --git a/Common/APRSCollector.h b/Common/APRSCollector.h new file mode 100644 index 0000000..2f955bf --- /dev/null +++ b/Common/APRSCollector.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010,2012,2018 by Jonathan Naylor G4KLX + * 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. + */ + +#ifndef APRSCollector_H +#define APRSCollector_H + +#include +#include + +#include "SlowDataCollector.h" +#include "Defs.h" + +enum APRS_STATE { + AS_NONE, + AS_GGA, + AS_RMC, + AS_MSG, + AS_CRC +}; + +class CAPRSCollector { +public: + CAPRSCollector(); + ~CAPRSCollector(); + + void writeHeader(const std::string& callsign); + + bool writeData(const unsigned char* data); + + void reset(); + + void sync(); + + unsigned int getData(unsigned char dataType, unsigned char* data, unsigned int length); + + void getData(std::function dataHandler); + + void clock(unsigned int ms); + +private: + std::vector m_collectors; +}; + +#endif diff --git a/APRSEntry.cpp b/Common/APRSEntry.cpp similarity index 98% rename from APRSEntry.cpp rename to Common/APRSEntry.cpp index 2d1c3d3..13d1b1a 100644 --- a/APRSEntry.cpp +++ b/Common/APRSEntry.cpp @@ -106,6 +106,7 @@ void CAPRSEntry::clock(unsigned int ms) { m_linkStatus.clock(ms); m_timer.clock(ms); + m_collector->clock(ms); } bool CAPRSEntry::isOK() diff --git a/APRSEntry.h b/Common/APRSEntry.h similarity index 100% rename from APRSEntry.h rename to Common/APRSEntry.h diff --git a/APRSEntryStatus.cpp b/Common/APRSEntryStatus.cpp similarity index 100% rename from APRSEntryStatus.cpp rename to Common/APRSEntryStatus.cpp diff --git a/APRSEntryStatus.h b/Common/APRSEntryStatus.h similarity index 100% rename from APRSEntryStatus.h rename to Common/APRSEntryStatus.h diff --git a/APRSFixedIdFrameProvider.cpp b/Common/APRSFixedIdFrameProvider.cpp similarity index 81% rename from APRSFixedIdFrameProvider.cpp rename to Common/APRSFixedIdFrameProvider.cpp index c75cbe9..9434175 100644 --- a/APRSFixedIdFrameProvider.cpp +++ b/Common/APRSFixedIdFrameProvider.cpp @@ -29,7 +29,7 @@ CAPRSIdFrameProvider(20U) // Initial timeout of 20 seconds } -bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) +bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) { if (entry == nullptr) return false; @@ -107,24 +107,33 @@ bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, c boost::replace_all(lat, ",", "."); boost::replace_all(lon, ",", "."); - std::string output = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%caRNG%04.0lf/A=%06.0lf %s %s\r\n", - gateway.c_str(), gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + std::string body = CStringUtils::string_format(";%-7s%-2s*%02d%02d%02dz%s%cD%s%caRNG%04.0lf/A=%06.0lf %s %s\r\n", + entry->getCallsign().c_str(), entry->getBand().c_str(), tm->tm_mday, tm->tm_hour, tm->tm_min, lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N', lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); + CAPRSFrame * frame = new CAPRSFrame(gateway + "-S", + "APD5T1", + { "TCPIP*", "qAC" , gateway + "-GS" }, + body, APFT_OBJECT); - frames.push_back(output); + frames.push_back(frame); if (entry->getBand().length() == 1U) { - output = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&RNG%04.0lf/A=%06.0lf %s %s\r\n", - entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + body = CStringUtils::string_format("!%s%cD%s%c&RNG%04.0lf/A=%06.0lf %s %s\r\n", lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N', lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); - frames.push_back(output); + frame = new CAPRSFrame(entry->getCallsign() + "-" + entry->getBand(), + "APD5T2", + { "TCPIP*", "qAC", entry->getCallsign() + "-" + entry->getBand() + "S"}, + body, APFT_POSITION); + + + frames.push_back(frame); } setTimeout(20U * 60U);//20 minutes, plenty enough for fixed diff --git a/APRSFixedIdFrameProvider.h b/Common/APRSFixedIdFrameProvider.h similarity index 93% rename from APRSFixedIdFrameProvider.h rename to Common/APRSFixedIdFrameProvider.h index d3bbc3d..8fa7032 100644 --- a/APRSFixedIdFrameProvider.h +++ b/Common/APRSFixedIdFrameProvider.h @@ -26,5 +26,5 @@ public: CAPRSFixedIdFrameProvider(); protected: - virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); }; diff --git a/APRSGPSDIdFrameProvider.cpp b/Common/APRSGPSDIdFrameProvider.cpp similarity index 81% rename from APRSGPSDIdFrameProvider.cpp rename to Common/APRSGPSDIdFrameProvider.cpp index 3a02916..f67f634 100644 --- a/APRSGPSDIdFrameProvider.cpp +++ b/Common/APRSGPSDIdFrameProvider.cpp @@ -16,7 +16,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#ifdef USE_DGPS +#ifdef USE_GPSD #include #include @@ -56,7 +56,7 @@ void CAPRSGPSDIdFrameProvider::close() } } -bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) +bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) { if(!m_hasConnection) { this->start(); @@ -169,40 +169,44 @@ bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, co boost::replace_all(lat, ",", "."); boost::replace_all(lon, ",", "."); - std::string output1 = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%ca/A=%06.0lf", - gateway.c_str(), gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + std::string body = CStringUtils::string_format(";%-7s%-2s*%02d%02d%02dz%s%cD%s%ca/A=%06.0lf", + entry->getCallsign().c_str(), entry->getBand().c_str(), tm->tm_mday, tm->tm_hour, tm->tm_min, lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E', rawAltitude * 3.28); - std::string output2; if (bearingSet && velocitySet) - output2 = CStringUtils::string_format("%03.0lf/%03.0lf", rawBearing, rawVelocity * 0.539957F); + body.append(CStringUtils::string_format("%03.0lf/%03.0lf", rawBearing, rawVelocity * 0.539957F)); - std::string output3; - output3 = CStringUtils::string_format("RNG%04.0lf %s %s\r\n", entry->getRange() * 0.6214, band.c_str(), desc.c_str()); + body.append(CStringUtils::string_format("RNG%04.0lf %s %s\r\n", entry->getRange() * 0.6214, band.c_str(), desc.c_str())); - CLog::logDebug("APRS ==> %s%s%s", output1.c_str(), output2.c_str(), output3.c_str()); - frames.push_back(output1.append(output2).append(output3)); + CAPRSFrame * frame = new CAPRSFrame(gateway + "-S", + "APD5T1", + { "TCPIP*", "qAC" , gateway + "-GS" }, + body, APFT_OBJECT); + + + frames.push_back(frame); if (entry->getBand().length() == 1U) { if (altitudeSet) - output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&/A=%06.0lf", - entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + body = CStringUtils::string_format("%s%cD%s%c&/A=%06.0lf", lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E', rawAltitude * 3.28); else - output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&", - entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + body = CStringUtils::string_format("!%s%cD%s%c&", lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E'); - CLog::logDebug("APRS ==> %s%s%s", output1.c_str(), output2.c_str(), output3.c_str()); + frame = new CAPRSFrame(gateway, + "APD5T2", + { "TCPIP*", "qAC" , gateway + "-GS" }, + body, APFT_POSITION); - frames.push_back(output1.append(output2).append(output3)); + frames.push_back(frame); } setTimeout(60U * 5U);//5 Minutes is plenty enough we aint an APRS tracker ! diff --git a/APRSGPSDIdFrameProvider.h b/Common/APRSGPSDIdFrameProvider.h similarity index 95% rename from APRSGPSDIdFrameProvider.h rename to Common/APRSGPSDIdFrameProvider.h index 77ca4b9..e982f9b 100644 --- a/APRSGPSDIdFrameProvider.h +++ b/Common/APRSGPSDIdFrameProvider.h @@ -35,7 +35,7 @@ public: virtual void close(); protected: - virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); private: std::string m_gpsdAddress; diff --git a/APRSWriter.cpp b/Common/APRSHandler.cpp similarity index 61% rename from APRSWriter.cpp rename to Common/APRSHandler.cpp index 57ea6e8..4d6b81a 100644 --- a/APRSWriter.cpp +++ b/Common/APRSHandler.cpp @@ -21,15 +21,20 @@ #include #include #include +#include #include "StringUtils.h" #include "Log.h" -#include "APRSWriter.h" +#include "APRSHandler.h" #include "DStarDefines.h" #include "Defs.h" #include "Log.h" +#include "APRSFrame.h" +#include "APRSParser.h" +#include "APRSFormater.h" +#include "APRSUtils.h" -CAPRSWriter::CAPRSWriter(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address) : +CAPRSHandler::CAPRSHandler(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address) : m_thread(NULL), m_gateway(), m_address(), @@ -42,14 +47,14 @@ m_idFrameProvider(nullptr) assert(!gateway.empty()); assert(!password.empty()); - m_thread = new CAPRSWriterThread(gateway, password, address, hostname, port); + m_thread = new CAPRSHandlerThread(gateway, password, address, hostname, port); m_gateway = gateway; m_gateway = m_gateway.substr(0, LONG_CALLSIGN_LENGTH - 1U); boost::trim(m_gateway); } -CAPRSWriter::~CAPRSWriter() +CAPRSHandler::~CAPRSHandler() { for(auto it = m_array.begin(); it != m_array.end(); it++) { delete it->second; @@ -58,7 +63,7 @@ CAPRSWriter::~CAPRSWriter() m_array.clear(); } -void CAPRSWriter::setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) +void CAPRSHandler::setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) { std::string temp = callsign; temp.resize(LONG_CALLSIGN_LENGTH - 1U, ' '); @@ -67,12 +72,12 @@ void CAPRSWriter::setPort(const std::string& callsign, const std::string& band, m_array[temp] = new CAPRSEntry(callsign, band, frequency, offset, range, latitude, longitude, agl); } -bool CAPRSWriter::open() +bool CAPRSHandler::open() { return m_thread->start(); } -void CAPRSWriter::writeHeader(const std::string& callsign, const CHeaderData& header) +void CAPRSHandler::writeHeader(const std::string& callsign, const CHeaderData& header) { CAPRSEntry* entry = m_array[callsign]; if (entry == NULL) { @@ -87,7 +92,7 @@ void CAPRSWriter::writeHeader(const std::string& callsign, const CHeaderData& he collector->writeHeader(header.getMyCall1()); } -void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data) +void CAPRSHandler::writeData(const std::string& callsign, const CAMBEData& data) { if (data.isEnd()) return; @@ -117,48 +122,31 @@ void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data) return; } - // Check the transmission timer - bool ok = entry->isOK(); - if (!ok) { - collector->reset(); - return; - } - - unsigned int length = collector->getData(buffer, 400U); - std::string text((char*)buffer, length); - - auto n = text.find(':'); - if (n == std::string::npos) { - collector->reset(); - return; - } - - std::string header = text.substr(0, n); - std::string body = text.substr(n + 1U); - - // If we already have a q-construct, don't send it on - n = header.find('q'); - if (n != std::string::npos) - return; - - // Remove the trailing \r - n = body.find('\r'); - if (n != std::string::npos) - body = body.substr(0, n); - - std::string output = CStringUtils::string_format("%s,qAR,%s-%s:%s", header.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), body.c_str()); + collector->getData([=](const std::string& text) + { + CAPRSFrame frame; + if(!CAPRSParser::parseFrame(text, frame)) { + CLog::logWarning("Failed to parse DPRS Frame : %s", text.c_str()); + return; + } - char ascii[500U]; - ::memset(ascii, 0x00, 500U); - for (unsigned int i = 0U; i < output.length(); i++) - ascii[i] = output[i]; + // If we already have a q-construct, don't send it on + if(std::any_of(frame.getPath().begin(), frame.getPath().end(), [] (std::string s) { return !s.empty() && s[0] == 'q'; })) { + CLog::logWarning("DPRS Frame already has q construct, not forwarding to APRS-IS: %s", text.c_str()); + return; + } - m_thread->write(ascii); + frame.getPath().push_back("qAR"); + frame.getPath().push_back(CStringUtils::string_format("%s-%s", entry->getCallsign().c_str(), entry->getBand().c_str())); + + std::string output ; + CAPRSFormater::frameToString(output, frame); - collector->reset(); + m_thread->write(frame); + }); } -void CAPRSWriter::writeStatus(const std::string& callsign, const std::string status) +void CAPRSHandler::writeStatus(const std::string& callsign, const std::string status) { CAPRSEntry* entry = m_array[callsign]; if (entry == NULL) { @@ -169,7 +157,7 @@ void CAPRSWriter::writeStatus(const std::string& callsign, const std::string sta entry->getStatus().setStatus(status); } -void CAPRSWriter::clock(unsigned int ms) +void CAPRSHandler::clock(unsigned int ms) { m_thread->clock(ms); @@ -189,48 +177,54 @@ void CAPRSWriter::clock(unsigned int ms) } } -void CAPRSWriter::sendStatusFrame(CAPRSEntry * entry) +void CAPRSHandler::sendStatusFrame(CAPRSEntry * entry) { assert(entry != nullptr); if(!m_thread->isConnected()) return; + auto linkStatus = entry->getStatus(); std::string body = boost::trim_copy(linkStatus.getStatus()); if(body[0] != '>') - body = '>' + body; + body.insert(0, ">"); - std::string output = CStringUtils::string_format("%s-%s>APD5T3,TCPIP*,qAC,%s-%sS:%s\r\n", - entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), - body.c_str()); + std::string sourCall = entry->getCallsign() + '-' + entry->getBand(); + + CAPRSFrame frame(sourCall, + "APD5T3", + { "TCPIP*", "qAC", sourCall + "S" }, + body, + APFT_STATUS); - m_thread->write(output.c_str()); + m_thread->write(frame); } -void CAPRSWriter::sendIdFrames() +void CAPRSHandler::sendIdFrames() { if(m_thread->isConnected()) { for(auto entry : m_array) { - std::vector frames; + std::vector frames; if(m_idFrameProvider->buildAPRSFrames(m_gateway, entry.second, frames)) { for(auto frame : frames) { - m_thread->write(frame.c_str()); + m_thread->write(*frame); + delete frame; } } } } } -bool CAPRSWriter::isConnected() const +bool CAPRSHandler::isConnected() const { return m_thread->isConnected(); } -void CAPRSWriter::close() +void CAPRSHandler::close() { if(m_idFrameProvider != nullptr) { m_idFrameProvider->close(); @@ -240,3 +234,8 @@ void CAPRSWriter::close() m_thread->stop(); } + +void CAPRSHandler::addReadAPRSCallback(IReadAPRSFrameCallback* cb) +{ + m_thread->addReadAPRSCallback(cb); +} diff --git a/APRSWriter.h b/Common/APRSHandler.h similarity index 86% rename from APRSWriter.h rename to Common/APRSHandler.h index 3543930..743ad37 100644 --- a/APRSWriter.h +++ b/Common/APRSHandler.h @@ -17,8 +17,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#ifndef APRSWriter_H -#define APRSWriter_H +#pragma once #include "Defs.h" @@ -27,7 +26,7 @@ #include "APRSEntry.h" -#include "APRSWriterThread.h" +#include "APRSHandlerThread.h" #include "UDPReaderWriter.h" #include "APRSCollector.h" #include "DStarDefines.h" @@ -36,10 +35,10 @@ #include "Timer.h" #include "APRSIdFrameProvider.h" -class CAPRSWriter { +class CAPRSHandler { public: - CAPRSWriter(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address); - ~CAPRSWriter(); + CAPRSHandler(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address); + ~CAPRSHandler(); bool open(); @@ -59,8 +58,10 @@ public: void close(); + void addReadAPRSCallback(IReadAPRSFrameCallback* cb); + private: - CAPRSWriterThread* m_thread; + CAPRSHandlerThread* m_thread; std::string m_gateway; in_addr m_address; unsigned int m_port; @@ -71,5 +72,3 @@ private: void sendStatusFrame(CAPRSEntry * entrry); }; -#endif - diff --git a/APRSWriterThread.cpp b/Common/APRSHandlerThread.cpp similarity index 62% rename from APRSWriterThread.cpp rename to Common/APRSHandlerThread.cpp index fe27a9b..1d6b099 100644 --- a/APRSWriterThread.cpp +++ b/Common/APRSHandlerThread.cpp @@ -21,18 +21,22 @@ #include #include -#include "APRSWriterThread.h" +#include "APRSHandlerThread.h" #include "DStarDefines.h" #include "Utils.h" #include "Defs.h" #include "Log.h" #include "Version.h" +#include "APRSFormater.h" +#include "APRSParser.h" // #define DUMP_TX const unsigned int APRS_TIMEOUT = 10U; +const unsigned int APRS_READ_TIMEOUT = 1U; +const unsigned int APRS_KEEP_ALIVE_TIMEOUT = 60U; -CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) : +CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) : CThread(), m_username(callsign), m_password(password), @@ -42,9 +46,10 @@ m_queue(20U), m_exit(false), m_connected(false), m_reconnectTimer(1000U), +m_keepAliveTimer(1000U, APRS_KEEP_ALIVE_TIMEOUT), m_tries(0U), -m_APRSReadCallback(NULL), -m_filter(""), +m_APRSReadCallbacks(), +m_filter(), m_clientName(FULL_PRODUCT_NAME) { assert(!callsign.empty()); @@ -59,7 +64,7 @@ m_clientName(FULL_PRODUCT_NAME) m_ssid = m_ssid.substr(LONG_CALLSIGN_LENGTH - 1U, 1); } -CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter, const std::string& clientName) : +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(), m_username(callsign), m_password(password), @@ -69,10 +74,11 @@ m_queue(20U), m_exit(false), m_connected(false), m_reconnectTimer(1000U), +m_keepAliveTimer(1000U, APRS_KEEP_ALIVE_TIMEOUT), m_tries(0U), -m_APRSReadCallback(NULL), +m_APRSReadCallbacks(), m_filter(filter), -m_clientName(clientName) +m_clientName(FULL_PRODUCT_NAME) { assert(!callsign.empty()); assert(!password.empty()); @@ -86,13 +92,20 @@ m_clientName(clientName) m_ssid = m_ssid.substr(LONG_CALLSIGN_LENGTH - 1U, 1); } -CAPRSWriterThread::~CAPRSWriterThread() +CAPRSHandlerThread::~CAPRSHandlerThread() { + std::vector callBacksCopy; + callBacksCopy.assign(m_APRSReadCallbacks.begin(), m_APRSReadCallbacks.end()); + + m_APRSReadCallbacks.clear(); + + callBacksCopy.clear(); + m_username.clear(); m_password.clear(); } -bool CAPRSWriterThread::start() +bool CAPRSHandlerThread::start() { Create(); Run(); @@ -100,7 +113,7 @@ bool CAPRSWriterThread::start() return true; } -void* CAPRSWriterThread::Entry() +void* CAPRSHandlerThread::Entry() { CLog::logInfo("Starting the APRS Writer thread"); @@ -110,17 +123,25 @@ void* CAPRSWriterThread::Entry() startReconnectionTimer(); } +#ifndef DEBUG_DSTARGW try { +#endif + m_keepAliveTimer.start(); while (!m_exit) { if (!m_connected) { + Sleep(100U); if (m_reconnectTimer.isRunning() && m_reconnectTimer.hasExpired()) { m_reconnectTimer.stop(); + CLog::logDebug("Trying to reconnect to the APRS server"); m_connected = connect(); if (!m_connected) { CLog::logInfo("Reconnect attempt to the APRS server has failed"); startReconnectionTimer(); } + else { + m_keepAliveTimer.start(); + } } } @@ -128,45 +149,43 @@ void* CAPRSWriterThread::Entry() m_tries = 0U; if(!m_queue.empty()){ - char* p = m_queue.getData(); - - std::string text(p); - CLog::logInfo("APRS ==> %s", text.c_str()); + auto frameStr = m_queue.getData(); - ::strcat(p, "\r\n"); + CLog::logInfo("APRS ==> %s", frameStr.c_str()); - bool ret = m_socket.write((unsigned char*)p, ::strlen(p)); + bool ret = m_socket.writeLine(frameStr); if (!ret) { m_connected = false; m_socket.close(); - CLog::logInfo("Connection to the APRS thread has failed"); + CLog::logInfo("Error when writing to the APRS server"); startReconnectionTimer(); } - - delete[] p; } { std::string line; - int length = m_socket.readLine(line, APRS_TIMEOUT); - - /*if (length == 0) - CLog::logWarning(("No response from the APRS server after %u seconds", APRS_TIMEOUT);*/ + int length = m_socket.readLine(line, APRS_READ_TIMEOUT); - if (length < 0) { + if (length < 0 || m_keepAliveTimer.hasExpired()) { m_connected = false; m_socket.close(); CLog::logError("Error when reading from the APRS server"); startReconnectionTimer(); } - - if(length > 0 && line[0] != '#'//check if we have something and if that something is an APRS frame - && m_APRSReadCallback != NULL)//do we have someone wanting an APRS Frame? - { - //CLog::logInfo("Received APRS Frame : ") + line); - m_APRSReadCallback(std::string(line)); + else if(length > 0 && line[0] == '#') { + m_keepAliveTimer.start(); + } + else if(line.length() > 0 && line[0] != '#') { + m_keepAliveTimer.start(); + CLog::logDebug("APRS <== %s", line.c_str()); + CAPRSFrame readFrame; + if(CAPRSParser::parseFrame(line, readFrame)) { + for(auto cb : m_APRSReadCallbacks) { + CAPRSFrame f(readFrame); + cb->readAPRSFrame(f); + } + } } } - } } @@ -174,62 +193,69 @@ void* CAPRSWriterThread::Entry() m_socket.close(); while (!m_queue.empty()) { - char* p = m_queue.getData(); - delete[] p; + auto s = m_queue.getData(); + s.clear(); } +#ifndef DEBUG_DSTARGW } catch (std::exception& e) { std::string message(e.what()); CLog::logInfo("Exception raised in the APRS Writer thread - \"%s\"", message.c_str()); + throw; } catch (...) { CLog::logInfo("Unknown exception raised in the APRS Writer thread"); + throw; } +#endif CLog::logInfo("Stopping the APRS Writer thread"); return NULL; } -void CAPRSWriterThread::setReadAPRSCallback(ReadAPRSFrameCallback cb) +void CAPRSHandlerThread::addReadAPRSCallback(IReadAPRSFrameCallback * cb) { - m_APRSReadCallback = cb; + assert(cb != nullptr); + m_APRSReadCallbacks.push_back(cb); } -void CAPRSWriterThread::write(const char* data) +void CAPRSHandlerThread::write(CAPRSFrame& frame) { - assert(data != NULL); - if (!m_connected) return; - unsigned int len = ::strlen(data); + std::string frameString; + if(CAPRSFormater::frameToString(frameString, frame)) { + boost::trim_if(frameString, [] (char c) { return c == '\r' || c == '\n'; }); // trim all CRLF, we will add our own, just to make sure we get rid of any garbage that might come from slow data + CLog::logTrace("Queued APRS Frame : %s", frameString.c_str()); + frameString.append("\r\n"); - char* p = new char[len + 5U]; - ::strcpy(p, data); - - m_queue.addData(p); + m_queue.addData(frameString); + } } -bool CAPRSWriterThread::isConnected() const +bool CAPRSHandlerThread::isConnected() const { return m_connected; } -void CAPRSWriterThread::stop() +void CAPRSHandlerThread::stop() { m_exit = true; Wait(); } -void CAPRSWriterThread::clock(unsigned int ms) +void CAPRSHandlerThread::clock(unsigned int ms) { m_reconnectTimer.clock(ms); + m_keepAliveTimer.clock(ms); } -bool CAPRSWriterThread::connect() +bool CAPRSHandlerThread::connect() { + m_socket.close(); bool ret = m_socket.open(); if (!ret) return false; @@ -266,7 +292,7 @@ bool CAPRSWriterThread::connect() return false; } if (length < 0) { - CLog::logInfo("Error when reading from the APRS server"); + CLog::logInfo("Error when reading from the APRS server (connect)"); m_socket.close(); return false; } @@ -278,13 +304,15 @@ bool CAPRSWriterThread::connect() return true; } -void CAPRSWriterThread::startReconnectionTimer() +void CAPRSHandlerThread::startReconnectionTimer() { // Clamp at a ten minutes reconnect time m_tries++; if (m_tries > 10U) m_tries = 10U; + CLog::logDebug("Next APRS reconnection try in %u minute", m_tries); + m_reconnectTimer.setTimeout(m_tries * 60U); m_reconnectTimer.start(); } diff --git a/APRSWriterThread.h b/Common/APRSHandlerThread.h similarity index 68% rename from APRSWriterThread.h rename to Common/APRSHandlerThread.h index aa07af2..01410de 100644 --- a/APRSWriterThread.h +++ b/Common/APRSHandlerThread.h @@ -19,24 +19,27 @@ #ifndef APRSWriterThread_H #define APRSWriterThread_H +#include + #include "TCPReaderWriterClient.h" #include "RingBuffer.h" #include "Timer.h" #include "Thread.h" +#include "ReadAPRSFrameCallback.h" +#include "APRSFrame.h" -typedef void (*ReadAPRSFrameCallback)(const std::string&); -class CAPRSWriterThread : public CThread { +class CAPRSHandlerThread : public CThread { public: - CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port); - CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter, const std::string& clientName); - virtual ~CAPRSWriterThread(); + CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port); + CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter); + virtual ~CAPRSHandlerThread(); bool start(); bool isConnected() const; - void write(const char* data); + void write(CAPRSFrame& data); void* Entry(); @@ -44,19 +47,20 @@ public: void clock(unsigned int ms); - void setReadAPRSCallback(ReadAPRSFrameCallback cb); + void addReadAPRSCallback(IReadAPRSFrameCallback* cb); private: std::string m_username; std::string m_password; std::string m_ssid; CTCPReaderWriterClient m_socket; - CRingBuffer m_queue; + CRingBuffer m_queue; bool m_exit; bool m_connected; CTimer m_reconnectTimer; + CTimer m_keepAliveTimer; unsigned int m_tries; - ReadAPRSFrameCallback m_APRSReadCallback; + std::vector m_APRSReadCallbacks; std::string m_filter; std::string m_clientName; diff --git a/APRSIdFrameProvider.cpp b/Common/APRSIdFrameProvider.cpp similarity index 95% rename from APRSIdFrameProvider.cpp rename to Common/APRSIdFrameProvider.cpp index 503720a..3637f9d 100644 --- a/APRSIdFrameProvider.cpp +++ b/Common/APRSIdFrameProvider.cpp @@ -31,7 +31,7 @@ CAPRSIdFrameProvider::~CAPRSIdFrameProvider() } -bool CAPRSIdFrameProvider::buildAPRSFrames(const std::string& gateway, const CAPRSEntry * entry, std::vector & frames) +bool CAPRSIdFrameProvider::buildAPRSFrames(const std::string& gateway, const CAPRSEntry * entry, std::vector & frames) { assert(entry != nullptr); diff --git a/APRSIdFrameProvider.h b/Common/APRSIdFrameProvider.h similarity index 90% rename from APRSIdFrameProvider.h rename to Common/APRSIdFrameProvider.h index f83deb7..48917ee 100644 --- a/APRSIdFrameProvider.h +++ b/Common/APRSIdFrameProvider.h @@ -22,6 +22,7 @@ #include "Timer.h" #include "APRSEntry.h" +#include "APRSFrame.h" class CAPRSIdFrameProvider { @@ -29,14 +30,14 @@ public: CAPRSIdFrameProvider(unsigned int timeOut); virtual ~CAPRSIdFrameProvider(); - bool buildAPRSFrames(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + bool buildAPRSFrames(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); void clock(unsigned int ms) { m_timer.clock(ms); } bool wantsToSend(); virtual void start() { }; virtual void close() { }; protected: - virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames) = 0; + virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames) = 0; void setTimeout(unsigned int timeout) { diff --git a/Common/APRSUnit.cpp b/Common/APRSUnit.cpp new file mode 100644 index 0000000..ad8f9e1 --- /dev/null +++ b/Common/APRSUnit.cpp @@ -0,0 +1,120 @@ +/* + * 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 "APRSUnit.h" +#include "APRSFormater.h" +#include "StringUtils.h" +#include "APRStoDPRS.h" + +CAPRSUnit::CAPRSUnit(IRepeaterCallback * repeaterHandler) : +m_frameBuffer(20U), +m_status(APS_IDLE), +m_repeaterHandler(repeaterHandler), +m_headerData(nullptr), +m_slowData(nullptr), +m_out(0U), +m_seq(0U), +m_totalNeeded(0U), +m_timer(1000U, 2U), +m_start() +{ + m_timer.start(); +} + +void CAPRSUnit::writeFrame(CAPRSFrame& frame) +{ + auto frameCopy = new CAPRSFrame(frame); + frameCopy->getPath().clear();//path is of no use for us, just clear it + + m_frameBuffer.push_back(frameCopy); + m_timer.start(); +} + +void CAPRSUnit::clock(unsigned int ms) +{ + m_timer.clock(ms); + if(m_status == APS_IDLE && !m_frameBuffer.empty() && m_timer.hasExpired()) { + + auto frame = m_frameBuffer.front(); + m_frameBuffer.pop_front(); + + m_headerData = new CHeaderData(); + std::string dprs, text; + if(!CAPRSToDPRS::aprsToDPRS(dprs, text, *m_headerData, *frame)) { + delete m_headerData; + m_headerData = nullptr; + return; + } + + m_slowData = new CSlowDataEncoder(); + + m_slowData->setHeaderData(*m_headerData); + m_slowData->setGPSData(dprs); + m_slowData->setTextData(text); + + m_totalNeeded = (m_slowData->getInterleavedDataLength() / (DATA_FRAME_LENGTH_BYTES)) * 2U; + + m_repeaterHandler->process(*m_headerData, DIR_INCOMING, AS_INFO); + + m_out = 0U; + m_seq = 0U; + + m_start = std::chrono::high_resolution_clock::now(); + m_status = APS_TRANSMIT; + return; + } + + if(m_status == APS_TRANSMIT) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + + while (m_out < needed && m_out < m_totalNeeded) { + CAMBEData data; + data.setId(m_headerData->getId()); + data.setSeq(m_seq); + if(m_out == m_totalNeeded - 1U) + data.setEnd(true); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (m_seq == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + } else { + m_slowData->getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); + m_out++; + } + + data.setData(buffer, DV_FRAME_LENGTH_BYTES); + m_repeaterHandler->process(data, DIR_INCOMING, AS_INFO); + + m_seq++; + if (m_seq == 21U) m_seq = 0U; + } + + if(m_out >= m_totalNeeded) { + m_status = APS_IDLE; + delete m_headerData; + delete m_slowData; + } + } +} \ No newline at end of file diff --git a/Common/APRSUnit.h b/Common/APRSUnit.h new file mode 100644 index 0000000..bc7a595 --- /dev/null +++ b/Common/APRSUnit.h @@ -0,0 +1,57 @@ +/* + * 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 +#include +#include + +#include "APRSFrame.h" +#include "RepeaterCallback.h" +#include "Timer.h" +#include "SlowDataEncoder.h" + +enum APRSUNIT_STATUS { + APS_IDLE, + APS_WAIT, + APS_TRANSMIT +}; + +class CAPRSUnit +{ +public: + CAPRSUnit(IRepeaterCallback * repeaterHandler); + void writeFrame(CAPRSFrame& aprsFrame); + void clock(unsigned ms); + +private: + // CRingBuffer m_frameBuffer; + boost::circular_buffer m_frameBuffer; + APRSUNIT_STATUS m_status; + IRepeaterCallback * m_repeaterHandler; + CHeaderData * m_headerData; + CSlowDataEncoder * m_slowData; + unsigned int m_out; + unsigned int m_seq; + unsigned int m_totalNeeded; + CTimer m_timer; + std::chrono::high_resolution_clock::time_point m_start; +}; + + diff --git a/Common/APRStoDPRS.cpp b/Common/APRStoDPRS.cpp new file mode 100644 index 0000000..d238a2f --- /dev/null +++ b/Common/APRStoDPRS.cpp @@ -0,0 +1,73 @@ +/* + * 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 "APRStoDPRS.h" +#include "Log.h" +#include "RSMS1AMessageBuilder.h" + + + +bool CAPRSToDPRS::aprsToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame) +{ + dprs.clear(); + text.clear(); + switch (frame.getType()) + { + case APFT_MESSAGE : + return messageToDPRS(dprs, text, header, frame); + default: + break; + } + + return false; +} + +bool CAPRSToDPRS::messageToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame) +{ + auto frameBody = frame.getBody(); + if(frameBody.length() < 11 || frameBody[0] != ':' || frameBody[10] != ':') { + CLog::logDebug("Invalid APRS message body : %s", frameBody.c_str()); + return false; + } + + // extract recipient + auto recipient = boost::trim_copy(frameBody.substr(1, 9)); + if(recipient.empty()) { + CLog::logDebug("APRS message has no recipient"); + return false; + } + auto dashPos = recipient.find_first_of('-'); + if(dashPos != std::string::npos) + recipient = recipient.substr(0, dashPos); + + //extract message body + auto messageBody = boost::trim_copy(frameBody.substr(11)); + + header.setId(header.createId()); + header.setMyCall1(frame.getSource()); + header.setMyCall2("MSG"); + header.setYourCall(recipient); + + CRSMS1AMessageBuilder::buildMessage(dprs, frame.getSource(), recipient, messageBody); + + text = messageBody; + + return true; +} \ No newline at end of file diff --git a/Common/APRStoDPRS.h b/Common/APRStoDPRS.h new file mode 100644 index 0000000..05191c0 --- /dev/null +++ b/Common/APRStoDPRS.h @@ -0,0 +1,34 @@ +/* + * 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 +#include + +#include "HeaderData.h" +#include "APRSFrame.h" + +class CAPRSToDPRS +{ +public: + static bool aprsToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame); + +private: + static bool messageToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame); +}; \ No newline at end of file diff --git a/AnnouncementUnit.cpp b/Common/AnnouncementUnit.cpp similarity index 100% rename from AnnouncementUnit.cpp rename to Common/AnnouncementUnit.cpp diff --git a/AnnouncementUnit.h b/Common/AnnouncementUnit.h similarity index 100% rename from AnnouncementUnit.h rename to Common/AnnouncementUnit.h diff --git a/AudioUnit.cpp b/Common/AudioUnit.cpp similarity index 100% rename from AudioUnit.cpp rename to Common/AudioUnit.cpp diff --git a/AudioUnit.h b/Common/AudioUnit.h similarity index 100% rename from AudioUnit.h rename to Common/AudioUnit.h diff --git a/CCSCallback.h b/Common/CCSCallback.h similarity index 100% rename from CCSCallback.h rename to Common/CCSCallback.h diff --git a/CCSData.cpp b/Common/CCSData.cpp similarity index 100% rename from CCSData.cpp rename to Common/CCSData.cpp diff --git a/CCSData.h b/Common/CCSData.h similarity index 100% rename from CCSData.h rename to Common/CCSData.h diff --git a/CCSProtocolHandler.cpp b/Common/CCSProtocolHandler.cpp similarity index 100% rename from CCSProtocolHandler.cpp rename to Common/CCSProtocolHandler.cpp diff --git a/CCSProtocolHandler.h b/Common/CCSProtocolHandler.h similarity index 100% rename from CCSProtocolHandler.h rename to Common/CCSProtocolHandler.h diff --git a/CacheManager.cpp b/Common/CacheManager.cpp similarity index 100% rename from CacheManager.cpp rename to Common/CacheManager.cpp diff --git a/CacheManager.h b/Common/CacheManager.h similarity index 100% rename from CacheManager.h rename to Common/CacheManager.h diff --git a/ConnectData.cpp b/Common/ConnectData.cpp similarity index 100% rename from ConnectData.cpp rename to Common/ConnectData.cpp diff --git a/ConnectData.h b/Common/ConnectData.h similarity index 100% rename from ConnectData.h rename to Common/ConnectData.h diff --git a/DCSHandler.cpp b/Common/DCSHandler.cpp similarity index 100% rename from DCSHandler.cpp rename to Common/DCSHandler.cpp diff --git a/DCSHandler.h b/Common/DCSHandler.h similarity index 100% rename from DCSHandler.h rename to Common/DCSHandler.h diff --git a/DCSProtocolHandler.cpp b/Common/DCSProtocolHandler.cpp similarity index 100% rename from DCSProtocolHandler.cpp rename to Common/DCSProtocolHandler.cpp diff --git a/DCSProtocolHandler.h b/Common/DCSProtocolHandler.h similarity index 100% rename from DCSProtocolHandler.h rename to Common/DCSProtocolHandler.h diff --git a/DCSProtocolHandlerPool.cpp b/Common/DCSProtocolHandlerPool.cpp similarity index 97% rename from DCSProtocolHandlerPool.cpp rename to Common/DCSProtocolHandlerPool.cpp index c0c87f8..cc00e43 100644 --- a/DCSProtocolHandlerPool.cpp +++ b/Common/DCSProtocolHandlerPool.cpp @@ -44,6 +44,10 @@ CDCSProtocolHandlerPool::~CDCSProtocolHandlerPool() CDCSProtocolHandler *CDCSProtocolHandlerPool::getIncomingHandler() { + auto it = m_pool.find(m_basePort); + if(it != m_pool.end()) + return it->second; + return getHandler(m_basePort); } diff --git a/DCSProtocolHandlerPool.h b/Common/DCSProtocolHandlerPool.h similarity index 100% rename from DCSProtocolHandlerPool.h rename to Common/DCSProtocolHandlerPool.h diff --git a/DDHandler.cpp b/Common/DDHandler.cpp similarity index 100% rename from DDHandler.cpp rename to Common/DDHandler.cpp diff --git a/DDHandler.h b/Common/DDHandler.h similarity index 100% rename from DDHandler.h rename to Common/DDHandler.h diff --git a/DExtraHandler.cpp b/Common/DExtraHandler.cpp similarity index 99% rename from DExtraHandler.cpp rename to Common/DExtraHandler.cpp index 68fbd1a..0268b19 100644 --- a/DExtraHandler.cpp +++ b/Common/DExtraHandler.cpp @@ -403,8 +403,9 @@ void CDExtraHandler::process(CConnectData& connect) } } -void CDExtraHandler::link(IReflectorCallback* handler, const std::string& repeater, const std::string &gateway, const in_addr& address) +void CDExtraHandler::link(IReflectorCallback* handler, const std::string& repeater, const std::string &gateway, const in_addr& address, unsigned int& localPort) { + localPort = 0U; CDExtraProtocolHandler* protoHandler = m_pool->getHandler(); if (protoHandler == NULL) return; @@ -422,6 +423,7 @@ void CDExtraHandler::link(IReflectorCallback* handler, const std::string& repeat } if (found) { + localPort = protoHandler->getPort(); CConnectData reply(repeater, gateway, CT_LINK1, address, DEXTRA_PORT); protoHandler->writeConnect(reply); } else { diff --git a/DExtraHandler.h b/Common/DExtraHandler.h similarity index 99% rename from DExtraHandler.h rename to Common/DExtraHandler.h index cd9ec0c..0ac616f 100644 --- a/DExtraHandler.h +++ b/Common/DExtraHandler.h @@ -52,7 +52,7 @@ public: static void setHeaderLogger(CHeaderLogger* logger); static void setMaxDongles(unsigned int maxDongles); - static void link(IReflectorCallback* handler, const std::string& repeater, const std::string& reflector, const in_addr& address); + static void link(IReflectorCallback* handler, const std::string& repeater, const std::string& reflector, const in_addr& address, unsigned int& localPort); static void unlink(IReflectorCallback* handler, const std::string& reflector = "", bool exclude = true); static void unlink(); diff --git a/DExtraProtocolHandler.cpp b/Common/DExtraProtocolHandler.cpp similarity index 93% rename from DExtraProtocolHandler.cpp rename to Common/DExtraProtocolHandler.cpp index 5ef827f..6963bd9 100644 --- a/DExtraProtocolHandler.cpp +++ b/Common/DExtraProtocolHandler.cpp @@ -19,7 +19,7 @@ */ #include "DExtraProtocolHandler.h" - +#include "Log.h" #include "Utils.h" // #define DUMP_TX @@ -113,6 +113,17 @@ bool CDExtraProtocolHandler::writeConnect(const CConnectData& connect) return true; } +void CDExtraProtocolHandler::traverseNat(const std::string& address, unsigned int remotePort) +{ + unsigned char buffer = 0x00U; + + in_addr addr = CUDPReaderWriter::lookup(address); + + CLog::logInfo("DExtra Punching hole to %s:%u", address.c_str(), remotePort); + + m_socket.write(&buffer, 1U, addr, remotePort); +} + DEXTRA_TYPE CDExtraProtocolHandler::read() { bool res = true; diff --git a/DExtraProtocolHandler.h b/Common/DExtraProtocolHandler.h similarity index 96% rename from DExtraProtocolHandler.h rename to Common/DExtraProtocolHandler.h index 7936bd1..24c2bef 100644 --- a/DExtraProtocolHandler.h +++ b/Common/DExtraProtocolHandler.h @@ -51,6 +51,7 @@ public: bool writeAMBE(const CAMBEData& data); bool writeConnect(const CConnectData& connect); bool writePoll(const CPollData& poll); + void traverseNat(const std::string& address, unsigned int remotePort); DEXTRA_TYPE read(); CHeaderData* readHeader(); diff --git a/DExtraProtocolHandlerPool.cpp b/Common/DExtraProtocolHandlerPool.cpp similarity index 97% rename from DExtraProtocolHandlerPool.cpp rename to Common/DExtraProtocolHandlerPool.cpp index 0b77b56..c356858 100644 --- a/DExtraProtocolHandlerPool.cpp +++ b/Common/DExtraProtocolHandlerPool.cpp @@ -43,6 +43,10 @@ CDExtraProtocolHandlerPool::~CDExtraProtocolHandlerPool() CDExtraProtocolHandler* CDExtraProtocolHandlerPool::getIncomingHandler() { + auto it = m_pool.find(m_basePort); + if(it != m_pool.end()) + return it->second; + return getHandler(m_basePort); } diff --git a/DExtraProtocolHandlerPool.h b/Common/DExtraProtocolHandlerPool.h similarity index 100% rename from DExtraProtocolHandlerPool.h rename to Common/DExtraProtocolHandlerPool.h diff --git a/DPlusAuthenticator.cpp b/Common/DPlusAuthenticator.cpp similarity index 98% rename from DPlusAuthenticator.cpp rename to Common/DPlusAuthenticator.cpp index 2921996..e67ccf7 100644 --- a/DPlusAuthenticator.cpp +++ b/Common/DPlusAuthenticator.cpp @@ -72,7 +72,9 @@ void* CDPlusAuthenticator::Entry() m_timer.start(); +#ifndef DEBUG_DSTARGW try { +#endif while (!m_killed) { if (m_timer.hasExpired()) { authenticate(m_loginCallsign, OPENDSTAR_HOSTNAME, OPENDSTAR_PORT, '2', true); @@ -83,14 +85,18 @@ void* CDPlusAuthenticator::Entry() m_timer.clock(); } +#ifndef DEBUG_DSTARGW } catch (std::exception& e) { std::string message(e.what()); CLog::logError("Exception raised in the D-Plus Authenticator thread - \"%s\"", message.c_str()); + throw; } catch (...) { CLog::logError("Unknown exception raised in the D-Plus Authenticator thread"); + throw; } +#endif CLog::logInfo("Stopping the D-Plus Authenticator thread"); diff --git a/DPlusAuthenticator.h b/Common/DPlusAuthenticator.h similarity index 100% rename from DPlusAuthenticator.h rename to Common/DPlusAuthenticator.h diff --git a/DPlusHandler.cpp b/Common/DPlusHandler.cpp similarity index 99% rename from DPlusHandler.cpp rename to Common/DPlusHandler.cpp index aa78395..178eefd 100644 --- a/DPlusHandler.cpp +++ b/Common/DPlusHandler.cpp @@ -357,8 +357,9 @@ void CDPlusHandler::process(CConnectData& connect) } } -void CDPlusHandler::link(IReflectorCallback* handler, const std::string& repeater, const std::string &gateway, const in_addr& address) +void CDPlusHandler::link(IReflectorCallback* handler, const std::string& repeater, const std::string &gateway, const in_addr& address, unsigned int& localPort) { + localPort = 0U; CDPlusProtocolHandler* protoHandler = m_pool->getHandler(); if (protoHandler == NULL) return; @@ -377,6 +378,7 @@ void CDPlusHandler::link(IReflectorCallback* handler, const std::string& repeate if (found) { CConnectData connect(CT_LINK1, address, DPLUS_PORT); + localPort = protoHandler->getPort(); protoHandler->writeConnect(connect); m_stateChange = true; } else { diff --git a/DPlusHandler.h b/Common/DPlusHandler.h similarity index 99% rename from DPlusHandler.h rename to Common/DPlusHandler.h index 2e6ea83..78b5b7a 100644 --- a/DPlusHandler.h +++ b/Common/DPlusHandler.h @@ -58,7 +58,7 @@ public: static void startAuthenticator(const std::string& address, CCacheManager* cache); - static void link(IReflectorCallback* handler, const std::string& repeater, const std::string& reflector, const in_addr& address); + static void link(IReflectorCallback* handler, const std::string& repeater, const std::string& reflector, const in_addr& address, unsigned int& localPort); static void relink(IReflectorCallback* handler, const std::string& reflector); static void unlink(IReflectorCallback* handler, const std::string& reflector = "", bool exclude = true); static void unlink(); diff --git a/DPlusProtocolHandler.cpp b/Common/DPlusProtocolHandler.cpp similarity index 94% rename from DPlusProtocolHandler.cpp rename to Common/DPlusProtocolHandler.cpp index 7414aa0..41e1e21 100644 --- a/DPlusProtocolHandler.cpp +++ b/Common/DPlusProtocolHandler.cpp @@ -18,7 +18,7 @@ */ #include "DPlusProtocolHandler.h" - +#include "Log.h" #include "DStarDefines.h" #include "Utils.h" @@ -107,6 +107,17 @@ bool CDPlusProtocolHandler::writeConnect(const CConnectData& connect) return m_socket.write(buffer, length, connect.getYourAddress(), connect.getYourPort()); } +void CDPlusProtocolHandler::traverseNat(const std::string& address, unsigned int remotePort) +{ + unsigned char buffer = 0x00U; + + in_addr addr = CUDPReaderWriter::lookup(address); + + CLog::logInfo("DPlus Punching hole to %s:%u", address.c_str(), remotePort); + + m_socket.write(&buffer, 1U, addr, remotePort); +} + DPLUS_TYPE CDPlusProtocolHandler::read() { bool res = true; diff --git a/DPlusProtocolHandler.h b/Common/DPlusProtocolHandler.h similarity index 95% rename from DPlusProtocolHandler.h rename to Common/DPlusProtocolHandler.h index 85b51f6..144dcf6 100644 --- a/DPlusProtocolHandler.h +++ b/Common/DPlusProtocolHandler.h @@ -27,6 +27,7 @@ #include "PollData.h" #include +#include enum DPLUS_TYPE { DP_NONE, @@ -49,6 +50,7 @@ public: bool writeAMBE(const CAMBEData& data); bool writeConnect(const CConnectData& connect); bool writePoll(const CPollData& poll); + void traverseNat(const std::string& address, unsigned int remotePort); DPLUS_TYPE read(); CHeaderData* readHeader(); diff --git a/DPlusProtocolHandlerPool.cpp b/Common/DPlusProtocolHandlerPool.cpp similarity index 97% rename from DPlusProtocolHandlerPool.cpp rename to Common/DPlusProtocolHandlerPool.cpp index db464a8..b9ef667 100644 --- a/DPlusProtocolHandlerPool.cpp +++ b/Common/DPlusProtocolHandlerPool.cpp @@ -44,6 +44,10 @@ CDPlusProtocolHandlerPool::~CDPlusProtocolHandlerPool() CDPlusProtocolHandler* CDPlusProtocolHandlerPool::getIncomingHandler() { + auto it = m_pool.find(m_basePort); + if(it != m_pool.end()) + return it->second; + return getHandler(m_basePort); } diff --git a/DPlusProtocolHandlerPool.h b/Common/DPlusProtocolHandlerPool.h similarity index 100% rename from DPlusProtocolHandlerPool.h rename to Common/DPlusProtocolHandlerPool.h diff --git a/Defs.h b/Common/Defs.h similarity index 100% rename from Defs.h rename to Common/Defs.h diff --git a/DummyRepeaterProtocolHandler.cpp b/Common/DummyRepeaterProtocolHandler.cpp similarity index 100% rename from DummyRepeaterProtocolHandler.cpp rename to Common/DummyRepeaterProtocolHandler.cpp diff --git a/DummyRepeaterProtocolHandler.h b/Common/DummyRepeaterProtocolHandler.h similarity index 100% rename from DummyRepeaterProtocolHandler.h rename to Common/DummyRepeaterProtocolHandler.h diff --git a/EchoUnit.cpp b/Common/EchoUnit.cpp similarity index 100% rename from EchoUnit.cpp rename to Common/EchoUnit.cpp diff --git a/EchoUnit.h b/Common/EchoUnit.h similarity index 100% rename from EchoUnit.h rename to Common/EchoUnit.h diff --git a/G2Handler.cpp b/Common/G2Handler.cpp similarity index 93% rename from G2Handler.cpp rename to Common/G2Handler.cpp index a767d42..1aaf362 100644 --- a/G2Handler.cpp +++ b/Common/G2Handler.cpp @@ -31,7 +31,7 @@ unsigned int CG2Handler::m_maxRoutes = 0U; CG2Handler** CG2Handler::m_routes = NULL; -CG2ProtocolHandler* CG2Handler::m_handler = NULL; +CG2ProtocolHandlerPool* CG2Handler::m_handler = NULL; CHeaderLogger* CG2Handler::m_headerLogger = NULL; @@ -60,7 +60,7 @@ void CG2Handler::initialise(unsigned int maxRoutes) m_routes[i] = NULL; } -void CG2Handler::setG2ProtocolHandler(CG2ProtocolHandler* handler) +void CG2Handler::setG2ProtocolHandlerPool(CG2ProtocolHandlerPool* handler) { assert(handler != NULL); @@ -101,16 +101,7 @@ void CG2Handler::process(CHeaderData& header) in_addr address = header.getYourAddress(); unsigned int id = header.getId(); - - for (unsigned int i = 0U; i < m_maxRoutes; i++) { - CG2Handler* route = m_routes[i]; - if (route != NULL) { - // Is this a duplicate header, ignore it - if (route->m_id == id) - return; - } - } - + // Find the destination repeater CRepeaterHandler* repeater = CRepeaterHandler::findDVRepeater(header.getRptCall2()); if (repeater == NULL) { @@ -175,6 +166,8 @@ void CG2Handler::process(CAMBEData& data) void CG2Handler::clock(unsigned int ms) { + m_handler->clock(ms); + for (unsigned int i = 0U; i < m_maxRoutes; i++) { CG2Handler* route = m_routes[i]; if (route != NULL) { @@ -206,3 +199,5 @@ bool CG2Handler::clockInt(unsigned int ms) return false; } + + diff --git a/G2Handler.h b/Common/G2Handler.h similarity index 92% rename from G2Handler.h rename to Common/G2Handler.h index d2b14cd..89f3291 100644 --- a/G2Handler.h +++ b/Common/G2Handler.h @@ -22,7 +22,7 @@ #include -#include "G2ProtocolHandler.h" +#include "G2ProtocolHandlerPool.h" #include "RepeaterHandler.h" #include "DStarDefines.h" #include "HeaderLogger.h" @@ -34,7 +34,7 @@ class CG2Handler { public: static void initialise(unsigned int maxRoutes); - static void setG2ProtocolHandler(CG2ProtocolHandler* handler); + static void setG2ProtocolHandlerPool(CG2ProtocolHandlerPool* handler); static void setHeaderLogger(CHeaderLogger* logger); static void process(CHeaderData& header); @@ -54,7 +54,7 @@ private: static unsigned int m_maxRoutes; static CG2Handler** m_routes; - static CG2ProtocolHandler* m_handler; + static CG2ProtocolHandlerPool* m_handler; static CHeaderLogger* m_headerLogger; diff --git a/G2ProtocolHandler.cpp b/Common/G2ProtocolHandler.cpp similarity index 55% rename from G2ProtocolHandler.cpp rename to Common/G2ProtocolHandler.cpp index 8047c89..5592acd 100644 --- a/G2ProtocolHandler.cpp +++ b/Common/G2ProtocolHandler.cpp @@ -18,6 +18,7 @@ */ #include +#include #include "G2ProtocolHandler.h" #include "Utils.h" @@ -27,30 +28,28 @@ const unsigned int BUFFER_LENGTH = 255U; -CG2ProtocolHandler::CG2ProtocolHandler(unsigned int port, const std::string& addr) : -m_socket(addr, port), +CG2ProtocolHandler::CG2ProtocolHandler(CUDPReaderWriter* socket, const struct sockaddr_storage& destination, unsigned int bufferSize) : +m_socket(socket), m_type(GT_NONE), -m_buffer(NULL), +m_buffer(nullptr), m_length(0U), -m_address(), -m_port(0U) +m_address(destination), +m_inactivityTimer(1000U, 29U), +m_id(0U) { - m_buffer = new unsigned char[BUFFER_LENGTH]; + m_inactivityTimer.start(); + m_buffer = new unsigned char[bufferSize]; + ::memset(m_buffer, 0, bufferSize); } CG2ProtocolHandler::~CG2ProtocolHandler() { delete[] m_buffer; - portmap.clear(); -} - -bool CG2ProtocolHandler::open() -{ - return m_socket.open(); } bool CG2ProtocolHandler::writeHeader(const CHeaderData& header) { + m_inactivityTimer.start(); unsigned char buffer[60U]; unsigned int length = header.getG2Data(buffer, 60U, true); @@ -58,12 +57,12 @@ bool CG2ProtocolHandler::writeHeader(const CHeaderData& header) CUtils::dump("Sending Header", buffer, length); #endif - in_addr addr = header.getYourAddress(); - auto found = portmap.find(addr.s_addr); - unsigned int port = (portmap.end()==found) ? header.getYourPort() : found->second; + assert(CNetUtils::match(header.getDestination(), m_address, IMT_ADDRESS_ONLY)); + + //CLog::logTrace("Write header to %s:%u", inet_ntoa(addr), ntohs(TOIPV4(m_address)->sin_port)); for (unsigned int i = 0U; i < 5U; i++) { - bool res = m_socket.write(buffer, length, addr, port); + bool res = m_socket->write(buffer, length, m_address); if (!res) return false; } @@ -73,6 +72,7 @@ bool CG2ProtocolHandler::writeHeader(const CHeaderData& header) bool CG2ProtocolHandler::writeAMBE(const CAMBEData& data) { + m_inactivityTimer.start(); unsigned char buffer[40U]; unsigned int length = data.getG2Data(buffer, 40U); @@ -80,54 +80,34 @@ bool CG2ProtocolHandler::writeAMBE(const CAMBEData& data) CUtils::dump("Sending Data", buffer, length); #endif - in_addr addr = data.getYourAddress(); - auto found = portmap.find(addr.s_addr); - unsigned int port = (portmap.end()==found) ? data.getYourPort() : found->second; - - return m_socket.write(buffer, length, addr, port); + assert(CNetUtils::match(data.getDestination(), m_address, IMT_ADDRESS_ONLY)); + //CLog::logTrace("Write ambe to %s:%u", inet_ntoa(addr), ntohs(TOIPV4(m_address)->sin_port)); + return m_socket->write(buffer, length, m_address); } -G2_TYPE CG2ProtocolHandler::read() +bool CG2ProtocolHandler::setBuffer(unsigned char * buffer, int length) { - bool res = true; - - // Loop until we have no more data from the socket or we have data for the higher layers - while (res) - res = readPackets(); + assert(buffer != nullptr); - return m_type; -} - -bool CG2ProtocolHandler::readPackets() -{ m_type = GT_NONE; + ::memcpy(m_buffer, buffer, length); - // No more data? - int length = m_socket.read(m_buffer, BUFFER_LENGTH, m_address, m_port); - if (length <= 0) + if(length <= 0) return false; m_length = length; - // save the incoming port (this is to enable mobile hotspots) - if (portmap.end() == portmap.find(m_address.s_addr)) { - CLog::logInfo("new address %s on port %u\n", inet_ntoa(m_address), m_port); - portmap[m_address.s_addr] = m_port; - } else { - if (portmap[m_address.s_addr] != m_port) { - CLog::logInfo("new port for %s is %u, was %u\n", inet_ntoa(m_address), m_port, portmap[m_address.s_addr]); - portmap[m_address.s_addr] = m_port; - } - } - if (m_buffer[0] != 'D' || m_buffer[1] != 'S' || m_buffer[2] != 'V' || m_buffer[3] != 'T') { + CLog::logTrace("DSVT"); return true; } else { // Header or data packet type? - if ((m_buffer[14] & 0x80) == 0x80) + if ((m_buffer[14] & 0x80) == 0x80) { m_type = GT_HEADER; - else + } + else { m_type = GT_AMBE; + } return false; } @@ -135,38 +115,43 @@ bool CG2ProtocolHandler::readPackets() CHeaderData* CG2ProtocolHandler::readHeader() { - if (m_type != GT_HEADER) - return NULL; + m_inactivityTimer.start(); + if (m_type != GT_HEADER || m_id != 0U) + return nullptr; + m_type = GT_NONE; // Header data has been consumed, reset our status CHeaderData* header = new CHeaderData; // G2 checksums are unreliable - bool res = header->setG2Data(m_buffer, m_length, false, m_address, m_port); + bool res = header->setG2Data(m_buffer, m_length, false, TOIPV4(m_address)->sin_addr, ntohs(GETPORT(m_address))); if (!res) { delete header; - return NULL; + return nullptr; } + m_id = header->getId();// remember the id so we do not read it duplicate + return header; } CAMBEData* CG2ProtocolHandler::readAMBE() { + m_inactivityTimer.start(); if (m_type != GT_AMBE) return NULL; + m_type = GT_NONE; // Ambe data has been consumed, reset our status CAMBEData* data = new CAMBEData; - bool res = data->setG2Data(m_buffer, m_length, m_address, m_port); + bool res = data->setG2Data(m_buffer, m_length, TOIPV4(m_address)->sin_addr, ntohs(GETPORT(m_address))); if (!res) { delete data; return NULL; } + if(data->isEnd()) + m_id = 0U; + return data; } -void CG2ProtocolHandler::close() -{ - m_socket.close(); -} diff --git a/G2ProtocolHandler.h b/Common/G2ProtocolHandler.h similarity index 70% rename from G2ProtocolHandler.h rename to Common/G2ProtocolHandler.h index b2c8502..92b42a0 100644 --- a/G2ProtocolHandler.h +++ b/Common/G2ProtocolHandler.h @@ -20,11 +20,14 @@ #pragma once #include +#include #include "UDPReaderWriter.h" #include "DStarDefines.h" #include "HeaderData.h" #include "AMBEData.h" +#include "NetUtils.h" +#include "Timer.h" enum G2_TYPE { GT_NONE, @@ -34,7 +37,7 @@ enum G2_TYPE { class CG2ProtocolHandler { public: - CG2ProtocolHandler(unsigned int port, const std::string& addr = std::string("")); + CG2ProtocolHandler(CUDPReaderWriter* socket, const struct sockaddr_storage& destination, unsigned int bufferSize); ~CG2ProtocolHandler(); bool open(); @@ -42,21 +45,25 @@ public: bool writeHeader(const CHeaderData& header); bool writeAMBE(const CAMBEData& data); - G2_TYPE read(); CHeaderData* readHeader(); CAMBEData* readAMBE(); - void close(); + struct sockaddr_storage getDestination() { return m_address; } + G2_TYPE getType() { return m_type; } -private: - std::unordered_map portmap; + bool setBuffer(unsigned char * buffer, int length); + + void clock(unsigned int ms) { m_inactivityTimer.clock(ms); } + bool isInactive() { return m_inactivityTimer.hasExpired(); } - CUDPReaderWriter m_socket; +private: + CUDPReaderWriter * m_socket; G2_TYPE m_type; unsigned char* m_buffer; unsigned int m_length; - in_addr m_address; - unsigned int m_port; + struct sockaddr_storage m_address; + CTimer m_inactivityTimer; + unsigned int m_id; bool readPackets(); }; diff --git a/Common/G2ProtocolHandlerPool.cpp b/Common/G2ProtocolHandlerPool.cpp new file mode 100644 index 0000000..7db90d8 --- /dev/null +++ b/Common/G2ProtocolHandlerPool.cpp @@ -0,0 +1,190 @@ +/* + * 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 "Log.h" +#include "G2ProtocolHandlerPool.h" + +const unsigned int G2_BUFFER_LENGTH = 255U; + +CG2ProtocolHandlerPool::CG2ProtocolHandlerPool(unsigned short port, const std::string& address) : +m_address(address), +m_basePort(port), +m_socket(address, port) +{ + assert(port > 0U); + m_index = m_pool.end(); +} + +CG2ProtocolHandlerPool::~CG2ProtocolHandlerPool() +{ + +} + +bool CG2ProtocolHandlerPool::open() +{ + bool res = m_socket.open(); + return res; +} + +void CG2ProtocolHandlerPool::close() +{ + for(auto handler : m_pool) { + delete handler; + } + m_pool.clear(); + m_index = m_pool.end(); + m_socket.close(); +} + +G2_TYPE CG2ProtocolHandlerPool::read() +{ + bool res = true; + while(res) + res = readPackets(); + + if(m_index == m_pool.end()) + m_index = m_pool.begin(); + + while(m_index != m_pool.end()) { + if((*m_index)->getType() != GT_NONE) { + return (*m_index)->getType(); + } + m_index++; + } + + return GT_NONE; +} + +CAMBEData * CG2ProtocolHandlerPool::readAMBE() +{ + if(m_index == m_pool.end() || (*m_index)->getType() != GT_AMBE) + return nullptr; + + return (*m_index)->readAMBE(); +} + +CHeaderData * CG2ProtocolHandlerPool::readHeader() +{ + if(m_index == m_pool.end() || (*m_index)->getType() != GT_HEADER) + return nullptr; + + return (*m_index)->readHeader(); +} + +bool CG2ProtocolHandlerPool::readPackets() +{ + unsigned char buffer[G2_BUFFER_LENGTH]; + struct sockaddr_storage addr; + ::memset(&addr, 0, sizeof(sockaddr_storage)); + + // No more data? + int length = m_socket.read(buffer, G2_BUFFER_LENGTH, addr); + if(length <= 0) return false; + + if(length == 1 && buffer[0] == 0U) { + CLog::logDebug("G2 Nat traversal packet received"); + } + + CG2ProtocolHandler * handler = findHandler(addr, IMT_ADDRESS_AND_PORT); + if(handler == nullptr) { + CLog::logTrace("new incoming G2 %s:%u", inet_ntoa(TOIPV4(addr)->sin_addr), ntohs(TOIPV4(addr)->sin_port)); + handler = new CG2ProtocolHandler(&m_socket, addr, G2_BUFFER_LENGTH); + m_pool.push_back(handler); + m_index = m_pool.end(); + } + + bool res = handler->setBuffer(buffer, length); + return res; +} + +void CG2ProtocolHandlerPool::traverseNat(const std::string& address) +{ + unsigned char buffer = 0x00U; + + in_addr addr = CUDPReaderWriter::lookup(address); + + CLog::logInfo("G2 Punching hole to %s", address.c_str()); + + m_socket.write(&buffer, 1U, addr, G2_DV_PORT); +} + +bool CG2ProtocolHandlerPool::writeHeader(const CHeaderData& header) +{ + auto handler = findHandler(header.getDestination(), IMT_ADDRESS_AND_PORT); + if(handler == nullptr) + handler = findHandler(header.getDestination(), IMT_ADDRESS_ONLY); + + if(handler == nullptr) { + handler = new CG2ProtocolHandler(&m_socket, header.getDestination(), G2_BUFFER_LENGTH); + m_pool.push_back(handler); + m_index = m_pool.end(); + } + return handler->writeHeader(header); +} + +bool CG2ProtocolHandlerPool::writeAMBE(const CAMBEData& data) +{ + auto handler = findHandler(data.getDestination(), IMT_ADDRESS_AND_PORT); + if(handler == nullptr) + handler = findHandler(data.getDestination(), IMT_ADDRESS_ONLY); + + if(handler == nullptr) { + handler = new CG2ProtocolHandler(&m_socket, data.getDestination(), G2_BUFFER_LENGTH); + m_pool.push_back(handler); + m_index = m_pool.end(); + } + + return handler->writeAMBE(data); +} + +CG2ProtocolHandler * CG2ProtocolHandlerPool::findHandler(const struct sockaddr_storage& addr, IPMATCHTYPE matchType) const +{ + for(auto handler : m_pool) { + if(handler != nullptr && CNetUtils::match(addr, handler->getDestination(), matchType)) + return handler; + } + + return nullptr; +} + +CG2ProtocolHandler * CG2ProtocolHandlerPool::findHandler(in_addr addr, unsigned int port, IPMATCHTYPE matchType) const +{ + struct sockaddr_storage addrStorage; + addrStorage.ss_family = AF_INET; + TOIPV4(addrStorage)->sin_addr = addr; + TOIPV4(addrStorage)->sin_port = port; + + return findHandler(addrStorage, matchType); +} + +void CG2ProtocolHandlerPool::clock(unsigned int ms) +{ + for(auto it = m_pool.begin(); it != m_pool.end();) { + (*it)->clock(ms); + if((*it)->isInactive()) { + delete (*it); + it = m_pool.erase(it); + m_index = m_pool.end(); + } + else { + it++; + } + } +} \ No newline at end of file diff --git a/Common/G2ProtocolHandlerPool.h b/Common/G2ProtocolHandlerPool.h new file mode 100644 index 0000000..9926fc3 --- /dev/null +++ b/Common/G2ProtocolHandlerPool.h @@ -0,0 +1,93 @@ +/* + * 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 +#include +#include +#include + +#include "G2ProtocolHandler.h" +#include "NetUtils.h" + +struct sockaddr_storage_map { + struct compAddrAndPort { + bool operator() (const struct sockaddr_storage& a, const struct sockaddr_storage& b) const { + return CNetUtils::match(a, b, IMT_ADDRESS_AND_PORT); + } + }; + struct hash { + std::size_t operator() (const sockaddr_storage& a) const { + switch(a.ss_family) + { + case AF_INET: { + auto ptr4 = ((struct sockaddr_in *)&a); + size_t res = AF_INET; + boost::hash_combine(res, ptr4->sin_port); + boost::hash_combine(res, ptr4->sin_addr.s_addr); + return res; + } + case AF_INET6: { + auto ptr6 = ((struct sockaddr_in6 *)&a); + size_t res = AF_INET6; + boost::hash_combine(res, ptr6->sin6_port); + auto in6Ptr = (unsigned int *)&(ptr6->sin6_addr); + boost::hash_combine(res, in6Ptr[0]); + boost::hash_combine(res, in6Ptr[1]); + boost::hash_combine(res, in6Ptr[2]); + boost::hash_combine(res, in6Ptr[3]); + return res; + } + default: + return 0U; + } + } + }; +}; + +class CG2ProtocolHandlerPool +{ +public: + CG2ProtocolHandlerPool(unsigned short g2Port, const std::string& address = ""); + ~CG2ProtocolHandlerPool(); + + bool open(); + void close(); + G2_TYPE read(); + CAMBEData * readAMBE(); + CHeaderData * readHeader(); + + bool writeAMBE(const CAMBEData& data); + bool writeHeader(const CHeaderData& header); + + void traverseNat(const std::string& address); + + void clock(unsigned int ms); + +private: + bool readPackets(); + CG2ProtocolHandler * findHandler(const struct sockaddr_storage& addr, IPMATCHTYPE matchType) const; + CG2ProtocolHandler * findHandler(in_addr addr, unsigned int port, IPMATCHTYPE matchType) const; + + std::string m_address; + unsigned int m_basePort; + CUDPReaderWriter m_socket; + std::vector m_pool; + std::vector::iterator m_index; +}; \ No newline at end of file diff --git a/Common/GPSACollector.cpp b/Common/GPSACollector.cpp new file mode 100644 index 0000000..3774713 --- /dev/null +++ b/Common/GPSACollector.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2010,2012,2018 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 +#include +#include + +#include "GPSACollector.h" +#include "StringUtils.h" +#include "Log.h" +#include "APRSUtils.h" + +const unsigned int APRS_CSUM_LENGTH = 4U; + +CGPSACollector::CGPSACollector() : +CSentenceCollector(SLOW_DATA_TYPE_GPS, "$$CRC", '\x0D') +{ + +} + +bool CGPSACollector::isValidSentence(const std::string& sentence) +{ + return isValidGPSA(sentence); +} + +bool CGPSACollector::isValidGPSA(const std::string& gpsa) +{ + if(gpsa.length() < 10U || !boost::starts_with(gpsa, "$$CRC")) + return false; + + auto csum = CAPRSUtils::calcGPSAIcomCRC(gpsa); + auto csumStr = CStringUtils::string_format("%04X", csum); + auto expectedCsum = gpsa.substr(5U, APRS_CSUM_LENGTH); + bool res = ::strcasecmp(csumStr.c_str(), expectedCsum.c_str()) == 0; + return res; +} + +unsigned int CGPSACollector::getDataInt(unsigned char * data, unsigned int length) +{ + if(data == nullptr || length == 0U) + return 0U; + + std::string aprsFrame; + + if(!getDataInt(aprsFrame)) + return 0U; + + auto aprsFrameLen = aprsFrame.length(); + + if(length < aprsFrameLen) { + CLog::logDebug("Not enough space to copy GPS-A APRS frame"); + return 0U; + } + + for(unsigned int i = 0U; i < aprsFrameLen; i++){ + data[i] = aprsFrame[i]; + } + + return aprsFrameLen; +} + +bool CGPSACollector::getDataInt(std::string& data) +{ + if(getSentence().empty()) + return false; + + data.clear(); + auto aprsFrame = getSentence(); + if(aprsFrame.length() < 11U)//do we have enough data beyond CRC? + return false; + + data = aprsFrame.substr(10); + + return true; +} \ No newline at end of file diff --git a/Common/GPSACollector.h b/Common/GPSACollector.h new file mode 100644 index 0000000..2f6d23c --- /dev/null +++ b/Common/GPSACollector.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2010,2012,2018 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 + +#include "SentenceCollector.h" + +class CGPSACollector : public CSentenceCollector +{ +public: + CGPSACollector(); + +protected: + unsigned int getDataInt(unsigned char * data, unsigned int length); + bool getDataInt(std::string& data); + bool isValidSentence(const std::string& sentence); + +private: + static unsigned int calcCRC(const std::string& gpsa); + static bool isValidGPSA(const std::string& gpsa); + + std::string m_sentence; + std::string m_collector; + +}; \ No newline at end of file diff --git a/GatewayCache.cpp b/Common/GatewayCache.cpp similarity index 100% rename from GatewayCache.cpp rename to Common/GatewayCache.cpp diff --git a/GatewayCache.h b/Common/GatewayCache.h similarity index 100% rename from GatewayCache.h rename to Common/GatewayCache.h diff --git a/HBRepeaterProtocolHandler.cpp b/Common/HBRepeaterProtocolHandler.cpp similarity index 100% rename from HBRepeaterProtocolHandler.cpp rename to Common/HBRepeaterProtocolHandler.cpp diff --git a/HBRepeaterProtocolHandler.h b/Common/HBRepeaterProtocolHandler.h similarity index 100% rename from HBRepeaterProtocolHandler.h rename to Common/HBRepeaterProtocolHandler.h diff --git a/HeaderLogger.cpp b/Common/HeaderLogger.cpp similarity index 100% rename from HeaderLogger.cpp rename to Common/HeaderLogger.cpp diff --git a/HeaderLogger.h b/Common/HeaderLogger.h similarity index 100% rename from HeaderLogger.h rename to Common/HeaderLogger.h diff --git a/HeardData.cpp b/Common/HeardData.cpp similarity index 100% rename from HeardData.cpp rename to Common/HeardData.cpp diff --git a/HeardData.h b/Common/HeardData.h similarity index 100% rename from HeardData.h rename to Common/HeardData.h diff --git a/IcomRepeaterProtocolHandler.cpp b/Common/IcomRepeaterProtocolHandler.cpp similarity index 99% rename from IcomRepeaterProtocolHandler.cpp rename to Common/IcomRepeaterProtocolHandler.cpp index d220690..d6e20f8 100644 --- a/IcomRepeaterProtocolHandler.cpp +++ b/Common/IcomRepeaterProtocolHandler.cpp @@ -135,7 +135,9 @@ void* CIcomRepeaterProtocolHandler::Entry() { CLog::logInfo("Starting the Icom Controller thread"); +#ifndef DEBUG_DSTARGW try { +#endif while (!m_killed) { sendGwyPackets(); @@ -145,14 +147,18 @@ void* CIcomRepeaterProtocolHandler::Entry() m_retryTimer.clock(); } +#ifndef DEBUG_DSTARGW } catch (std::exception& e) { std::string message(e.what()); CLog::logError("Exception raised in the Icom Controller thread - \"%s\"", message.c_str()); + throw; } catch (...) { CLog::logError("Unknown exception raised in the Icom Controller thread"); + throw; } +#endif CLog::logInfo("Stopping the Icom Controller thread"); diff --git a/IcomRepeaterProtocolHandler.h b/Common/IcomRepeaterProtocolHandler.h similarity index 100% rename from IcomRepeaterProtocolHandler.h rename to Common/IcomRepeaterProtocolHandler.h diff --git a/Common/Makefile b/Common/Makefile new file mode 100644 index 0000000..f04541f --- /dev/null +++ b/Common/Makefile @@ -0,0 +1,19 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +Common.a: $(OBJS) ../APRS/APRS.a ../BaseCommon/BaseCommon.a ../DStarBase/DStarBase.a ../IRCDDB/IRCDDB.a ../VersionInfo/GitVersion.h + $(AR) rcs Common.a $(OBJS) + +%.o : %.cpp + $(CC) -I../APRS -I../BaseCommon -I../DStarBase -I../IRCDDB -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ + +.PHONY clean: +clean: + $(RM) *.o *.d Common.a + +../APRS/APRS.a: +../BaseCommon/BaseCommon.a: +../DStarBase/DStarBase.a: +../IRCDDB/IRCDDB.a: +../VersionInfo/GitVersion.h: diff --git a/Common/NMEASentenceCollector.cpp b/Common/NMEASentenceCollector.cpp new file mode 100644 index 0000000..9caa630 --- /dev/null +++ b/Common/NMEASentenceCollector.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010,2012,2018 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 +#include +#include + +#include "NMEASentenceCollector.h" +#include "StringUtils.h" +#include "Log.h" +#include "APRSUtils.h" + +CNMEASentenceCollector::CNMEASentenceCollector(const std::string& sentence) : +CSentenceCollector(SLOW_DATA_TYPE_GPS, sentence, '\x0A') +{ + +} + +bool CNMEASentenceCollector::isValidSentence(const std::string& sentence) +{ + return isValidNMEA(sentence); +} + +bool CNMEASentenceCollector::isValidNMEA(const std::string& nmea) +{ + if(nmea.empty() || nmea[0] != '$') + return false; + + auto posStar = nmea.find_last_of('*'); + if(posStar == 0U || posStar == std::string::npos) + return false; + + auto csum = calcXOR(nmea); + auto csumStr = CStringUtils::string_format("%02X", csum); + auto expctedCSumStr = nmea.substr(posStar + 1, 2U); + auto res = ::strcasecmp(expctedCSumStr.c_str(), csumStr.c_str()) == 0; + return res; +} + +unsigned char CNMEASentenceCollector::calcXOR(const std::string& nmea) +{ + unsigned char res = 0U; + + if(!nmea.empty()) { + unsigned int i = nmea[0] == '$' ? 1U : 0U; //skip $ it it is there + while(i < nmea.length()) + { + if(nmea[i] != '*') { + res ^= (unsigned char)nmea[i]; + } + else { + break; + } + i++; + } + } + + return res; +} + +unsigned int CNMEASentenceCollector::getDataInt(unsigned char * data, unsigned int length) +{ + if(data == nullptr || length == 0U) + return 0U; + + std::string aprsFrame; + if(!getDataInt(aprsFrame)) + return 0U; + + auto aprsFrameLen = aprsFrame.length(); + if(length < aprsFrameLen) { + CLog::logDebug("Not enough space to copy NMEA APRS frame"); + return 0U; + } + + for(unsigned int i = 0U; i < aprsFrameLen; i++){ + data[i] = aprsFrame[i]; + } + + return aprsFrameLen; +} + +bool CNMEASentenceCollector::getDataInt(std::string& data) +{ + if(getMyCall().empty() || getSentence().empty()) + return false; + + data.clear(); + auto nmea = getSentence(); + fixUpNMEATimeStamp(nmea); + + std::string fromCall = getMyCall(); + CAPRSUtils::dstarCallsignToAPRS(fromCall); + std::string aprsFrame(fromCall); + aprsFrame.append("-5>GPS30,DSTAR*:") + .append(nmea); + + data.assign(aprsFrame); + return true; +} + +// When set on manual position Icom radios send 000000.00 as NMEA timestamps +// this is a dirty hack to correct this issue. Actually I am not sure about APRS +// software being peeky about this except APRS.fi +void CNMEASentenceCollector::fixUpNMEATimeStamp(std::string& nmea) +{ + auto posStar = nmea.find_last_of('*'); + if(posStar != std::string::npos) + nmea = nmea.substr(0, posStar); + + time_t now; + ::time(&now); + struct tm* tm = ::gmtime(&now); + auto timeStamp = CStringUtils::string_format(",%02d%02d%02d.00,", tm->tm_hour, tm->tm_min, tm->tm_sec); + + boost::replace_all(nmea, ",000000.00,", timeStamp); + //recalculate checksum + nmea = CStringUtils::string_format("%s*%02x", nmea.c_str(), calcXOR(nmea)); +} \ No newline at end of file diff --git a/Common/NMEASentenceCollector.h b/Common/NMEASentenceCollector.h new file mode 100644 index 0000000..d6726c5 --- /dev/null +++ b/Common/NMEASentenceCollector.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010,2012,2018 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 + +#include "SentenceCollector.h" + +class CNMEASentenceCollector : public CSentenceCollector +{ +public: + CNMEASentenceCollector(const std::string& sentence); + +protected: + unsigned int getDataInt(unsigned char * data, unsigned int length); + bool getDataInt(std::string& data); + bool isValidSentence(const std::string& sentence); + +private: + static void dstarCallsignToAPRS(std::string& call); + static bool isValidNMEA(const std::string& nmea); + static unsigned char calcXOR(const std::string& nmea); + static void fixUpNMEATimeStamp(std::string& nmea); +}; \ No newline at end of file diff --git a/PollData.cpp b/Common/PollData.cpp similarity index 100% rename from PollData.cpp rename to Common/PollData.cpp diff --git a/PollData.h b/Common/PollData.h similarity index 100% rename from PollData.h rename to Common/PollData.h diff --git a/Common/RSMS1AMessageBuilder.cpp b/Common/RSMS1AMessageBuilder.cpp new file mode 100644 index 0000000..e7d3d34 --- /dev/null +++ b/Common/RSMS1AMessageBuilder.cpp @@ -0,0 +1,157 @@ +/* + * 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 "RSMS1AMessageBuilder.h" +#include "StringUtils.h" + +std::vector CRSMS1AMessageBuilder::m_charsToEscape = {-17, 0, 17, 19, -2, -25, 26, -3, -1, 36, 13, 44}; + +void CRSMS1AMessageBuilder::buildMessage(std::string& message, const std::string& sender, const std::string& recipient, const std::string body) +{ + auto bodyCrc = calculateBodyCRC(body); + std::string bodyTmp; + escapeBody(bodyTmp, body + (char)bodyCrc); + + std::string header = CStringUtils::string_format("%s,%s,0011", sender.c_str(), recipient.c_str()); + signed char c1, c2; + calcMsgIcomCRC(header, c1, c2); + header.push_back(c1); + header.push_back(c2); + message = "$$Msg," + header + bodyTmp + '\r'; +} + +signed char CRSMS1AMessageBuilder::calculateBodyCRC(const std::string& body) +{ + if(body.length() == 1) + return body[0]; + + signed int num = 0; + for(signed char c : body) { + num += c; + } + + signed int res = (num & 255); + if(res >= 128) + res -= 128; + + return (signed char)res; +} + +void CRSMS1AMessageBuilder::escapeBody(std::string& output, const std::string& body) +{ + output.clear(); + for(char c : body) { + if(std::find(m_charsToEscape.begin(), m_charsToEscape.end(), c) != m_charsToEscape.end()) { + output.push_back('o'); + } + output.push_back(c); + } +} + +void CRSMS1AMessageBuilder::calcMsgIcomCRC(const std::string& msg, signed char& c1, signed char& c2) +{ + int num = 0; + for(unsigned int i = 0U; i < msg.length(); i++) { + num += msg[i]; + } + + c1 = doWhatever((signed char)((num >> 4) & 15)); + c2 = doWhatever((signed char)(num & 15)); +} + +signed char CRSMS1AMessageBuilder::doWhatever(signed char b2) { + int i; + int i2 = b2 & 255; + if (i2 >= 0 && i2 <= 9) { + i = b2 + 48; + } else if (10 > i2 || i2 > 15) { + return 0; + } else { + i = b2 + 55; + } + return (signed char) i; +} + +RSMS1A_PARSE_STATUS CRSMS1AMessageBuilder::parseMessage(std::string& sender, std::string& recipient, std::string& body, const std::string& message) +{ + sender.clear(); + recipient.clear(); + body.clear(); + + if(!boost::starts_with(message, "$$Msg,")) + return RSMS_FAIL; + + auto firstCommaPos = message.find_first_of(','); + if(firstCommaPos == std::string::npos || (firstCommaPos + 1) >= message.length()) + return RSMS_FAIL; + + auto secondCommaPos = message.find_first_of(',', firstCommaPos + 1); + if(secondCommaPos == std::string::npos || (secondCommaPos + 1) >= message.length()) + return RSMS_FAIL; + + auto thirdCommaPos = message.find_first_of(',', secondCommaPos + 1); + if(thirdCommaPos == std::string::npos || (thirdCommaPos + 1 + 6) >= message.length()) + return RSMS_FAIL; + + sender.assign(message.substr(firstCommaPos + 1, secondCommaPos - firstCommaPos - 1)); + recipient.assign(message.substr(secondCommaPos + 1, thirdCommaPos - secondCommaPos - 1)); + body.assign(message.substr(thirdCommaPos + 1 + 6)); + unescapeBody(body, std::string(body)); + if(body.length() >= 2U) body.resize(body.length() - 2U); + + // build a message out of what we received in order to check if all checksums matches + // use only header of message to match checksum + std::string messagecheck; + buildMessage(messagecheck, sender, recipient, " "); + messagecheck.resize(messagecheck.length() - 3U);// get rid of body, body check byte and trailing \r + + if(messagecheck != message.substr(0, messagecheck.length())) { + sender.clear(); + recipient.clear(); + body.clear(); + return RSMS_FAIL; // we do not allow any errors in message header + } + + //rebuild complete messsage with body + buildMessage(messagecheck, sender, recipient, body); + if(messagecheck != message) + return RSMS_ERRONEOUS_MSG; // we allow erros to occur in the message body + + return RSMS_OK; +} + +void CRSMS1AMessageBuilder::unescapeBody(std::string& output, const std::string& body) +{ + output.clear(); + if(body.empty()) + return; + + for(unsigned int i = 0U; i < body.length(); i++) { + auto c = body[i]; + auto next = body[i + 1U]; + if(c == 'o' && std::find(m_charsToEscape.begin(), m_charsToEscape.end(), next) != m_charsToEscape.end()) { + output.push_back(next); + i++; + continue; + } + + output.push_back(c); + } +} \ No newline at end of file diff --git a/Common/RSMS1AMessageBuilder.h b/Common/RSMS1AMessageBuilder.h new file mode 100644 index 0000000..142c0e2 --- /dev/null +++ b/Common/RSMS1AMessageBuilder.h @@ -0,0 +1,45 @@ +/* + * 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 +#include + +enum RSMS1A_PARSE_STATUS { + RSMS_FAIL, + RSMS_ERRONEOUS_MSG, + RSMS_OK +}; + +class CRSMS1AMessageBuilder +{ +public: + static void buildMessage(std::string& message, const std::string& sender, const std::string& recipient, const std::string body); + static RSMS1A_PARSE_STATUS parseMessage(std::string& sender, std::string& recipient, std::string& body, const std::string& message); + +private: + static void calcMsgIcomCRC(const std::string& msg, signed char& c1, signed char& c2); + static void escapeBody(std::string& output, const std::string& body); + static void unescapeBody(std::string& output, const std::string& body); + static void escapeBytes(std::vector output, const std::vector input); + static signed char calculateBodyCRC(const std::string& body); + static signed char doWhatever(signed char b2); + + static std::vector m_charsToEscape; +}; \ No newline at end of file diff --git a/Common/RSMS1AMessageCollector.cpp b/Common/RSMS1AMessageCollector.cpp new file mode 100644 index 0000000..aa62ee7 --- /dev/null +++ b/Common/RSMS1AMessageCollector.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010,2012,2018 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 +#include +#include +#include +#include +#include + +#include "RSMS1AMessageCollector.h" +#include "StringUtils.h" +#include "Log.h" +#include "Utils.h" +#include "APRSUtils.h" +#include "RSMS1AMessageBuilder.h" + + +const unsigned int APRS_CSUM_LENGTH = 4U; + +CRSMS1AMessageCollector::CRSMS1AMessageCollector() : +CSentenceCollector(SLOW_DATA_TYPE_GPS, "$$Msg", '\x0D') +{ + +} + +bool CRSMS1AMessageCollector::isValidSentence(const std::string& sentence) +{ + return isValidMsg(sentence); +} + +bool CRSMS1AMessageCollector::isValidMsg(const std::string& msg) +{ + CLog::logDebug("RS-MS1A"); + // checking validity involves parsing, so we do minumum checks here. Big chekc done in getDataInt + return !msg.empty() && boost::starts_with(msg, "$$Msg"); +} + +unsigned int CRSMS1AMessageCollector::getDataInt(unsigned char * data, unsigned int length) +{ + if(data == nullptr || length == 0U) + return 0U; + + std::string aprsFrame; + if(!getDataInt(aprsFrame)) + return 0U; + + auto aprsFrameLen = aprsFrame.length(); + + if(length < aprsFrameLen) { + CLog::logDebug("Not enough space to copy RSMS1A message frame"); + return 0U; + } + + for(unsigned int i = 0U; i < aprsFrameLen; i++){ + data[i] = aprsFrame[i]; + } + + return aprsFrameLen; +} + +bool CRSMS1AMessageCollector::getDataInt(std::string& data) +{ + if(getSentence().empty()) + return false; + + std::string sender, recipient, body, sentence; + sentence = getSentence(); + auto parseRes = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, sentence); + + CUtils::dump(CStringUtils::string_format("RS-MS1A Message parsed with result %s", parseRes == RSMS_OK ? "OK" : (parseRes == RSMS_ERRONEOUS_MSG ? "Error in msg body" : "failed")).c_str(), + (unsigned char *)sentence.c_str(), sentence.length()); + + if(parseRes == RSMS_FAIL) + return false; + + CAPRSUtils::dstarCallsignToAPRS(sender); + CAPRSUtils::dstarCallsignToAPRS(recipient); + recipient.resize(9U, ' '); + + auto seqNum = rand() % 0xFFFFFU; + CStringUtils::string_format_in_place(data, "%s-5>APDPRS,DSTAR*::%s:%s{%05X", sender.c_str(), recipient.c_str(), body.c_str(), seqNum); + + return true; +} \ No newline at end of file diff --git a/Common/RSMS1AMessageCollector.h b/Common/RSMS1AMessageCollector.h new file mode 100644 index 0000000..f741e92 --- /dev/null +++ b/Common/RSMS1AMessageCollector.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010,2012,2018 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 + +#include "SentenceCollector.h" + +class CRSMS1AMessageCollector : public CSentenceCollector +{ +public: + CRSMS1AMessageCollector(); + +protected: + unsigned int getDataInt(unsigned char * data, unsigned int length); + bool getDataInt(std::string& data); + bool isValidSentence(const std::string& sentence); + +private: + static unsigned int calcCRC(const std::string& gpsa, unsigned int start, unsigned int length); + static bool isValidMsg(const std::string& gpsa); + + std::string m_sentence; + std::string m_collector; +}; \ No newline at end of file diff --git a/Common/ReadAPRSFrameCallback.h b/Common/ReadAPRSFrameCallback.h new file mode 100644 index 0000000..9ce37d3 --- /dev/null +++ b/Common/ReadAPRSFrameCallback.h @@ -0,0 +1,31 @@ +/* + * 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 "APRSFrame.h" + +class IReadAPRSFrameCallback +{ +public: + virtual void readAPRSFrame(CAPRSFrame& aprsFrame) = 0; +}; + diff --git a/ReflectorCallback.h b/Common/ReflectorCallback.h similarity index 100% rename from ReflectorCallback.h rename to Common/ReflectorCallback.h diff --git a/RemoteHandler.cpp b/Common/RemoteHandler.cpp similarity index 99% rename from RemoteHandler.cpp rename to Common/RemoteHandler.cpp index d4f4df2..7372e98 100644 --- a/RemoteHandler.cpp +++ b/Common/RemoteHandler.cpp @@ -62,6 +62,7 @@ void CRemoteHandler::process() m_handler.sendRandom(m_random); break; case RPHT_HASH: { + CLog::logTrace("Read Hash %X", m_random); bool valid = m_handler.readHash(m_password, m_random); if (valid) { CLog::logInfo("Remote control user has logged in"); diff --git a/RemoteHandler.h b/Common/RemoteHandler.h similarity index 100% rename from RemoteHandler.h rename to Common/RemoteHandler.h diff --git a/RemoteLinkData.cpp b/Common/RemoteLinkData.cpp similarity index 100% rename from RemoteLinkData.cpp rename to Common/RemoteLinkData.cpp diff --git a/RemoteLinkData.h b/Common/RemoteLinkData.h similarity index 100% rename from RemoteLinkData.h rename to Common/RemoteLinkData.h diff --git a/RemoteProtocolHandler.cpp b/Common/RemoteProtocolHandler.cpp similarity index 94% rename from RemoteProtocolHandler.cpp rename to Common/RemoteProtocolHandler.cpp index e4857b2..fd38c24 100644 --- a/RemoteProtocolHandler.cpp +++ b/Common/RemoteProtocolHandler.cpp @@ -25,6 +25,7 @@ #include "SHA256.h" #include "Utils.h" #include "StringUtils.h" +#include "Log.h" const unsigned int BUFFER_LENGTH = 2000U; @@ -226,7 +227,7 @@ bool CRemoteProtocolHandler::readLink(std::string& callsign, RECONNECT& reconnec int32_t temp; ::memcpy(&temp, m_inBuffer + 3U + LONG_CALLSIGN_LENGTH, sizeof(int32_t)); - reconnect = RECONNECT(CUtils::swap_endian(temp)); + reconnect = RECONNECT(CUtils::swap_endian_be(temp)); reflector = std::string((char*)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH + sizeof(int32_t)),LONG_CALLSIGN_LENGTH); @@ -245,7 +246,7 @@ bool CRemoteProtocolHandler::readUnlink(std::string& callsign, PROTOCOL& protoco int32_t temp; ::memcpy(&temp, m_inBuffer + 3U + LONG_CALLSIGN_LENGTH, sizeof(int32_t)); - protocol = PROTOCOL(CUtils::swap_endian(temp)); + protocol = PROTOCOL(CUtils::swap_endian_be(temp)); reflector = std::string((char*)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH + sizeof(int32_t)),LONG_CALLSIGN_LENGTH); @@ -315,7 +316,7 @@ bool CRemoteProtocolHandler::sendRepeater(const CRemoteRepeaterData& data) p[i] = data.getCallsign()[i]; p += LONG_CALLSIGN_LENGTH; - int32_t reconnect = CUtils::swap_endian(data.getReconnect()); + int32_t reconnect = CUtils::swap_endian_be(data.getReconnect()); ::memcpy(p, &reconnect, sizeof(int32_t)); p += sizeof(int32_t); @@ -332,19 +333,19 @@ bool CRemoteProtocolHandler::sendRepeater(const CRemoteRepeaterData& data) p[i] = link->getCallsign()[i]; p += LONG_CALLSIGN_LENGTH; - int32_t protocol = CUtils::swap_endian(link->getProtocol()); + int32_t protocol = CUtils::swap_endian_be(link->getProtocol()); ::memcpy(p, &protocol, sizeof(int32_t)); p += sizeof(int32_t); - int32_t linked = CUtils::swap_endian(link->isLinked()); + int32_t linked = CUtils::swap_endian_be(link->isLinked()); ::memcpy(p, &linked, sizeof(int32_t)); p += sizeof(int32_t); - int32_t direction = CUtils::swap_endian(link->getDirection()); + int32_t direction = CUtils::swap_endian_be(link->getDirection()); ::memcpy(p, &direction, sizeof(int32_t)); p += sizeof(int32_t); - int32_t dongle = CUtils::swap_endian(link->isDongle()); + int32_t dongle = CUtils::swap_endian_be(link->isDongle()); ::memcpy(p, &dongle, sizeof(int32_t)); p += sizeof(int32_t); } @@ -364,12 +365,12 @@ bool CRemoteProtocolHandler::sendStarNetGroup(const CRemoteStarNetGroup& data) ::memset(p, ' ', LONG_CALLSIGN_LENGTH); for (unsigned int i = 0U; i < data.getCallsign().length(); i++) - p[i] = data.getCallsign().GetChar(i); + p[i] = data.getCallsign().at(i); p += LONG_CALLSIGN_LENGTH; ::memset(p, ' ', LONG_CALLSIGN_LENGTH); for (unsigned int i = 0U; i < data.getLogoff().length(); i++) - p[i] = data.getLogoff().GetChar(i); + p[i] = data.getLogoff().at(i); p += LONG_CALLSIGN_LENGTH; uint32_t timer = wxUINT32_SWAP_ON_BE(data.getTimer()); @@ -385,7 +386,7 @@ bool CRemoteProtocolHandler::sendStarNetGroup(const CRemoteStarNetGroup& data) ::memset(p, ' ', LONG_CALLSIGN_LENGTH); for (unsigned int i = 0U; i < user.getCallsign().length(); i++) - p[i] = user.getCallsign().GetChar(i); + p[i] = user.getCallsign().at(i); p += LONG_CALLSIGN_LENGTH; timer = wxUINT32_SWAP_ON_BE(user.getTimer()); @@ -440,7 +441,8 @@ bool CRemoteProtocolHandler::sendRandom(uint32_t random) { ::memcpy(m_outBuffer + 0U, "RND", 3U); - uint32_t temp = CUtils::swap_endian(random); + CLog::logTrace("Send Random %X", random); + uint32_t temp = CUtils::swap_endian_be(random); ::memcpy(m_outBuffer + 3U, &temp, sizeof(uint32_t)); // CUtils::dump("Outgoing", m_outBuffer, 3U + sizeof(uint32_t)); diff --git a/RemoteProtocolHandler.h b/Common/RemoteProtocolHandler.h similarity index 100% rename from RemoteProtocolHandler.h rename to Common/RemoteProtocolHandler.h diff --git a/RemoteRepeaterData.cpp b/Common/RemoteRepeaterData.cpp similarity index 100% rename from RemoteRepeaterData.cpp rename to Common/RemoteRepeaterData.cpp diff --git a/RemoteRepeaterData.h b/Common/RemoteRepeaterData.h similarity index 100% rename from RemoteRepeaterData.h rename to Common/RemoteRepeaterData.h diff --git a/RemoteUser.cpp b/Common/RemoteUser.cpp similarity index 100% rename from RemoteUser.cpp rename to Common/RemoteUser.cpp diff --git a/RemoteUser.h b/Common/RemoteUser.h similarity index 100% rename from RemoteUser.h rename to Common/RemoteUser.h diff --git a/RepeaterCache.cpp b/Common/RepeaterCache.cpp similarity index 100% rename from RepeaterCache.cpp rename to Common/RepeaterCache.cpp diff --git a/RepeaterCache.h b/Common/RepeaterCache.h similarity index 100% rename from RepeaterCache.h rename to Common/RepeaterCache.h diff --git a/RepeaterCallback.h b/Common/RepeaterCallback.h similarity index 100% rename from RepeaterCallback.h rename to Common/RepeaterCallback.h diff --git a/RepeaterHandler.cpp b/Common/RepeaterHandler.cpp similarity index 97% rename from RepeaterHandler.cpp rename to Common/RepeaterHandler.cpp index 13635b3..7c486ee 100644 --- a/RepeaterHandler.cpp +++ b/Common/RepeaterHandler.cpp @@ -45,7 +45,7 @@ unsigned int CRepeaterHandler::m_maxRepeaters = 0U; CRepeaterHandler** CRepeaterHandler::m_repeaters = NULL; std::string CRepeaterHandler::m_localAddress; -CG2ProtocolHandler* CRepeaterHandler::m_g2Handler = NULL; +CG2ProtocolHandlerPool* CRepeaterHandler::m_g2HandlerPool = NULL; CIRCDDB* CRepeaterHandler::m_irc = NULL; CCacheManager* CRepeaterHandler::m_cache = NULL; std::string CRepeaterHandler::m_gateway; @@ -59,7 +59,7 @@ bool CRepeaterHandler::m_dtmfEnabled = true; CHeaderLogger* CRepeaterHandler::m_headerLogger = NULL; -CAPRSWriter* CRepeaterHandler::m_aprsWriter = NULL; +CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL; CCallsignList* CRepeaterHandler::m_restrictList = NULL; @@ -226,6 +226,7 @@ m_heardTimer(1000U, 0U, 100U) // 100ms m_echo = new CEchoUnit(this, callsign); m_infoAudio = new CAudioUnit(this, callsign); m_version = new CVersionUnit(this, callsign); + m_aprsUnit = new CAPRSUnit(this); #ifdef USE_DRATS if (dratsEnabled) { @@ -300,11 +301,11 @@ void CRepeaterHandler::add(const std::string& callsign, const std::string& band, delete repeater; } -void CRepeaterHandler::setG2Handler(CG2ProtocolHandler* handler) +void CRepeaterHandler::setG2HandlerPool(CG2ProtocolHandlerPool* handler) { assert(handler != NULL); - m_g2Handler = handler; + m_g2HandlerPool = handler; } void CRepeaterHandler::setCache(CCacheManager* cache) @@ -366,7 +367,7 @@ void CRepeaterHandler::setHeaderLogger(CHeaderLogger* logger) m_headerLogger = logger; } -void CRepeaterHandler::setAPRSWriter(CAPRSWriter* writer) +void CRepeaterHandler::setAPRSWriter(CAPRSHandler* writer) { m_aprsWriter = writer; } @@ -882,7 +883,7 @@ void CRepeaterHandler::processRepeater(CAMBEData& data) case G2_OK: data.setDestination(m_g2Address, G2_DV_PORT); - m_g2Handler->writeAMBE(data); + m_g2HandlerPool->writeAMBE(data); if (data.isEnd()) { m_repeaterId = 0x00U; @@ -1282,7 +1283,7 @@ void CRepeaterHandler::resolveUserInt(const std::string& user, const std::string m_g2Header->setDestination(m_g2Address, G2_DV_PORT); m_g2Header->setRepeaters(m_g2Gateway, m_g2Repeater); - m_g2Handler->writeHeader(*m_g2Header); + m_g2HandlerPool->writeHeader(*m_g2Header); delete m_g2Header; m_g2Status = G2_OK; @@ -1314,7 +1315,7 @@ void CRepeaterHandler::resolveRepeaterInt(const std::string& repeater, const std m_g2Header->setDestination(m_g2Address, G2_DV_PORT); m_g2Header->setRepeaters(m_g2Gateway, m_g2Repeater); - m_g2Handler->writeHeader(*m_g2Header); + m_g2HandlerPool->writeHeader(*m_g2Header); delete m_g2Header; m_g2Status = G2_OK; @@ -1341,8 +1342,9 @@ void CRepeaterHandler::resolveRepeaterInt(const std::string& repeater, const std case DP_DPLUS: if (m_dplusEnabled) { m_linkGateway = gateway; + unsigned int localPort = 0U; addr.s_addr = ::inet_addr(address.c_str()); - CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, addr); + CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, addr, localPort); m_linkStatus = LS_LINKING_DPLUS; } else { CLog::logInfo("Require D-Plus for linking to %s, but D-Plus is disabled", repeater.c_str()); @@ -1380,8 +1382,9 @@ void CRepeaterHandler::resolveRepeaterInt(const std::string& repeater, const std default: if (m_dextraEnabled) { m_linkGateway = gateway; + unsigned int localPort = 0U; addr.s_addr = ::inet_addr(address.c_str()); - CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, addr); + CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, addr, localPort); m_linkStatus = LS_LINKING_DEXTRA; } else { CLog::logInfo("Require DExtra for linking to %s, but DExtra is disabled", repeater.c_str()); @@ -1416,6 +1419,9 @@ void CRepeaterHandler::clockInt(unsigned int ms) m_echo->clock(ms); m_version->clock(ms); + if(m_aprsUnit != nullptr) + m_aprsUnit->clock(ms); + m_linkReconnectTimer.clock(ms); m_watchdogTimer.clock(ms); m_queryTimer.clock(ms); @@ -2022,7 +2028,7 @@ void CRepeaterHandler::g2CommandHandler(const std::string& callsign, const std:: if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) return; - if (callsign.substr(0,1) == "/") { + if (!callsign.empty() && callsign[0] == '/') { if (m_irc == NULL) { CLog::logInfo("%s is trying to G2 route with ircDDB disabled", user.c_str()); m_g2Status = G2_LOCAL; @@ -2053,6 +2059,7 @@ void CRepeaterHandler::g2CommandHandler(const std::string& callsign, const std:: m_g2User = "CQCQCQ "; CRepeaterData* data = m_cache->findRepeater(m_g2Repeater); + m_irc->notifyRepeaterG2NatTraversal(m_g2Repeater); if (data == NULL) { m_g2Status = G2_REPEATER; @@ -2065,7 +2072,7 @@ void CRepeaterHandler::g2CommandHandler(const std::string& callsign, const std:: m_g2Gateway = data->getGateway(); header.setDestination(m_g2Address, G2_DV_PORT); header.setRepeaters(m_g2Gateway, m_g2Repeater); - m_g2Handler->writeHeader(header); + m_g2HandlerPool->writeHeader(header); delete data; } } else if (string_right(callsign, 1) != "L" && string_right(callsign, 1) != "U") { @@ -2104,10 +2111,11 @@ void CRepeaterHandler::g2CommandHandler(const std::string& callsign, const std:: m_g2User = callsign; m_g2Address = data->getAddress(); m_g2Repeater = data->getRepeater(); + m_irc->notifyRepeaterG2NatTraversal(m_g2Repeater); m_g2Gateway = data->getGateway(); header.setDestination(m_g2Address, G2_DV_PORT); header.setRepeaters(m_g2Gateway, m_g2Repeater); - m_g2Handler->writeHeader(header); + m_g2HandlerPool->writeHeader(header); delete data; } @@ -2280,8 +2288,11 @@ void CRepeaterHandler::linkInt(const std::string& callsign) switch (data->getProtocol()) { case DP_DPLUS: if (m_dplusEnabled) { + unsigned int localPort = 0U; m_linkStatus = LS_LINKING_DPLUS; - CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); + CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort); + if(m_irc != nullptr && localPort > 0U) + m_irc->notifyRepeaterDPlusNatTraversal(m_linkRepeater, localPort); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { @@ -2315,8 +2326,11 @@ void CRepeaterHandler::linkInt(const std::string& callsign) default: if (m_dextraEnabled) { + unsigned int localPort = 0U; m_linkStatus = LS_LINKING_DEXTRA; - CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); + CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort); + if(m_irc != nullptr && localPort > 0U) + m_irc->notifyRepeaterDextraNatTraversal(m_linkRepeater, localPort); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { @@ -2422,6 +2436,10 @@ void CRepeaterHandler::startupInt() m_irc->rptrQTH(callsign, m_latitude, m_longitude, m_description1, m_description2, m_url); } + if(m_aprsWriter != nullptr) { + m_aprsWriter->addReadAPRSCallback(this); + } + #ifdef USE_CCS m_ccsHandler = new CCCSHandler(this, m_rptCallsign, m_index + 1U, m_latitude, m_longitude, m_frequency, m_offset, m_description1, m_description2, m_url, CCS_PORT + m_index); #endif @@ -2448,8 +2466,11 @@ void CRepeaterHandler::startupInt() switch (protocol) { case DP_DPLUS: if (m_dplusEnabled) { + unsigned int localPort = 0U; m_linkStatus = LS_LINKING_DPLUS; - CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); + CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort); + if(m_irc != nullptr && localPort > 0U) + m_irc->notifyRepeaterDPlusNatTraversal(m_linkRepeater, localPort); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { @@ -2483,8 +2504,11 @@ void CRepeaterHandler::startupInt() default: if (m_dextraEnabled) { + unsigned int localPort = 0U; m_linkStatus = LS_LINKING_DEXTRA; - CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); + CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort); + if(m_irc != nullptr && localPort > 0U) + m_irc->notifyRepeaterDextraNatTraversal(m_linkRepeater, localPort); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { @@ -3069,25 +3093,32 @@ void CRepeaterHandler::triggerInfo() } } +void CRepeaterHandler::readAPRSFrame(CAPRSFrame& frame) +{ + if(m_aprsUnit != nullptr) { + m_aprsUnit->writeFrame(frame); + } +} + #ifdef USE_CCS bool CRepeaterHandler::isCCSCommand(const std::string& command) const { if (command.IsSameAs("CA "))) return true; - wxChar c = command.GetChar(0U); + wxChar c = command.at(0U); if (c != wxT('C')) return false; - c = command.GetChar(1U); + c = command.at(1U); if (c < wxT('0') || c > wxT('9')) return false; - c = command.GetChar(2U); + c = command.at(2U); if (c < wxT('0') || c > wxT('9')) return false; - c = command.GetChar(3U); + c = command.at(3U); if (c < wxT('0') || c > wxT('9')) return false; diff --git a/RepeaterHandler.h b/Common/RepeaterHandler.h similarity index 96% rename from RepeaterHandler.h rename to Common/RepeaterHandler.h index 229b09c..0b29b85 100644 --- a/RepeaterHandler.h +++ b/Common/RepeaterHandler.h @@ -28,7 +28,7 @@ #include "DExtraProtocolHandler.h" #include "DPlusProtocolHandler.h" #include "RemoteRepeaterData.h" -#include "G2ProtocolHandler.h" +#include "G2ProtocolHandlerPool.h" #include "ReflectorCallback.h" #include "RepeaterCallback.h" #include "AnnouncementUnit.h" @@ -48,7 +48,7 @@ #include "CCSHandler.h" #endif #include "StatusData.h" -#include "APRSWriter.h" +#include "APRSHandler.h" #include "HeardData.h" #include "AudioUnit.h" #include "EchoUnit.h" @@ -58,11 +58,13 @@ #include "Timer.h" #include "DTMF.h" #include "Defs.h" +#include "ReadAPRSFrameCallback.h" +#include "APRSUnit.h" #include -class CRepeaterHandler : public IRepeaterCallback, public IReflectorCallback, public ICCSCallback { +class CRepeaterHandler : public IRepeaterCallback, public IReflectorCallback, public ICCSCallback, public IReadAPRSFrameCallback { public: static void initialise(unsigned int maxRepeaters); @@ -73,7 +75,7 @@ public: #endif static void setLocalAddress(const std::string& address); - static void setG2Handler(CG2ProtocolHandler* handler); + static void setG2HandlerPool(CG2ProtocolHandlerPool* handler); static void setIRC(CIRCDDB* irc); static void setCache(CCacheManager* cache); static void setGateway(const std::string& gateway); @@ -82,7 +84,7 @@ public: static void setDPlusEnabled(bool enabled); static void setDCSEnabled(bool enabled); static void setHeaderLogger(CHeaderLogger* logger); - static void setAPRSWriter(CAPRSWriter* writer); + static void setAPRSWriter(CAPRSHandler* writer); static void setInfoEnabled(bool enabled); static void setEchoEnabled(bool enabled); static void setDTMFEnabled(bool enabled); @@ -142,6 +144,8 @@ public: virtual void ccsLinkFailed(const std::string& dtmf, DIRECTION direction); virtual void ccsLinkEnded(const std::string& callsign, DIRECTION direction); + 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); @@ -164,7 +168,7 @@ private: static CRepeaterHandler** m_repeaters; static std::string m_localAddress; - static CG2ProtocolHandler* m_g2Handler; + static CG2ProtocolHandlerPool* m_g2HandlerPool; static CCacheManager* m_cache; static std::string m_gateway; static CIRCDDB* m_irc; @@ -178,7 +182,7 @@ private: static CHeaderLogger* m_headerLogger; - static CAPRSWriter* m_aprsWriter; + static CAPRSHandler* m_aprsWriter; static CCallsignList* m_whiteList; static CCallsignList* m_blackList; @@ -275,6 +279,9 @@ private: // Version information CVersionUnit* m_version; + // APRS to DPRS + CAPRSUnit* m_aprsUnit; + #ifdef USE_DRATS // D-RATS handler CDRATSServer* m_drats; diff --git a/RepeaterProtocolHandler.h b/Common/RepeaterProtocolHandler.h similarity index 100% rename from RepeaterProtocolHandler.h rename to Common/RepeaterProtocolHandler.h diff --git a/Common/SentenceCollector.cpp b/Common/SentenceCollector.cpp new file mode 100644 index 0000000..551b7d7 --- /dev/null +++ b/Common/SentenceCollector.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010,2012,2018 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 + +#include "SentenceCollector.h" + +CSentenceCollector::CSentenceCollector(unsigned char slowDataType, const std::string& sentenceIdentifier, unsigned char endMarker) : +CSlowDataCollector(slowDataType), +m_collector(), +m_sentenceIdentifier(sentenceIdentifier), +m_sentence(), +m_endMarker(endMarker) +{ + assert(!sentenceIdentifier.empty()); +} + +bool CSentenceCollector::addData(const unsigned char * data) +{ + m_collector.append(std::string((char*)data, 5U)); + + std::string::size_type n2 = m_collector.find_last_of(m_endMarker); + if (n2 == std::string::npos) + return false; + + std::string::size_type n1 = m_collector.find(m_sentenceIdentifier); + if (n1 == std::string::npos) + return false; + + if(n2 < n1) + return false; + + std::string sentence; + for(unsigned int i = n1; i <= n2; i++) { + sentence.push_back(m_collector[i] & 0x7FU); + } + + bool ret = isValidSentence(sentence); + if(ret) { + m_sentence = sentence; + } else { + m_sentence.clear(); + } + + m_collector = m_collector.substr(n2); + + return ret; +} + +std::string CSentenceCollector::getSentence() +{ + return m_sentence; +} + +void CSentenceCollector::resetInt() +{ + m_collector.clear(); + m_sentence.clear(); +} \ No newline at end of file diff --git a/Common/SentenceCollector.h b/Common/SentenceCollector.h new file mode 100644 index 0000000..2c3b1b9 --- /dev/null +++ b/Common/SentenceCollector.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010,2012,2018 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 + +#include "SlowDataCollector.h" + +class CSentenceCollector : public CSlowDataCollector +{ +public: + CSentenceCollector(unsigned char slowDataType, const std::string& sentenceIdentifier, unsigned char endMarker); + +protected: + bool addData(const unsigned char * data); + + virtual unsigned int getDataInt(unsigned char * data, unsigned int length) = 0; + virtual bool getDataInt(std::string& data) = 0; + static void dstarCallsignToAPRS(std::string& call); + std::string getSentence(); + +private: + virtual bool isValidSentence(const std::string& sentence) = 0; + virtual void resetInt(); + + std::string m_collector; + std::string m_sentenceIdentifier; + std::string m_sentence; + unsigned char m_endMarker; +}; \ No newline at end of file diff --git a/Common/SlowDataCollector.cpp b/Common/SlowDataCollector.cpp new file mode 100644 index 0000000..b9caad0 --- /dev/null +++ b/Common/SlowDataCollector.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010,2012,2018 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 +#include + +#include "SlowDataCollector.h" +#include "Log.h" + +const unsigned int SLOW_DATA_BLOCK_LENGTH = 6U; + +CSlowDataCollector::CSlowDataCollector(unsigned char slowDataType) : +m_slowDataType(slowDataType), +m_myCall(), +m_state(SS_FIRST) +{ + m_buffer = new unsigned char[SLOW_DATA_BLOCK_LENGTH]; + std::memset(m_buffer, 0, SLOW_DATA_BLOCK_LENGTH); +} + +CSlowDataCollector::~CSlowDataCollector() +{ + delete[] m_buffer; +} + +std::string CSlowDataCollector::getMyCall() const +{ + return m_myCall; +} + +void CSlowDataCollector::setMyCall(const std::string& myCall) +{ + m_myCall = myCall; +} + +bool CSlowDataCollector::writeData(const unsigned char* data) +{ + assert(data != nullptr); + + switch (m_state) { + case SS_FIRST: + m_buffer[0U] = data[0U] ^ SCRAMBLER_BYTE1; + m_buffer[1U] = data[1U] ^ SCRAMBLER_BYTE2; + m_buffer[2U] = data[2U] ^ SCRAMBLER_BYTE3; + m_state = SS_SECOND; + return false; + + case SS_SECOND: + m_buffer[3U] = data[0U] ^ SCRAMBLER_BYTE1; + m_buffer[4U] = data[1U] ^ SCRAMBLER_BYTE2; + m_buffer[5U] = data[2U] ^ SCRAMBLER_BYTE3; + m_state = SS_FIRST; + break; + } + + if((m_buffer[0] & SLOW_DATA_TYPE_MASK) == m_slowDataType) + return addData(m_buffer + 1U); + + return false; +} + +void CSlowDataCollector::sync() +{ + m_state = SS_FIRST; +} + +unsigned int CSlowDataCollector::getData(unsigned char * data, unsigned int length) +{ + return getDataInt(data, length); +} + +bool CSlowDataCollector::getData(std::string& data) +{ + return getDataInt(data); +} + +void CSlowDataCollector::reset() +{ + m_state = SS_FIRST; + resetInt(); +} + +unsigned char CSlowDataCollector::getDataType() +{ + return m_slowDataType; +} \ No newline at end of file diff --git a/Common/SlowDataCollector.h b/Common/SlowDataCollector.h new file mode 100644 index 0000000..f2389f9 --- /dev/null +++ b/Common/SlowDataCollector.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010,2012,2018 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 + +#include "DStarDefines.h" +#include "Defs.h" + +class ISlowDataCollector +{ +public: + virtual ~ISlowDataCollector() { } ; + + virtual std::string getMyCall() const = 0; + virtual void setMyCall(const std::string& mycall) = 0; + virtual bool writeData(const unsigned char* data) = 0; + virtual void sync() = 0; + virtual unsigned int getData(unsigned char* data, unsigned int length) = 0; + virtual bool getData(std::string& data) = 0; + virtual void reset() = 0; + virtual unsigned char getDataType() = 0; + virtual void clock(unsigned int ms) = 0; +}; + +class CSlowDataCollector : public ISlowDataCollector +{ +public: + CSlowDataCollector(unsigned char slowDataType); + virtual ~CSlowDataCollector(); + + std::string getMyCall() const; + void setMyCall(const std::string& mycall); + bool writeData(const unsigned char* data); + void sync(); + unsigned int getData(unsigned char* data, unsigned int length); + bool getData(std::string& data); + void reset(); + unsigned char getDataType(); + void clock(unsigned int) { }; + +protected: + virtual bool addData(const unsigned char* data) = 0; + virtual unsigned int getDataInt(unsigned char* data, unsigned int length) = 0; + virtual bool getDataInt(std::string& data) = 0; + virtual void resetInt() { } + +private: + unsigned char m_slowDataType; + std::string m_myCall; + SLOWDATA_STATE m_state; + unsigned char * m_buffer; +}; \ No newline at end of file diff --git a/Common/SlowDataCollectorThrottle.cpp b/Common/SlowDataCollectorThrottle.cpp new file mode 100644 index 0000000..8d97fe1 --- /dev/null +++ b/Common/SlowDataCollectorThrottle.cpp @@ -0,0 +1,98 @@ +/* + * 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 "SlowDataCollectorThrottle.h" + +CSlowDataCollectorThrottle::CSlowDataCollectorThrottle(ISlowDataCollector* collector, unsigned int timeout) : +m_collector(collector), +m_timer(1000U, timeout), +m_isFirst(true) +{ + assert(collector != nullptr); +} + +CSlowDataCollectorThrottle::~CSlowDataCollectorThrottle() +{ + delete m_collector; +} + +std::string CSlowDataCollectorThrottle::getMyCall() const +{ + return m_collector->getMyCall(); +} + +void CSlowDataCollectorThrottle::setMyCall(const std::string& mycall) +{ + m_isFirst = true; + m_collector->setMyCall(mycall); +} +bool CSlowDataCollectorThrottle::writeData(const unsigned char* data) +{ + m_isComplete = false; + bool complete = m_collector->writeData(data); + if(complete){ + if(m_isFirst) { + m_isFirst = false; + m_isComplete = true; + m_timer.start(); + return true; + } + + if(m_timer.hasExpired()) { + m_isComplete = true; + m_timer.start(); + return true; + } + } + + return false; +} +void CSlowDataCollectorThrottle::sync() +{ + m_collector->sync(); +} +unsigned int CSlowDataCollectorThrottle::getData(unsigned char* data, unsigned int length) +{ + if(m_isComplete) + return m_collector->getData(data, length); + + return 0U; +} +bool CSlowDataCollectorThrottle::getData(std::string& data) +{ + if(m_isComplete) + return m_collector->getData(data); + + return false; +} +void CSlowDataCollectorThrottle::reset() +{ + m_timer.start(); + m_collector->reset(); +} +unsigned char CSlowDataCollectorThrottle::getDataType() +{ + return m_collector->getDataType(); +} + +void CSlowDataCollectorThrottle::clock(unsigned int ms) +{ + m_timer.clock(ms); +} \ No newline at end of file diff --git a/Common/SlowDataCollectorThrottle.h b/Common/SlowDataCollectorThrottle.h new file mode 100644 index 0000000..f7fbd23 --- /dev/null +++ b/Common/SlowDataCollectorThrottle.h @@ -0,0 +1,46 @@ +/* + * 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 + +#include "SlowDataCollector.h" +#include "Timer.h" + +class CSlowDataCollectorThrottle : public ISlowDataCollector +{ +public: + CSlowDataCollectorThrottle(ISlowDataCollector* collector, unsigned int timeout); + ~CSlowDataCollectorThrottle(); + std::string getMyCall() const; + void setMyCall(const std::string& mycall); + bool writeData(const unsigned char* data); + void sync(); + unsigned int getData(unsigned char* data, unsigned int length); + bool getData(std::string& data); + void reset(); + unsigned char getDataType(); + void clock(unsigned int ms); + +private: + ISlowDataCollector* m_collector; + CTimer m_timer; + bool m_isFirst; + bool m_isComplete; +}; \ No newline at end of file diff --git a/SlowDataEncoder.cpp b/Common/SlowDataEncoder.cpp similarity index 85% rename from SlowDataEncoder.cpp rename to Common/SlowDataEncoder.cpp index 2dd3953..f519ee6 100644 --- a/SlowDataEncoder.cpp +++ b/Common/SlowDataEncoder.cpp @@ -18,6 +18,9 @@ #include "CCITTChecksum.h" #include "DStarDefines.h" +// Only works for positive numbers +#define roundUpToMultipleOf(n, m)(((n + m - 1) / m) * m) + const unsigned int SLOW_DATA_BLOCK_SIZE = 6U; const unsigned int SLOW_DATA_FULL_BLOCK_SIZE = SLOW_DATA_BLOCK_SIZE * 10U; @@ -130,14 +133,14 @@ void CSlowDataEncoder::getInterleavedData(unsigned char* data) getHeaderData(data); else { buildInterleavedData(); - getData(m_interleavedData, data, m_interleavedPtr, m_gpsDataFullSize); + getData(m_interleavedData, data, m_interleavedPtr, m_interleavedDataFullSize); } } void CSlowDataEncoder::buildInterleavedData() { - //first build interleaved data if we do not have it - if(!m_interleavedData) + //first build interleaved data if we do not h(x + multiple - 1 - (x % multiple))ave it + if(m_interleavedData == nullptr) { getInterleavedDataLength(); m_interleavedData = new unsigned char[m_interleavedDataFullSize]; @@ -145,33 +148,35 @@ void CSlowDataEncoder::buildInterleavedData() unsigned int textPtr = 0U; unsigned int gpsPtr = 0U; - unsigned int headerPtr = 0U; //now proceed with data copying, according to this document http://www.qsl.net/kb9mwr/projects/dv/dstar/Slow%20Data.pdf - if(m_textData && m_gpsData){ - for(unsigned int interleavedPtr = 0; interleavedPtr < m_interleavedDataFullSize; interleavedPtr += SLOW_DATA_BLOCK_SIZE){ + if(m_textData != nullptr && m_gpsData != nullptr){ + unsigned int interleavedPtr = 0; + while(textPtr < TEXT_SIZE || gpsPtr < m_gpsDataSize){ if(textPtr < TEXT_SIZE && ((interleavedPtr / SLOW_DATA_BLOCK_SIZE) & 0x01U) == 0) { ::memcpy(m_interleavedData + interleavedPtr, m_textData + textPtr, SLOW_DATA_BLOCK_SIZE); textPtr += SLOW_DATA_BLOCK_SIZE; } - else if(gpsPtr < m_gpsDataSize){ + else if(gpsPtr < m_gpsDataSize) { ::memcpy(m_interleavedData + interleavedPtr, m_gpsData + gpsPtr, SLOW_DATA_BLOCK_SIZE); gpsPtr += SLOW_DATA_BLOCK_SIZE; } - else if(m_headerData && headerPtr < HEADER_SIZE){ - ::memcpy(m_interleavedData + interleavedPtr, m_headerData + headerPtr, SLOW_DATA_BLOCK_SIZE); - headerPtr += SLOW_DATA_BLOCK_SIZE; - } + interleavedPtr += SLOW_DATA_BLOCK_SIZE; + } + + if(m_headerData != nullptr) { + //append header dat in one block at the end + ::memcpy(m_interleavedData + roundUpToMultipleOf(interleavedPtr, SLOW_DATA_FULL_BLOCK_SIZE), m_headerData, HEADER_SIZE); } } - else if(m_textData && !m_gpsData && m_headerData){ + else if(m_textData != nullptr && m_gpsData == nullptr && m_headerData != nullptr){ //according to above doc, header and text are not interleaved, just on after the other. filler bytes between resync bytes. ::memcpy(m_interleavedData, m_textData, SLOW_DATA_FULL_BLOCK_SIZE); ::memcpy(m_interleavedData + SLOW_DATA_FULL_BLOCK_SIZE, m_headerData, SLOW_DATA_FULL_BLOCK_SIZE); } - else if(!m_textData && m_gpsData && m_headerData){ + else if(m_textData == nullptr && m_gpsData != nullptr && m_headerData != nullptr){ //could not find any spec about this particular case, let's put the data one after the other ::memcpy(m_interleavedData, m_gpsData, SLOW_DATA_FULL_BLOCK_SIZE); ::memcpy(m_interleavedData + SLOW_DATA_FULL_BLOCK_SIZE, m_headerData, SLOW_DATA_FULL_BLOCK_SIZE); @@ -184,9 +189,9 @@ unsigned int CSlowDataEncoder::getInterleavedDataLength() //calculate size (including filler bytes); m_interleavedDataFullSize = 0U; if(m_textData) m_interleavedDataFullSize += TEXT_SIZE; - if(m_headerData) m_interleavedDataFullSize += HEADER_SIZE; + if(m_headerData) m_interleavedDataFullSize += SLOW_DATA_FULL_BLOCK_SIZE; // the is because header data is transmitted as one contiguous block. Unused bytes fileld with 'f' if(m_gpsData) m_interleavedDataFullSize += m_gpsDataSize; - m_interleavedDataFullSize = SLOW_DATA_FULL_BLOCK_SIZE * (1U + ((m_interleavedDataFullSize - 1U) / SLOW_DATA_FULL_BLOCK_SIZE)); + m_interleavedDataFullSize = roundUpToMultipleOf(m_interleavedDataFullSize, SLOW_DATA_FULL_BLOCK_SIZE); //SLOW_DATA_FULL_BLOCK_SIZE * (1U + ((m_interleavedDataFullSize - 1U) / SLOW_DATA_FULL_BLOCK_SIZE)); return m_interleavedDataFullSize; } @@ -340,15 +345,15 @@ void CSlowDataEncoder::setGPSData(const std::string& gpsData) if((gpsDataStrLen = gpsData.size()) > 0){ unsigned int gpsDataPos; unsigned int strPos = 0; - m_gpsDataSize = 1U + ((gpsDataStrLen - 1U) / 6U);//to make room for the type bytes - m_gpsDataSize += gpsDataStrLen; - m_gpsDataFullSize = SLOW_DATA_FULL_BLOCK_SIZE * (1U + ((m_gpsDataSize - 1U) / SLOW_DATA_FULL_BLOCK_SIZE)); + unsigned int typeBytesCount = 1U + ((gpsDataStrLen - 1U) / SLOW_DATA_BLOCK_SIZE);//to make room for the type bytes + m_gpsDataSize = typeBytesCount + gpsDataStrLen; + m_gpsDataFullSize = roundUpToMultipleOf(m_gpsDataSize, SLOW_DATA_FULL_BLOCK_SIZE); m_gpsData = new unsigned char[m_gpsDataFullSize]; ::memset(m_gpsData, 'f', m_gpsDataFullSize); - for(gpsDataPos = 0; gpsDataPos < m_gpsDataFullSize;){ - unsigned int dataLen = gpsDataStrLen - strPos < 5U ? gpsDataStrLen - strPos : 5U; + for(gpsDataPos = 0; gpsDataPos < m_gpsDataSize;){ + unsigned int dataLen = gpsDataStrLen - strPos< 5U ? gpsDataStrLen - strPos : 5U; m_gpsData[gpsDataPos++] = SLOW_DATA_TYPE_GPS | dataLen; for(unsigned int i = 0U; i < dataLen; i++){ diff --git a/SlowDataEncoder.h b/Common/SlowDataEncoder.h similarity index 100% rename from SlowDataEncoder.h rename to Common/SlowDataEncoder.h diff --git a/StatusData.cpp b/Common/StatusData.cpp similarity index 100% rename from StatusData.cpp rename to Common/StatusData.cpp diff --git a/StatusData.h b/Common/StatusData.h similarity index 100% rename from StatusData.h rename to Common/StatusData.h diff --git a/TextCollector.cpp b/Common/TextCollector.cpp similarity index 100% rename from TextCollector.cpp rename to Common/TextCollector.cpp diff --git a/TextCollector.h b/Common/TextCollector.h similarity index 100% rename from TextCollector.h rename to Common/TextCollector.h diff --git a/TextData.cpp b/Common/TextData.cpp similarity index 100% rename from TextData.cpp rename to Common/TextData.cpp diff --git a/TextData.h b/Common/TextData.h similarity index 100% rename from TextData.h rename to Common/TextData.h diff --git a/UserCache.cpp b/Common/UserCache.cpp similarity index 100% rename from UserCache.cpp rename to Common/UserCache.cpp diff --git a/UserCache.h b/Common/UserCache.h similarity index 100% rename from UserCache.h rename to Common/UserCache.h diff --git a/VersionUnit.cpp b/Common/VersionUnit.cpp similarity index 98% rename from VersionUnit.cpp rename to Common/VersionUnit.cpp index a382039..ac877aa 100644 --- a/VersionUnit.cpp +++ b/Common/VersionUnit.cpp @@ -48,7 +48,7 @@ m_out(0U) auto vstr = SHORT_PRODUCT_NAME; vstr.resize(NUM_FRAMES, ' '); - CLog::logInfo("Version text set to \"%s\"\n", vstr); + CLog::logInfo("Version text set to \"%s\"\n", vstr.c_str()); CSlowDataEncoder encoder; encoder.setTextData(vstr); diff --git a/VersionUnit.h b/Common/VersionUnit.h similarity index 100% rename from VersionUnit.h rename to Common/VersionUnit.h diff --git a/XLXHostsFileDownloader.cpp b/Common/XLXHostsFileDownloader.cpp similarity index 100% rename from XLXHostsFileDownloader.cpp rename to Common/XLXHostsFileDownloader.cpp diff --git a/XLXHostsFileDownloader.h b/Common/XLXHostsFileDownloader.h similarity index 100% rename from XLXHostsFileDownloader.h rename to Common/XLXHostsFileDownloader.h diff --git a/DGWRemoteControl/DGWRemoteControlApp.cpp b/DGWRemoteControl/DGWRemoteControlApp.cpp new file mode 100644 index 0000000..6f8c0fe --- /dev/null +++ b/DGWRemoteControl/DGWRemoteControlApp.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2010,2011 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 "DGWRemoteControlApp.h" +#include "DGWRemoteControlConfig.h" +#include "Version.h" +#include "ProgramArgs.h" +#include "DStarDefines.h" +#include "SHA256.h" + +const std::string NAME_OPTION("name"); +const std::string REPEATER_PARAM("Callsign"); +const std::string ACTION_PARAM("Action"); +const std::string RECONNECT_PARAM("Param1"); +const std::string REFLECTOR_PARAM("Param2"); +const std::string CONFIG_FILENAME("dgwremotecontrol.cfg"); + +int main(int argc, const char* argv[]) +{ + std::string name, repeater, actionText, user, reflector; + RECONNECT reconnect; + + if(!getCLIParams(argc, argv, name, repeater, actionText, reconnect, user, reflector)) { + ::fprintf(stderr, "\ndgwremotecontrol v%s : invalid command line usage:\n\n", LONG_VERSION.c_str()); + ::fprintf(stderr, "\t\tdgwremotecontrol [-name ] link \n"); + ::fprintf(stderr, "\t\tdgwremotecontrol [-name ] unlink\n"); + ::fprintf(stderr, "\t\tdgwremotecontrol [-name ] drop \n"); + ::fprintf(stderr, "\t\tdgwremotecontrol [-name ] drop all\n\n"); + return 1; + } + + CDGWRemoteControlConfig config(std::string(CFG_DIR) + "/" + CONFIG_FILENAME); + TRemoteGateway gatewayConfig; + + if(!config.load() || !config.getGateway(name, gatewayConfig)) { + ::fprintf(stderr, "Configuration failed to load\n"); + return 1; + } + + std::string password(gatewayConfig.m_password); + + CRemoteControlRemoteControlHandler handler(gatewayConfig.m_address, gatewayConfig.m_port); + + bool ret = handler.open(); + if (!ret) { + ::fprintf(stderr, "dgwremotecontrol: unable to open the UDP port\n"); + return 1; + } + + ret = handler.login(); + if (!ret) { + handler.close(); + ::fprintf(stderr, "dgwremotecontrol: unable to login to the gateway/starnetserver\n"); + return 1; + } + + unsigned int count = 0U; + while (count < 10U) { + std::this_thread::sleep_for(std::chrono::milliseconds(100U)); + + RC_TYPE type = handler.readType(); + if (type == RCT_RANDOM) + break; + + if (type == RCT_NONE) + handler.retry(); + + count++; + } + + if (count >= 10U) { + handler.close(); + ::fprintf(stderr, "dgwremotecontrol: unable to get a response from the gateway/starnetserver\n"); + return 1; + } + + unsigned int rnd = handler.readRandom(); + sendHash(&handler, password, rnd); + + count = 0U; + while (count < 10U) { + std::this_thread::sleep_for(std::chrono::milliseconds(100U)); + + RC_TYPE type = handler.readType(); + if (type == RCT_ACK) + break; + + if (type == RCT_NAK) { + handler.close(); + ::fprintf(stderr, "dgwremotecontrol: invalid password sent to the gateway/starnetserver\n"); + return 1; + } + + if (type == RCT_NONE) + handler.retry(); + + count++; + } + + if (count >= 10U) { + handler.close(); + ::fprintf(stderr, "dgwremotecontrol: unable to get a response from the gateway/starnetserver\n"); + return 1; + } + + handler.setLoggedIn(true); + + if (actionText == "drop") + handler.logoff(repeater, user); + else + handler.link(repeater, reconnect, reflector); + + count = 0U; + while (count < 10U) { + std::this_thread::sleep_for(std::chrono::milliseconds(100U)); + + RC_TYPE type = handler.readType(); + if (type == RCT_ACK) + break; + + if (type == RCT_NAK) { + handler.close(); + ::fprintf(stderr, "dgwremotecontrol: drop/link/unlink command rejected by the gateway/starnetserver\n"); + return 1; + } + + if (type == RCT_NONE) + handler.retry(); + + count++; + } + + if (count >= 10U) { + handler.close(); + ::fprintf(stderr, "dgwremotecontrol: unable to get a response from the gateway/starnetserver\n"); + return 1; + } + + ::fprintf(stdout, "dgwremotecontrol: command accepted by the gateway/starnetserver\n"); + + handler.logout(); + handler.close(); + + + return 0; +} + +bool getCLIParams(int argc, const char* argv[], std::string& name, std::string& repeater, std::string& actionText, RECONNECT& reconnect, std::string& user, std::string& reflector) +{ + bool ret = true; + + std::unordered_map namedArgs; + std::vector positionalArgs; + + CProgramArgs::eatArguments(argc, argv, namedArgs, positionalArgs); + + if(namedArgs.count(NAME_OPTION) > 0U) { + name = namedArgs[NAME_OPTION]; + } + + if(positionalArgs.size() < 1) { + return false; + } + + // dgwremotecontrol [-name ] link + if(positionalArgs.size() == 4U) { + repeater = positionalArgs[0]; + + actionText = boost::to_lower_copy(positionalArgs[1]); + if(actionText != "link") { + ::fprintf(stderr, "Invalid action %s. Expected link\n", positionalArgs[1].c_str()); + ret = false; + } + + std::string reconnectText = boost::to_lower_copy(positionalArgs[2]); + if(reconnectText == "never") reconnect = RECONNECT_NEVER; + else if(reconnectText == "5") reconnect = RECONNECT_5MINS; + else if(reconnectText == "10") reconnect = RECONNECT_10MINS; + else if(reconnectText == "15") reconnect = RECONNECT_15MINS; + else if(reconnectText == "20") reconnect = RECONNECT_20MINS; + else if(reconnectText == "25") reconnect = RECONNECT_25MINS; + else if(reconnectText == "30") reconnect = RECONNECT_30MINS; + else if(reconnectText == "60") reconnect = RECONNECT_60MINS; + else if(reconnectText == "90") reconnect = RECONNECT_90MINS; + else if(reconnectText == "120") reconnect = RECONNECT_120MINS; + else if(reconnectText == "180") reconnect = RECONNECT_180MINS; + else if(reconnectText == "fixed") reconnect = RECONNECT_FIXED; + else { + ::fprintf(stderr, "Invalid reconnect value %s. Valid values are 5,10,15,20,25,30,60,90,120,180,fixed\n", positionalArgs[2].c_str()); + ret = false; + } + reflector = boost::to_upper_copy(positionalArgs[3]); + boost::replace_all(reflector, "_", " "); + } + // dgwremotecontrol [-name ] unlink + else if(positionalArgs.size() == 2U) { + repeater = positionalArgs[0]; + actionText = boost::to_lower_copy(positionalArgs[1]); + if(actionText != "unlink") { + ::fprintf(stderr, "Invalid action %s. Expected unlink\n", positionalArgs[1].c_str()); + ret = false; + } + reconnect = RECONNECT_NEVER; + } + // dgwremotecontrol [-name ] drop + // dgwremotecontrol [-name ] drop all + else if(positionalArgs.size() == 3U) { + repeater = positionalArgs[0]; + + actionText = boost::to_lower_copy(positionalArgs[1]); + if(actionText != "drop") { + ::fprintf(stderr, "Invalid action %s. Expected drop\n", positionalArgs[1].c_str()); + ret = false; + } + + user = positionalArgs[2]; + } + + boost::to_upper(repeater); + repeater.resize(LONG_CALLSIGN_LENGTH, ' '); + boost::replace_all(repeater, "_", " "); + + boost::to_upper(reflector); + reflector.resize(LONG_CALLSIGN_LENGTH, ' '); + boost::replace_all(reflector, "_", " "); + + boost::to_upper(user); + user.resize(LONG_CALLSIGN_LENGTH, ' '); + boost::replace_all(user, "_", " "); + + return ret; +} + +void sendHash(CRemoteControlRemoteControlHandler* handler, const std::string& password, unsigned int rnd) +{ + assert(handler != NULL); + + unsigned int len = password.length() + sizeof(unsigned int); + unsigned char* in = new unsigned char[len]; + unsigned char* out = new unsigned char[32U]; + + ::memcpy(in, &rnd, sizeof(unsigned int)); + for (unsigned int i = 0U; i < password.length(); i++) + in[i + sizeof(unsigned int)] = password.at(i); + + CSHA256 sha256; + sha256.buffer(in, len, out); + + handler->sendHash(out, 32U); + + delete[] in; + delete[] out; +} \ No newline at end of file diff --git a/DGWRemoteControl/DGWRemoteControlApp.h b/DGWRemoteControl/DGWRemoteControlApp.h new file mode 100644 index 0000000..b23b6a1 --- /dev/null +++ b/DGWRemoteControl/DGWRemoteControlApp.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010,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 + +#include "Defs.h" +#include "RemoteControlRemoteControlHandler.h" + +bool getCLIParams(int argc, const char* argv[], std::string& name, std::string& repeater, std::string& actionText, RECONNECT& reconnect, std::string& user, std::string& reflector); +void sendHash(CRemoteControlRemoteControlHandler* handler, const std::string& password, unsigned int rnd); \ No newline at end of file diff --git a/DGWRemoteControl/DGWRemoteControlConfig.cpp b/DGWRemoteControl/DGWRemoteControlConfig.cpp new file mode 100644 index 0000000..5a881d4 --- /dev/null +++ b/DGWRemoteControl/DGWRemoteControlConfig.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2010,2011 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 "DGWRemoteControlConfig.h" +#include "Log.h" +#include "StringUtils.h" + +CDGWRemoteControlConfig::CDGWRemoteControlConfig(const std::string& fileName) : +m_fileName(fileName), +m_gateways() +{ + assert(!fileName.empty()); +} + +CDGWRemoteControlConfig::~CDGWRemoteControlConfig() +{ + for(auto gw : m_gateways) { + delete gw; + } + + m_gateways.clear(); +} + +bool CDGWRemoteControlConfig::load() +{ + CConfig cfg(m_fileName); + bool ret = open(cfg); + if(ret) { + ret = loadGateways(cfg) && ret; + } + + return ret; +} + +bool CDGWRemoteControlConfig::loadGateways(CConfig & cfg) +{ + for(unsigned int i = 1U; i <= 4U; i++) { + auto section = CStringUtils::string_format("gateway_%u", i); + TRemoteGateway * gateway = new TRemoteGateway(); + + bool ret = cfg.getValue(section, "name", gateway->m_name, 0U, 1024U, section); + ret = cfg.getValue(section, "address", gateway->m_address, 0U, 1024U, "127.0.0.1") && ret; + ret = cfg.getValue(section, "port", gateway->m_port, 1U, 65535U, 4242U) && ret; + ret = cfg.getValue(section, "password", gateway->m_password, 0U, 1024U, "") && ret; + + if(!ret || gateway->m_password.empty()) { + delete gateway; + continue; + } + + m_gateways.push_back(gateway); + } + + return m_gateways.size() != 0; +} + +bool CDGWRemoteControlConfig::getGateway(const std::string& name, TRemoteGateway& gateway) +{ + if(m_gateways.size() == 0U) + return false; + + for(auto gw : m_gateways) { + if(strcasecmp(name.c_str(), gw->m_name.c_str()) == 0) + { + gateway = *gw; + return true; + } + } + + gateway = *(m_gateways[0]); + + return true; +} + +bool CDGWRemoteControlConfig::open(CConfig & cfg) +{ + try { + return cfg.load(); + } + catch(...) { + CLog::logError("Can't read %s\n", m_fileName.c_str()); + return false; + } + return true; +} \ No newline at end of file diff --git a/DGWRemoteControl/DGWRemoteControlConfig.h b/DGWRemoteControl/DGWRemoteControlConfig.h new file mode 100644 index 0000000..ea6ab43 --- /dev/null +++ b/DGWRemoteControl/DGWRemoteControlConfig.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010,2011 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 + +#include "Config.h" + +typedef struct { + std::string m_name; + std::string m_address; + unsigned int m_port; + std::string m_password; +} TRemoteGateway; + +class CDGWRemoteControlConfig +{ +public: + CDGWRemoteControlConfig(const std::string& fileName); + ~CDGWRemoteControlConfig(); + bool load(); + bool getGateway(const std::string& name, TRemoteGateway& gateway); + +private: + bool open(CConfig& config); + bool loadGateways(CConfig& config); + + std::string m_fileName; + std::vector m_gateways; +}; \ No newline at end of file diff --git a/DGWRemoteControl/Makefile b/DGWRemoteControl/Makefile new file mode 100644 index 0000000..44e1fc3 --- /dev/null +++ b/DGWRemoteControl/Makefile @@ -0,0 +1,27 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dgwremotecontrol: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.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 $@ + +.PHONY clean: +clean: + $(RM) *.o *.d dgwremotecontrol + +.PHONY install: +install: dgwremotecontrol +# copy executable + @cp -f dgwremotecontrol $(BIN_DIR) + +# 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: +../VersionInfo/GitVersion.h: diff --git a/DGWRemoteControl/README.md b/DGWRemoteControl/README.md new file mode 100644 index 0000000..8c35730 --- /dev/null +++ b/DGWRemoteControl/README.md @@ -0,0 +1,15 @@ +This is the remote control software for DstarGateway. +After installation edit the config file with the detail of your gateway(s). If you did not alter build settings config file is locate in /usr/local/etc/dgwremotecontrol.cfg + +Usage examples : +``` +# connect repeater F4ABC B to reflector dcs208 c and reconnect to defaut reflector after 30 minutes +dgwremotecontrol F4ABC__B link 30 dcs208_c + +# unlink reflector +dgwremotecontrol F4ABC__B unlink + +# connect repeater F4ABC B of gateway hill_top to reflector dcs208 c and reconnect to defaut reflector after 30 minutes +dgwremotecontrol -name hill_top F4ABC__B link 30 dcs208_c + +``` diff --git a/DGWRemoteControl/RemoteControlCallsignData.cpp b/DGWRemoteControl/RemoteControlCallsignData.cpp new file mode 100644 index 0000000..710811c --- /dev/null +++ b/DGWRemoteControl/RemoteControlCallsignData.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 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 "RemoteControlCallsignData.h" + +CRemoteControlCallsignData::CRemoteControlCallsignData() : +m_repeaters(), +m_starNets() +{ +} + +CRemoteControlCallsignData::~CRemoteControlCallsignData() +{ +} + +void CRemoteControlCallsignData::addRepeater(const std::string& callsign) +{ + m_repeaters.push_back(callsign); +} + +void CRemoteControlCallsignData::addStarNet(const std::string& callsign) +{ + m_starNets.push_back(callsign); +} + +unsigned int CRemoteControlCallsignData::getRepeaterCount() const +{ + return m_repeaters.size(); +} + +std::string CRemoteControlCallsignData::getRepeater(unsigned int n) const +{ + return m_repeaters[n]; +} + +unsigned int CRemoteControlCallsignData::getStarNetCount() const +{ + return m_starNets.size(); +} + +std::string CRemoteControlCallsignData::getStarNet(unsigned int n) const +{ + return m_starNets[n]; +} diff --git a/DGWRemoteControl/RemoteControlCallsignData.h b/DGWRemoteControl/RemoteControlCallsignData.h new file mode 100644 index 0000000..c21ac6a --- /dev/null +++ b/DGWRemoteControl/RemoteControlCallsignData.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 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 RemoteControlCallsignData_H +#define RemoteControlCallsignData_H + +#include +#include + +class CRemoteControlCallsignData { +public: + CRemoteControlCallsignData(); + ~CRemoteControlCallsignData(); + + void addRepeater(const std::string& callsign); + void addStarNet(const std::string& callsign); + + unsigned int getRepeaterCount() const; + unsigned int getStarNetCount() const; + + std::string getRepeater(unsigned int n) const; + std::string getStarNet(unsigned int n) const; + +private: + std::vector m_repeaters; + std::vector m_starNets; +}; + +#endif diff --git a/DGWRemoteControl/RemoteControlDefs.h b/DGWRemoteControl/RemoteControlDefs.h new file mode 100644 index 0000000..5e41383 --- /dev/null +++ b/DGWRemoteControl/RemoteControlDefs.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 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 RemoteControlDefs_H +#define RemoteControlDefs_H + +#include + +const std::string APPLICATION_NAME = wxT("Remote Control"); + +const std::string CONFIG_FILE_NAME = wxT(".Remote Control"); + +#endif diff --git a/DGWRemoteControl/RemoteControlLinkData.cpp b/DGWRemoteControl/RemoteControlLinkData.cpp new file mode 100644 index 0000000..c869595 --- /dev/null +++ b/DGWRemoteControl/RemoteControlLinkData.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 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 "RemoteControlLinkData.h" + +CRemoteControlLinkData::CRemoteControlLinkData(const std::string& callsign, int32_t protocol, int32_t linked, int32_t direction, int32_t dongle) : +m_callsign(callsign), +m_protocol(PROTOCOL(protocol)), +m_linked(false), +m_direction(DIRECTION(direction)), +m_dongle(false) +{ + m_linked = linked == 1; + m_dongle = dongle == 1; +} + +CRemoteControlLinkData::~CRemoteControlLinkData() +{ +} + +std::string CRemoteControlLinkData::getCallsign() const +{ + return m_callsign; +} + +PROTOCOL CRemoteControlLinkData::getProtocol() const +{ + return m_protocol; +} + +bool CRemoteControlLinkData::isLinked() const +{ + return m_linked; +} + +DIRECTION CRemoteControlLinkData::getDirection() const +{ + return m_direction; +} + +bool CRemoteControlLinkData::isDongle() const +{ + return m_dongle; +} diff --git a/DGWRemoteControl/RemoteControlLinkData.h b/DGWRemoteControl/RemoteControlLinkData.h new file mode 100644 index 0000000..dd6aa1c --- /dev/null +++ b/DGWRemoteControl/RemoteControlLinkData.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 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 RemoteControlLinkData_H +#define RemoteControlLinkData_H + +#include + +#include "Defs.h" + + +class CRemoteControlLinkData { +public: + CRemoteControlLinkData(const std::string& callsign, int32_t protocol, int32_t linked, int32_t direction, int32_t dongle); + ~CRemoteControlLinkData(); + + std::string getCallsign() const; + PROTOCOL getProtocol() const; + bool isLinked() const; + DIRECTION getDirection() const; + bool isDongle() const; + +private: + std::string m_callsign; + PROTOCOL m_protocol; + bool m_linked; + DIRECTION m_direction; + bool m_dongle; +}; + +#endif diff --git a/DGWRemoteControl/RemoteControlRemoteControlHandler.cpp b/DGWRemoteControl/RemoteControlRemoteControlHandler.cpp new file mode 100644 index 0000000..f2e9d8d --- /dev/null +++ b/DGWRemoteControl/RemoteControlRemoteControlHandler.cpp @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2011,2013 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 +#include + +#include "Utils.h" +#include "RemoteControlRemoteControlHandler.h" +#include "DStarDefines.h" + +const unsigned int BUFFER_LENGTH = 2000U; + +const unsigned int MAX_RETRIES = 3U; + +CRemoteControlRemoteControlHandler::CRemoteControlRemoteControlHandler(const std::string& address, unsigned int port) : +m_socket("", 0U), +m_address(), +m_port(port), +m_loggedIn(false), +m_retryCount(0U), +m_type(RCT_NONE), +m_inBuffer(NULL), +m_inLength(0U), +m_outBuffer(NULL), +m_outLength(0U) +{ + assert(!address.empty()); + assert(port > 0U); + + m_address = CUDPReaderWriter::lookup(address); + + m_inBuffer = new unsigned char[BUFFER_LENGTH]; + m_outBuffer = new unsigned char[BUFFER_LENGTH]; +} + +CRemoteControlRemoteControlHandler::~CRemoteControlRemoteControlHandler() +{ + delete[] m_inBuffer; + delete[] m_outBuffer; +} + +bool CRemoteControlRemoteControlHandler::open() +{ + return m_socket.open(); +} + +RC_TYPE CRemoteControlRemoteControlHandler::readType() +{ + m_type = RCT_NONE; + + in_addr address; + unsigned int port; + + int length = m_socket.read(m_inBuffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return m_type; + + m_inLength = length; + + if (::memcmp(m_inBuffer, "ACK", 3U) == 0) { + m_retryCount = 0U; + m_type = RCT_ACK; + return m_type; + } else if (::memcmp(m_inBuffer, "NAK", 3U) == 0) { + m_retryCount = 0U; + m_type = RCT_NAK; + return m_type; + } else if (::memcmp(m_inBuffer, "RND", 3U) == 0) { + m_retryCount = 0U; + m_type = RCT_RANDOM; + return m_type; + } else if (::memcmp(m_inBuffer, "CAL", 3U) == 0) { + m_retryCount = 0U; + m_type = RCT_CALLSIGNS; + return m_type; + } else if (::memcmp(m_inBuffer, "RPT", 3U) == 0) { + m_retryCount = 0U; + m_type = RCT_REPEATER; + return m_type; + } else if (::memcmp(m_inBuffer, "SNT", 3U) == 0) { + m_retryCount = 0U; + m_type = RCT_STARNET; + return m_type; + } + + return m_type; +} + +std::string CRemoteControlRemoteControlHandler::readNAK() +{ + if (m_type != RCT_NAK) + return ""; + + std::string text((char*)(m_inBuffer + 3U)); + + return text; +} + +unsigned int CRemoteControlRemoteControlHandler::readRandom() +{ + if (m_type != RCT_RANDOM) + return 0U; + + uint32_t random; + ::memcpy(&random, m_inBuffer + 3U, sizeof(uint32_t)); + + return CUtils::swap_endian_be(random); +} + +CRemoteControlCallsignData* CRemoteControlRemoteControlHandler::readCallsigns() +{ + if (m_type != RCT_CALLSIGNS) + return NULL; + + CRemoteControlCallsignData* data = new CRemoteControlCallsignData; + + unsigned char* p = m_inBuffer + 3U; + unsigned int pos = 3U; + + while (pos < m_inLength) { + unsigned char type = *p; + pos += 1U; + p += 1U; + + std::string callsign((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + switch (type) { + case 'R': + data->addRepeater(callsign); + break; + case 'S': + data->addStarNet(callsign); + break; + default: // ???? + break; + } + } + + return data; +} + +CRemoteControlRepeaterData* CRemoteControlRemoteControlHandler::readRepeater() +{ + if (m_type != RCT_REPEATER) + return NULL; + + unsigned char* p = m_inBuffer + 3U; + unsigned int pos = 3U; + + std::string callsign((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + int32_t reconnect; + ::memcpy(&reconnect, p, sizeof(int32_t)); + pos += sizeof(int32_t); + p += sizeof(int32_t); + + std::string reflector((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + CRemoteControlRepeaterData* data = new CRemoteControlRepeaterData(callsign, CUtils::swap_endian_be(reconnect), reflector); + + while (pos < m_inLength) { + std::string callsign((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + int32_t protocol; + ::memcpy(&protocol, p, sizeof(int32_t)); + pos += sizeof(int32_t); + p += sizeof(int32_t); + + int32_t linked; + ::memcpy(&linked, p, sizeof(int32_t)); + pos += sizeof(int32_t); + p += sizeof(int32_t); + + int32_t direction; + ::memcpy(&direction, p, sizeof(int32_t)); + pos += sizeof(int32_t); + p += sizeof(int32_t); + + int32_t dongle; + ::memcpy(&dongle, p, sizeof(int32_t)); + pos += sizeof(int32_t); + p += sizeof(int32_t); + + data->addLink(callsign, CUtils::swap_endian_be(protocol), CUtils::swap_endian_be(linked), CUtils::swap_endian_be(direction), CUtils::swap_endian_be(dongle)); + } + + return data; +} + +CRemoteControlStarNetGroup* CRemoteControlRemoteControlHandler::readStarNetGroup() +{ + if (m_type != RCT_STARNET) + return NULL; + + unsigned char* p = m_inBuffer + 3U; + unsigned int pos = 3U; + + std::string callsign((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + std::string logoff((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + uint32_t timer; + ::memcpy(&timer, p, sizeof(uint32_t)); + pos += sizeof(uint32_t); + p += sizeof(uint32_t); + + uint32_t timeout; + ::memcpy(&timeout, p, sizeof(uint32_t)); + pos += sizeof(uint32_t); + p += sizeof(uint32_t); + + CRemoteControlStarNetGroup* group = new CRemoteControlStarNetGroup(callsign, logoff, CUtils::swap_endian_be(timer), CUtils::swap_endian_be(timeout)); + + while (pos < m_inLength) { + std::string callsign((char*)p, LONG_CALLSIGN_LENGTH); + pos += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + ::memcpy(&timer, p, sizeof(uint32_t)); + pos += sizeof(uint32_t); + p += sizeof(uint32_t); + + ::memcpy(&timeout, p, sizeof(uint32_t)); + pos += sizeof(uint32_t); + p += sizeof(uint32_t); + + group->addUser(callsign, CUtils::swap_endian_be(timer), CUtils::swap_endian_be(timeout)); + } + + return group; +} + +bool CRemoteControlRemoteControlHandler::login() +{ + if (m_loggedIn) + return false; + + if (m_address.s_addr == INADDR_NONE) + return false; + + ::memcpy(m_outBuffer, "LIN", 3U); + m_outLength = 3U; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +void CRemoteControlRemoteControlHandler::setLoggedIn(bool set) +{ + m_loggedIn = set; +} + +bool CRemoteControlRemoteControlHandler::getCallsigns() +{ + if (!m_loggedIn || m_retryCount > 0U) + return false; + + ::memcpy(m_outBuffer, "GCS", 3U); + m_outLength = 3U; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::sendHash(const unsigned char* hash, unsigned int length) +{ + assert(hash != NULL); + assert(length > 0U); + + if (m_loggedIn || m_retryCount > 0U) + return false; + + unsigned char* p = m_outBuffer; + m_outLength = 0U; + + ::memcpy(p, "SHA", 3U); + m_outLength += 3U; + p += 3U; + + ::memcpy(p, hash, length); + m_outLength += length; + p += length; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::getRepeater(const std::string& callsign) +{ + assert(!callsign.empty()); + + if (!m_loggedIn || m_retryCount > 0U) + return false; + + unsigned char* p = m_outBuffer; + m_outLength = 0U; + + ::memcpy(p, "GRP", 3U); + m_outLength += 3U; + p += 3U; + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < callsign.length(); i++) + p[i] = callsign.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::getStarNet(const std::string& callsign) +{ + assert(!callsign.empty()); + + if (!m_loggedIn || m_retryCount > 0U) + return false; + + unsigned char* p = m_outBuffer; + m_outLength = 0U; + + ::memcpy(p, "GSN", 3U); + m_outLength += 3U; + p += 3U; + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < callsign.length(); i++) + p[i] = callsign.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::link(const std::string& callsign, RECONNECT reconnect, const std::string& reflector) +{ + assert(!callsign.empty()); + + if (!m_loggedIn || m_retryCount > 0U) + return false; + + unsigned char* p = m_outBuffer; + m_outLength = 0U; + + ::memcpy(p, "LNK", 3U); + m_outLength += 3U; + p += 3U; + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < callsign.length(); i++) + p[i] = callsign.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + int32_t temp1 = int32_t(reconnect); + int32_t temp2 = CUtils::swap_endian_be(temp1); + ::memcpy(p, &temp2, sizeof(int32_t)); + m_outLength += sizeof(int32_t); + p += sizeof(int32_t); + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < reflector.length(); i++) + p[i] = reflector.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::unlink(const std::string& callsign, PROTOCOL protocol, const std::string& reflector) +{ + assert(!callsign.empty()); + + if (!m_loggedIn || m_retryCount > 0U) + return false; + + unsigned char* p = m_outBuffer; + m_outLength = 0U; + + ::memcpy(p, "UNL", 3U); + m_outLength += 3U; + p += 3U; + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < callsign.length(); i++) + p[i] = callsign.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + int32_t temp1 = int32_t(protocol); + int32_t temp2 = CUtils::swap_endian_be(temp1); + ::memcpy(p, &temp2, sizeof(int32_t)); + m_outLength += sizeof(int32_t); + p += sizeof(int32_t); + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < reflector.length(); i++) + p[i] = reflector.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::logoff(const std::string& callsign, const std::string& user) +{ + assert(!callsign.empty()); + assert(!user.empty()); + + if (!m_loggedIn || m_retryCount > 0U) + return false; + + unsigned char* p = m_outBuffer; + m_outLength = 0U; + + ::memcpy(p, "LGO", 3U); + m_outLength += 3U; + p += 3U; + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < callsign.length(); i++) + p[i] = callsign.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + ::memset(p, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < user.length(); i++) + p[i] = user.at(i); + + m_outLength += LONG_CALLSIGN_LENGTH; + p += LONG_CALLSIGN_LENGTH; + + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } else { + m_retryCount = 1U; + return true; + } +} + +bool CRemoteControlRemoteControlHandler::logout() +{ + if (!m_loggedIn || m_retryCount > 0U) + return false; + + ::memcpy(m_outBuffer, "LOG", 3U); + m_outLength = 3U; + + for (unsigned int i = 0U; i < 5U; i++) { + bool ret = m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + if (!ret) { + m_retryCount = 0U; + return false; + } + } + + m_retryCount = 1U; + + return true; +} + +bool CRemoteControlRemoteControlHandler::retry() +{ + if (m_retryCount > 0U) { + m_retryCount++; + if (m_retryCount >= MAX_RETRIES) { + m_retryCount = 0U; + return false; + } + + m_socket.write(m_outBuffer, m_outLength, m_address, m_port); + } + + return true; +} + +void CRemoteControlRemoteControlHandler::close() +{ + m_socket.close(); +} diff --git a/DGWRemoteControl/RemoteControlRemoteControlHandler.h b/DGWRemoteControl/RemoteControlRemoteControlHandler.h new file mode 100644 index 0000000..dd434e1 --- /dev/null +++ b/DGWRemoteControl/RemoteControlRemoteControlHandler.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011,2013 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 RemoteControlRemoteControlHandler_H +#define RemoteControlRemoteControlHandler_H + +#include "RemoteControlRepeaterData.h" +#include "RemoteControlStarNetGroup.h" +#include "RemoteControlCallsignData.h" +#include "UDPReaderWriter.h" + +enum RC_TYPE { + RCT_NONE, + RCT_ACK, + RCT_NAK, + RCT_RANDOM, + RCT_CALLSIGNS, + RCT_REPEATER, + RCT_STARNET +}; + +class CRemoteControlRemoteControlHandler { +public: + CRemoteControlRemoteControlHandler(const std::string& address, unsigned int port); + ~CRemoteControlRemoteControlHandler(); + + bool open(); + + RC_TYPE readType(); + + std::string readNAK(); + unsigned int readRandom(); + CRemoteControlCallsignData* readCallsigns(); + CRemoteControlRepeaterData* readRepeater(); + CRemoteControlStarNetGroup* readStarNetGroup(); + + bool login(); + bool sendHash(const unsigned char* hash, unsigned int length); + + void setLoggedIn(bool set); + + bool getCallsigns(); + bool getRepeater(const std::string& callsign); + bool getStarNet(const std::string& callsign); + + bool link(const std::string& callsign, RECONNECT reconnect, const std::string& reflector); + bool unlink(const std::string& callsign, PROTOCOL protocol, const std::string& reflector); + bool logoff(const std::string& callsign, const std::string& user); + + bool logout(); + + bool retry(); + + void close(); + +private: + CUDPReaderWriter m_socket; + in_addr m_address; + unsigned int m_port; + bool m_loggedIn; + unsigned int m_retryCount; + RC_TYPE m_type; + unsigned char* m_inBuffer; + unsigned int m_inLength; + unsigned char* m_outBuffer; + unsigned int m_outLength; +}; + +#endif diff --git a/DGWRemoteControl/RemoteControlRepeaterData.cpp b/DGWRemoteControl/RemoteControlRepeaterData.cpp new file mode 100644 index 0000000..241bf45 --- /dev/null +++ b/DGWRemoteControl/RemoteControlRepeaterData.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 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 "RemoteControlRepeaterData.h" + + +CRemoteControlRepeaterData::CRemoteControlRepeaterData(const std::string& callsign, int32_t reconnect, const std::string& reflector) : +m_callsign(callsign), +m_reconnect(RECONNECT(reconnect)), +m_reflector(reflector), +m_links() +{ +} + +CRemoteControlRepeaterData::~CRemoteControlRepeaterData() +{ + for(auto data : m_links) { + delete data; + } + m_links.clear(); +} + +void CRemoteControlRepeaterData::addLink(const std::string& callsign, int32_t protocol, int32_t linked, int32_t direction, int32_t dongle) +{ + CRemoteControlLinkData * data = new CRemoteControlLinkData(callsign, protocol, linked, direction, dongle); + + m_links.push_back(data); +} + +std::string CRemoteControlRepeaterData::getCallsign() const +{ + return m_callsign; +} + +RECONNECT CRemoteControlRepeaterData::getReconnect() const +{ + return m_reconnect; +} + +std::string CRemoteControlRepeaterData::getReflector() const +{ + return m_reflector; +} + +unsigned int CRemoteControlRepeaterData::getLinkCount() const +{ + return m_links.size(); +} + +CRemoteControlLinkData* CRemoteControlRepeaterData::getLink(unsigned int n) const +{ + return m_links[n]; +} diff --git a/DGWRemoteControl/RemoteControlRepeaterData.h b/DGWRemoteControl/RemoteControlRepeaterData.h new file mode 100644 index 0000000..1025328 --- /dev/null +++ b/DGWRemoteControl/RemoteControlRepeaterData.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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 RemoteControlRepeaterData_H +#define RemoteControlRepeaterData_H + +#include + +#include "RemoteControlLinkData.h" + + +class CRemoteControlRepeaterData { +public: + CRemoteControlRepeaterData(const std::string& callsign, int32_t reconnect, const std::string& reflector); + ~CRemoteControlRepeaterData(); + + void addLink(const std::string& callsign, int32_t protocol, int32_t linked, int32_t direction, int32_t dongle); + + std::string getCallsign() const; + RECONNECT getReconnect() const; + std::string getReflector() const; + + unsigned int getLinkCount() const; + CRemoteControlLinkData * getLink(unsigned int n) const; + +private: + std::string m_callsign; + RECONNECT m_reconnect; + std::string m_reflector; + std::vector m_links; +}; + +#endif diff --git a/DGWRemoteControl/RemoteControlStarNetGroup.cpp b/DGWRemoteControl/RemoteControlStarNetGroup.cpp new file mode 100644 index 0000000..9fa7a98 --- /dev/null +++ b/DGWRemoteControl/RemoteControlStarNetGroup.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 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 "RemoteControlStarNetGroup.h" + + +CRemoteControlStarNetGroup::CRemoteControlStarNetGroup(const std::string& callsign, const std::string& logoff, uint32_t timer, uint32_t timeout) : +m_callsign(callsign), +m_logoff(logoff), +m_timer((unsigned int)timer), +m_timeout((unsigned int)timeout), +m_users() +{ + if (m_logoff == " ") + m_logoff.clear(); +} + +CRemoteControlStarNetGroup::~CRemoteControlStarNetGroup() +{ + for(auto user : m_users) { + delete user; + } + m_users.clear(); +} + +void CRemoteControlStarNetGroup::addUser(const std::string& callsign, uint32_t timer, uint32_t timeout) +{ + CRemoteControlStarNetUser * user = new CRemoteControlStarNetUser(callsign, timer, timeout); + + m_users.push_back(user); +} + +std::string CRemoteControlStarNetGroup::getCallsign() const +{ + return m_callsign; +} + +std::string CRemoteControlStarNetGroup::getLogoff() const +{ + return m_logoff; +} + +unsigned int CRemoteControlStarNetGroup::getTimer() const +{ + return m_timer; +} + +unsigned int CRemoteControlStarNetGroup::getTimeout() const +{ + return m_timeout; +} + +unsigned int CRemoteControlStarNetGroup::getUserCount() const +{ + return m_users.size(); +} + +CRemoteControlStarNetUser * CRemoteControlStarNetGroup::getUser(unsigned int n) const +{ + return m_users[n]; +} diff --git a/DGWRemoteControl/RemoteControlStarNetGroup.h b/DGWRemoteControl/RemoteControlStarNetGroup.h new file mode 100644 index 0000000..351aea3 --- /dev/null +++ b/DGWRemoteControl/RemoteControlStarNetGroup.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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 RemoteControlStarNetGroup_H +#define RemoteControlStarNetGroup_H + +#include + +#include "RemoteControlStarNetUser.h" + + + +class CRemoteControlStarNetGroup { +public: + CRemoteControlStarNetGroup(const std::string& callsign, const std::string& logoff, uint32_t timer, uint32_t timeout); + ~CRemoteControlStarNetGroup(); + + void addUser(const std::string& callsign, uint32_t timer, uint32_t timeout); + + std::string getCallsign() const; + std::string getLogoff() const; + unsigned int getTimer() const; + unsigned int getTimeout() const; + + unsigned int getUserCount() const; + CRemoteControlStarNetUser * getUser(unsigned int n) const; + +private: + std::string m_callsign; + std::string m_logoff; + unsigned int m_timer; + unsigned int m_timeout; + std::vector m_users; +}; + +#endif diff --git a/DGWRemoteControl/RemoteControlStarNetUser.cpp b/DGWRemoteControl/RemoteControlStarNetUser.cpp new file mode 100644 index 0000000..e692901 --- /dev/null +++ b/DGWRemoteControl/RemoteControlStarNetUser.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 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 "RemoteControlStarNetUser.h" + +CRemoteControlStarNetUser::CRemoteControlStarNetUser(const std::string& callsign, uint32_t timer, uint32_t timeout) : +m_callsign(callsign), +m_timer((unsigned int)timer), +m_timeout((unsigned int)timeout) +{ +} + +CRemoteControlStarNetUser::~CRemoteControlStarNetUser() +{ +} + +std::string CRemoteControlStarNetUser::getCallsign() const +{ + return m_callsign; +} + +unsigned int CRemoteControlStarNetUser::getTimer() const +{ + return m_timer; +} + +unsigned int CRemoteControlStarNetUser::getTimeout() const +{ + return m_timeout; +} diff --git a/DGWRemoteControl/RemoteControlStarNetUser.h b/DGWRemoteControl/RemoteControlStarNetUser.h new file mode 100644 index 0000000..df42db7 --- /dev/null +++ b/DGWRemoteControl/RemoteControlStarNetUser.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011 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 RemoteControlStarNetUser_H +#define RemoteControlStarNetUser_H + +#include + +class CRemoteControlStarNetUser { +public: + CRemoteControlStarNetUser(const std::string& callsign, uint32_t timer, uint32_t timeout); + ~CRemoteControlStarNetUser(); + + std::string getCallsign() const; + unsigned int getTimer() const; + unsigned int getTimeout() const; + +private: + std::string m_callsign; + unsigned int m_timer; + unsigned int m_timeout; +}; + +#endif diff --git a/DGWRemoteControl/example.cfg b/DGWRemoteControl/example.cfg new file mode 100644 index 0000000..5077c2b --- /dev/null +++ b/DGWRemoteControl/example.cfg @@ -0,0 +1,14 @@ +# You can specifiy up to 4 sections with different target gateways +# When invoking dgwremotecontrol without name parameter, the first gateway of the config file is used + +[gateway_1] +name=hill_top +address=127.0.0.1 +port=4242 +password=CHANGE_ME + +[gateway_2] +name=city_center +address=127.0.0.1 +port=4242 +password=CHANGE_ME diff --git a/AMBEData.cpp b/DStarBase/AMBEData.cpp similarity index 97% rename from AMBEData.cpp rename to DStarBase/AMBEData.cpp index 1c2bf87..2cef8c0 100644 --- a/AMBEData.cpp +++ b/DStarBase/AMBEData.cpp @@ -24,6 +24,7 @@ #include "AMBEData.h" #include "DStarDefines.h" #include "Utils.h" +#include "NetUtils.h" CAMBEData::CAMBEData() : m_rptSeq(0U), @@ -582,6 +583,17 @@ unsigned int CAMBEData::getMyPort() const return m_myPort; } +struct sockaddr_storage CAMBEData::getDestination() const +{ + struct sockaddr_storage dest; + ::memset(&dest, 0, sizeof(sockaddr_storage)); + dest.ss_family = AF_INET; + TOIPV4(dest)->sin_addr = m_yourAddress; + TOIPV4(dest)->sin_port = htons(m_yourPort); + + return dest; +} + CHeaderData& CAMBEData::getHeader() { return m_header; diff --git a/AMBEData.h b/DStarBase/AMBEData.h similarity index 98% rename from AMBEData.h rename to DStarBase/AMBEData.h index 8282852..695a5c2 100644 --- a/AMBEData.h +++ b/DStarBase/AMBEData.h @@ -77,6 +77,7 @@ public: in_addr getYourAddress() const; unsigned int getYourPort() const; + struct sockaddr_storage getDestination() const; unsigned int getMyPort() const; unsigned int getErrors() const; diff --git a/CallsignList.cpp b/DStarBase/CallsignList.cpp similarity index 100% rename from CallsignList.cpp rename to DStarBase/CallsignList.cpp diff --git a/CallsignList.h b/DStarBase/CallsignList.h similarity index 100% rename from CallsignList.h rename to DStarBase/CallsignList.h diff --git a/DDData.cpp b/DStarBase/DDData.cpp similarity index 100% rename from DDData.cpp rename to DStarBase/DDData.cpp diff --git a/DDData.h b/DStarBase/DDData.h similarity index 100% rename from DDData.h rename to DStarBase/DDData.h diff --git a/DStarDefines.h b/DStarBase/DStarDefines.h similarity index 93% rename from DStarDefines.h rename to DStarBase/DStarDefines.h index 1dcd22f..b4ed991 100644 --- a/DStarDefines.h +++ b/DStarBase/DStarDefines.h @@ -81,14 +81,14 @@ const unsigned int DATA_BLOCK_SIZE_BYTES = 21U * DV_FRAME_LENGTH_BYTES; const unsigned int LONG_CALLSIGN_LENGTH = 8U; const unsigned int SHORT_CALLSIGN_LENGTH = 4U; -const unsigned char SLOW_DATA_TYPE_MASK = 0xF0U; -const unsigned char SLOW_DATA_TYPE_GPS = 0x30U; -const unsigned char SLOW_DATA_TYPE_TEXT = 0x40U; -const unsigned char SLOW_DATA_TYPE_HEADER = 0x50U; -const unsigned char SLOW_DATA_TYPE_FAST_DATA1 = 0x80U; -const unsigned char SLOW_DATA_TYPE_FAST_DATA2 = 0x90U; -const unsigned char SLOW_DATA_TYPE_SQUELCH = 0xC0U; -const unsigned char SLOW_DATA_LENGTH_MASK = 0x0FU; +const unsigned char SLOW_DATA_TYPE_MASK = 0xF0U; +const unsigned char SLOW_DATA_TYPE_GPS = 0x30U; +const unsigned char SLOW_DATA_TYPE_TEXT = 0x40U; +const unsigned char SLOW_DATA_TYPE_HEADER = 0x50U; +const unsigned char SLOW_DATA_TYPE_FAST_DATA1 = 0x80U; +const unsigned char SLOW_DATA_TYPE_FAST_DATA2 = 0x90U; +const unsigned char SLOW_DATA_TYPE_SQUELCH = 0xC0U; +const unsigned char SLOW_DATA_LENGTH_MASK = 0x0FU; const unsigned char DATA_MASK = 0x80U; const unsigned char REPEATER_MASK = 0x40U; diff --git a/DTMF.cpp b/DStarBase/DTMF.cpp similarity index 100% rename from DTMF.cpp rename to DStarBase/DTMF.cpp diff --git a/DTMF.h b/DStarBase/DTMF.h similarity index 100% rename from DTMF.h rename to DStarBase/DTMF.h diff --git a/DVTOOLFileReader.cpp b/DStarBase/DVTOOLFileReader.cpp similarity index 100% rename from DVTOOLFileReader.cpp rename to DStarBase/DVTOOLFileReader.cpp diff --git a/DVTOOLFileReader.h b/DStarBase/DVTOOLFileReader.h similarity index 100% rename from DVTOOLFileReader.h rename to DStarBase/DVTOOLFileReader.h diff --git a/HeaderData.cpp b/DStarBase/HeaderData.cpp similarity index 98% rename from HeaderData.cpp rename to DStarBase/HeaderData.cpp index b29ac0a..6747571 100644 --- a/HeaderData.cpp +++ b/DStarBase/HeaderData.cpp @@ -23,7 +23,7 @@ #include #include #include "HeaderData.h" - +#include "NetUtils.h" #include "CCITTChecksum.h" #include "DStarDefines.h" #include "Utils.h" @@ -922,6 +922,17 @@ unsigned int CHeaderData::getYourPort() const return m_yourPort; } +struct sockaddr_storage CHeaderData::getDestination() const +{ + struct sockaddr_storage dest; + ::memset(&dest, 0, sizeof(sockaddr_storage)); + dest.ss_family = AF_INET; + TOIPV4(dest)->sin_addr = m_yourAddress; + TOIPV4(dest)->sin_port = htons(m_yourPort); + + return dest; +} + unsigned int CHeaderData::getMyPort() const { return m_myPort; diff --git a/HeaderData.h b/DStarBase/HeaderData.h similarity index 99% rename from HeaderData.h rename to DStarBase/HeaderData.h index c4ced7c..d004503 100644 --- a/HeaderData.h +++ b/DStarBase/HeaderData.h @@ -94,6 +94,7 @@ public: in_addr getYourAddress() const; unsigned int getYourPort() const; + struct sockaddr_storage getDestination() const; unsigned int getMyPort() const; unsigned int getErrors() const; diff --git a/HostFile.cpp b/DStarBase/HostFile.cpp similarity index 100% rename from HostFile.cpp rename to DStarBase/HostFile.cpp diff --git a/HostFile.h b/DStarBase/HostFile.h similarity index 100% rename from HostFile.h rename to DStarBase/HostFile.h diff --git a/DStarBase/Makefile b/DStarBase/Makefile new file mode 100644 index 0000000..c6e019d --- /dev/null +++ b/DStarBase/Makefile @@ -0,0 +1,14 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +DStarBase.a: $(OBJS) + $(AR) rcs DStarBase.a $(OBJS) + +%.o : %.cpp + $(CC) -I../BaseCommon $(CPPFLAGS) -MMD -MD -c $< -o $@ + +clean: + $(RM) *.o *.d DStarBase.a + +../BaseCommon/BaseCommon.a: diff --git a/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp similarity index 74% rename from DStarGatewayApp.cpp rename to DStarGateway/DStarGatewayApp.cpp index c8fd520..15de622 100644 --- a/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2010,2011 by Jonathan Naylor G4KLX - * Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA + * 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 @@ -23,6 +23,11 @@ #include #include #include +#include +#include +#ifdef DEBUG_DSTARGW +#include +#endif #include "DStarGatewayDefs.h" #include "DStarGatewayConfig.h" @@ -41,8 +46,20 @@ #include "APRSGPSDIdFrameProvider.h" #include "APRSFixedIdFrameProvider.h" +#ifdef UNIT_TESTS +int fakemain(int argc, char *argv[]) +#else int main(int argc, char *argv[]) +#endif { + std::set_terminate(CDStarGatewayApp::terminateHandler); + + signal(SIGSEGV, CDStarGatewayApp::sigHandlerFatal); + signal(SIGILL, CDStarGatewayApp::sigHandlerFatal); + signal(SIGFPE, CDStarGatewayApp::sigHandlerFatal); + signal(SIGABRT, CDStarGatewayApp::sigHandlerFatal); + // signal(SIGTERM, CDStarGatewayApp::sigHandler); + setbuf(stdout, NULL); if (2 != argc) { printf("usage: %s path_to_config_file\n", argv[0]); @@ -81,6 +98,7 @@ CDStarGatewayApp::~CDStarGatewayApp() 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() @@ -130,9 +148,9 @@ bool CDStarGatewayApp::createThread() // Setup APRS TAPRS aprsConfig; config.getAPRS(aprsConfig); - CAPRSWriter * aprsWriter = NULL; + CAPRSHandler * aprsWriter = NULL; if(aprsConfig.enabled && !aprsConfig.password.empty()) { - aprsWriter = new CAPRSWriter(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address); + aprsWriter = new CAPRSHandler(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address); if(aprsWriter->open()) { #ifdef USE_GPSD CAPRSIdFrameProvider * idFrameProvider = aprsConfig.m_positionSource == POSSRC_GPSD ? (CAPRSIdFrameProvider *)new CAPRSGPSDIdFrameProvider(gpsdConfig.m_address, gpsdConfig.m_port) @@ -151,15 +169,16 @@ bool CDStarGatewayApp::createThread() } // Setup the repeaters - if(config.getRepeaterCount() == 0U) { - CLog::logInfo("No repeater configured\n"); - return false; - } bool ddEnabled = false; + bool atLeastOneRepeater = false; CRepeaterProtocolHandlerFactory repeaterProtocolFactory; for(unsigned int i = 0U; i < config.getRepeaterCount(); i++) { TRepeater rptrConfig; config.getRepeater(i, rptrConfig); + auto repeaterProtocolHandler = repeaterProtocolFactory.getRepeaterProtocolHandler(rptrConfig.hwType, gatewayConfig, rptrConfig.address, rptrConfig.port); + if(repeaterProtocolHandler == nullptr) + continue; + atLeastOneRepeater = true; m_thread->addRepeater(rptrConfig.callsign, rptrConfig.band, rptrConfig.address, @@ -177,7 +196,7 @@ bool CDStarGatewayApp::createThread() rptrConfig.description1, rptrConfig.description2, rptrConfig.url, - repeaterProtocolFactory.getRepeaterProtocolHandler(rptrConfig.hwType, gatewayConfig, rptrConfig.address, rptrConfig.port), + repeaterProtocolHandler, rptrConfig.band1, rptrConfig.band2, rptrConfig.band3); @@ -186,25 +205,34 @@ bool CDStarGatewayApp::createThread() if(!ddEnabled) ddEnabled = rptrConfig.band.length() > 1U; } + + if(!atLeastOneRepeater) { + CLog::logError("Error: no repeaters are enabled or opening network communication to repeater failed"); + return false; + } + m_thread->setDDModeEnabled(ddEnabled); CLog::logInfo("DD Mode enabled: %d", int(ddEnabled)); // Setup ircddb + auto ircddbVersionInfo = "linux_" + PRODUCT_NAME + "-" + VERSION; std::vector clients; for(unsigned int i=0; i < config.getIrcDDBCount(); i++) { TircDDB ircDDBConfig; 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, FULL_PRODUCT_NAME, gatewayConfig.address, ircDDBConfig.isQuadNet); + CIRCDDB * ircDDB = new CIRCDDBClient(ircDDBConfig.hostname, 9007U, ircDDBConfig.username, ircDDBConfig.password, ircddbVersionInfo, gatewayConfig.address, ircDDBConfig.isQuadNet); clients.push_back(ircDDB); } - CIRCDDBMultiClient* multiClient = new CIRCDDBMultiClient(clients); - bool res = multiClient->open(); - if (!res) { - CLog::logInfo("Cannot initialise the ircDDB protocol handler\n"); - return false; + if(clients.size() > 0U) { + CIRCDDBMultiClient* multiClient = new CIRCDDBMultiClient(clients); + bool res = multiClient->open(); + if (!res) { + CLog::logInfo("Cannot initialise the ircDDB protocol handler\n"); + return false; + } + m_thread->setIRC(multiClient); } - m_thread->setIRC(multiClient); // Setup Dextra TDextra dextraConfig; @@ -248,3 +276,41 @@ bool CDStarGatewayApp::createThread() return true; } +void CDStarGatewayApp::sigHandlerFatal(int sig) +{ + CLog::logFatal("Caught signal : %s", strsignal(sig)); +#ifdef DEBUG_DSTARGW + std::stringstream stackTrace; + stackTrace << boost::stacktrace::stacktrace(); + CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str()); +#endif + exit(1); +} + +void CDStarGatewayApp::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"); + } + } catch(const std::exception& e) { + CLog::logFatal("Unhandled exception occured %s", e.what()); + } + +#ifdef DEBUG_DSTARGW + CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str()); +#endif + exit(1); +} diff --git a/DStarGatewayApp.h b/DStarGateway/DStarGatewayApp.h similarity index 91% rename from DStarGatewayApp.h rename to DStarGateway/DStarGatewayApp.h index 2af3de9..08c67cb 100644 --- a/DStarGatewayApp.h +++ b/DStarGateway/DStarGatewayApp.h @@ -21,6 +21,8 @@ #include "DStarGatewayThread.h" +void __sigHandler(int sig); + class CDStarGatewayApp { private: @@ -34,4 +36,7 @@ public: bool init(); void run(); + + static void sigHandlerFatal(int sig); + static void terminateHandler(); }; diff --git a/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp similarity index 100% rename from DStarGatewayConfig.cpp rename to DStarGateway/DStarGatewayConfig.cpp diff --git a/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h similarity index 100% rename from DStarGatewayConfig.h rename to DStarGateway/DStarGatewayConfig.h diff --git a/DStarGatewayDefs.h b/DStarGateway/DStarGatewayDefs.h similarity index 100% rename from DStarGatewayDefs.h rename to DStarGateway/DStarGatewayDefs.h diff --git a/DStarGatewayStatusData.cpp b/DStarGateway/DStarGatewayStatusData.cpp similarity index 62% rename from DStarGatewayStatusData.cpp rename to DStarGateway/DStarGatewayStatusData.cpp index 0798788..c3337ee 100644 --- a/DStarGatewayStatusData.cpp +++ b/DStarGateway/DStarGatewayStatusData.cpp @@ -21,17 +21,17 @@ #include "DStarGatewayStatusData.h" -CIRCDDBGatewayStatusData::CIRCDDBGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus) : +CDStarGatewayStatusData::CDStarGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus) : m_ircDDBStatus(ircDDBStatus), m_dprsStatus(dprsStatus) { } -CIRCDDBGatewayStatusData::~CIRCDDBGatewayStatusData() +CDStarGatewayStatusData::~CDStarGatewayStatusData() { } -void CIRCDDBGatewayStatusData::setRepeater(unsigned int n, const std::string& callsign, LINK_STATUS linkStatus, const std::string& linkCallsign, const std::string& incoming) +void CDStarGatewayStatusData::setRepeater(unsigned int n, const std::string& callsign, LINK_STATUS linkStatus, const std::string& linkCallsign, const std::string& incoming) { assert(n < 4U); @@ -41,50 +41,50 @@ void CIRCDDBGatewayStatusData::setRepeater(unsigned int n, const std::string& ca m_incoming[n] = incoming; } -void CIRCDDBGatewayStatusData::setDongles(const std::string& dongles) +void CDStarGatewayStatusData::setDongles(const std::string& dongles) { m_dongles = dongles; } -IRCDDB_STATUS CIRCDDBGatewayStatusData::getIrcDDBStatus() const +IRCDDB_STATUS CDStarGatewayStatusData::getIrcDDBStatus() const { return m_ircDDBStatus; } -bool CIRCDDBGatewayStatusData::getDPRSStatus() const +bool CDStarGatewayStatusData::getDPRSStatus() const { return m_dprsStatus; } -std::string CIRCDDBGatewayStatusData::getCallsign(unsigned int n) const +std::string CDStarGatewayStatusData::getCallsign(unsigned int n) const { assert(n < 4U); return m_callsign[n]; } -LINK_STATUS CIRCDDBGatewayStatusData::getLinkStatus(unsigned int n) const +LINK_STATUS CDStarGatewayStatusData::getLinkStatus(unsigned int n) const { assert(n < 4U); return m_linkStatus[n]; } -std::string CIRCDDBGatewayStatusData::getLinkCallsign(unsigned int n) const +std::string CDStarGatewayStatusData::getLinkCallsign(unsigned int n) const { assert(n < 4U); return m_linkCallsign[n]; } -std::string CIRCDDBGatewayStatusData::getIncoming(unsigned int n) const +std::string CDStarGatewayStatusData::getIncoming(unsigned int n) const { assert(n < 4U); return m_incoming[n]; } -std::string CIRCDDBGatewayStatusData::getDongles() const +std::string CDStarGatewayStatusData::getDongles() const { return m_dongles; } diff --git a/DStarGatewayStatusData.h b/DStarGateway/DStarGatewayStatusData.h similarity index 92% rename from DStarGatewayStatusData.h rename to DStarGateway/DStarGatewayStatusData.h index 7768b16..5291508 100644 --- a/DStarGatewayStatusData.h +++ b/DStarGateway/DStarGatewayStatusData.h @@ -22,10 +22,10 @@ #include "Defs.h" -class CIRCDDBGatewayStatusData { +class CDStarGatewayStatusData { public: - CIRCDDBGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus); - ~CIRCDDBGatewayStatusData(); + CDStarGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus); + ~CDStarGatewayStatusData(); void setRepeater(unsigned int n, const std::string& callsign, LINK_STATUS linkStatus, const std::string& linkCallsign, const std::string& incoming); diff --git a/DStarGatewayThread.cpp b/DStarGateway/DStarGatewayThread.cpp similarity index 90% rename from DStarGatewayThread.cpp rename to DStarGateway/DStarGatewayThread.cpp index 23aa7a2..693075d 100644 --- a/DStarGatewayThread.cpp +++ b/DStarGateway/DStarGatewayThread.cpp @@ -75,10 +75,7 @@ m_dummyRepeaterHandler(NULL), m_dextraPool(NULL), m_dplusPool(NULL), m_dcsPool(NULL), -m_g2Handler(NULL), -#if defined(ENABLE_NAT_TRAVERSAL) -m_natTraversal(NULL), -#endif +m_g2HandlerPool(NULL), m_aprsWriter(NULL), m_irc(NULL), m_cache(), @@ -201,23 +198,16 @@ void* CDStarGatewayThread::Entry() CLog::logError("Failed to allocate incoming DCS handler\n"); } - m_g2Handler = new CG2ProtocolHandler(G2_DV_PORT, m_gatewayAddress); - bool ret = m_g2Handler->open(); + m_g2HandlerPool = new CG2ProtocolHandlerPool(G2_DV_PORT, m_gatewayAddress); + bool ret = m_g2HandlerPool->open(); if (!ret) { CLog::logError("Could not open the G2 protocol handler"); - delete m_g2Handler; - m_g2Handler = NULL; - } - -#if defined(ENABLE_NAT_TRAVERSAL) - if(m_g2Handler != NULL) { - m_natTraversal = new CNatTraversalHandler(); - m_natTraversal->setG2Handler(m_g2Handler); + delete m_g2HandlerPool; + m_g2HandlerPool = NULL; } -#endif // Wait here until we have the essentials to run - while (!m_killed && (m_dextraPool == NULL || m_dplusPool == NULL || m_dcsPool == NULL || m_g2Handler == NULL || (m_icomRepeaterHandler == NULL && m_hbRepeaterHandler == NULL && m_dummyRepeaterHandler == NULL) || m_gatewayCallsign.empty())) + while (!m_killed && (m_dextraPool == NULL || m_dplusPool == NULL || m_dcsPool == NULL || m_g2HandlerPool == NULL || (m_icomRepeaterHandler == NULL && m_hbRepeaterHandler == NULL && m_dummyRepeaterHandler == NULL) || m_gatewayCallsign.empty())) ::std::this_thread::sleep_for(std::chrono::milliseconds(500UL)); // 1/2 sec if (m_killed) @@ -242,7 +232,7 @@ void* CDStarGatewayThread::Entry() loadGateways(); loadAllReflectors(); - CG2Handler::setG2ProtocolHandler(m_g2Handler); + CG2Handler::setG2ProtocolHandlerPool(m_g2HandlerPool); CG2Handler::setHeaderLogger(headerLogger); CDExtraHandler::setCallsign(m_gatewayCallsign); @@ -260,7 +250,7 @@ void* CDStarGatewayThread::Entry() CDCSHandler::setHeaderLogger(headerLogger); CRepeaterHandler::setLocalAddress(m_gatewayAddress); - CRepeaterHandler::setG2Handler(m_g2Handler); + CRepeaterHandler::setG2HandlerPool(m_g2HandlerPool); if (m_irc != NULL) CRepeaterHandler::setIRC(m_irc); @@ -294,7 +284,7 @@ void* CDStarGatewayThread::Entry() #ifdef USE_STARNET CStarNetHandler::setCache(&m_cache); CStarNetHandler::setGateway(m_gatewayCallsign); - CStarNetHandler::setG2Handler(m_g2Handler); + CStarNetHandler::setG2HandlerPool(m_g2HandlerPool); if (m_irc != NULL) CStarNetHandler::setIRC(m_irc); @@ -345,7 +335,9 @@ void* CDStarGatewayThread::Entry() m_statusFileTimer.start(); m_statusTimer2.start(); +#ifndef DEBUG_DSTARGW try { +#endif while (!m_killed) { if (m_icomRepeaterHandler != NULL) processRepeater(m_icomRepeaterHandler); @@ -421,14 +413,18 @@ void* CDStarGatewayThread::Entry() ::std::this_thread::sleep_for(std::chrono::milliseconds(TIME_PER_TIC_MS)); } +#ifndef DEBUG_DSTARGW } catch (std::exception& e) { std::string message(e.what()); - CLog::logError("Exception raised in the main thread - \"%s\"", message.c_str()); + CLog::logFatal("Exception raised in the main thread - \"%s\"", message.c_str()); + throw; } catch (...) { - CLog::logError("Unknown exception raised in the main thread"); + CLog::logFatal("Unknown exception raised in the main thread"); + throw; } +#endif CLog::logInfo("Stopping the ircDDB Gateway thread"); @@ -457,8 +453,8 @@ void* CDStarGatewayThread::Entry() m_dcsPool->close(); delete m_dcsPool; - m_g2Handler->close(); - delete m_g2Handler; + m_g2HandlerPool->close(); + delete m_g2HandlerPool; if (m_irc != NULL) { m_irc->close(); @@ -638,7 +634,7 @@ void CDStarGatewayThread::setLog(bool enabled) m_logEnabled = enabled; } -void CDStarGatewayThread::setAPRSWriter(CAPRSWriter* writer) +void CDStarGatewayThread::setAPRSWriter(CAPRSHandler* writer) { m_aprsWriter = writer; } @@ -733,6 +729,7 @@ void CDStarGatewayThread::processIrcDDB() m_statusTimer2.start(); } + // Process incoming ircDDB messages, updating the caches for (;;) { IRCDDB_RESPONSE_TYPE type = m_irc->getMessageType(); @@ -747,9 +744,6 @@ void CDStarGatewayThread::processIrcDDB() if (!address.empty()) { CLog::logDebug("USER: %s %s %s %s", user.c_str(), repeater.c_str(), gateway.c_str(), address.c_str()); m_cache.updateUser(user, repeater, gateway, address, timestamp, DP_DEXTRA, false, false); -#if defined(ENABLE_NAT_TRAVERSAL) - m_natTraversal->traverseNatG2(address); -#endif } else { CLog::logDebug("USER: %s NOT FOUND", user.c_str()); } @@ -766,9 +760,6 @@ void CDStarGatewayThread::processIrcDDB() if (!address.empty()) { CLog::logDebug("REPEATER: %s %s %s", repeater.c_str(), gateway.c_str(), address.c_str()); m_cache.updateRepeater(repeater, gateway, address, DP_DEXTRA, false, false); -#if defined(ENABLE_NAT_TRAVERSAL) - m_natTraversal->traverseNatG2(address); -#endif } else { CLog::logDebug("REPEATER: %s NOT FOUND", repeater.c_str()); } @@ -786,15 +777,60 @@ void CDStarGatewayThread::processIrcDDB() if (!address.empty()) { CLog::logDebug("GATEWAY: %s %s", gateway.c_str(), address.c_str()); m_cache.updateGateway(gateway, address, DP_DEXTRA, false, false); -#if defined(ENABLE_NAT_TRAVERSAL) - m_natTraversal->traverseNatG2(address); -#endif } else { CLog::logDebug("GATEWAY: %s NOT FOUND", gateway.c_str()); } } break; - + case IDRT_NATTRAVERSAL_G2: { + std::string address; + bool res = m_irc->receiveNATTraversalG2(address); + if(!res) + return; + + if(m_g2HandlerPool != nullptr) { + CLog::logInfo("%s wants to G2 route to us, punching UDP Holes through NAT", address.c_str()); + m_g2HandlerPool->traverseNat(address); + } + else { + CLog::logInfo("%s wants to G2 route to us, but G2 is disabled", address.c_str()); + } + } + break; + case IDRT_NATTRAVERSAL_DEXTRA: { + std::string address, remotePort; + bool res = m_irc->receiveNATTraversalDextra(address, remotePort); + if(!res) + return; + + auto remotePortInt = CStringUtils::stringToPort(remotePort); + if(m_dextraEnabled && remotePortInt > 0U && m_dextraPool != nullptr && m_dextraPool->getIncomingHandler() != nullptr) { + CLog::logInfo("%s wants to DExtra connect to us, punching UDP Holes through NAT, remote port %s", address.c_str(), remotePort.c_str()); + m_dextraPool->getIncomingHandler()->traverseNat(address, remotePortInt); + } + else { + CLog::logInfo("%s wants to DExtra connect to us, punching UDP Holes through NAT, remote port %s, but DExtra is Disabled", address.c_str(), remotePort.c_str()); + } + } + break; + case IDRT_NATTRAVERSAL_DPLUS: { + std::string address, remotePort; + bool res = m_irc->receiveNATTraversalDPlus(address, remotePort); + if(!res) + return; + + auto remotePortInt = CStringUtils::stringToPort(remotePort); + if(m_dplusEnabled && remotePortInt > 0U && m_dplusPool != nullptr && m_dplusPool->getIncomingHandler() != nullptr) { + CLog::logInfo("%s wants to DPlus connect to us, punching UDP Holes through NAT, remote port %s", address.c_str(), remotePort.c_str()); + m_dplusPool->getIncomingHandler()->traverseNat(address, remotePortInt); + } + else { + CLog::logInfo("%s wants to DPlus connect to us, punching UDP Holes through NAT, remote port %s, but DPlus is Disabled", address.c_str(), remotePort.c_str()); + } + } + break; + case IDRT_NONE: + return; default: return; } @@ -1060,13 +1096,13 @@ void CDStarGatewayThread::processDCS() void CDStarGatewayThread::processG2() { for (;;) { - G2_TYPE type = m_g2Handler->read(); + G2_TYPE type = m_g2HandlerPool->read(); switch (type) { case GT_HEADER: { - CHeaderData* header = m_g2Handler->readHeader(); + CHeaderData* header = m_g2HandlerPool->readHeader(); if (header != NULL) { - // CLog::logInfo("G2 header - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X", header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + CLog::logDebug("G2 header - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X", header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); CG2Handler::process(*header); delete header; } @@ -1074,7 +1110,7 @@ void CDStarGatewayThread::processG2() break; case GT_AMBE: { - CAMBEData* data = m_g2Handler->readAMBE(); + CAMBEData* data = m_g2HandlerPool->readAMBE(); if (data != NULL) { CG2Handler::process(*data); delete data; @@ -1203,13 +1239,13 @@ void CDStarGatewayThread::writeStatus() file.close(); } -CIRCDDBGatewayStatusData* CDStarGatewayThread::getStatus() const +CDStarGatewayStatusData* CDStarGatewayThread::getStatus() const { bool aprsStatus = false; if (m_aprsWriter != NULL) aprsStatus = m_aprsWriter->isConnected(); - CIRCDDBGatewayStatusData* status = new CIRCDDBGatewayStatusData(m_lastStatus, aprsStatus); + CDStarGatewayStatusData* status = new CDStarGatewayStatusData(m_lastStatus, aprsStatus); for (unsigned int i = 0U; i < 4U; i++) { std::string callsign, linkCallsign; diff --git a/DStarGatewayThread.h b/DStarGateway/DStarGatewayThread.h similarity index 94% rename from DStarGatewayThread.h rename to DStarGateway/DStarGatewayThread.h index 6b5d912..5b4fe3c 100644 --- a/DStarGatewayThread.h +++ b/DStarGateway/DStarGatewayThread.h @@ -28,18 +28,15 @@ #include "RepeaterProtocolHandler.h" #include "DStarGatewayStatusData.h" #include "DCSProtocolHandlerPool.h" -#include "G2ProtocolHandler.h" +#include "G2ProtocolHandlerPool.h" #include "RemoteHandler.h" #include "CacheManager.h" #include "CallsignList.h" -#include "APRSWriter.h" +#include "APRSHandler.h" #include "IRCDDB.h" #include "Timer.h" #include "Defs.h" #include "Thread.h" -#if defined(ENABLE_NAT_TRAVERSAL) -#include "NatTraversalHandler.h" -#endif class CDStarGatewayThread : public CThread{ public: @@ -72,7 +69,7 @@ virtual void addRepeater(const std::string& callsign, const std::string& band, c virtual void setCCS(bool enabled, const std::string& host); #endif virtual void setLog(bool enabled); - virtual void setAPRSWriter(CAPRSWriter* writer); + virtual void setAPRSWriter(CAPRSHandler* writer); virtual void setInfoEnabled(bool enabled); virtual void setEchoEnabled(bool enabled); virtual void setDTMFEnabled(bool enabled); @@ -83,7 +80,7 @@ virtual void addRepeater(const std::string& callsign, const std::string& band, c virtual void setBlackList(CCallsignList* list); virtual void setRestrictList(CCallsignList* list); - virtual CIRCDDBGatewayStatusData* getStatus() const; + virtual CDStarGatewayStatusData* getStatus() const; virtual void kill(); @@ -105,11 +102,8 @@ private: CDExtraProtocolHandlerPool* m_dextraPool; CDPlusProtocolHandlerPool* m_dplusPool; CDCSProtocolHandlerPool* m_dcsPool; - CG2ProtocolHandler* m_g2Handler; -#if defined(ENABLE_NAT_TRAVERSAL) - CNatTraversalHandler* m_natTraversal; -#endif - CAPRSWriter* m_aprsWriter; + CG2ProtocolHandlerPool* m_g2HandlerPool; + CAPRSHandler* m_aprsWriter; CIRCDDB* m_irc; CCacheManager m_cache; TEXT_LANG m_language; diff --git a/DStarGateway/Makefile b/DStarGateway/Makefile new file mode 100644 index 0000000..73318d1 --- /dev/null +++ b/DStarGateway/Makefile @@ -0,0 +1,30 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dstargateway: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB/IRCDDB.a ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a ../Common/Common.a + $(CC) $(CPPFLAGS) -o dstargateway $(OBJS) ../Common/Common.a ../APRS/APRS.a ../DStarBase/DStarBase.a ../IRCDDB/IRCDDB.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + +%.o : %.cpp + $(CC) -I../APRS -I../Common -I../BaseCommon -I../DStarBase -I../IRCDDB -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ + +.PHONY clean: +clean: + $(RM) *.o *.d dstargateway + +.PHONY install: +install: dstargateway +# copy executable + @cp -f dstargateway $(BIN_DIR) + +# copy and adjust config + @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 + +../APRS/APRS.a: +../Common/Common.a: +../DStarBase/DStarBase.a: +../BaseCommon/BaseCommon.a: +../IRCDDB/IRCDDB.a: +../VersionInfo/GitVersion.h: diff --git a/RepeaterProtocolHandlerFactory.cpp b/DStarGateway/RepeaterProtocolHandlerFactory.cpp similarity index 100% rename from RepeaterProtocolHandlerFactory.cpp rename to DStarGateway/RepeaterProtocolHandlerFactory.cpp diff --git a/RepeaterProtocolHandlerFactory.h b/DStarGateway/RepeaterProtocolHandlerFactory.h similarity index 100% rename from RepeaterProtocolHandlerFactory.h rename to DStarGateway/RepeaterProtocolHandlerFactory.h diff --git a/example.cfg b/DStarGateway/example.cfg similarity index 98% rename from example.cfg rename to DStarGateway/example.cfg index 81c1103..b83f0d6 100644 --- a/example.cfg +++ b/DStarGateway/example.cfg @@ -32,12 +32,14 @@ username= # The ircDDB username defaults to the value defined for g password= [ircddb_3] -hostname=false +enabled=false +hostname= username=CHNGME # The ircDDB username defaults to the value defined for gateway callsign. password= [ircddb_4] -hostname=false +enabled=false +hostname= username=CHNGME # The ircDDB username defaults to the value defined for gateway callsign. password= @@ -162,7 +164,7 @@ maxDongles=5 [DPlus] enabled=true # There is no reason to disable this maxDongles=5 -login=F4FXL # defaults to gateway callsign +login= # defaults to gateway callsign [DCS] enabled=true # There is no reason to disable this diff --git a/IRCApplication.h b/IRCDDB/IRCApplication.h similarity index 100% rename from IRCApplication.h rename to IRCDDB/IRCApplication.h diff --git a/IRCClient.cpp b/IRCDDB/IRCClient.cpp similarity index 100% rename from IRCClient.cpp rename to IRCDDB/IRCClient.cpp diff --git a/IRCClient.h b/IRCDDB/IRCClient.h similarity index 100% rename from IRCClient.h rename to IRCDDB/IRCClient.h diff --git a/IRCDDB.cpp b/IRCDDB/IRCDDB.cpp similarity index 100% rename from IRCDDB.cpp rename to IRCDDB/IRCDDB.cpp diff --git a/IRCDDB.h b/IRCDDB/IRCDDB.h similarity index 90% rename from IRCDDB.h rename to IRCDDB/IRCDDB.h index 8f31825..6b5534a 100644 --- a/IRCDDB.h +++ b/IRCDDB/IRCDDB.h @@ -31,7 +31,10 @@ enum IRCDDB_RESPONSE_TYPE { IDRT_NONE, IDRT_USER, IDRT_GATEWAY, - IDRT_REPEATER + IDRT_REPEATER, + IDRT_NATTRAVERSAL_G2, + IDRT_NATTRAVERSAL_DEXTRA, + IDRT_NATTRAVERSAL_DPLUS, }; @@ -111,6 +114,11 @@ public: // Send query for a user, a false return implies a network error virtual bool findUser(const std::string& userCallsign) = 0; + + // notify another repeater for NAT Traversal, a false return implies a network error + virtual bool notifyRepeaterG2NatTraversal(const std::string& repeater) = 0; + virtual bool notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myport) = 0; + virtual bool notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myport) = 0; // Support for the Smart Group Server virtual void sendDStarGatewayInfo(const std::string subcommand, const std::vector parms) = 0; @@ -134,6 +142,10 @@ public: virtual bool receiveUser(std::string& userCallsign, std::string& repeaterCallsign, std::string& gatewayCallsign, std::string& address, std::string& timeStamp) = 0; + virtual bool receiveNATTraversalG2(std::string& address) = 0; + virtual bool receiveNATTraversalDextra(std::string& address, std::string& remotePort) = 0; + virtual bool receiveNATTraversalDPlus(std::string& address, std::string& remotePort) = 0; + virtual void close() = 0; // Implictely kills any threads in the IRC code }; diff --git a/IRCDDBApp.cpp b/IRCDDB/IRCDDBApp.cpp similarity index 56% rename from IRCDDBApp.cpp rename to IRCDDB/IRCDDBApp.cpp index 2731fb0..58909de 100644 --- a/IRCDDBApp.cpp +++ b/IRCDDB/IRCDDBApp.cpp @@ -27,6 +27,7 @@ along with this program. If not, see . #include #include #include +#include #include "IRCDDBApp.h" #include "Utils.h" @@ -35,11 +36,11 @@ along with this program. If not, see . class IRCDDBAppUserObject { public: - std::string nick; - std::string name; - std::string host; - bool op; - unsigned int usn; + std::string m_nick; + std::string m_name; + std::string m_host; + bool m_op; + unsigned int m_usn; IRCDDBAppUserObject() { @@ -48,20 +49,20 @@ public: IRCDDBAppUserObject(const std::string& n, const std::string& nm, const std::string& h) { - nick = n; - name = nm; - host = h; - op = false; - usn = 0; + m_nick = n; + m_name = nm; + m_host = h; + m_op = false; + m_usn = 0; } }; class IRCDDBAppRptrObject { public: - std::string arearp_cs; - time_t lastChanged; - std::string zonerp_cs; + std::string m_arearp_cs; + time_t m_lastChanged; + std::string m_zonerp_cs; IRCDDBAppRptrObject () { @@ -69,9 +70,9 @@ public: IRCDDBAppRptrObject (time_t &dt, std::string& repeaterCallsign, std::string& gatewayCallsign, time_t &maxTime) { - arearp_cs = repeaterCallsign; - lastChanged = dt; - zonerp_cs = gatewayCallsign; + m_arearp_cs = repeaterCallsign; + m_lastChanged = dt; + m_zonerp_cs = gatewayCallsign; if (dt > maxTime) maxTime = dt; @@ -82,74 +83,76 @@ class IRCDDBAppPrivate { public: IRCDDBAppPrivate() - : tablePattern("^[0-9]$") - , datePattern("^20[0-9][0-9]-((1[0-2])|(0[1-9]))-((3[01])|([12][0-9])|(0[1-9]))$") - , timePattern("^((2[0-3])|([01][0-9])):[0-5][0-9]:[0-5][0-9]$") - , dbPattern("^[0-9A-Z_]{8}$") + : m_tablePattern("^[0-9]$") + , m_datePattern("^20[0-9][0-9]-((1[0-2])|(0[1-9]))-((3[01])|([12][0-9])|(0[1-9]))$") + , m_timePattern("^((2[0-3])|([01][0-9])):[0-5][0-9]:[0-5][0-9]$") + , m_dbPattern("^[0-9A-Z_]{8}$") + , m_fromPattern("\\(from: (.*)\\)") { } - int state; - int timer; - int infoTimer; - int wdTimer; + int m_state; + int m_timer; + int m_infoTimer; + int m_wdTimer; - IRCMessageQueue *sendQ; - IRCMessageQueue replyQ; + IRCMessageQueue *m_sendQ; + IRCMessageQueue m_replyQ; - std::string currentServer; - std::string myNick; - std::string updateChannel; - std::string channelTopic; - std::string bestServer; + std::string m_currentServer; + std::string m_myNick; + std::string m_updateChannel; + std::string m_channelTopic; + std::string m_bestServer; - std::regex tablePattern; - std::regex datePattern; - std::regex timePattern; - std::regex dbPattern; + std::regex m_tablePattern; + std::regex m_datePattern; + std::regex m_timePattern; + std::regex m_dbPattern; + std::regex m_fromPattern; - bool initReady; - bool terminateThread; + bool m_initReady; + bool m_terminateThread; - std::map user; - std::mutex userMapMutex; + std::map m_userMap; + std::mutex m_userMapMutex; - std::map rptrMap; - std::mutex rptrMapMutex; + std::map m_rptrMap; + std::mutex m_rptrMapMutex; - std::map moduleQRG; - std::mutex moduleQRGMutex; + std::map m_moduleQRG; + std::mutex m_moduleQRGMutex; - std::map moduleQTH; - std::map moduleURL; - std::mutex moduleQTHURLMutex; + std::map m_moduleQTH; + std::map m_moduleURL; + std::mutex m_moduleQTHURLMutex; - std::map moduleWD; - std::mutex moduleWDMutex; + std::map m_moduleWD; + std::mutex m_moduleWDMutex; }; IRCDDBApp::IRCDDBApp(const std::string& u_chan) - : d(new IRCDDBAppPrivate) - , m_maxTime((time_t)950000000) //februray 2000 + : m_d(new IRCDDBAppPrivate) + , m_maxTime((time_t)time(0) - (time_t)(60 * 24 * 60 * 60)) // look 60 days in the past { - d->sendQ = NULL; - d->initReady = false; + m_d->m_sendQ = NULL; + m_d->m_initReady = false; userListReset(); - d->state = 0; - d->timer = 0; - d->myNick = std::string("none"); + m_d->m_state = 0; + m_d->m_timer = 0; + m_d->m_myNick = std::string("none"); - d->updateChannel = u_chan; + m_d->m_updateChannel = u_chan; - d->terminateThread = false; + m_d->m_terminateThread = false; } IRCDDBApp::~IRCDDBApp() { - delete d->sendQ; - delete d; + delete m_d->m_sendQ; + delete m_d; } void IRCDDBApp::rptrQTH(const std::string& callsign, double latitude, double longitude, const std::string& desc1, const std::string& desc2, const std::string& infoURL) @@ -177,11 +180,11 @@ void IRCDDBApp::rptrQTH(const std::string& callsign, double latitude, double lon CUtils::ReplaceChar(d2, ' ', '_'); CUtils::ReplaceChar(cs, ' ', '_'); - std::lock_guard lochQTHURL(d->moduleQTHURLMutex); + std::lock_guard lochQTHURL(m_d->m_moduleQTHURLMutex); - d->moduleQTH[cs] = cs + std::string(" ") + pos + std::string(" ") + d1 + std::string(" ") + d2; + m_d->m_moduleQTH[cs] = cs + std::string(" ") + pos + std::string(" ") + d1 + std::string(" ") + d2; - CLog::logInfo("QTH: %s\n", d->moduleQTH[cs].c_str()); + CLog::logInfo("QTH: %s\n", m_d->m_moduleQTH[cs].c_str()); std::string url = infoURL; @@ -190,11 +193,11 @@ void IRCDDBApp::rptrQTH(const std::string& callsign, double latitude, double lon url.erase(sm.position(0), sm.length()); if (url.size()) { - d->moduleURL[cs] = cs + std::string(" ") + url; - CLog::logInfo("URL: %s\n", d->moduleURL[cs].c_str()); + m_d->m_moduleURL[cs] = cs + std::string(" ") + url; + CLog::logInfo("URL: %s\n", m_d->m_moduleURL[cs].c_str()); } - d->infoTimer = 5; // send info in 5 seconds + m_d->m_infoTimer = 5; // send info in 5 seconds } void IRCDDBApp::rptrQRG(const std::string& callsign, double txFrequency, double duplexShift, double range, double agl) @@ -207,11 +210,11 @@ void IRCDDBApp::rptrQRG(const std::string& callsign, double txFrequency, double std::string f(fstr); CUtils::ReplaceChar(f, ',', '.'); - std::lock_guard lockModuleQRG(d->moduleQRGMutex); - d->moduleQRG[cs] = cs + std::string(" ") + f; - CLog::logInfo("QRG: %s\n", d->moduleQRG[cs].c_str()); + std::lock_guard lockModuleQRG(m_d->m_moduleQRGMutex); + m_d->m_moduleQRG[cs] = cs + std::string(" ") + f; + CLog::logInfo("QRG: %s\n", m_d->m_moduleQRG[cs].c_str()); - d->infoTimer = 5; // send info in 5 seconds + m_d->m_infoTimer = 5; // send info in 5 seconds } void IRCDDBApp::kickWatchdog(const std::string& callsign, const std::string& s) @@ -227,20 +230,20 @@ void IRCDDBApp::kickWatchdog(const std::string& callsign, const std::string& s) std::string cs = callsign; CUtils::ReplaceChar(cs, ' ', '_'); - std::lock_guard lockModuleWD(d->moduleWDMutex); - d->moduleWD[cs] = cs + std::string(" ") + text; - d->wdTimer = 60; + std::lock_guard lockModuleWD(m_d->m_moduleWDMutex); + m_d->m_moduleWD[cs] = cs + std::string(" ") + text; + m_d->m_wdTimer = 60; } } int IRCDDBApp::getConnectionState() { - return d->state; + return m_d->m_state; } IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType() { - IRCMessage *m = d->replyQ.peekFirst(); + IRCMessage *m = m_d->m_replyQ.peekFirst(); if (m == NULL) return IDRT_NONE; @@ -255,6 +258,15 @@ IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType() if (0 == msgType.compare("IDRT_GATEWAY")) return IDRT_GATEWAY; + if(msgType.compare("NATTRAVERSAL_G2") == 0) + return IDRT_NATTRAVERSAL_G2; + + if(msgType.compare("NATTRAVERSAL_DEXTRA") == 0) + return IDRT_NATTRAVERSAL_DEXTRA; + + if(msgType.compare("NATTRAVERSAL_DPLUS") == 0) + return IDRT_NATTRAVERSAL_DPLUS; + CLog::logWarning("IRCDDBApp::getMessageType: unknown msg type: %s\n", msgType.c_str()); return IDRT_NONE; @@ -262,18 +274,18 @@ IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType() IRCMessage *IRCDDBApp::getReplyMessage() { - return d->replyQ.getMessage(); + return m_d->m_replyQ.getMessage(); } void IRCDDBApp::startWork() { - d->terminateThread = false; + m_d->m_terminateThread = false; m_future = std::async(std::launch::async, &IRCDDBApp::Entry, this); } void IRCDDBApp::stopWork() { - d->terminateThread = true; + m_d->m_terminateThread = true; m_future.get(); } @@ -286,10 +298,10 @@ unsigned int IRCDDBApp::calculateUsn(const std::string& nick) for (int i = 1; i <= 4; i++) { std::string ircUser = lnick + std::to_string(i); - if (d->user.count(ircUser) == 1) { - IRCDDBAppUserObject obj = d->user[ircUser]; - if (obj.usn > maxUsn) - maxUsn = obj.usn; + if (m_d->m_userMap.count(ircUser) == 1) { + IRCDDBAppUserObject obj = m_d->m_userMap[ircUser]; + if (obj.m_usn > maxUsn) + maxUsn = obj.m_usn; } } return maxUsn + 1; @@ -297,17 +309,17 @@ unsigned int IRCDDBApp::calculateUsn(const std::string& nick) void IRCDDBApp::userJoin(const std::string& nick, const std::string& name, const std::string& host) { - std::lock_guard lockUserMap(d->userMapMutex); + std::lock_guard lockUserMap(m_d->m_userMapMutex); std::string lnick = nick; CUtils::ToLower(lnick); IRCDDBAppUserObject u(lnick, name, host); - u.usn = calculateUsn(lnick); + u.m_usn = calculateUsn(lnick); - d->user[lnick] = u; + m_d->m_userMap[lnick] = u; - if (d->initReady) { + /*if (m_d->m_initReady)*/ { std::string::size_type hyphenPos = nick.find('-'); if ((hyphenPos >= 4) && (hyphenPos <= 6)) { @@ -319,7 +331,7 @@ void IRCDDBApp::userJoin(const std::string& nick, const std::string& name, const IRCMessage *m2 = new IRCMessage("IDRT_GATEWAY"); m2->addParam(gatewayCallsign); m2->addParam(host); - d->replyQ.putMessage(m2); + m_d->m_replyQ.putMessage(m2); } } } @@ -329,25 +341,25 @@ void IRCDDBApp::userLeave(const std::string& nick) std::string lnick = nick; CUtils::ToLower(lnick); - std::lock_guard lockUserMap(d->userMapMutex); - d->user.erase(lnick); + std::lock_guard lockUserMap(m_d->m_userMapMutex); + m_d->m_userMap.erase(lnick); - if (d->currentServer.size()) { - if (d->user.count(d->myNick) != 1) { + if (m_d->m_currentServer.size()) { + if (m_d->m_userMap.count(m_d->m_myNick) != 1) { CLog::logInfo("IRCDDBApp::userLeave: could not find own nick\n"); return; } - IRCDDBAppUserObject me = d->user[d->myNick]; + IRCDDBAppUserObject me = m_d->m_userMap[m_d->m_myNick]; - if (me.op == false) { + if (me.m_op == false) { // if I am not op, then look for new server - if (0 == d->currentServer.compare(lnick)) { - // currentServer = null; - d->state = 2; // choose new server - d->timer = 200; - d->initReady = false; + if (0 == m_d->m_currentServer.compare(lnick)) { + // m_currentServer = null; + m_d->m_state = 2; // choose new server + m_d->m_timer = 200; + m_d->m_initReady = false; } } } @@ -355,39 +367,39 @@ void IRCDDBApp::userLeave(const std::string& nick) void IRCDDBApp::userListReset() { - std::lock_guard lockUserMap(d->userMapMutex); - d->user.clear(); + std::lock_guard lockUserMap(m_d->m_userMapMutex); + m_d->m_userMap.clear(); } void IRCDDBApp::setCurrentNick(const std::string& nick) { - d->myNick = nick; + m_d->m_myNick = nick; CLog::logInfo("IRCDDBApp::setCurrentNick %s\n", nick.c_str()); } void IRCDDBApp::setBestServer(const std::string& ircUser) { - d->bestServer = ircUser; + m_d->m_bestServer = ircUser; CLog::logInfo("IRCDDBApp::setBestServer %s\n", ircUser.c_str()); } void IRCDDBApp::setTopic(const std::string& topic) { - d->channelTopic = topic; + m_d->m_channelTopic = topic; } bool IRCDDBApp::findServerUser() { bool found = false; - std::lock_guard lockUserMap(d->userMapMutex); + std::lock_guard lockUserMap(m_d->m_userMapMutex); std::map::iterator it; - for (it = d->user.begin(); it != d->user.end(); ++it) { + for (it = m_d->m_userMap.begin(); it != m_d->m_userMap.end(); ++it) { IRCDDBAppUserObject u = it->second; - if (0==u.nick.compare(0, 2, "s-") && u.op && d->myNick.compare(u.nick) && 0==u.nick.compare(d->bestServer)) { - d->currentServer = u.nick; + if (0==u.m_nick.compare(0, 2, "s-") && u.m_op && m_d->m_myNick.compare(u.m_nick) && 0==u.m_nick.compare(m_d->m_bestServer)) { + m_d->m_currentServer = u.m_nick; found = true; break; } @@ -397,12 +409,12 @@ bool IRCDDBApp::findServerUser() return true; } - if (8 == d->bestServer.size()) { - for (it = d->user.begin(); it != d->user.end(); ++it) { + if (8 == m_d->m_bestServer.size()) { + for (it = m_d->m_userMap.begin(); it != m_d->m_userMap.end(); ++it) { IRCDDBAppUserObject u = it->second; - if (0==u.nick.compare(d->bestServer.substr(0,7)) && u.op && d->myNick.compare(u.nick) ) { - d->currentServer = u.nick; + if (0==u.m_nick.compare(m_d->m_bestServer.substr(0,7)) && u.m_op && m_d->m_myNick.compare(u.m_nick) ) { + m_d->m_currentServer = u.m_nick; found = true; break; } @@ -413,10 +425,10 @@ bool IRCDDBApp::findServerUser() return true; } - for (it = d->user.begin(); it != d->user.end(); ++it) { + for (it = m_d->m_userMap.begin(); it != m_d->m_userMap.end(); ++it) { IRCDDBAppUserObject u = it->second; - if (0==u.nick.compare(0, 2, "s-") && u.op && d->myNick.compare(u.nick)) { - d->currentServer = u.nick; + if (0==u.m_nick.compare(0, 2, "s-") && u.m_op && m_d->m_myNick.compare(u.m_nick)) { + m_d->m_currentServer = u.m_nick; found = true; break; } @@ -426,18 +438,18 @@ bool IRCDDBApp::findServerUser() void IRCDDBApp::userChanOp(const std::string& nick, bool op) { - std::lock_guard lockUserMap(d->userMapMutex); + std::lock_guard lockUserMap(m_d->m_userMapMutex); std::string lnick = nick; CUtils::ToLower(lnick); - if (d->user.count(lnick) == 1) - d->user[lnick].op = op; + if (m_d->m_userMap.count(lnick) == 1) + m_d->m_userMap[lnick].m_op = op; } static const int numberOfTables = 2; -std::string IRCDDBApp::getIPAddress(std::string& zonerp_cs) +std::string IRCDDBApp::getIPAddressFromCall(std::string& zonerp_cs) { unsigned int max_usn = 0; std::string ipAddr; @@ -446,30 +458,41 @@ std::string IRCDDBApp::getIPAddress(std::string& zonerp_cs) CUtils::ToLower(gw); CUtils::Trim(gw); - std::lock_guard lockUserMap(d->userMapMutex); + std::lock_guard lockUserMap(m_d->m_userMapMutex); for (int j=1; j <= 4; j++) { std::string ircUser = gw + std::string("-") + std::to_string(j); - if (d->user.count(ircUser) == 1) { - IRCDDBAppUserObject o = d->user[ircUser]; + if (m_d->m_userMap.count(ircUser) == 1) { + IRCDDBAppUserObject o = m_d->m_userMap[ircUser]; - if (o.usn >= max_usn) { - max_usn = o.usn; - ipAddr = o.host.c_str(); + if (o.m_usn >= max_usn) { + max_usn = o.m_usn; + ipAddr = o.m_host.c_str(); } } } return ipAddr; } +std::string IRCDDBApp::getIPAddressFromNick(std::string& ircUser) +{ + std::string ipAddress; + + if (m_d->m_userMap.count(ircUser) == 1) { + ipAddress.assign(m_d->m_userMap[ircUser].m_host); + } + + return ipAddress; +} + bool IRCDDBApp::findGateway(const std::string& gwCall) { std::string s = gwCall.substr(0,6); IRCMessage *m2 = new IRCMessage("IDRT_GATEWAY"); m2->addParam(gwCall); - m2->addParam(getIPAddress(s)); - d->replyQ.putMessage(m2); + m2->addParam(getIPAddressFromCall(s)); + m_d->m_replyQ.putMessage(m2); return true; } @@ -504,13 +527,13 @@ static void findReflector(const std::string& rptrCall, IRCDDBAppPrivate *d) m2->addParam(rptrCall); m2->addParam(zonerp_cs); m2->addParam(ipAddr); - d->replyQ.putMessage(m2); + d->m_replyQ.putMessage(m2); } bool IRCDDBApp::findRepeater(const std::string& rptrCall) { - if (0==rptrCall.compare(0, 3, "XRF") || 0==rptrCall.compare(0, 3, "REF")) { - findReflector(rptrCall, d); + if (0==rptrCall.compare(0, 3, "XRF") || 0==rptrCall.compare(0, 3, "REF") || 0==rptrCall.compare(0, 3, "DCS") || 0==rptrCall.compare(0, 3, "XLX") ) { + findReflector(rptrCall, m_d); return true; } @@ -519,22 +542,22 @@ bool IRCDDBApp::findRepeater(const std::string& rptrCall) std::string s("NONE"); std::string zonerp_cs; - std::lock_guard lockRptrMap(d->rptrMapMutex); + std::lock_guard lockRptrMap(m_d->m_rptrMapMutex); - if (1 == d->rptrMap.count(arearp_cs)) { - IRCDDBAppRptrObject o = d->rptrMap[arearp_cs]; - zonerp_cs = o.zonerp_cs; + if (1 == m_d->m_rptrMap.count(arearp_cs)) { + IRCDDBAppRptrObject o = m_d->m_rptrMap[arearp_cs]; + zonerp_cs = o.m_zonerp_cs; CUtils::ReplaceChar(zonerp_cs, '_', ' '); zonerp_cs.resize(7, ' '); zonerp_cs.push_back('G'); - s = o.zonerp_cs; + s = o.m_zonerp_cs; } IRCMessage * m2 = new IRCMessage("IDRT_REPEATER"); m2->addParam(rptrCall); m2->addParam(zonerp_cs); - m2->addParam(getIPAddress(s)); - d->replyQ.putMessage(m2); + m2->addParam(getIPAddressFromCall(s)); + m_d->m_replyQ.putMessage(m2); return true; } @@ -542,8 +565,8 @@ bool IRCDDBApp::findRepeater(const std::string& rptrCall) void IRCDDBApp::sendDStarGatewayInfo(const std::string &subcommand, const std::vector &pars) { IRCMessageQueue *q = getSendQ(); - std::string srv(d->currentServer); - if (srv.size() && d->state>=6 && q) { + std::string srv(m_d->m_currentServer); + if (srv.size() && m_d->m_state>=6 && q) { std::string command("DStarGateway "); command.append(subcommand); for (auto it=pars.begin(); it!=pars.end(); it++) { @@ -582,10 +605,10 @@ bool IRCDDBApp::sendHeard(const std::string& myCall, const std::string& myCallEx bool statsMsg = (tx_stats.size() > 0); - std::string srv(d->currentServer); + std::string srv(m_d->m_currentServer); IRCMessageQueue *q = getSendQ(); - if (srv.size() && d->state>=6 && q) { + if (srv.size() && m_d->m_state>=6 && q) { std::string cmd("UPDATE "); cmd += CUtils::getCurrentTime(); @@ -616,10 +639,10 @@ bool IRCDDBApp::sendHeard(const std::string& myCall, const std::string& myCallEx bool IRCDDBApp::findUser(const std::string& usrCall) { - std::string srv(d->currentServer); + std::string srv(m_d->m_currentServer); IRCMessageQueue *q = getSendQ(); - if (srv.size()>0 && d->state>=6 && q) { + if (srv.size()>0 && m_d->m_state>=6 && q) { std::string usr(usrCall); CUtils::ReplaceChar(usr, ' ', '_'); IRCMessage * m =new IRCMessage(srv, std::string("FIND ") + usr); @@ -629,15 +652,79 @@ bool IRCDDBApp::findUser(const std::string& usrCall) m2->addParam(usrCall); for (int i=0; i<4; i++) m2->addParam(std::string("")); - d->replyQ.putMessage(m2); + m_d->m_replyQ.putMessage(m2); } return true; } +bool IRCDDBApp::notifyRepeaterG2NatTraversal(const std::string& repeater) +{ + std::string nick; + + if(!getNickForRepeater(repeater, nick)) + return true; //return true because this return value is handled as a network error, whoch is actually uncleve + + IRCMessage * ircMessage = new IRCMessage(nick, "NATTRAVERSAL_G2"); + m_d->m_sendQ->putMessage(ircMessage); + + return true; +} + +bool IRCDDBApp::notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myLocalPort) +{ + std::string nick; + + if(!getNickForRepeater(repeater, nick)) + return true; //return true because this return value is handled as a network error, whoch is actually uncleve + + IRCMessage * ircMessage = new IRCMessage(nick, "NATTRAVERSAL_DEXTRA"); + ircMessage->addParam(std::to_string(myLocalPort)); + m_d->m_sendQ->putMessage(ircMessage); + + return true; +} + +bool IRCDDBApp::notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myLocalPort) +{ + std::string nick; + + if(!getNickForRepeater(repeater, nick)) + return true; //return true because this return value is handled as a network error, whoch is actually unclever + + IRCMessage * ircMessage = new IRCMessage(nick, "NATTRAVERSAL_DPLUS"); + ircMessage->addParam(std::to_string(myLocalPort)); + m_d->m_sendQ->putMessage(ircMessage); + + return true; +} + +bool IRCDDBApp::getNickForRepeater(const std::string& repeater, std::string& nick) const +{ + std::lock_guard lockUserMap(m_d->m_userMapMutex); + + auto firstSpacePos = repeater.find_first_of(' '); + if(firstSpacePos == std::string::npos) + return false; + + auto lrepeater = repeater.substr(0, firstSpacePos); + CUtils::ToLower(lrepeater); + + for(unsigned int i = 1; i <= 4U; i++) { + nick = lrepeater + "-" + std::to_string(i); + if(m_d->m_userMap.count(nick) == 1) { + return true; + } + } + + nick.clear(); + CLog::logDebug("Unable to find IRC nick for repeater %s", repeater.c_str()); + return false; +} + void IRCDDBApp::msgChannel(IRCMessage *m) { - if (0==m->getPrefixNick().compare(0, 2, "s-") && m->numParams>=2) // server msg - doUpdate(m->params[1]); + if (0==m->getPrefixNick().compare(0, 2, "s-") && m->m_numParams>=2) // server msg + doUpdate(m->m_params[1]); } void IRCDDBApp::doNotFound(std::string& msg, std::string& retval) @@ -651,7 +738,7 @@ void IRCDDBApp::doNotFound(std::string& msg, std::string& retval) std::string tk = tkz.front(); tkz.erase(tkz.begin()); - if (std::regex_match(tk, d->tablePattern)) { + if (std::regex_match(tk, m_d->m_tablePattern)) { tableID = std::stoi(tk); if (tableID<0 || tableID>=numberOfTables) { @@ -667,7 +754,7 @@ void IRCDDBApp::doNotFound(std::string& msg, std::string& retval) } if (0 == tableID) { - if (! std::regex_match(tk, d->dbPattern)) + if (! std::regex_match(tk, m_d->m_dbPattern)) return; // no valid key retval = tk; } @@ -683,7 +770,7 @@ void IRCDDBApp::doUpdate(std::string& msg) std::string tk = tkz.front(); tkz.erase(tkz.begin()); - if (std::regex_match(tk, d->tablePattern)) { + if (std::regex_match(tk, m_d->m_tablePattern)) { tableID = std::stoi(tk); if ((tableID < 0) || (tableID >= numberOfTables)) { CLog::logInfo("invalid table ID %d\n", tableID); @@ -697,13 +784,13 @@ void IRCDDBApp::doUpdate(std::string& msg) tkz.erase(tkz.begin()); } - if (std::regex_match(tk, d->datePattern)) { + if (std::regex_match(tk, m_d->m_datePattern)) { if (tkz.empty()) return; // nothing after date string std::string timeToken = tkz.front(); // time token tkz.erase(tkz.begin()); - if (! std::regex_match(timeToken, d->timePattern)) + if (! std::regex_match(timeToken, m_d->m_timePattern)) return; // no time string after date string time_t dt = CUtils::parseTime(tk + std::string(" ") + timeToken); @@ -715,7 +802,7 @@ void IRCDDBApp::doUpdate(std::string& msg) std::string key = tkz.front(); tkz.erase(tkz.begin()); - if (! std::regex_match(key, d->dbPattern)) + if (! std::regex_match(key, m_d->m_dbPattern)) return; // no valid key if (tkz.empty()) @@ -724,15 +811,15 @@ void IRCDDBApp::doUpdate(std::string& msg) std::string value = tkz.front(); tkz.erase(tkz.begin()); - if (! std::regex_match(value, d->dbPattern)) + if (! std::regex_match(value, m_d->m_dbPattern)) return; // no valid key if (tableID == 1) { - std::lock_guard lockRptrMap(d->rptrMapMutex); + std::lock_guard lockRptrMap(m_d->m_rptrMapMutex); IRCDDBAppRptrObject newRptr(dt, key, value, m_maxTime); - d->rptrMap[key] = newRptr; + m_d->m_rptrMap[key] = newRptr; - if (d->initReady) { + if (m_d->m_initReady) { std::string arearp_cs(key); std::string zonerp_cs(value); CUtils::ReplaceChar(arearp_cs, '_', ' '); @@ -743,11 +830,11 @@ void IRCDDBApp::doUpdate(std::string& msg) IRCMessage *m2 = new IRCMessage("IDRT_REPEATER"); m2->addParam(arearp_cs); m2->addParam(zonerp_cs); - m2->addParam(getIPAddress(value)); - d->replyQ.putMessage(m2); + m2->addParam(getIPAddressFromCall(value)); + m_d->m_replyQ.putMessage(m2); } - } else if (0==tableID && d->initReady) { - std::lock_guard lockRptrMap(d->rptrMapMutex); + } else if (0==tableID && m_d->m_initReady) { + std::lock_guard lockRptrMap(m_d->m_rptrMapMutex); std::string userCallsign(key); std::string arearp_cs(value); std::string zonerp_cs; @@ -755,13 +842,31 @@ void IRCDDBApp::doUpdate(std::string& msg) CUtils::ReplaceChar(userCallsign, '_', ' '); CUtils::ReplaceChar(arearp_cs, '_', ' '); - if (1 == d->rptrMap.count(value)) { - IRCDDBAppRptrObject o = d->rptrMap[value]; - zonerp_cs = o.zonerp_cs; + std::smatch sm1; + std::string nick; + if(std::regex_search(msg, sm1, m_d->m_fromPattern)) + nick = sm1[1]; + + if (1 == m_d->m_rptrMap.count(value)) { + // CLog::logTrace("doUptate RPTR already present"); + IRCDDBAppRptrObject o = m_d->m_rptrMap[value]; + zonerp_cs = o.m_zonerp_cs; CUtils::ReplaceChar(zonerp_cs, '_', ' '); zonerp_cs.resize(7, ' '); + ip_addr = nick.empty() ? getIPAddressFromCall(zonerp_cs) : getIPAddressFromNick(nick); zonerp_cs.push_back('G'); - ip_addr = getIPAddress(o.zonerp_cs); + } + else { + // CLog::logTrace("doUptate RPTR not present"); + zonerp_cs = arearp_cs.substr(0, arearp_cs.length() - 1U); + ip_addr = nick.empty() ? getIPAddressFromCall(zonerp_cs) : getIPAddressFromNick(nick); + zonerp_cs.push_back('G'); + + if(!ip_addr.empty()) { + auto tmp = boost::replace_all_copy(zonerp_cs, " ", "_"); + IRCDDBAppRptrObject newRptr(dt, value, tmp, m_maxTime); + m_d->m_rptrMap[value] = newRptr; + } } IRCMessage *m2 = new IRCMessage("IDRT_USER"); @@ -770,7 +875,7 @@ void IRCDDBApp::doUpdate(std::string& msg) m2->addParam(zonerp_cs); m2->addParam(ip_addr); m2->addParam(tk + std::string(" ") + timeToken); - d->replyQ.putMessage(m2); + m_d->m_replyQ.putMessage(m2); } } } @@ -792,8 +897,8 @@ static std::string getTableIDString(int tableID, bool spaceBeforeNumber) void IRCDDBApp::msgQuery(IRCMessage *m) { - if (0==m->getPrefixNick().compare(0, 2, "s-") && m->numParams>=2) { // server msg - std::string msg(m->params[1]); + if (0 == m->getPrefixNick().compare(0, 2, "s-") && m->m_numParams >=2 ) { // server msg + std::string msg(m->m_params[1]); std::vector tkz = CUtils::stringTokenizer(msg); if (tkz.empty()) @@ -812,11 +917,11 @@ void IRCDDBApp::msgQuery(IRCMessage *m) } doUpdate(restOfLine); } else if (0 == cmd.compare("LIST_END")) { - if (5 == d->state) // if in sendlist processing state - d->state = 3; // get next table + if (5 == m_d->m_state) // if in sendlist processing state + m_d->m_state = 3; // get next table } else if (0 == cmd.compare("LIST_MORE")) { - if (5 == d->state) // if in sendlist processing state - d->state = 4; // send next SENDLIST + if (5 == m_d->m_state) // if in sendlist processing state + m_d->m_state = 4; // send next SENDLIST } else if (0 == cmd.compare("NOT_FOUND")) { std::string callsign; std::string restOfLine; @@ -834,20 +939,41 @@ void IRCDDBApp::msgQuery(IRCMessage *m) m2->addParam(callsign); for (int i=0; i<4; i++) m2->addParam(std::string("")); - d->replyQ.putMessage(m2); + m_d->m_replyQ.putMessage(m2); } } } + else if(m->m_params[0] == m_d->m_myNick) { + if(m->m_params.size() >= 2U && m->m_params[1] == "NATTRAVERSAL_G2") { + IRCMessage * m2 = new IRCMessage(m->m_params[1]); + m2->addParam(m->getPrefixHost()); + m_d->m_replyQ.putMessage(m2); + } + else if(m->m_params.size() >= 2U && boost::starts_with(m->m_params[1], "NATTRAVERSAL_DEXTRA")) { + IRCMessage * m2 = new IRCMessage(m->m_params[1].substr(0, (std::string("NATTRAVERSAL_DEXTRA")).length())); + m2->addParam(m->getPrefixHost()); + std::string remotePort = boost::trim_copy(boost::replace_all_copy(m->m_params[1], "NATTRAVERSAL_DEXTRA", "")); + m2->addParam(remotePort); + m_d->m_replyQ.putMessage(m2); + } + else if(m->m_params.size() >= 2U && boost::starts_with(m->m_params[1], "NATTRAVERSAL_DPLUS")) { + IRCMessage * m2 = new IRCMessage(m->m_params[1].substr(0, (std::string("NATTRAVERSAL_DPLUS")).length())); + m2->addParam(m->getPrefixHost()); + std::string remotePort = boost::trim_copy(boost::replace_all_copy(m->m_params[1], "NATTRAVERSAL_DPLUS", "")); + m2->addParam(remotePort); + m_d->m_replyQ.putMessage(m2); + } + } } void IRCDDBApp::setSendQ(IRCMessageQueue *s) { - d->sendQ = s; + m_d->m_sendQ = s; } IRCMessageQueue *IRCDDBApp::getSendQ() { - return d->sendQ; + return m_d->m_sendQ; } std::string IRCDDBApp::getLastEntryTime(int tableID) @@ -870,30 +996,30 @@ static bool needsDatabaseUpdate(int tableID) void IRCDDBApp::Entry() { int sendlistTableID = 0; - while (!d->terminateThread) { - if (d->timer > 0) - d->timer--; - switch(d->state) { + while (!m_d->m_terminateThread) { + if (m_d->m_timer > 0) + m_d->m_timer--; + switch(m_d->m_state) { case 0: // wait for network to start if (getSendQ()) - d->state = 1; + m_d->m_state = 1; break; case 1: // connect to db - d->state = 2; - d->timer = 200; + m_d->m_state = 2; + m_d->m_timer = 200; break; case 2: // choose server CLog::logInfo("IRCDDBApp: state=2 choose new 's-'-user\n"); if (NULL == getSendQ()) - d->state = 10; + m_d->m_state = 10; else { if (findServerUser()) { sendlistTableID = numberOfTables; - d->state = 3; // next: send "SENDLIST" - } else if (0 == d->timer) { - d->state = 10; + m_d->m_state = 3; // next: send "SENDLIST" + } else if (0 == m_d->m_timer) { + m_d->m_state = 10; IRCMessage *m = new IRCMessage("QUIT"); m->addParam("no op user with 's-' found."); IRCMessageQueue *q = getSendQ(); @@ -905,39 +1031,39 @@ void IRCDDBApp::Entry() case 3: if (NULL == getSendQ()) - d->state = 10; // disconnect DB + m_d->m_state = 10; // disconnect DB else { sendlistTableID--; if (sendlistTableID < 0) - d->state = 6; // end of sendlist + m_d->m_state = 6; // end of sendlist else { CLog::logInfo("IRCDDBApp: state=3 tableID=%d\n", sendlistTableID); - d->state = 4; // send "SENDLIST" - d->timer = 900; // 15 minutes max for update + m_d->m_state = 4; // send "SENDLIST" + m_d->m_timer = 900; // 15 minutes max for update } } break; case 4: if (NULL == getSendQ()) - d->state = 10; // disconnect DB + m_d->m_state = 10; // disconnect DB else { if (needsDatabaseUpdate(sendlistTableID)) { - IRCMessage *m = new IRCMessage(d->currentServer, std::string("SENDLIST") + getTableIDString(sendlistTableID, true) + std::string(" ") + getLastEntryTime(sendlistTableID)); + IRCMessage *m = new IRCMessage(m_d->m_currentServer, std::string("SENDLIST") + getTableIDString(sendlistTableID, true) + std::string(" ") + getLastEntryTime(sendlistTableID)); IRCMessageQueue *q = getSendQ(); if (q) q->putMessage(m); - d->state = 5; // wait for answers + m_d->m_state = 5; // wait for answers } else - d->state = 3; // don't send SENDLIST for this table, go to next table + m_d->m_state = 3; // don't send SENDLIST for this table, go to next table } break; case 5: // sendlist processing if (NULL == getSendQ()) - d->state = 10; // disconnect DB - else if (0 == d->timer) { - d->state = 10; // disconnect DB + m_d->m_state = 10; // disconnect DB + else if (0 == m_d->m_timer) { + m_d->m_state = 10; // disconnect DB IRCMessage *m = new IRCMessage("QUIT"); m->addParam("timeout SENDLIST"); IRCMessageQueue *q = getSendQ(); @@ -948,79 +1074,79 @@ void IRCDDBApp::Entry() case 6: if (NULL == getSendQ()) - d->state = 10; // disconnect DB + m_d->m_state = 10; // disconnect DB else { CLog::logInfo( "IRCDDBApp: state=6 initialization completed\n"); - d->infoTimer = 2; - d->initReady = true; - d->state = 7; + m_d->m_infoTimer = 2; + m_d->m_initReady = true; + m_d->m_state = 7; } break; case 7: // standby state after initialization if (NULL == getSendQ()) - d->state = 10; // disconnect DB + m_d->m_state = 10; // disconnect DB - if (d->infoTimer > 0) { - d->infoTimer--; + if (m_d->m_infoTimer > 0) { + m_d->m_infoTimer--; - if (0 == d->infoTimer) { + if (0 == m_d->m_infoTimer) { { // Scope for mutext locking - std::lock_guard lochQTHURL(d->moduleQTHURLMutex); - for (auto it = d->moduleQTH.begin(); it != d->moduleQTH.end(); ++it) { + std::lock_guard lochQTHURL(m_d->m_moduleQTHURLMutex); + for (auto it = m_d->m_moduleQTH.begin(); it != m_d->m_moduleQTH.end(); ++it) { std::string value = it->second; - IRCMessage *m = new IRCMessage(d->currentServer, std::string("IRCDDB RPTRQTH: ") + value); + IRCMessage *m = new IRCMessage(m_d->m_currentServer, std::string("IRCDDB RPTRQTH: ") + value); IRCMessageQueue *q = getSendQ(); if (q != NULL) q->putMessage(m); } - d->moduleQTH.clear(); + m_d->m_moduleQTH.clear(); - for (auto it = d->moduleURL.begin(); it != d->moduleURL.end(); ++it) { + for (auto it = m_d->m_moduleURL.begin(); it != m_d->m_moduleURL.end(); ++it) { std::string value = it->second; - IRCMessage *m = new IRCMessage(d->currentServer, std::string("IRCDDB RPTRURL: ") + value); + IRCMessage *m = new IRCMessage(m_d->m_currentServer, std::string("IRCDDB RPTRURL: ") + value); IRCMessageQueue *q = getSendQ(); if (q != NULL) q->putMessage(m); } - d->moduleURL.clear(); + m_d->m_moduleURL.clear(); } - std::lock_guard lockModuleQRG(d->moduleQRGMutex); - for (auto it = d->moduleQRG.begin(); it != d->moduleQRG.end(); ++it) { + std::lock_guard lockModuleQRG(m_d->m_moduleQRGMutex); + for (auto it = m_d->m_moduleQRG.begin(); it != m_d->m_moduleQRG.end(); ++it) { std::string value = it->second; - IRCMessage* m = new IRCMessage(d->currentServer, std::string("IRCDDB RPTRQRG: ") + value); + IRCMessage* m = new IRCMessage(m_d->m_currentServer, std::string("IRCDDB RPTRQRG: ") + value); IRCMessageQueue* q = getSendQ(); if (q != NULL) q->putMessage(m); } - d->moduleQRG.clear(); + m_d->m_moduleQRG.clear(); } } - if (d->wdTimer > 0) { - d->wdTimer--; + if (m_d->m_wdTimer > 0) { + m_d->m_wdTimer--; - if (0 == d->wdTimer) { - std::lock_guard lockModuleWD(d->moduleWDMutex); + if (0 == m_d->m_wdTimer) { + std::lock_guard lockModuleWD(m_d->m_moduleWDMutex); - for (auto it = d->moduleWD.begin(); it != d->moduleWD.end(); ++it) { + for (auto it = m_d->m_moduleWD.begin(); it != m_d->m_moduleWD.end(); ++it) { std::string value = it->second; - IRCMessage *m = new IRCMessage(d->currentServer, std::string("IRCDDB RPTRSW: ") + value); + IRCMessage *m = new IRCMessage(m_d->m_currentServer, std::string("IRCDDB RPTRSW: ") + value); IRCMessageQueue *q = getSendQ(); if (q) q->putMessage(m); } - d->moduleWD.clear(); + m_d->m_moduleWD.clear(); } } break; case 10: // disconnect db - d->state = 0; - d->timer = 0; - d->initReady = false; + m_d->m_state = 0; + m_d->m_timer = 0; + m_d->m_initReady = false; break; } std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/IRCDDBApp.h b/IRCDDB/IRCDDBApp.h similarity index 86% rename from IRCDDBApp.h rename to IRCDDB/IRCDDBApp.h index 2039a58..a53ed34 100644 --- a/IRCDDBApp.h +++ b/IRCDDB/IRCDDBApp.h @@ -67,6 +67,10 @@ public: bool findRepeater(const std::string& s); bool findGateway(const std::string& s); + bool notifyRepeaterG2NatTraversal(const std::string& repeater); + bool notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myLocalPort); + bool notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myLocalPort); + bool sendHeard(const std::string& myCall, const std::string& myCallExt, const std::string& yourCall, const std::string& rpt1, const std::string& rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string& destination, const std::string& tx_msg, const std::string& tx_stats); @@ -86,11 +90,14 @@ protected: private: void doUpdate(std::string& msg); void doNotFound(std::string& msg, std::string& retval); - std::string getIPAddress(std::string& zonerp_cs); + std::string getIPAddressFromCall(std::string& zonerp_cs); + std::string getIPAddressFromNick(std::string& ircUser); bool findServerUser(); unsigned int calculateUsn(const std::string& nick); std::string getLastEntryTime(int tableID); - IRCDDBAppPrivate *d; + bool getNickForRepeater(const std::string& repeater, std::string& user) const; + + IRCDDBAppPrivate *m_d; time_t m_maxTime; std::future m_future; }; diff --git a/IRCDDBClient.cpp b/IRCDDB/IRCDDBClient.cpp similarity index 68% rename from IRCDDBClient.cpp rename to IRCDDB/IRCDDBClient.cpp index bf5130a..644ef35 100644 --- a/IRCDDBClient.cpp +++ b/IRCDDB/IRCDDBClient.cpp @@ -28,23 +28,23 @@ along with this program. If not, see . struct CIRCDDBClientPrivate { IRCClient *client; - IRCDDBApp *app; + IRCDDBApp *m_app; }; CIRCDDBClient::CIRCDDBClient(const std::string& hostName, unsigned int port, const std::string& callsign, const std::string& password, const std::string& versionInfo, const std::string& localAddr, bool isQuadNet ) : -d(new CIRCDDBClientPrivate), +m_d(new CIRCDDBClientPrivate), m_isQuadNet(isQuadNet) { std::string update_channel("#dstar"); - d->app = new IRCDDBApp(update_channel); - d->client = new IRCClient(d->app, update_channel, hostName, port, callsign, password, versionInfo, localAddr); + m_d->m_app = new IRCDDBApp(update_channel); + m_d->client = new IRCClient(m_d->m_app, update_channel, hostName, port, callsign, password, versionInfo, localAddr); } CIRCDDBClient::~CIRCDDBClient() { - delete d->client; - delete d->app; - delete d; + delete m_d->client; + delete m_d->m_app; + delete m_d; } @@ -52,33 +52,33 @@ CIRCDDBClient::~CIRCDDBClient() bool CIRCDDBClient::open() { CLog::logInfo("start client and app\n"); - d->client->startWork(); - d->app->startWork(); + m_d->client->startWork(); + m_d->m_app->startWork(); return true; } int CIRCDDBClient::getConnectionState() { - return d->app->getConnectionState(); + return m_d->m_app->getConnectionState(); } void CIRCDDBClient::rptrQTH(const std::string& callsign, double latitude, double longitude, const std::string& desc1, const std::string& desc2, const std::string& infoURL) { - d->app->rptrQTH(callsign, latitude, longitude, desc1, desc2, infoURL); + m_d->m_app->rptrQTH(callsign, latitude, longitude, desc1, desc2, infoURL); } void CIRCDDBClient::rptrQRG(const std::string& callsign, double txFrequency, double duplexShift, double range, double agl) { - d->app->rptrQRG(callsign, txFrequency, duplexShift, range, agl); + m_d->m_app->rptrQRG(callsign, txFrequency, duplexShift, range, agl); } void CIRCDDBClient::kickWatchdog(const std::string& callsign, const std::string& wdInfo) { - d->app->kickWatchdog(callsign, wdInfo); + m_d->m_app->kickWatchdog(callsign, wdInfo); } @@ -112,7 +112,7 @@ bool CIRCDDBClient::sendHeard( const std::string& myCall, const std::string& myC return false; } - return d->app->sendHeard(myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, std::string(" "), std::string(""), std::string("")); + return m_d->m_app->sendHeard(myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, std::string(" "), std::string(""), std::string("")); } void CIRCDDBClient::sendDStarGatewayInfo(const std::string subcommand, const std::vector parms) @@ -123,7 +123,7 @@ void CIRCDDBClient::sendDStarGatewayInfo(const std::string subcommand, const std CLog::logInfo("\n"); if(m_isQuadNet) { - d->app->sendDStarGatewayInfo(subcommand, parms); + m_d->m_app->sendDStarGatewayInfo(subcommand, parms); } } @@ -175,7 +175,7 @@ bool CIRCDDBClient::sendHeardWithTXMsg(const std::string& myCall, const std::str msg.push_back('_'); } } - return d->app->sendHeard(myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, dest, msg, std::string("")); + return m_d->m_app->sendHeard(myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, dest, msg, std::string("")); } @@ -238,7 +238,7 @@ bool CIRCDDBClient::sendHeardWithTXStats( const std::string& myCall, const std:: } stats.resize(20, '_'); - return d->app->sendHeard(myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, std::string(" "), std::string(""), stats); + return m_d->m_app->sendHeard(myCall, myCallExt, yourCall, rpt1, rpt2, flag1, flag2, flag3, std::string(" "), std::string(""), stats); } // Send query for a gateway/reflector, a false return implies a network error @@ -250,7 +250,7 @@ bool CIRCDDBClient::findGateway(const std::string& gatewayCallsign) } std::string gw(gatewayCallsign); CUtils::ToUpper(gw); - return d->app->findGateway(gw); + return m_d->m_app->findGateway(gw); } @@ -262,7 +262,7 @@ bool CIRCDDBClient::findRepeater(const std::string& repeaterCallsign) } std::string rptr(repeaterCallsign); CUtils::ToUpper(rptr); - return d->app->findRepeater(rptr); + return m_d->m_app->findRepeater(rptr); } // Send query for a user, a false return implies a network error @@ -272,9 +272,25 @@ bool CIRCDDBClient::findUser(const std::string& userCallsign) CLog::logDebug("CIRCDDBClient::findUser:userCall='%s' len != 8\n", userCallsign.c_str()); return false; } + CLog::logTrace("IRC Find user %s", userCallsign.c_str()); std::string usr(userCallsign); CUtils::ToUpper(usr); - return d->app->findUser(usr); + return m_d->m_app->findUser(usr); +} + +bool CIRCDDBClient::notifyRepeaterG2NatTraversal(const std::string& repeater) +{ + return m_d->m_app->notifyRepeaterG2NatTraversal(repeater); +} + +bool CIRCDDBClient::notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myPort) +{ + return m_d->m_app->notifyRepeaterDextraNatTraversal(repeater, myPort); +} + +bool CIRCDDBClient::notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myPort) +{ + return m_d->m_app->notifyRepeaterDPlusNatTraversal(repeater, myPort); } // The following functions are for processing received messages @@ -282,21 +298,21 @@ bool CIRCDDBClient::findUser(const std::string& userCallsign) // Get the waiting message type IRCDDB_RESPONSE_TYPE CIRCDDBClient::getMessageType() { - return d->app->getReplyMessageType(); + return m_d->m_app->getReplyMessageType(); } // Get a gateway message, as a result of IDRT_REPEATER returned from getMessageType() // A false return implies a network error bool CIRCDDBClient::receiveRepeater(std::string& repeaterCallsign, std::string& gatewayCallsign, std::string& address) { - IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); + IRCDDB_RESPONSE_TYPE rt = m_d->m_app->getReplyMessageType(); if (rt != IDRT_REPEATER) { CLog::logDebug("CIRCDDBClient::receiveRepeater: unexpected response type=%d\n", rt); return false; } - IRCMessage *m = d->app->getReplyMessage(); + IRCMessage *m = m_d->m_app->getReplyMessage(); if (m == NULL) { CLog::logDebug("CIRCDDBClient::receiveRepeater: no message\n"); return false; @@ -325,14 +341,14 @@ bool CIRCDDBClient::receiveRepeater(std::string& repeaterCallsign, std::string& // A false return implies a network error bool CIRCDDBClient::receiveGateway(std::string& gatewayCallsign, std::string& address) { - IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); + IRCDDB_RESPONSE_TYPE rt = m_d->m_app->getReplyMessageType(); if (rt != IDRT_GATEWAY) { CLog::logDebug("CIRCDDBClient::receiveGateway: unexpected response type=%d\n", rt); return false; } - IRCMessage *m = d->app->getReplyMessage(); + IRCMessage *m = m_d->m_app->getReplyMessage(); if (m == NULL) { CLog::logDebug("CIRCDDBClient::receiveGateway: no message\n"); @@ -367,14 +383,14 @@ bool CIRCDDBClient::receiveUser(std::string& userCallsign, std::string& repeater bool CIRCDDBClient::receiveUser(std::string& userCallsign, std::string& repeaterCallsign, std::string& gatewayCallsign, std::string& address, std::string& timeStamp) { - IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); + IRCDDB_RESPONSE_TYPE rt = m_d->m_app->getReplyMessageType(); if (rt != IDRT_USER) { CLog::logDebug("CIRCDDBClient::receiveUser: unexpected response type=%d\n", rt); return false; } - IRCMessage * m = d->app->getReplyMessage(); + IRCMessage * m = m_d->m_app->getReplyMessage(); if (m == NULL) { CLog::logDebug("CIRCDDBClient::receiveUser: no message\n"); @@ -398,13 +414,119 @@ bool CIRCDDBClient::receiveUser(std::string& userCallsign, std::string& repeater gatewayCallsign = m->getParam(2); address = m->getParam(3); timeStamp = m->getParam(4); + + //CLog::logTrace("IRC Receive User %s %s %s %s %s", userCallsign.c_str(), repeaterCallsign.c_str(), gatewayCallsign.c_str(), address.c_str(), timeStamp.c_str()); + delete m; return true; } -void CIRCDDBClient::close() // Implictely kills any threads in the IRC code +bool CIRCDDBClient::receiveNATTraversalG2(std::string& address) { - d->client -> stopWork(); - d->app -> stopWork(); + IRCDDB_RESPONSE_TYPE rt = m_d->m_app->getReplyMessageType(); + + if(rt != IDRT_NATTRAVERSAL_G2) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalG2: unexpected response type=%d\n", rt); + return false; + } + + IRCMessage * m = m_d->m_app->getReplyMessage(); + + if (m == NULL) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalG2: no message\n"); + return false; + } + + if (m->getCommand().compare("NATTRAVERSAL_G2")) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalG2: wrong message type, expected 'NATTRAVERSAL_G2', got '%s'\n", m->getCommand().c_str()); + delete m; + return false; + } + + if (1 != m->getParamCount()) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalG2: unexpected number of message parameters, expected 1, got %d\n", m->getParamCount()); + delete m; + return false; + } + + address = m->m_params[0]; + delete m; + + return true; } +bool CIRCDDBClient::receiveNATTraversalDextra(std::string& address, std::string& remotePort) +{ + IRCDDB_RESPONSE_TYPE rt = m_d->m_app->getReplyMessageType(); + + if(rt != IDRT_NATTRAVERSAL_DEXTRA) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDextra: unexpected response type=%d\n", rt); + return false; + } + + IRCMessage * m = m_d->m_app->getReplyMessage(); + + if (m == NULL) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDextra: no message\n"); + return false; + } + + if (m->getCommand().compare("NATTRAVERSAL_DEXTRA")) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDextra: wrong message type, expected 'NATTRAVERSAL_DEXTRA', got '%s'\n", m->getCommand().c_str()); + delete m; + return false; + } + + if (2 != m->getParamCount()) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDextra: unexpected number of message parameters, expected 2, got %d\n", m->getParamCount()); + delete m; + return false; + } + + address = m->m_params[0]; + remotePort = m->m_params[1]; + delete m; + + return true; +} + +bool CIRCDDBClient::receiveNATTraversalDPlus(std::string& address, std::string& remotePort) +{ + IRCDDB_RESPONSE_TYPE rt = m_d->m_app->getReplyMessageType(); + + if(rt != IDRT_NATTRAVERSAL_DPLUS) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDPlus: unexpected response type=%d\n", rt); + return false; + } + + IRCMessage * m = m_d->m_app->getReplyMessage(); + + if (m == NULL) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDPlus: no message\n"); + return false; + } + + if (m->getCommand().compare("NATTRAVERSAL_DPLUS")) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDPlus: wrong message type, expected 'NATTRAVERSAL_DPLUS', got '%s'\n", m->getCommand().c_str()); + delete m; + return false; + } + + if (2 != m->getParamCount()) { + CLog::logDebug("CIRCDDBClient::receiveNATTraversalDPlus: unexpected number of message parameters, expected 2, got %d\n", m->getParamCount()); + delete m; + return false; + } + + address = m->m_params[0]; + remotePort = m->m_params[1]; + delete m; + + return true; +} + +void CIRCDDBClient::close() // Implictely kills any threads in the IRC code +{ + m_d->client -> stopWork(); + m_d->m_app -> stopWork(); +} diff --git a/IRCDDBClient.h b/IRCDDB/IRCDDBClient.h similarity index 92% rename from IRCDDBClient.h rename to IRCDDB/IRCDDBClient.h index a41cfab..4a0f8d2 100644 --- a/IRCDDBClient.h +++ b/IRCDDB/IRCDDBClient.h @@ -106,6 +106,11 @@ public: // Send query for a user, a false return implies a network error bool findUser(const std::string& userCallsign); + // notify another repeater for NAT Traversal, a false return implies a network error + bool notifyRepeaterG2NatTraversal(const std::string& repeater); + bool notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myport); + bool notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myport); + // Support for the Smart Group Server void sendDStarGatewayInfo(const std::string subcommand, const std::vector parms); @@ -128,10 +133,14 @@ public: bool receiveUser(std::string& userCallsign, std::string& repeaterCallsign, std::string& gatewayCallsign, std::string& address, std::string& timeStamp); + bool receiveNATTraversalG2(std::string& address); + bool receiveNATTraversalDextra(std::string& address, std::string& remotePort); + bool receiveNATTraversalDPlus(std::string& address, std::string& remotePort); + void close(); // Implictely kills any threads in the IRC code private: - struct CIRCDDBClientPrivate * const d; + struct CIRCDDBClientPrivate * const m_d; bool m_isQuadNet; }; diff --git a/IRCDDBMultiClient.cpp b/IRCDDB/IRCDDBMultiClient.cpp similarity index 78% rename from IRCDDBMultiClient.cpp rename to IRCDDB/IRCDDBMultiClient.cpp index 7d76424..bd8378e 100644 --- a/IRCDDBMultiClient.cpp +++ b/IRCDDB/IRCDDBMultiClient.cpp @@ -150,7 +150,7 @@ void CIRCDDBMultiClient::sendDStarGatewayInfo(const std::string subcommand, cons bool CIRCDDBMultiClient::findGateway(const std::string & gatewayCallsign) { - pushQuery(IDRT_GATEWAY, gatewayCallsign, new CIRCDDBMultiClientQuery("", "", gatewayCallsign, "", "", IDRT_GATEWAY)); + pushQuery(IDRT_GATEWAY, gatewayCallsign, new CIRCDDBMultiClientQuery("", "", gatewayCallsign, "", "", "", IDRT_GATEWAY)); bool result = true; for (unsigned int i = 0; i < m_clients.size(); i++) { result = m_clients[i]->findGateway(gatewayCallsign) && result; @@ -161,7 +161,7 @@ bool CIRCDDBMultiClient::findGateway(const std::string & gatewayCallsign) bool CIRCDDBMultiClient::findRepeater(const std::string & repeaterCallsign) { - pushQuery(IDRT_REPEATER, repeaterCallsign, new CIRCDDBMultiClientQuery("", repeaterCallsign, "", "", "", IDRT_REPEATER)); + pushQuery(IDRT_REPEATER, repeaterCallsign, new CIRCDDBMultiClientQuery("", repeaterCallsign, "", "", "", "", IDRT_REPEATER)); bool result = true; for (unsigned int i = 0; i < m_clients.size(); i++) { result = m_clients[i]->findRepeater(repeaterCallsign) && result; @@ -172,7 +172,7 @@ bool CIRCDDBMultiClient::findRepeater(const std::string & repeaterCallsign) bool CIRCDDBMultiClient::findUser(const std::string & userCallsign) { - pushQuery(IDRT_USER, userCallsign, new CIRCDDBMultiClientQuery(userCallsign, "", "", "", "", IDRT_USER)); + pushQuery(IDRT_USER, userCallsign, new CIRCDDBMultiClientQuery(userCallsign, "", "", "", "", "", IDRT_USER)); bool result = true; for (unsigned int i = 0; i < m_clients.size(); i++) { result = m_clients[i]->findUser(userCallsign) && result; @@ -181,11 +181,79 @@ bool CIRCDDBMultiClient::findUser(const std::string & userCallsign) return result; } +bool CIRCDDBMultiClient::notifyRepeaterG2NatTraversal(const std::string& repeater) +{ + // NAT traversal message does not expect a response over IRC + bool result = true; + for (unsigned int i = 0; i < m_clients.size(); i++) { + result = m_clients[i]->notifyRepeaterG2NatTraversal(repeater) && result; + } + + return result; +} + +bool CIRCDDBMultiClient::notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myPort) +{ + // NAT traversal message does not expect a response over IRC + bool result = true; + for (unsigned int i = 0; i < m_clients.size(); i++) { + result = m_clients[i]->notifyRepeaterDextraNatTraversal(repeater, myPort) && result; + } + + return result; +} + +bool CIRCDDBMultiClient::notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myPort) +{ + // NAT traversal message does not expect a response over IRC + bool result = true; + for (unsigned int i = 0; i < m_clients.size(); i++) { + result = m_clients[i]->notifyRepeaterDPlusNatTraversal(repeater, myPort) && result; + } + + return result; +} + +bool CIRCDDBMultiClient::receiveNATTraversalG2(std::string& address) +{ + CIRCDDBMultiClientQuery * item = checkAndGetNextResponse(IDRT_NATTRAVERSAL_G2, "CIRCDDBMultiClient::receiveNATTraversalG2: unexpected response type"); + if (item == NULL) + return false; + + address = item->getAddress(); + + return true; +} + +bool CIRCDDBMultiClient::receiveNATTraversalDextra(std::string& address, std::string& remotePort) +{ + CIRCDDBMultiClientQuery * item = checkAndGetNextResponse(IDRT_NATTRAVERSAL_DEXTRA, "CIRCDDBMultiClient::receiveNATTraversalDextra: unexpected response type"); + if (item == NULL) + return false; + + address = item->getAddress(); + remotePort = item->getRemotePort(); + + return true; +} + +bool CIRCDDBMultiClient::receiveNATTraversalDPlus(std::string& address, std::string& remotePort) +{ + CIRCDDBMultiClientQuery * item = checkAndGetNextResponse(IDRT_NATTRAVERSAL_DPLUS, "CIRCDDBMultiClient::receiveNATTraversalDextra: unexpected response type"); + if (item == NULL) + return false; + + address = item->getAddress(); + remotePort = item->getRemotePort(); + + return true; +} + IRCDDB_RESPONSE_TYPE CIRCDDBMultiClient::getMessageType() { - //procees the inner clients at each call + //process the inner clients at each call for (unsigned int i = 0; i < m_clients.size(); i++) { - std::string user = "", repeater = "", gateway = "", address = "", timestamp = "", key = ""; + std::string user = "", repeater = "", gateway = "", address = "", timestamp = "", key = "", port =""; IRCDDB_RESPONSE_TYPE type = m_clients[i]->getMessageType(); @@ -208,6 +276,24 @@ IRCDDB_RESPONSE_TYPE CIRCDDBMultiClient::getMessageType() key = repeater; break; } + case IDRT_NATTRAVERSAL_G2: { + if (!m_clients[i]->receiveNATTraversalG2(address)) + type = IDRT_NATTRAVERSAL_G2; + key = "NAT_TRAVERSAL_G2"; + break; + } + case IDRT_NATTRAVERSAL_DEXTRA: { + if (!m_clients[i]->receiveNATTraversalDextra(address, port)) + type = IDRT_NATTRAVERSAL_DEXTRA; + key = "NAT_TRAVERSAL_DEXTRA"; + break; + } + case IDRT_NATTRAVERSAL_DPLUS: { + if (!m_clients[i]->receiveNATTraversalDPlus(address, port)) + type = IDRT_NATTRAVERSAL_DEXTRA; + key = "NAT_TRAVERSAL_DPLUS"; + break; + } case IDRT_NONE: { default: break; @@ -223,12 +309,12 @@ IRCDDB_RESPONSE_TYPE CIRCDDBMultiClient::getMessageType() CIRCDDBMultiClientQuery * item = popQuery(type, key); if (item != NULL) {//is this a response to a query we've sent ? - item->Update(user, repeater, gateway, address, timestamp);//update item (if needed) + item->Update(user, repeater, gateway, address, timestamp, port);//update item (if needed) canAddToQueue = (item->incrementResponseCount() >= m_clients.size()); //did all the clients respond or did we have an answer ? wasQuery = true; } else { - item = new CIRCDDBMultiClientQuery(user, repeater, gateway, address, timestamp, type); + item = new CIRCDDBMultiClientQuery(user, repeater, gateway, address, timestamp, port, type); canAddToQueue = true; } @@ -244,7 +330,7 @@ IRCDDB_RESPONSE_TYPE CIRCDDBMultiClient::getMessageType() } } - IRCDDB_RESPONSE_TYPE result = IDRT_NONE; + IRCDDB_RESPONSE_TYPE result = IDRT_NONE; m_responseQueueLock.lock(); if (m_responseQueue.size() != 0) result = m_responseQueue[0]->getType(); @@ -370,5 +456,3 @@ CIRCDDBMultiClientQuery_HashMap * CIRCDDBMultiClient::getQueriesHashMap(IRCDDB_R return NULL; } } - - diff --git a/IRCDDBMultiClient.h b/IRCDDB/IRCDDBMultiClient.h similarity index 89% rename from IRCDDBMultiClient.h rename to IRCDDB/IRCDDBMultiClient.h index 172b529..2b47a5f 100644 --- a/IRCDDBMultiClient.h +++ b/IRCDDB/IRCDDBMultiClient.h @@ -40,12 +40,14 @@ public: const std::string& gateway, const std::string& address, const std::string& timestamp, + const std::string& remotePort, IRCDDB_RESPONSE_TYPE type) : m_user(user), m_repeater(repeater), m_gateway(gateway), m_address(address), m_timestamp(timestamp), + m_remotePort(remotePort), m_type(type), m_responseCount(0) { @@ -77,6 +79,11 @@ public: return m_timestamp; } + std::string getRemotePort() const + { + return m_remotePort; + } + unsigned int getResponseCount() { return m_responseCount; @@ -92,7 +99,7 @@ public: /* Updates the entry, but only if the timestamp is newer. if an address was already specified it is kept. */ - void Update(const std::string& user, const std::string& repeater, const std::string& gateway, const std::string& address, const std::string& timestamp) + void Update(const std::string& user, const std::string& repeater, const std::string& gateway, const std::string& address, const std::string& timestamp, const std::string& remotePort) { //wxLogMessage("Before : %s"), toString()); if (timestamp.empty() || timestamp.compare(m_timestamp) >= 0) { @@ -100,6 +107,7 @@ public: m_repeater = repeater; m_gateway = gateway; m_timestamp = timestamp; + m_remotePort = remotePort; if(m_address.empty() && !address.empty()) m_address = address; @@ -125,6 +133,7 @@ private: std::string m_gateway; std::string m_address; std::string m_timestamp; + std::string m_remotePort; IRCDDB_RESPONSE_TYPE m_type; unsigned int m_responseCount; }; @@ -150,11 +159,17 @@ public: virtual bool findGateway(const std::string & gatewayCallsign); virtual bool findRepeater(const std::string & repeaterCallsign); virtual bool findUser(const std::string & userCallsign); + virtual bool notifyRepeaterG2NatTraversal(const std::string& repeater); + virtual bool notifyRepeaterDextraNatTraversal(const std::string& repeater, unsigned int myPort); + virtual bool notifyRepeaterDPlusNatTraversal(const std::string& repeater, unsigned int myPort); virtual IRCDDB_RESPONSE_TYPE getMessageType(); virtual bool receiveRepeater(std::string & repeaterCallsign, std::string & gatewayCallsign, std::string & address); virtual bool receiveGateway(std::string & gatewayCallsign, std::string & address); virtual bool receiveUser(std::string & userCallsign, std::string & repeaterCallsign, std::string & gatewayCallsign, std::string & address); virtual bool receiveUser(std::string & userCallsign, std::string & repeaterCallsign, std::string & gatewayCallsign, std::string & address, std::string & timeStamp); + virtual bool receiveNATTraversalG2(std::string& address); + virtual bool receiveNATTraversalDextra(std::string& address, std::string& remotePort); + virtual bool receiveNATTraversalDPlus(std::string& address, std::string& remotePort); virtual void sendDStarGatewayInfo(const std::string subcommand, const std::vector parms); virtual void close(); diff --git a/IRCMessage.cpp b/IRCDDB/IRCMessage.cpp similarity index 57% rename from IRCMessage.cpp rename to IRCDDB/IRCMessage.cpp index 8f6fb08..b881470 100644 --- a/IRCMessage.cpp +++ b/IRCDDB/IRCMessage.cpp @@ -24,24 +24,24 @@ along with this program. If not, see . IRCMessage::IRCMessage() { - numParams = 0; - prefixParsed = false; + m_numParams = 0; + m_prefixParsed = false; } IRCMessage::IRCMessage(const std::string& toNick, const std::string& msg) { - command.assign("PRIVMSG"); - numParams = 2; - params.push_back(toNick); - params.push_back(msg); - prefixParsed = false; + m_command.assign("PRIVMSG"); + m_numParams = 2; + m_params.push_back(toNick); + m_params.push_back(msg); + m_prefixParsed = false; } IRCMessage::IRCMessage(const std::string& cmd) { - command = cmd; - numParams = 0; - prefixParsed = false; + m_command = cmd; + m_numParams = 0; + m_prefixParsed = false; } IRCMessage::~IRCMessage() @@ -51,79 +51,79 @@ IRCMessage::~IRCMessage() void IRCMessage::addParam(const std::string& p) { - params.push_back(p); - numParams = params.size(); + m_params.push_back(p); + m_numParams = m_params.size(); } int IRCMessage::getParamCount() { - return params.size(); + return m_params.size(); } std::string IRCMessage::getParam(int pos) { - return params[pos]; + return m_params[pos]; } std::string IRCMessage::getCommand() { - return command; + return m_command; } bool IRCMessage::parsePrefix() { - std::string::size_type p1 = prefix.find('!'); + std::string::size_type p1 = m_prefix.find('!'); if (std::string::npos == p1) return false; - std::string::size_type p2 = prefix.find('@'); + std::string::size_type p2 = m_prefix.find('@'); if (std::string::npos == p2) return false; - prefixComponents.push_back(prefix.substr(0, p1)); - prefixComponents.push_back(prefix.substr(p1+1, p2-p1-1)); - prefixComponents.push_back(prefix.substr(p2 + 1)); + m_prefixComponents.push_back(m_prefix.substr(0, p1)); + m_prefixComponents.push_back(m_prefix.substr(p1+1, p2-p1-1)); + m_prefixComponents.push_back(m_prefix.substr(p2 + 1)); return true; } std::string& IRCMessage::getPrefixNick() { - if (!prefixParsed) - prefixParsed = parsePrefix(); + if (!m_prefixParsed) + m_prefixParsed = parsePrefix(); - return prefixParsed ? prefixComponents[0] : prefix; + return m_prefixParsed ? m_prefixComponents[0] : m_prefix; } std::string& IRCMessage::getPrefixName() { - if (!prefixParsed) - prefixParsed = parsePrefix(); + if (!m_prefixParsed) + m_prefixParsed = parsePrefix(); - return prefixParsed ? prefixComponents[1] : prefix; + return m_prefixParsed ? m_prefixComponents[1] : m_prefix; } std::string& IRCMessage::getPrefixHost() { - if (!prefixParsed) - prefixParsed = parsePrefix(); + if (!m_prefixParsed) + m_prefixParsed = parsePrefix(); - return prefixParsed ? prefixComponents[2] : prefix; + return m_prefixParsed ? m_prefixComponents[2] : m_prefix; } void IRCMessage::composeMessage(std::string& output) { std::string o; - if (prefix.size() > 0) - o = std::string(":") + prefix + std::string(" "); + if (m_prefix.size() > 0) + o = std::string(":") + m_prefix + std::string(" "); - o.append(command); + o.append(m_command); - for (int i=0; i < numParams; i++) { - if (i == (numParams - 1)) - o.append(std::string(" :") + params[i]); + for (int i=0; i < m_numParams; i++) { + if (i == (m_numParams - 1)) + o.append(std::string(" :") + m_params[i]); else - o.append(std::string(" ") + params[i]); + o.append(std::string(" ") + m_params[i]); } o.append(std::string("\r\n")); diff --git a/IRCMessage.h b/IRCDDB/IRCMessage.h similarity index 88% rename from IRCMessage.h rename to IRCDDB/IRCMessage.h index e157405..35c579e 100644 --- a/IRCMessage.h +++ b/IRCDDB/IRCMessage.h @@ -32,11 +32,11 @@ public: IRCMessage(const std::string& command); ~IRCMessage(); - std::string prefix; - std::string command; - std::vector params; + std::string m_prefix; + std::string m_command; + std::vector m_params; - int numParams; + int m_numParams; std::string& getPrefixNick(); std::string& getPrefixName(); std::string& getPrefixHost(); @@ -49,6 +49,6 @@ public: private: bool parsePrefix(); - std::vector prefixComponents; - bool prefixParsed; + std::vector m_prefixComponents; + bool m_prefixParsed; }; diff --git a/IRCMessageQueue.cpp b/IRCDDB/IRCMessageQueue.cpp similarity index 86% rename from IRCMessageQueue.cpp rename to IRCDDB/IRCMessageQueue.cpp index 035e903..eea7d09 100644 --- a/IRCMessageQueue.cpp +++ b/IRCDDB/IRCMessageQueue.cpp @@ -33,7 +33,7 @@ IRCMessageQueue::IRCMessageQueue() IRCMessageQueue::~IRCMessageQueue() { - std::lock_guard lockAccessQueue(accessMutex); + std::lock_guard lockAccessQueue(m_accessMutex); while (! m_queue.empty()) { delete m_queue.front(); m_queue.pop(); @@ -52,7 +52,7 @@ void IRCMessageQueue::signalEOF() bool IRCMessageQueue::messageAvailable() { - std::lock_guard lockAccessQueue(accessMutex); + std::lock_guard lockAccessQueue(m_accessMutex); bool retv = ! m_queue.empty(); return retv; @@ -60,14 +60,14 @@ bool IRCMessageQueue::messageAvailable() IRCMessage *IRCMessageQueue::peekFirst() { - std::lock_guard lockAccessQueue(accessMutex); + std::lock_guard lockAccessQueue(m_accessMutex); IRCMessage *msg = m_queue.empty() ? NULL : m_queue.front(); return msg; } IRCMessage *IRCMessageQueue::getMessage() { - std::lock_guard lockAccessQueue(accessMutex); + std::lock_guard lockAccessQueue(m_accessMutex); IRCMessage *msg = m_queue.empty() ? NULL : m_queue.front(); if (msg) m_queue.pop(); @@ -77,7 +77,7 @@ IRCMessage *IRCMessageQueue::getMessage() void IRCMessageQueue::putMessage(IRCMessage *m) { - std::lock_guard lockAccessQueue(accessMutex); + std::lock_guard lockAccessQueue(m_accessMutex); m_queue.push(m); } diff --git a/IRCMessageQueue.h b/IRCDDB/IRCMessageQueue.h similarity index 97% rename from IRCMessageQueue.h rename to IRCDDB/IRCMessageQueue.h index 3ab656b..4a97caf 100644 --- a/IRCMessageQueue.h +++ b/IRCDDB/IRCMessageQueue.h @@ -46,7 +46,7 @@ public: private: bool m_eof; - std::mutex accessMutex; + std::mutex m_accessMutex; std::queue m_queue; }; diff --git a/IRCProtocol.cpp b/IRCDDB/IRCProtocol.cpp similarity index 63% rename from IRCProtocol.cpp rename to IRCDDB/IRCProtocol.cpp index 0c43b50..c23ced8 100644 --- a/IRCProtocol.cpp +++ b/IRCDDB/IRCProtocol.cpp @@ -92,26 +92,26 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) while (recvQ->messageAvailable()) { IRCMessage *m = recvQ->getMessage(); - if (0 == m->command.compare("004")) { + if (0 == m->m_command.compare("004")) { if (4 == m_state) { - if (m->params.size() > 1) { + if (m->m_params.size() > 1) { std::regex serverNamePattern("^grp[1-9]s[1-9].ircDDB$"); - if (std::regex_match(m->params[1], serverNamePattern)) - m_app->setBestServer(std::string("s-") + m->params[1].substr(0,6)); + if (std::regex_match(m->m_params[1], serverNamePattern)) + m_app->setBestServer(std::string("s-") + m->m_params[1].substr(0,6)); } m_state = 5; // next: JOIN m_app->setCurrentNick(m_currentNick); } - } else if (0 == m->command.compare("PING")) { + } else if (0 == m->m_command.compare("PING")) { IRCMessage *m2 = new IRCMessage(); - m2->command = std::string("PONG"); - if (m->params.size() > 0) { - m2->numParams = 1; - m2->params.push_back(m->params[0]); + m2->m_command = std::string("PONG"); + if (m->m_params.size() > 0) { + m2->m_numParams = 1; + m2->m_params.push_back(m->m_params[0]); } sendQ -> putMessage(m2); - } else if (0 == m->command.compare("JOIN")) { - if (m->numParams>=1 && 0==m->params[0].compare(m_channel)) { + } else if (0 == m->m_command.compare("JOIN")) { + if (m->m_numParams>=1 && 0==m->m_params[0].compare(m_channel)) { if (0==m->getPrefixNick().compare(m_currentNick) && 6==m_state) { if (m_debugChannel.size()) m_state = 7; // next: join debug_channel @@ -121,69 +121,69 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) m_app->userJoin(m->getPrefixNick(), m->getPrefixName(), m->getPrefixHost()); } - if (m->numParams>=1 && 0==m->params[0].compare(m_debugChannel)) { + if (m->m_numParams>=1 && 0==m->m_params[0].compare(m_debugChannel)) { if (0==m->getPrefixNick().compare(m_currentNick) && 8==m_state) m_state = 10; // next: WHO * } - } else if (0 == m->command.compare("PONG")) { + } else if (0 == m->m_command.compare("PONG")) { if (12 == m_state) { m_timer = m_pingTimer; m_state = 11; } - } else if (0 == m->command.compare("PART")) { - if (m->numParams>=1 && 0==m->params[0].compare(m_channel)) { + } else if (0 == m->m_command.compare("PART")) { + if (m->m_numParams>=1 && 0==m->m_params[0].compare(m_channel)) { if (m_app != NULL) m_app->userLeave(m->getPrefixNick()); } - } else if (0 == m->command.compare("KICK")) { - if (m->numParams>=2 && 0==m->params[0].compare(m_channel)) { - if (0 == m->params[1].compare(m_currentNick)) { + } else if (0 == m->m_command.compare("KICK")) { + if (m->m_numParams>=2 && 0==m->m_params[0].compare(m_channel)) { + if (0 == m->m_params[1].compare(m_currentNick)) { // i was kicked!! delete m; return false; } else if (m_app) - m_app->userLeave(m->params[1]); + m_app->userLeave(m->m_params[1]); } - } else if (0 == m->command.compare("QUIT")) { + } else if (0 == m->m_command.compare("QUIT")) { if (m_app) m_app->userLeave(m->getPrefixNick()); - } else if (0 == m->command.compare("MODE")) { - if (m->numParams>=3 && 0==m->params[0].compare(m_channel)) { + } else if (0 == m->m_command.compare("MODE")) { + if (m->m_numParams>=3 && 0==m->m_params[0].compare(m_channel)) { if (m_app) { - std::string mode = m->params[1]; + std::string mode = m->m_params[1]; - for (size_t i=1; inumParams>=i+2; i++) { + for (size_t i=1; im_numParams>=i+2; i++) { if ('o' == mode[i]) { if ('+' == mode[0]) - m_app->userChanOp(m->params[i+1], true); + m_app->userChanOp(m->m_params[i+1], true); else if ('-' == mode[0]) - m_app->userChanOp(m->params[i+1], false); + m_app->userChanOp(m->m_params[i+1], false); } } // for } } - } else if (0 == m->command.compare("PRIVMSG")) { - if (m->numParams==2 && m_app) { - if (0 == m->params[0].compare(m_channel) && m_app) + } else if (0 == m->m_command.compare("PRIVMSG")) { + if (m->m_numParams==2 && m_app) { + if (0 == m->m_params[0].compare(m_channel) && m_app) m_app->msgChannel(m); - else if (0 == m->params[0].compare(m_currentNick) && m_app) + else if (0 == m->m_params[0].compare(m_currentNick) && m_app) m_app->msgQuery(m); } - } else if (0 == m->command.compare("352")) { // WHO list - if (m->numParams>=7 && 0==m->params[0].compare(m_currentNick) && 0==m->params[1].compare(m_channel)) { + } else if (0 == m->m_command.compare("352")) { // WHO list + if (m->m_numParams>=7 && 0==m->m_params[0].compare(m_currentNick) && 0==m->m_params[1].compare(m_channel)) { if (m_app) { - m_app->userJoin(m->params[5], m->params[2], m->params[3]); - m_app->userChanOp(m->params[5], 0==m->params[6].compare("H@")); + m_app->userJoin(m->m_params[5], m->m_params[2], m->m_params[3]); + m_app->userChanOp(m->m_params[5], 0==m->m_params[6].compare("H@")); } } - } else if (0 == m->command.compare("433")) { // nick collision + } else if (0 == m->m_command.compare("433")) { // nick collision if (2 == m_state) { m_state = 3; // nick collision, choose new nick m_timer = 10; // wait 5 seconds.. } - } else if (0==m->command.compare("332") || 0==m->command.compare("TOPIC")) { // topic - if (2==m->numParams && m_app && 0==m->params[0].compare(m_channel)) - m_app->setTopic(m->params[1]); + } else if (0==m->m_command.compare("332") || 0==m->m_command.compare("TOPIC")) { // topic + if (2==m->m_numParams && m_app && 0==m->m_params[0].compare(m_channel)) + m_app->setTopic(m->m_params[1]); } delete m; @@ -193,15 +193,15 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) switch (m_state) { case 1: m = new IRCMessage(); - m->command = std::string("PASS"); - m->numParams = 1; - m->params.push_back(m_password); + m->m_command = std::string("PASS"); + m->m_numParams = 1; + m->m_params.push_back(m_password); sendQ->putMessage(m); m = new IRCMessage(); - m->command = std::string("NICK"); - m->numParams = 1; - m->params.push_back(m_currentNick); + m->m_command = std::string("NICK"); + m->m_numParams = 1; + m->m_params.push_back(m_currentNick); sendQ->putMessage(m); m_timer = 10; // wait for possible nick collision message @@ -211,12 +211,12 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) case 2: if (0 == m_timer) { m = new IRCMessage(); - m->command = std::string("USER"); - m->numParams = 4; - m->params.push_back(m_name); - m->params.push_back(std::string("0")); - m->params.push_back(std::string("*")); - m->params.push_back(m_versionInfo); + m->m_command = std::string("USER"); + m->m_numParams = 4; + m->m_params.push_back(m_name); + m->m_params.push_back(std::string("0")); + m->m_params.push_back(std::string("*")); + m->m_params.push_back(m_versionInfo); sendQ->putMessage(m); m_timer = 30; @@ -228,9 +228,9 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) if (0 == m_timer) { chooseNewNick(); m = new IRCMessage(); - m->command = std::string("NICK"); - m->numParams = 1; - m->params.push_back(m_currentNick); + m->m_command = std::string("NICK"); + m->m_numParams = 1; + m->m_params.push_back(m_currentNick); sendQ->putMessage(m); m_timer = 10; // wait for possible nick collision message @@ -245,9 +245,9 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) case 5: m = new IRCMessage(); - m->command = std::string("JOIN"); - m->numParams = 1; - m->params.push_back(m_channel); + m->m_command = std::string("JOIN"); + m->m_numParams = 1; + m->m_params.push_back(m_channel); sendQ->putMessage(m); m_timer = 30; @@ -264,9 +264,9 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) return false; // this state cannot be processed if there is no debug_channel m = new IRCMessage(); - m->command = std::string("JOIN"); - m->numParams = 1; - m->params.push_back(m_debugChannel); + m->m_command = std::string("JOIN"); + m->m_numParams = 1; + m->m_params.push_back(m_debugChannel); sendQ->putMessage(m); m_timer = 30; @@ -280,10 +280,10 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) case 10: m = new IRCMessage(); - m->command = std::string("WHO"); - m->numParams = 2; - m->params.push_back(m_channel); - m->params.push_back(std::string("*")); + m->m_command = std::string("WHO"); + m->m_numParams = 2; + m->m_params.push_back(m_channel); + m->m_params.push_back(std::string("*")); sendQ->putMessage(m); m_timer = m_pingTimer; @@ -296,9 +296,9 @@ bool IRCProtocol::processQueues(IRCMessageQueue *recvQ, IRCMessageQueue *sendQ) case 11: if (0 == m_timer) { m = new IRCMessage(); - m->command = std::string("PING"); - m->numParams = 1; - m->params.push_back(m_currentNick); + m->m_command = std::string("PING"); + m->m_numParams = 1; + m->m_params.push_back(m_currentNick); sendQ->putMessage(m); m_timer = m_pingTimer; diff --git a/IRCProtocol.h b/IRCDDB/IRCProtocol.h similarity index 100% rename from IRCProtocol.h rename to IRCDDB/IRCProtocol.h diff --git a/IRCReceiver.cpp b/IRCDDB/IRCReceiver.cpp similarity index 85% rename from IRCReceiver.cpp rename to IRCDDB/IRCReceiver.cpp index b23128b..24620a0 100644 --- a/IRCReceiver.cpp +++ b/IRCDDB/IRCReceiver.cpp @@ -114,43 +114,43 @@ void IRCReceiver::Entry() switch (state) { case 0: // command if (b == ':') - state = 1; // prefix + state = 1; // m_prefix else if (b != ' ') { - m->command.push_back(b); + m->m_command.push_back(b); state = 2; // command } break; - case 1: // prefix + case 1: // m_prefix if (b == ' ') state = 2; // command is next else - m->prefix.push_back(b); + m->m_prefix.push_back(b); break; case 2: if (b == ' ') { state = 3; // params are next - m->numParams = 1; - m->params.push_back(std::string("")); + m->m_numParams = 1; + m->m_params.push_back(std::string("")); } else - m->command.push_back(b); + m->m_command.push_back(b); break; case 3: if (b == ' ') { - m->numParams++; - if (m->numParams >= 15) + m->m_numParams++; + if (m->m_numParams >= 15) state = 5; // ignore the rest - m->params.push_back(std::string("")); - } else if (b==':' && m->params[m->numParams-1].size()==0) + m->m_params.push_back(std::string("")); + } else if (b==':' && m->m_params[m->m_numParams-1].size()==0) state = 4; // rest of line is this param else - m->params[m->numParams-1].push_back(b); + m->m_params[m->m_numParams-1].push_back(b); break; case 4: - m->params[m->numParams-1].push_back(b); + m->m_params[m->m_numParams-1].push_back(b); break; } // switch } diff --git a/IRCReceiver.h b/IRCDDB/IRCReceiver.h similarity index 100% rename from IRCReceiver.h rename to IRCDDB/IRCReceiver.h diff --git a/IRCDDB/Makefile b/IRCDDB/Makefile new file mode 100644 index 0000000..d84f384 --- /dev/null +++ b/IRCDDB/Makefile @@ -0,0 +1,16 @@ +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +IRCDDB.a: $(OBJS) ../VersionInfo/GitVersion.h ../BaseCommon/BaseCommon.a + $(AR) rcs IRCDDB.a $(OBJS) + +%.o : %.cpp + $(CC) -I../BaseCommon -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ + +.PHONY clean: +clean: + $(RM) *.o *.d IRCDDB.a + +../BaseCommon/BaseCommon.a: +../VersionInfo/GitVersion.h: diff --git a/LogFileTarget.cpp b/LogFileTarget.cpp deleted file mode 100644 index a44dd35..0000000 --- a/LogFileTarget.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 -#include -#include - -#include "LogFileTarget.h" - -CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) : -CLogTarget(logLevel), -m_dir(dir), -m_rotate(rotate) -{ - -} - -void CLogFileTarget::printLogInt(const std::string& msg) -{ - // construct filename - std::string fileName(m_dir); - if(fileName[fileName.length() - 1U] != '/') fileName.push_back('/'); - fileName.append("dstargateway"); - - if(m_rotate) { - std::time_t now = std::time(0); - std::tm* now_tm = std::gmtime(&now); - char buf[64]; - std::strftime(buf, 42, "-%Y-%m-%d", now_tm); - fileName.append(std::string(buf)); - } - fileName.append(".log"); - - std::ofstream file; - file.open(fileName, std::ios::app); - if(file.is_open()) { - file << msg; - file.close(); - } -} \ No newline at end of file diff --git a/Makefile b/Makefile index d959810..cb395a8 100644 --- a/Makefile +++ b/Makefile @@ -21,52 +21,61 @@ export CFG_DIR=/usr/local/etc/ export DATA_DIR=/usr/local/share/dstargateway.d/ export LOG_DIR=/var/log/dstargateway/ +ifeq ($(ENABLE_DEBUG), 1) # choose this if you want debugging help -CPPFLAGS=-g -ggdb -W -Wall -std=c++17 +export CPPFLAGS=-g -rdynamic -DBOOST_STACKTRACE_USE_ADDR2LINE -DDEBUG_DSTARGW -no-pie -fno-pie -ggdb -W -Wall -Werror -std=c++17 +export LDFLAGS=-ldl -no-pie -fno-pie +else # or, you can choose this for a much smaller executable without debugging help -#CPPFLAGS=-W -Wall -std=c++17 +export CPPFLAGS=-W -O3 -Wall -Werror -std=c++17 +endif -LDFLAGS:=-lcurl -pthread +export CC=g++ +export LDFLAGS+= -lcurl -pthread ifeq ($(USE_GPSD), 1) -CPPFLAGS+= -DUSE_GPSD -LDFLAGS+= -lgps +export CPPFLAGS+= -DUSE_GPSD +export LDFLAGS+= -lgps endif -SRCS = $(wildcard *.cpp) -OBJS = $(SRCS:.cpp=.o) -DEPS = $(SRCS:.cpp=.d) .PHONY: all -all: dstargateway +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol #tests -dstargateway : GitVersion.h $(OBJS) - g++ $(CPPFLAGS) -o dstargateway $(OBJS) $(LDFLAGS) +APRS/APRS.a: BaseCommon/BaseCommon.a FORCE + $(MAKE) -C APRS -%.o : %.cpp - g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ +Common/Common.a: VersionInfo/GitVersion.h APRS/APRS.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C Common -GitVersion.h : FORCE -ifneq ("$(wildcard .git/index)","") - @echo "#pragma once" > /tmp/$@ - @echo "#include " >> /tmp/$@ - @echo "const std::string gitversion(\"$(shell git rev-parse --short HEAD)\");" >> /tmp/$@ -else - @echo "#pragma once" > /tmp/$@ - @echo "#include " >> /tmp/$@ - @echo "const std::string gitversion(\"0000000\");" >> /tmp/$@ -endif - @cmp -s /tmp/$@ $@; \ - RETVAL=$$?; \ - if [ $$RETVAL -ne 0 ]; then \ - echo "Git version has changed"; \ - cp -f /tmp/$@ $@; \ - fi; \ - rm /tmp/$@; +BaseCommon/BaseCommon.a: FORCE + $(MAKE) -C BaseCommon + +DStarBase/DStarBase.a: BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DStarBase + +DStarGateway/dstargateway : VersionInfo/GitVersion.h $(OBJS) APRS/APRS.a Common/Common.a DStarBase/DStarBase.a IRCDDB/IRCDDB.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DStarGateway + +DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWRemoteControl + +IRCDDB/IRCDDB.a: VersionInfo/GitVersion.h BaseCommon/BaseCommon.a FORCE + $(MAKE) -C IRCDDB + +VersionInfo/GitVersion.h: FORCE + $(MAKE) -C VersionInfo .PHONY: clean clean: - $(RM) GitVersion.h $(OBJS) $(DEPS) dstargateway + $(MAKE) -C Tests clean + $(MAKE) -C APRS clean + $(MAKE) -C Common clean + $(MAKE) -C BaseCommon clean + $(MAKE) -C DStarBase clean + $(MAKE) -C DStarGateway clean + $(MAKE) -C IRCDDB clean + $(MAKE) -C VersionInfo -include $(DEPS) @@ -80,7 +89,9 @@ newhostfiles : @wget http://www.pistar.uk/downloads/DPlus_Hosts.txt -nv -O $(DATA_DIR)/DPlus_Hosts.txt .PHONY: install -install : dstargateway +install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol +# install remote control + $(MAKE) -C DGWRemoteControl install # create user for daemon @useradd --user-group -M --system dstar --shell /bin/false || true @@ -95,13 +106,8 @@ install : dstargateway $(MAKE) -C Data install @chown -R dstar:dstar $(DATA_DIR) -# copy and adjust config - @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 - -# copy binary - @cp -f dstargateway $(BIN_DIR) +#install executables + $(MAKE) -C DStarGateway install # SystemD service install @cp -f debian/dstargateway.service /lib/systemd/system/ @@ -129,6 +135,13 @@ removehostfiles : @rm -f $(DATA_DIR)/DCS_Hosts.txt @rm -f $(DATA_DIR)/DPlus_Hosts.txt +.PHONY tests: +tests : VersionInfo/GitVersion.h $(OBJS) APRS/APRS.a Common/Common.a DStarBase/DStarBase.a IRCDDB/IRCDDB.a BaseCommon/BaseCommon.a FORCE + @$(MAKE) -C Tests dstargateway_tests +.PHONY run-tests: +run-tests: tests + @$(MAKE) -C Tests run-tests -FORCE: \ No newline at end of file +FORCE: + @true diff --git a/README.md b/README.md index b04a1a7..8cdfa2b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ - [1. Introduction](#1-introduction) - [2. Current State](#2-current-state) - [2.1. Code sanity](#21-code-sanity) - - [2.2. Code Credit](#22-code-credit) - - [2.3. Features](#23-features) + - [2.2. Code Credits](#22-code-credits) + - [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-) - [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) @@ -10,15 +13,18 @@ - [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.6. Installing](#36-installing) - [3.7. Configuring](#37-configuring) - [4. Contributing](#4-contributing) - [4.1. Work Flow](#41-work-flow) + - [4.2. Continuous Integration](#42-continuous-integration) - [5. Version History](#5-version-history) - - [5.1. Version 0.4](#51-version-04) - - [5.2. Version 0.3](#52-version-03) - - [5.3. Version 0.2](#53-version-02) - - [5.4. Version 0.1](#54-version-01) + - [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) - [6. Future](#6-future) @@ -32,14 +38,17 @@ The current code is working, yet ugly IMHO as it is a mix of C and C++ of variou 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. Quite a few classes are more or less copy/paste from each other some sanitization by using base classes or template classes would greatly improve code maintainibility. Maybe one day ;) -## 2.2. Code Credit +## 2.2. Code Credits - Jonathan Naylor G4KLX (The original author of [ircddbGateway](https://github.com/g4klx/ircDDBGateway)) - Thomas A. Early N7TAE (Code taken from his [smart-group](https://github.com/n7tae/smart-group-server) software) - Geoffrey Merck F4FXL / KC3FRA [That's me !](https://github.com/F4FXL/) -## 2.3. Features -All the features found in ircddbGateway are supposed to be working. I have mixed feelings about putting these back in or not. +## 2.3. Thanks +- Cyrille F1MHV / DF1CHB for the testing +- Jonathan Naylor G4KLX for all the work ahead +## 2.4. Features +All the features found in ircddbGateway are supposed to be working. Except the ones listed below -Features that where left out : +### 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. - 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. @@ -47,6 +56,11 @@ Features that where left out : - 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. +- Repeater Link status is sent to APRS-IS as a status frame + # 3. Building and installing ## 3.1. Initial setup Clone the repository (only required initally) @@ -70,11 +84,11 @@ git checkout develop ## 3.4. Prerequisites and dependencies Before first time building you need to install dependencies and prerequisites ``` -apt install build-essential libcurl4-openssl-dev libboost-dev +sudo apt install build-essential libcurl4-openssl-dev libboost-dev ``` If you are going to build with gpsd support, also install libgps-dev ``` -apt install libgps-dev +sudo apt install libgps-dev ``` ## 3.5. Building Regular building @@ -85,10 +99,15 @@ make ``` make USE_GPS=1 ``` +#### 3.5.0.2. Debug Build +``` +make ENABLE_DEBUG=1 +``` +Note that this will link with libl ## 3.6. Installing The program is meant to run as a systemd service. All bits an pieces are provided. ``` -sudo make install +sudo make install newhostfiles ``` ## 3.7. Configuring After installing you have to edit the configuration file. If you went with default paths, the config file is located in `/usr/local/etc/dstargateway.cfg` @@ -109,28 +128,41 @@ I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my work - You have tested your code thoroughly - Compilation produces no warnings - 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. + # 5. Version History -## 5.1. Version 0.4 +## 5.1. 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 +## 5.2. 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 (https://github.com/F4FXL/DStarGateway/issues/6) -- [Improvement] Log enhancements ([#4])(https://github.com/F4FXL/DStarGateway/issues/4) -## 5.2. Version 0.3 +- [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 - [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. -## 5.3. Version 0.2 +## 5.4. Version 0.2 - [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization -## 5.4. Version 0.1 +## 5.5. 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 : -- ☒ Better NatTraversal +- ☑ Better NatTraversal - No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer - - Support for all protocols (G2, DExtra, DCS, REF) + - 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 ...) - ☒ 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 - Everything that might come handy to make dstar the most powerful system ever :) diff --git a/Tests/APRSFormater/frameToString.cpp b/Tests/APRSFormater/frameToString.cpp new file mode 100644 index 0000000..da17cbe --- /dev/null +++ b/Tests/APRSFormater/frameToString.cpp @@ -0,0 +1,115 @@ +/* + * 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 "APRSFormater.h" + + class APRSFormater_frameToString : public ::testing::Test { + +}; + +TEST_F(APRSFormater_frameToString, EmptyFrame) { + + CAPRSFrame aprsFrame("", "", { }, "", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds"); +} + +TEST_F(APRSFormater_frameToString, SourceOnly) { + + CAPRSFrame aprsFrame("N0CALL", "", { }, "", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds"); +} + +TEST_F(APRSFormater_frameToString, DestinationOnly) { + + CAPRSFrame aprsFrame("", "APRS", { }, "", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds"); +} + +TEST_F(APRSFormater_frameToString, PathOnly) { + + CAPRSFrame aprsFrame("", "", { "WIDE1-1, WIDE2-1" }, "", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds"); +} + +TEST_F(APRSFormater_frameToString, BodyOnly) { + + CAPRSFrame aprsFrame("", "", { }, "Lorem Ipsum", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds"); +} + +TEST_F(APRSFormater_frameToString, CorrectWithoutPath) { + + CAPRSFrame aprsFrame("N0CALL", "APRS", { }, "Lorem Ipsum", APFT_UNKNOWN); + std::string output("This should NOT be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STREQ(output.c_str(), "N0CALL>APRS:Lorem Ipsum"); +} + +TEST_F(APRSFormater_frameToString, CorrectWithPath) { + + CAPRSFrame aprsFrame("N0CALL", "APRS", { "WIDE1-1", "WIDE2-2" }, "Lorem Ipsum", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STREQ(output.c_str(), "N0CALL>APRS,WIDE1-1,WIDE2-2:Lorem Ipsum"); +} + +TEST_F(APRSFormater_frameToString, WithSomeEmptyPath) { + + CAPRSFrame aprsFrame("N0CALL", "APRS", { "WIDE1-1", "", "WIDE2-2" }, "Lorem Ipsum", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STREQ(output.c_str(), "N0CALL>APRS,WIDE1-1,WIDE2-2:Lorem Ipsum"); +} + +TEST_F(APRSFormater_frameToString, WithSomeBlankPath) { + + CAPRSFrame aprsFrame("N0CALL", "APRS", { "WIDE1-1", "", "WIDE2-2" }, "Lorem Ipsum", APFT_UNKNOWN); + std::string output("This should be left untouched if the test succeeds"); + bool retVal = CAPRSFormater::frameToString(output, aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STREQ(output.c_str(), "N0CALL>APRS,WIDE1-1,WIDE2-2:Lorem Ipsum"); +} \ No newline at end of file diff --git a/Tests/APRSParser/parseAPRSFrame.cpp b/Tests/APRSParser/parseAPRSFrame.cpp new file mode 100644 index 0000000..a2d1ae1 --- /dev/null +++ b/Tests/APRSParser/parseAPRSFrame.cpp @@ -0,0 +1,201 @@ +/* + * 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 "APRSParser.h" + +namespace APRSParserTests +{ + class APRSParser_parseAPRSFrame : public ::testing::Test { + + }; + + TEST_F(APRSParser_parseAPRSFrame, EmpyString) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("", aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), ""); + EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN); + EXPECT_EQ(aprsFrame.getPath().size(), 0U); + } + + TEST_F(APRSParser_parseAPRSFrame, NoSourceCallsign) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame(">APRS::F4ABC Test Message", aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), ""); + EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN); + EXPECT_EQ(aprsFrame.getPath().size(), 0U); + } + + TEST_F(APRSParser_parseAPRSFrame, NoDestCallsign) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("N0CALL>::F4ABC Test Message", aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), ""); + EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN); + EXPECT_EQ(aprsFrame.getPath().size(), 0U); + } + + TEST_F(APRSParser_parseAPRSFrame, CorrectMessageFrameWithDigipeater) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("N0CALL>APRS,WIDE1-1,WIDE2-2::F4ABC :Test Message", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "N0CALL"); + EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE); + EXPECT_EQ(aprsFrame.getPath().size(), 2); + EXPECT_STREQ(aprsFrame.getPath()[0].c_str(), "WIDE1-1"); + EXPECT_STREQ(aprsFrame.getPath()[1].c_str(), "WIDE2-2"); + } + + TEST_F(APRSParser_parseAPRSFrame, CorrectMessageFrameWithoutDigipeater) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("N0CALL>APRS::F4ABC :Test Message", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "N0CALL"); + EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } + + TEST_F(APRSParser_parseAPRSFrame, InvalidMessageFrame) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("N0CALL>APRS::F4ABC&@#$:Test Message", aprsFrame); + + EXPECT_FALSE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), ""); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), ""); + EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN); + EXPECT_EQ(aprsFrame.getPath().size(), 0U); + } + + TEST_F(APRSParser_parseAPRSFrame, ID51) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F4FXL-8>API51,DSTAR:!1234.56N/12345.67E[/A=000886QRV DStar\r\r\n", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "!1234.56N/12345.67E[/A=000886QRV DStar\r\r\n"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "API51"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F4FXL-8"); + EXPECT_EQ(aprsFrame.getType(), APFT_POSITION); + EXPECT_EQ(aprsFrame.getPath().size(), 1); + EXPECT_STREQ(aprsFrame.getPath()[0].c_str(), "DSTAR"); + } + + TEST_F(APRSParser_parseAPRSFrame, telemetryLabels) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS::F5ZEE-C :PARM.PA Temp", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F5ZEE-C :PARM.PA Temp"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C"); + EXPECT_EQ(aprsFrame.getType(), APFT_TELEMETRY); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } + + TEST_F(APRSParser_parseAPRSFrame, telemetryEQNS) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS::F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C"); + EXPECT_EQ(aprsFrame.getType(), APFT_TELEMETRY); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } + + TEST_F(APRSParser_parseAPRSFrame, telemetryEQNSWithSeqnum) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS::F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0{ABCD", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0{ABCD"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C"); + EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } + + + TEST_F(APRSParser_parseAPRSFrame, telemetryReport) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS:T#581,342,000,000,000,000,00000000", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "T#581,342,000,000,000,000,00000000"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C"); + EXPECT_EQ(aprsFrame.getType(), APFT_TELEMETRY); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } + + TEST_F(APRSParser_parseAPRSFrame, messageToSelf) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F4ABC>APRS::F4ABC :Test Message{ABCD", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message{ABCD"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F4ABC"); + EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } + + TEST_F(APRSParser_parseAPRSFrame, messageToSelfNoSeqNum) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F4ABC>APRS::F4ABC :Test Message", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F4ABC"); + EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE); + EXPECT_EQ(aprsFrame.getPath().size(), 0); + } +} diff --git a/Tests/APRStoDPRS/aprsToDPRS.cpp b/Tests/APRStoDPRS/aprsToDPRS.cpp new file mode 100644 index 0000000..1334e8f --- /dev/null +++ b/Tests/APRStoDPRS/aprsToDPRS.cpp @@ -0,0 +1,76 @@ +/* + * 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 "APRStoDPRS.h" + +namespace APRStoDPRSTests +{ + class APRStoDPRS_aprsToDPRS : public ::testing::Test { + + }; + + TEST_F(APRStoDPRS_aprsToDPRS, validMessage) + { + CAPRSFrame frame("KC3FRA", "APRS", {"WIDE1-1", "WIDE2-2"}, ":F4FXL :Salut, comment vas tu?", APFT_MESSAGE); + + std::string dprs, text; + CHeaderData header; + bool ret = CAPRSToDPRS::aprsToDPRS(dprs, text, header, frame); + + EXPECT_TRUE(ret); + EXPECT_STREQ(dprs.c_str(), "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r"); + EXPECT_STREQ(text.c_str(), "Salut, comment vas tu?"); + EXPECT_STREQ(header.getMyCall1().c_str(), "KC3FRA "); + EXPECT_STREQ(header.getMyCall2().c_str(), "MSG "); + EXPECT_STREQ(header.getYourCall().c_str(), "F4FXL "); + } + + TEST_F(APRStoDPRS_aprsToDPRS, validMessageRecipientWithSSID) + { + CAPRSFrame frame("KC3FRA", "APRS", {"WIDE1-1", "WIDE2-2"}, ":F4FXL-7 :Salut, comment vas tu?", APFT_MESSAGE); + + std::string dprs, text; + CHeaderData header; + bool ret = CAPRSToDPRS::aprsToDPRS(dprs, text, header, frame); + + EXPECT_TRUE(ret); + EXPECT_STREQ(dprs.c_str(), "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r"); + EXPECT_STREQ(text.c_str(), "Salut, comment vas tu?"); + EXPECT_STREQ(header.getMyCall1().c_str(), "KC3FRA "); + EXPECT_STREQ(header.getMyCall2().c_str(), "MSG "); + EXPECT_STREQ(header.getYourCall().c_str(), "F4FXL "); + } + + TEST_F(APRStoDPRS_aprsToDPRS, emptyRecipient) + { + CAPRSFrame frame("KC3FRA", "APRS", {"WIDE1-1", "WIDE2-2"}, ": :Salut, comment vas tu?", APFT_MESSAGE); + + std::string dprs, text; + CHeaderData header; + bool ret = CAPRSToDPRS::aprsToDPRS(dprs, text, header, frame); + + EXPECT_FALSE(ret); + EXPECT_STREQ(dprs.c_str(), ""); + EXPECT_STREQ(text.c_str(), ""); + EXPECT_STREQ(header.getMyCall1().c_str(), " "); + EXPECT_STREQ(header.getMyCall2().c_str(), " "); + EXPECT_STREQ(header.getYourCall().c_str(), " "); + } +} diff --git a/Tests/Config/getValue.cpp b/Tests/Config/getValue.cpp new file mode 100644 index 0000000..c7c0d15 --- /dev/null +++ b/Tests/Config/getValue.cpp @@ -0,0 +1,57 @@ +/* + * 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 +#include + +#include "Config.h" + +namespace ConfigTests +{ + + class Config_getValue : public ::testing::Test + { + protected: + std::string m_configPath; + + void SetUp() override + { + char buf[2048]; + auto size = ::readlink("/proc/self/exe", buf, 2048); + if(size > 0) { + m_configPath.assign(buf, size); + auto lastSlashPos = m_configPath.find_last_of('/'); + m_configPath.resize(lastSlashPos); + m_configPath.append("/Config/test.cfg"); + } + } + }; + + TEST_F(Config_getValue, getURL) + { + CConfig config(m_configPath); + + bool ret = config.load(); + std::string value; + config.getValue("[XLX]", "hostfileUrl", value, 0U, 2048U, "http://www.f4fxl.org?src=github"); + + EXPECT_TRUE(ret); + EXPECT_STREQ(value.c_str(), "http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster"); + } +} \ No newline at end of file diff --git a/Tests/Config/load.cpp b/Tests/Config/load.cpp new file mode 100644 index 0000000..3d600e8 --- /dev/null +++ b/Tests/Config/load.cpp @@ -0,0 +1,61 @@ +/* + * 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 +#include + +#include "Config.h" + +namespace ConfigTests +{ + + class Config_load : public ::testing::Test + { + protected: + std::string m_configPath; + + void SetUp() override + { + char buf[2048]; + auto size = ::readlink("/proc/self/exe", buf, 2048); + if(size > 0) { + m_configPath.assign(buf, size); + auto lastSlashPos = m_configPath.find_last_of('/'); + m_configPath.resize(lastSlashPos); + m_configPath.append("/Config/test.cfg"); + } + } + }; + + TEST_F(Config_load, fileExist) + { + CConfig config(m_configPath); + + bool ret = config.load(); + EXPECT_TRUE(ret); + } + + TEST_F(Config_load, fileDoesNotExist) + { + CConfig config("/this/file/does/not/exist/and/if/exists/we/have/a/serious/issue"); + + bool ret = config.load(); + EXPECT_FALSE(ret); + } +} \ No newline at end of file diff --git a/Tests/Config/test.cfg b/Tests/Config/test.cfg new file mode 100644 index 0000000..c7b9920 --- /dev/null +++ b/Tests/Config/test.cfg @@ -0,0 +1,4 @@ +[XLX] +hostfileUrl=http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster + + diff --git a/Tests/DCSProtocolHandlerPool/getHandler.cpp b/Tests/DCSProtocolHandlerPool/getHandler.cpp new file mode 100644 index 0000000..9721803 --- /dev/null +++ b/Tests/DCSProtocolHandlerPool/getHandler.cpp @@ -0,0 +1,55 @@ +/* + * 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 "DCSProtocolHandlerPool.h" + +namespace DCSProtocolHandlerPoolTests +{ + class DCSProtocolHandler_getHandler : public ::testing::Test { + + }; + + TEST_F(DCSProtocolHandler_getHandler, successiveCallsReturnsDifferentHandlerAndNotIncoming) + { + CDCSProtocolHandlerPool pool(DCS_PORT, "127.0.0.1"); + + auto handler1 = pool.getHandler(); + auto handler2 = pool.getHandler(); + auto incoming = pool.getIncomingHandler(); + + EXPECT_NE(handler1, nullptr); + EXPECT_NE(handler2, nullptr); + EXPECT_NE(incoming, nullptr); + + EXPECT_NE(handler1, handler2); + EXPECT_NE(handler1, incoming); + EXPECT_NE(handler2, incoming); + + // DCS_PORT is reserved for incoming handler + EXPECT_NE(handler1->getPort(), DCS_PORT); + EXPECT_NE(handler2->getPort(), DCS_PORT); + + EXPECT_EQ(incoming->getPort(), DCS_PORT); + + pool.release(handler1); + pool.release(handler2); + pool.release(incoming); + } +} \ No newline at end of file diff --git a/Tests/DCSProtocolHandlerPool/getIncomingHandler.cpp b/Tests/DCSProtocolHandlerPool/getIncomingHandler.cpp new file mode 100644 index 0000000..b675b05 --- /dev/null +++ b/Tests/DCSProtocolHandlerPool/getIncomingHandler.cpp @@ -0,0 +1,45 @@ +/* + * 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 "DCSProtocolHandlerPool.h" + +namespace DCSProtocolHandlerPoolTests +{ + class DCSProtocolHandler_getIncomingHandler : public ::testing::Test { + + }; + + TEST_F(DCSProtocolHandler_getIncomingHandler, successiveCallAlwaysReturnsIncomingHandler) + { + CDCSProtocolHandlerPool pool(DCS_PORT, "127.0.0.1"); + + auto handler1 = pool.getIncomingHandler(); + auto handler2 = pool.getIncomingHandler(); + + EXPECT_NE(handler1, nullptr); + EXPECT_NE(handler2, nullptr); + EXPECT_EQ(handler1, handler2); + EXPECT_EQ(handler1->getPort(), DCS_PORT); + EXPECT_EQ(handler2->getPort(), DCS_PORT); + + pool.release(handler1); + pool.release(handler2); + } +} \ No newline at end of file diff --git a/Tests/DPlusProtocolHandlerPool/getHandler.cpp b/Tests/DPlusProtocolHandlerPool/getHandler.cpp new file mode 100644 index 0000000..dbb6948 --- /dev/null +++ b/Tests/DPlusProtocolHandlerPool/getHandler.cpp @@ -0,0 +1,55 @@ +/* + * 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 "DPlusProtocolHandlerPool.h" + +namespace DPlusProtocolHandlerPoolTests +{ + class DPlusProtocolHandler_getHandler : public ::testing::Test { + + }; + + TEST_F(DPlusProtocolHandler_getHandler, successiveCallsReturnsDifferentHandlerAndNotIncoming) + { + CDPlusProtocolHandlerPool pool(DPLUS_PORT, "127.0.0.1"); + + auto handler1 = pool.getHandler(); + auto handler2 = pool.getHandler(); + auto incoming = pool.getIncomingHandler(); + + EXPECT_NE(handler1, nullptr); + EXPECT_NE(handler2, nullptr); + EXPECT_NE(incoming, nullptr); + + EXPECT_NE(handler1, handler2); + EXPECT_NE(handler1, incoming); + EXPECT_NE(handler2, incoming); + + // DPLUS_PORT is reserved for incoming handler + EXPECT_NE(handler1->getPort(), DPLUS_PORT); + EXPECT_NE(handler2->getPort(), DPLUS_PORT); + + EXPECT_EQ(incoming->getPort(), DPLUS_PORT); + + pool.release(handler1); + pool.release(handler2); + pool.release(incoming); + } +} \ No newline at end of file diff --git a/Tests/DPlusProtocolHandlerPool/getIncomingHandler.cpp b/Tests/DPlusProtocolHandlerPool/getIncomingHandler.cpp new file mode 100644 index 0000000..14ddf6e --- /dev/null +++ b/Tests/DPlusProtocolHandlerPool/getIncomingHandler.cpp @@ -0,0 +1,45 @@ +/* + * 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 "DPlusProtocolHandlerPool.h" + +namespace DPlusProtocolHandlerPoolTests +{ + class DPlusProtocolHandler_getIncomingHandler : public ::testing::Test { + + }; + + TEST_F(DPlusProtocolHandler_getIncomingHandler, successiveCallAlwaysReturnsIncomingHandler) + { + CDPlusProtocolHandlerPool pool(DPLUS_PORT, "127.0.0.1"); + + auto handler1 = pool.getIncomingHandler(); + auto handler2 = pool.getIncomingHandler(); + + EXPECT_NE(handler1, nullptr); + EXPECT_NE(handler2, nullptr); + EXPECT_EQ(handler1, handler2); + EXPECT_EQ(handler1->getPort(), DPLUS_PORT); + EXPECT_EQ(handler2->getPort(), DPLUS_PORT); + + pool.release(handler1); + pool.release(handler2); + } +} \ No newline at end of file diff --git a/Tests/DextraProtocolHandlerPool/getHandler.cpp b/Tests/DextraProtocolHandlerPool/getHandler.cpp new file mode 100644 index 0000000..0f0f6dc --- /dev/null +++ b/Tests/DextraProtocolHandlerPool/getHandler.cpp @@ -0,0 +1,55 @@ +/* + * 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 "DExtraProtocolHandlerPool.h" + +namespace DextraProtocolHandlerPoolTests +{ + class DextraProtocolHandler_getHandler : public ::testing::Test { + + }; + + TEST_F(DextraProtocolHandler_getHandler, successiveCallsReturnsDifferentHandlerAndNotIncoming) + { + CDExtraProtocolHandlerPool pool(DEXTRA_PORT, "127.0.0.1"); + + auto handler1 = pool.getHandler(); + auto handler2 = pool.getHandler(); + auto incoming = pool.getIncomingHandler(); + + EXPECT_NE(handler1, nullptr); + EXPECT_NE(handler2, nullptr); + EXPECT_NE(incoming, nullptr); + + EXPECT_NE(handler1, handler2); + EXPECT_NE(handler1, incoming); + EXPECT_NE(handler2, incoming); + + // DEXTRA_PORT is reserved for incoming handler + EXPECT_NE(handler1->getPort(), DEXTRA_PORT); + EXPECT_NE(handler2->getPort(), DEXTRA_PORT); + + EXPECT_EQ(incoming->getPort(), DEXTRA_PORT); + + pool.release(handler1); + pool.release(handler2); + pool.release(incoming); + } +} \ No newline at end of file diff --git a/Tests/DextraProtocolHandlerPool/getIncomingHandler.cpp b/Tests/DextraProtocolHandlerPool/getIncomingHandler.cpp new file mode 100644 index 0000000..ae90c3d --- /dev/null +++ b/Tests/DextraProtocolHandlerPool/getIncomingHandler.cpp @@ -0,0 +1,45 @@ +/* + * 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 "DExtraProtocolHandlerPool.h" + +namespace DextraProtocolHandlerPoolTests +{ + class DextraProtocolHandler_getIncomingHandler : public ::testing::Test { + + }; + + TEST_F(DextraProtocolHandler_getIncomingHandler, successiveCallAlwaysReturnsIncomingHandler) + { + CDExtraProtocolHandlerPool pool(DEXTRA_PORT, "127.0.0.1"); + + auto handler1 = pool.getIncomingHandler(); + auto handler2 = pool.getIncomingHandler(); + + EXPECT_NE(handler1, nullptr); + EXPECT_NE(handler2, nullptr); + EXPECT_EQ(handler1, handler2); + EXPECT_EQ(handler1->getPort(), DEXTRA_PORT); + EXPECT_EQ(handler2->getPort(), DEXTRA_PORT); + + pool.release(handler1); + pool.release(handler2); + } +} \ No newline at end of file diff --git a/Tests/Makefile b/Tests/Makefile new file mode 100644 index 0000000..2e29d24 --- /dev/null +++ b/Tests/Makefile @@ -0,0 +1,25 @@ +SRCS = $(wildcard *.cpp) $(wildcard */*.cpp) +OBJS = $(SRCS:.cpp=.o) +DEPS = $(SRCS:.cpp=.d) + +dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB/IRCDDB.a ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a ../Common/Common.a + $(CC) $(CPPFLAGS) -o dstargateway_tests $(OBJS) ../Common/Common.a ../APRS/APRS.a ../DStarBase/DStarBase.a ../IRCDDB/IRCDDB.a ../BaseCommon/BaseCommon.a $(LDFLAGS) -lgtest -lgtest_main + +%.o : %.cpp + $(CC) $(CPPFLAGS) -DUNIT_TESTS -I../APRS -I../Common -I../BaseCommon -I../DStarBase -I../IRCDDB -I../VersionInfo -MMD -MD -c $< -o $@ + +-include $(DEPS) + +.PHONY run-tests: dstargateway_tests + ./dstargateway_tests + +.PHONY clean : +clean : + @$(RM) *.o *.d dstargateway_tests + +../APRS/APRS.a: +../Common/Common.a: +../DStarBase/DStarBase.a: +../BaseCommon/BaseCommon.a: +../IRCDDB/IRCDDB.a: +../VersionInfo/GitVersion.h: diff --git a/Tests/NetUtils/lookup.cpp b/Tests/NetUtils/lookup.cpp new file mode 100644 index 0000000..e499f59 --- /dev/null +++ b/Tests/NetUtils/lookup.cpp @@ -0,0 +1,59 @@ +/* + * 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 + +#include "NetUtils.h" + +namespace NetUtilsTests +{ + class NetUtils_lookup: public ::testing::Test { + + }; + + TEST_F(NetUtils_lookup, googleShallAlwaysSucceed) + { + sockaddr_storage addr; + struct addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + + bool res = CNetUtils::lookup("google.fr", addr, hints); + + bool familyOk = addr.ss_family == AF_INET6 || addr.ss_family == AF_INET; + EXPECT_TRUE(familyOk); + EXPECT_TRUE(res); + } + + TEST_F(NetUtils_lookup, erroneousAddress) + { + sockaddr_storage addr; + struct addrinfo hints; + ::memset(&hints, 0, sizeof(hints)); + + bool res = CNetUtils::lookup("gfilufgclqsegfuligyhfcguyhfguilfguils4df64sdw46fcq6sfgvd6f6d7f67d6f7c6sd7f6s7gfv6fc7d6f76tf.fr", addr, hints); + + EXPECT_EQ(addr.ss_family, AF_INET); + + auto ptr = (sockaddr_in*)(&addr); + + EXPECT_EQ((uint32_t)(ptr->sin_addr.s_addr), (uint32_t)INADDR_NONE); + EXPECT_FALSE(res); + } +} \ No newline at end of file diff --git a/Tests/NetUtils/lookupV4.cpp b/Tests/NetUtils/lookupV4.cpp new file mode 100644 index 0000000..da5b88e --- /dev/null +++ b/Tests/NetUtils/lookupV4.cpp @@ -0,0 +1,68 @@ +/* + * 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 + +#include "NetUtils.h" + +namespace NetUtilsTests +{ + class NetUtils_lookupV4 : public ::testing::Test { + + }; + + TEST_F(NetUtils_lookupV4, googleShallAlwaysSucceed) + { + sockaddr_storage addr; + + bool res = CNetUtils::lookupV4("google.fr", addr); + + EXPECT_EQ(addr.ss_family, AF_INET); + EXPECT_TRUE(res); + } + + TEST_F(NetUtils_lookupV4, erroneousAddress) + { + sockaddr_storage addr; + + bool res = CNetUtils::lookupV4("gfilufgclqsegfuligyhfcguyhfguilfguils4df64sdw46fcq6sfgvd6f6d7f67d6f7c6sd7f6s7gfv6fc7d6f76tf.fr", addr); + + EXPECT_EQ(addr.ss_family, AF_INET); + + auto ptr = (sockaddr_in*)(&addr); + + EXPECT_EQ((uint32_t)(ptr->sin_addr.s_addr), (uint32_t)INADDR_NONE); + EXPECT_FALSE(res); + } + + TEST_F(NetUtils_lookupV4, addressWithNoIPV4) + { + sockaddr_storage addr; + + bool res = CNetUtils::lookupV4("ircv6.openquad.net", addr); + + EXPECT_EQ(addr.ss_family, AF_INET); + + auto ptr = (sockaddr_in*)(&addr); + + EXPECT_EQ((uint32_t)(ptr->sin_addr.s_addr), (uint32_t)INADDR_NONE); + EXPECT_FALSE(res); + } +} \ No newline at end of file diff --git a/Tests/NetUtils/lookupV6.cpp b/Tests/NetUtils/lookupV6.cpp new file mode 100644 index 0000000..99b95af --- /dev/null +++ b/Tests/NetUtils/lookupV6.cpp @@ -0,0 +1,68 @@ +/* + * 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 + +#include "NetUtils.h" + +namespace NetUtilsTests +{ + class NetUtils_lookupV6 : public ::testing::Test { + + }; + + TEST_F(NetUtils_lookupV6, googleShallAlwaysSucceed) + { + sockaddr_storage addr; + + bool res = CNetUtils::lookupV6("google.fr", addr); + + EXPECT_EQ(addr.ss_family, AF_INET6); + EXPECT_TRUE(res); + } + + TEST_F(NetUtils_lookupV6, erroneousAddress) + { + sockaddr_storage addr; + + bool res = CNetUtils::lookupV6("gfilufgclqsegfuligyhfcguyhfguilfguils4df64sdw46fcq6sfgvd6f6d7f67d6f7c6sd7f6s7gfv6fc7d6f76tf.fr", addr); + + EXPECT_EQ(addr.ss_family, AF_INET); + + auto ptr = (sockaddr_in*)(&addr); + + EXPECT_EQ((uint32_t)(ptr->sin_addr.s_addr), (uint32_t)INADDR_NONE); + EXPECT_FALSE(res); + } + + TEST_F(NetUtils_lookupV6, addressWithNoIPV6) + { + sockaddr_storage addr; + + bool res = CNetUtils::lookupV6("ircv4.openquad.net", addr); + + EXPECT_EQ(addr.ss_family, AF_INET); + + auto ptr = (sockaddr_in*)(&addr); + + EXPECT_EQ((uint32_t)(ptr->sin_addr.s_addr), (uint32_t)INADDR_NONE); + EXPECT_FALSE(res); + } +} \ No newline at end of file diff --git a/Tests/NetUtils/match.cpp b/Tests/NetUtils/match.cpp new file mode 100644 index 0000000..bade2b5 --- /dev/null +++ b/Tests/NetUtils/match.cpp @@ -0,0 +1,90 @@ +/* + * 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 + +#include "NetUtils.h" + +namespace NetUtilsTests +{ + class NetUtils_match: public ::testing::Test { + + }; + + TEST_F(NetUtils_match, MatchIPAndPort_differentFamilySamePort) + { + struct sockaddr_storage addr1, addr2; + addr1.ss_family = AF_INET6; + addr2.ss_family = AF_INET; + + ((struct sockaddr_in6 *)&addr1)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr1)->sin6_port = 123; + + ((struct sockaddr_in *)&addr2)->sin_addr.s_addr = INADDR_LOOPBACK; + ((struct sockaddr_in *)&addr2)->sin_port = 123; + + EXPECT_FALSE(CNetUtils::match(addr1, addr2, IMT_ADDRESS_AND_PORT)); + } + + TEST_F(NetUtils_match, MatchIPAndPort_SameFamilySamePort) + { + struct sockaddr_storage addr1, addr2; + addr1.ss_family = AF_INET6; + addr2.ss_family = AF_INET6; + + ((struct sockaddr_in6 *)&addr1)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr1)->sin6_port = 123; + + ((struct sockaddr_in6 *)&addr2)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr2)->sin6_port = 123; + + EXPECT_TRUE(CNetUtils::match(addr1, addr2, IMT_ADDRESS_AND_PORT)); + } + + TEST_F(NetUtils_match, MatchIPAndPort_SameFamilyDifferentPort) + { + struct sockaddr_storage addr1, addr2; + addr1.ss_family = AF_INET6; + addr2.ss_family = AF_INET6; + + ((struct sockaddr_in6 *)&addr1)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr1)->sin6_port = 123; + + ((struct sockaddr_in6 *)&addr2)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr2)->sin6_port = 456; + + EXPECT_FALSE(CNetUtils::match(addr1, addr2, IMT_ADDRESS_AND_PORT)); + } + + TEST_F(NetUtils_match, MatchIP_SameFamilyDifferentPort) + { + struct sockaddr_storage addr1, addr2; + addr1.ss_family = AF_INET6; + addr2.ss_family = AF_INET6; + + ((struct sockaddr_in6 *)&addr1)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr1)->sin6_port = 123; + + ((struct sockaddr_in6 *)&addr2)->sin6_addr = IN6ADDR_LOOPBACK_INIT; + ((struct sockaddr_in6 *)&addr2)->sin6_port = 456; + + EXPECT_TRUE(CNetUtils::match(addr1, addr2, IMT_ADDRESS_ONLY)); + } +} \ No newline at end of file diff --git a/Tests/ProgramArgs.cpp b/Tests/ProgramArgs.cpp new file mode 100644 index 0000000..67c6e0d --- /dev/null +++ b/Tests/ProgramArgs.cpp @@ -0,0 +1,91 @@ +/* + * 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 "ProgramArgs.h" + +namespace ProgramArgsTests +{ + class ProgramArgs_eatArguments : public ::testing::Test { + + }; + + TEST_F(ProgramArgs_eatArguments, OnlyExecutable) + { + const char *argv[] { "ACME" }; + std::unordered_map namedArgs; + std::vector positionalArgs; + + CProgramArgs::eatArguments(1, argv, namedArgs, positionalArgs); + + EXPECT_EQ(namedArgs.size(), 0U) << "Named Args shall be empty because we did not pass any args"; + EXPECT_EQ(positionalArgs.size(), 0U) << "Positional Args shall be empty because we did not pass any args"; + } + + TEST_F(ProgramArgs_eatArguments, OneNamedArgsWithValue) + { + const char *argv[] { "ACME", "-name", "giovanni" }; + std::unordered_map namedArgs; + std::vector positionalArgs; + + CProgramArgs::eatArguments(3, argv, namedArgs, positionalArgs); + + EXPECT_EQ(namedArgs.size(), 1U) << "Named args shall have one argument"; + EXPECT_EQ(namedArgs.count("name"), 1U) << "Named args shall contained name"; + EXPECT_STREQ(namedArgs["name"].c_str(), "giovanni") << "Name shall be giovanni"; + + EXPECT_EQ(positionalArgs.size(), 0U) << "Positional Args shall be empty because we did not pass any positional args"; + } + + TEST_F(ProgramArgs_eatArguments, OneNamedArgsWithoutValue) + { + const char *argv[] { "ACME", "-name" }; + std::unordered_map namedArgs; + std::vector positionalArgs; + + CProgramArgs::eatArguments(2, argv, namedArgs, positionalArgs); + + EXPECT_EQ(namedArgs.size(), 1U) << "Named args shall have one argument"; + EXPECT_EQ(namedArgs.count("name"), 1U) << "Named args shall contained name"; + EXPECT_STREQ(namedArgs["name"].c_str(), "") << "Name shall be empty"; + + EXPECT_EQ(positionalArgs.size(), 0U) << "Positional Args shall be empty because we did not pass any positional args"; + } + + TEST_F(ProgramArgs_eatArguments, OneNamedArgsWithValueSeveralPositionalArgs) + { + const char *argv[] { "ACME", "my", "-name", "is", "giovanni", "giorgio" }; + std::unordered_map namedArgs; + std::vector positionalArgs; + + CProgramArgs::eatArguments(6, argv, namedArgs, positionalArgs); + + EXPECT_EQ(namedArgs.size(), 1U) << "Named args shall have one argument"; + EXPECT_EQ(namedArgs.count("name"), 1U) << "Named args shall contained name"; + EXPECT_STREQ(namedArgs["name"].c_str(), "is") << "Name shall be giovanni"; + + EXPECT_EQ(positionalArgs.size(), 3U) << "Positional Args shall contain 3 values"; + EXPECT_STREQ(positionalArgs[0].c_str(), "my"); + EXPECT_STREQ(positionalArgs[1].c_str(), "giovanni"); + EXPECT_STREQ(positionalArgs[2].c_str(), "giorgio"); + } +} \ No newline at end of file diff --git a/Tests/RSMS1AMessageBuilder/buildMessage.cpp b/Tests/RSMS1AMessageBuilder/buildMessage.cpp new file mode 100644 index 0000000..42ef3d0 --- /dev/null +++ b/Tests/RSMS1AMessageBuilder/buildMessage.cpp @@ -0,0 +1,86 @@ +/* + * 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 "RSMS1AMessageBuilder.h" + +namespace RSMS1AMessageBuilderTests +{ + + class RSMS1AMessageBuilder_buildMessage : public ::testing::Test { + + }; + + TEST_F(RSMS1AMessageBuilder_buildMessage, ABC) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "ABC"); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118ABCF\r"); + } + + TEST_F(RSMS1AMessageBuilder_buildMessage, A) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "A"); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118AA\r"); + } + + TEST_F(RSMS1AMessageBuilder_buildMessage, AA) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "AA"); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118AA\02\r"); + } + + TEST_F(RSMS1AMessageBuilder_buildMessage, SalutCommentVasTu) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "Salut, comment vas tu?"); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r"); + } + + TEST_F(RSMS1AMessageBuilder_buildMessage, escapeComma) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", ","); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118o,o,\r"); + } + + TEST_F(RSMS1AMessageBuilder_buildMessage, INeedMoreDollars) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "I need more $$$$"); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118I need more o$o$o$o$\x08\r"); + } + + //"$$Msg,KC3FRA,F4FXL 7,00116Fhello\024\r" + TEST_F(RSMS1AMessageBuilder_buildMessage, hello) + { + std::string message; + CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL 7", "hello"); + + EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL 7,00116Fhello\024\r"); + } +}; \ No newline at end of file diff --git a/Tests/RSMS1AMessageBuilder/parseMessage.cpp b/Tests/RSMS1AMessageBuilder/parseMessage.cpp new file mode 100644 index 0000000..c614c13 --- /dev/null +++ b/Tests/RSMS1AMessageBuilder/parseMessage.cpp @@ -0,0 +1,67 @@ +/* + * 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 "RSMS1AMessageBuilder.h" + +namespace RSMS1AMessageBuilderTests +{ + class RSMS1AMessageBuilder_parseMessage : public ::testing::Test { + + }; + + TEST_F(RSMS1AMessageBuilder_parseMessage, NoError) + { + std::string message = "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r"; + std::string sender, recipient, body; + + auto ret = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, message); + + EXPECT_EQ(ret, RSMS_OK); + EXPECT_STREQ(sender.c_str(), "KC3FRA"); + EXPECT_STREQ(recipient.c_str(), "F4FXL"); + EXPECT_STREQ(body.c_str(), "Salut, comment vas tu?"); + } + + TEST_F(RSMS1AMessageBuilder_parseMessage, ErrorInMessage) + { + std::string message = "$$Msg,KC3FRA,F4FXL,001118Saluto, comlent vas tu?z\r"; + std::string sender, recipient, body; + + auto ret = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, message); + + EXPECT_EQ(ret, RSMS_ERRONEOUS_MSG); + EXPECT_STREQ(sender.c_str(), "KC3FRA"); + EXPECT_STREQ(recipient.c_str(), "F4FXL"); + EXPECT_STREQ(body.c_str(), "Salut, comlent vas tu?"); + } + + TEST_F(RSMS1AMessageBuilder_parseMessage, ErrorInHeader) + { + std::string message = "$$Msg,KC3FRB,F4FXL,001118Saluto, comment vas tu?z\r"; + std::string sender, recipient, body; + + auto ret = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, message); + + EXPECT_EQ(ret, RSMS_FAIL); + EXPECT_STREQ(sender.c_str(), ""); + EXPECT_STREQ(recipient.c_str(), ""); + EXPECT_STREQ(body.c_str(), ""); + } +}; \ No newline at end of file diff --git a/Tests/SlowDataEncoder/GPSData.cpp b/Tests/SlowDataEncoder/GPSData.cpp new file mode 100644 index 0000000..85fe6c6 --- /dev/null +++ b/Tests/SlowDataEncoder/GPSData.cpp @@ -0,0 +1,78 @@ +/* + * 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 "SlowDataEncoder.h" +#include "DStarDefines.h" + +namespace SlowDataEncoderTests +{ + class SlowDataEncoder_gpsData : public ::testing::Test { + + }; + + TEST_F(SlowDataEncoder_gpsData, gpsDataCorrectlySet) + { + CSlowDataEncoder encoder; + encoder.setGPSData("ABCDEFGHIJKLMN"); + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getGPSData(buffer); + encoder.getGPSData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E'); + + encoder.getGPSData(buffer); + encoder.getGPSData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'I'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'J'); + + encoder.getGPSData(buffer); + encoder.getGPSData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x4U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'K'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'M'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'N'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + // all the remaining data shall be filled with 'f' + for(unsigned int i = 18; i < 60U; i+= 6U) { + encoder.getGPSData(buffer); + encoder.getGPSData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + } +} diff --git a/Tests/SlowDataEncoder/HeaderData.cpp b/Tests/SlowDataEncoder/HeaderData.cpp new file mode 100644 index 0000000..9948e78 --- /dev/null +++ b/Tests/SlowDataEncoder/HeaderData.cpp @@ -0,0 +1,139 @@ +/* + * 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 "SlowDataEncoder.h" +#include "DStarDefines.h" + +namespace SlowDataEncoderTests +{ + class SlowDataEncoder_headerData : public ::testing::Test { + + }; + + TEST_F(SlowDataEncoder_headerData, headerDataCorrectlySet) + { + CHeaderData header; + header.setFlags('1', '2', '3'); + header.setMyCall1("F4FXL"); + header.setMyCall2("5100"); + header.setYourCall("CQCQCQ"); + header.setRptCall1("F5ZEE B"); + header.setRptCall2("F5ZEE G"); + + CSlowDataEncoder encoder; + encoder.setHeaderData(header); + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '1'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '2'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '3'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'F'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, '5'); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Z'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'E'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'E'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'F'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '5'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'Z'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E'); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'E'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'B'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'C'); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Q'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'C'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'Q'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'C'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'Q'); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'F'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '4'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'F'); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'X'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '5'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '1'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '0'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '0'); + EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); // we do not check the actual result of the CRC, we just make sure is inot 'f' + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U); + EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); // done with crc check, remaining shall be filled with 'f' + + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + encoder.getHeaderData(buffer); + encoder.getHeaderData(buffer + 3); + // all the remaining data shall be filled with 'f' + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } +} diff --git a/Tests/SlowDataEncoder/InterleavedData.cpp b/Tests/SlowDataEncoder/InterleavedData.cpp new file mode 100644 index 0000000..7d225e1 --- /dev/null +++ b/Tests/SlowDataEncoder/InterleavedData.cpp @@ -0,0 +1,865 @@ +/* + * 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 + +#include "SlowDataEncoder.h" +#include "DStarDefines.h" + +namespace SlowDataEncoderTests +{ + class SlowDataEncoder_interleavedData : public ::testing::Test { + + }; + + TEST_F(SlowDataEncoder_interleavedData, gpsAndHeaderData) + { + // Header is never interleaved, text and header are sent as two blocks + CHeaderData header; + unsigned char headerData[RADIO_HEADER_LENGTH_BYTES]; + ::memset(headerData, 'H', RADIO_HEADER_LENGTH_BYTES); + header.setData(headerData, RADIO_HEADER_LENGTH_BYTES, false); + + //here we only test for correct interleaving + CSlowDataEncoder encoder; + encoder.setGPSData("GGGGGGGG"); // 8 times G + encoder.setHeaderData(header); + + for(unsigned int i = 0U; i < 2U; i++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x3U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + // all the remaining data shall be filled with 'f' until next block + for(unsigned int j = 12; j < 60U; j+= 6U) { + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f');// this is checksum byte, just make sure is is not 'f' + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U); + EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f');// this is checksum byte, just make sure is is not 'f' + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + //remaining data is only 'f' + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + + TEST_F(SlowDataEncoder_interleavedData, textAndHeaderData) + { + // Header is never interleaved, text and header are sent as two blocks + CHeaderData header; + unsigned char headerData[RADIO_HEADER_LENGTH_BYTES]; + ::memset(headerData, 'H', RADIO_HEADER_LENGTH_BYTES); + header.setData(headerData, RADIO_HEADER_LENGTH_BYTES, false); + + //here we only test for correct interleaving + CSlowDataEncoder encoder; + encoder.setTextData("TTTTTT"); // 6 times T + encoder.setHeaderData(header); + + auto dataLen = encoder.getInterleavedDataLength(); + EXPECT_EQ(dataLen, 120); //2* 60 + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'T'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'T'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'T'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + // all the remaining data shall be filled with 'f' until next block + for(unsigned int j = 24; j < 60U; j+= 6U) { + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f');// this is checksum byte, just make sure is is not 'f' + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U); + EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f');// this is checksum byte, just make sure is is not 'f' + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + //remaining data is only 'f' + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + + TEST_F(SlowDataEncoder_interleavedData, gpsTextAndHeaderData) + { + CHeaderData header; + unsigned char headerData[RADIO_HEADER_LENGTH_BYTES]; + ::memset(headerData, 'H', RADIO_HEADER_LENGTH_BYTES); + header.setData(headerData, RADIO_HEADER_LENGTH_BYTES, false); + + //here we only test for correct interleaving + CSlowDataEncoder encoder; + encoder.setTextData("TTTTTT"); // 6 times T + encoder.setGPSData("GGGGGGGGGGGGGGGGGGGGGG"); // 22 times G + encoder.setHeaderData(header); + + auto dataLen = encoder.getInterleavedDataLength(); + + EXPECT_EQ(dataLen, 120);// including data type bytes we need 54 (20 + 5 + 22 + 6) bytes, this shall be rounded up to next block size multiple, in this case 60 + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'T'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'T'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'T'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + // header is not interleaved, attached as one contiguous block, but it only starts at the nex block + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H'); + EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f');// this is checksum byte, just make sure is is not 'f' + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U); + EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f');// this is checksum byte, just make sure is is not 'f' + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + //remaining data is only 'f' + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + + TEST_F(SlowDataEncoder_interleavedData, gpsAndTextData) + { + //here we only test for correct interleaving + CSlowDataEncoder encoder; + encoder.setTextData("TTTTTT"); // 6 times T + encoder.setGPSData("GGGGGGGGGGGGGGGGGGGGGG"); // 22 times G + + auto dataLen = encoder.getInterleavedDataLength(); + + EXPECT_EQ(dataLen, 60);// including data type bytes we need 54 (20 + 5 + 22 + 6) bytes, this shall be rounded up to next block size multiple, in this case 60 + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'T'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'T'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'T'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G'); + + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + //remaining shall only be 'f" + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + + TEST_F(SlowDataEncoder_interleavedData, onlyGPSData) + { + CSlowDataEncoder encoder; + encoder.setGPSData("ABCDEFGHIJKLMN"); + + auto dataLen = encoder.getInterleavedDataLength(); + EXPECT_EQ(dataLen, 60); + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'I'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'J'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x4U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'K'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'M'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'N'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + // all the remaining data shall be filled with 'f' + for(unsigned int i = 18; i < 60U; i+= 6U) { + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + } + + TEST_F(SlowDataEncoder_interleavedData, onlyTextData) + { + CSlowDataEncoder encoder; + encoder.setTextData("ABCDEFG"); + + auto dataLen = encoder.getInterleavedDataLength(); + EXPECT_EQ(dataLen, 60); + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + // all the remaining data shall be filled with 'f' + for(unsigned int i = 24; i < 60U; i+= 6U) { + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + } + + TEST_F(SlowDataEncoder_interleavedData, onlyHeaderData) + { + CHeaderData header; + header.setFlags('1', '2', '3'); + header.setMyCall1("F4FXL"); + header.setMyCall2("5100"); + header.setYourCall("CQCQCQ"); + header.setRptCall1("F5ZEE B"); + header.setRptCall2("F5ZEE G"); + + CSlowDataEncoder encoder; + encoder.setHeaderData(header); + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '1'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '2'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '3'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'F'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, '5'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Z'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'E'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'E'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'F'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '5'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'Z'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'E'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'B'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'C'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Q'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'C'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'Q'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'C'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'Q'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'F'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '4'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'F'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'X'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '5'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '1'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '0'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '0'); + EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); // we do not check the actual result of the CRC, we just make sure is inot 'f' + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U); + EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); // done with crc check, remaining shall be filled with 'f' + + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + + encoder.getInterleavedData(buffer); + encoder.getInterleavedData(buffer + 3); + // all the remaining data shall be filled with 'f' + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } +} diff --git a/Tests/SlowDataEncoder/TextData.cpp b/Tests/SlowDataEncoder/TextData.cpp new file mode 100644 index 0000000..22c4c7d --- /dev/null +++ b/Tests/SlowDataEncoder/TextData.cpp @@ -0,0 +1,87 @@ +/* + * 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 "SlowDataEncoder.h" +#include "DStarDefines.h" + +namespace SlowDataEncoderTests +{ + class SlowDataEncoder_textData : public ::testing::Test { + + }; + + TEST_F(SlowDataEncoder_textData, textDataCorrectlySet) + { + CSlowDataEncoder encoder; + encoder.setTextData("ABCDEFG"); + + for(unsigned int testCount = 0U; testCount < 2U; testCount++) { + unsigned char buffer[6U]; + + encoder.getTextData(buffer); + encoder.getTextData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E'); + + encoder.getTextData(buffer); + encoder.getTextData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getTextData(buffer); + encoder.getTextData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + encoder.getTextData(buffer); + encoder.getTextData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' '); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' '); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' '); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' '); + + // all the remaining data shall be filled with 'f' + for(unsigned int i = 24; i < 60U; i+= 6U) { + encoder.getTextData(buffer); + encoder.getTextData(buffer + 3); + EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f'); + EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f'); + EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f'); + EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); + } + } + } +} diff --git a/Tests/StringUtils/stringToPort.cpp b/Tests/StringUtils/stringToPort.cpp new file mode 100644 index 0000000..6770a89 --- /dev/null +++ b/Tests/StringUtils/stringToPort.cpp @@ -0,0 +1,86 @@ +/* + * 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 "StringUtils.h" + +namespace StringUtilsTests +{ + + class StringUtils_stringToPort : public ::testing::Test { + + }; + + TEST_F(StringUtils_stringToPort, EmptyStringReturn0) + { + unsigned int port = CStringUtils::stringToPort(""); + + EXPECT_EQ(port, 0U); + } + + TEST_F(StringUtils_stringToPort, SpaceStringReturn0) + { + unsigned int port = CStringUtils::stringToPort(" "); + + EXPECT_EQ(port, 0U); + } + + TEST_F(StringUtils_stringToPort, NumberStringReturnsCorrectValue) + { + unsigned int port = CStringUtils::stringToPort("12345"); + + EXPECT_EQ(port, 12345U); + } + + TEST_F(StringUtils_stringToPort, NumberStringWithSpacesReturnsCorrectValue) + { + unsigned int port = CStringUtils::stringToPort(" 12345 "); + + EXPECT_EQ(port, 12345U); + } + + TEST_F(StringUtils_stringToPort, StringWithMixedAlphaAndNumbersreturns0) + { + unsigned int port = CStringUtils::stringToPort("123abc456"); + + EXPECT_EQ(port, 0U); + } + + TEST_F(StringUtils_stringToPort, TooLargeValueReturns0) + { + unsigned int port = CStringUtils::stringToPort("999999"); + + EXPECT_EQ(port, 0U); + } + + + TEST_F(StringUtils_stringToPort, TestAllNumDigits) + { + EXPECT_EQ(CStringUtils::stringToPort("10"), 10U); + EXPECT_EQ(CStringUtils::stringToPort("11"), 11U); + EXPECT_EQ(CStringUtils::stringToPort("12"), 12U); + EXPECT_EQ(CStringUtils::stringToPort("13"), 13U); + EXPECT_EQ(CStringUtils::stringToPort("14"), 14U); + EXPECT_EQ(CStringUtils::stringToPort("15"), 15U); + EXPECT_EQ(CStringUtils::stringToPort("16"), 16U); + EXPECT_EQ(CStringUtils::stringToPort("17"), 17U); + EXPECT_EQ(CStringUtils::stringToPort("18"), 18U); + EXPECT_EQ(CStringUtils::stringToPort("19"), 19U); + } +} \ No newline at end of file diff --git a/Tests/Utils/swap_endian.cpp b/Tests/Utils/swap_endian.cpp new file mode 100644 index 0000000..af94adc --- /dev/null +++ b/Tests/Utils/swap_endian.cpp @@ -0,0 +1,33 @@ +/* + * 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 "Utils.h" + +class Utils_swap_endian : public ::testing::Test { + +}; + +TEST_F(Utils_swap_endian, SwapUINT32) { + uint32_t test = 0x12345678U; + + uint32_t res = CUtils::swap_endian(test); + + EXPECT_EQ(res, 0x78563412U); +} \ No newline at end of file diff --git a/Tests/Utils/swap_endian_be.cpp b/Tests/Utils/swap_endian_be.cpp new file mode 100644 index 0000000..50f7cb2 --- /dev/null +++ b/Tests/Utils/swap_endian_be.cpp @@ -0,0 +1,45 @@ +/* + * 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 "Utils.h" +#include "StringUtils.h" +#include "Log.h" + +class Utils_swap_endian_be : public ::testing::Test { + +}; + +// This test will fail on big endian systems.... +TEST_F(Utils_swap_endian_be, SwapUINT32_be) { + uint32_t test = 0x12345678U; + + uint32_t res = CUtils::swap_endian_be(test); + + EXPECT_EQ(res, 0x12345678U); +} + + +TEST_F(Utils_swap_endian_be, blabla) { + std::vector charsToEscape = {-17, 0, 17, 19, 128-2, -25, 26, 128-3, -1, 36, 13, 44}; + + for(unsigned int i = 1U; i < charsToEscape.size(); i++) { + CLog::logDebug("%08x - %08x", charsToEscape[i], (unsigned char)charsToEscape[i]); + } +} diff --git a/VersionInfo/Makefile b/VersionInfo/Makefile new file mode 100644 index 0000000..0753691 --- /dev/null +++ b/VersionInfo/Makefile @@ -0,0 +1,23 @@ +GitVersion.h : FORCE +ifneq ("$(wildcard ../.git/index)","") + @echo "#pragma once" > /tmp/$@ + @echo "#include " >> /tmp/$@ + @echo "const std::string gitversion(\"$(shell git rev-parse --short HEAD)\");" >> /tmp/$@ +else + @echo "#pragma once" > /tmp/$@ + @echo "#include " >> /tmp/$@ + @echo "const std::string gitversion(\"0000000\");" >> /tmp/$@ +endif + @cmp -s /tmp/$@ $@; \ + RETVAL=$$?; \ + if [ $$RETVAL -ne 0 ]; then \ + echo "Git version has changed"; \ + cp -f /tmp/$@ $@; \ + fi; \ + rm /tmp/$@; + +.PHONY: clean +clean: + $(RM) *.o *.d GitVersion.h + +FORCE: diff --git a/Version.h b/VersionInfo/Version.h similarity index 92% rename from Version.h rename to VersionInfo/Version.h index e888470..ddad33d 100644 --- a/Version.h +++ b/VersionInfo/Version.h @@ -26,6 +26,7 @@ const std::string PRODUCT_NAME("DStarGateway"); const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA and Contributors"); -const std::string VERSION("0.4"); +const std::string VERSION("0.5"); +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; diff --git a/debian/dstargateway.service b/debian/dstargateway.service index eeada84..4f8c069 100644 --- a/debian/dstargateway.service +++ b/debian/dstargateway.service @@ -3,14 +3,16 @@ User=dstar Description=D-STAR Gateway Daemon After=network.target,network-online.target Wants=network-online.target -RestartSec=5 -StartLimitIntervalSec=60 -StartLimitBurst=0 - [Service] Type=simple ExecStart=/usr/local/bin/dstargateway %CFG_DIR%/dstargateway.cfg +Restart=on-failure +RestartSec=5 +StartLimitIntervalSec=60 +StartLimitBurst=0 [Install] WantedBy=multi-user.target + +