From 38d016d5408e98cfa2b63bc346b78e345ce8e336 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Wed, 29 Dec 2021 22:23:10 +0100 Subject: [PATCH] Remove dependency to libconfig++ #2 --- Config.cpp | 281 +++++++++++++++++++++++ Config.h | 114 ++++++++++ DStarGatewayApp.cpp | 7 +- DStarGatewayConfig.cpp | 498 +++++++++++++++-------------------------- DStarGatewayConfig.h | 49 ++-- Makefile | 2 +- 6 files changed, 600 insertions(+), 351 deletions(-) create mode 100644 Config.cpp create mode 100644 Config.h diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 0000000..210d26a --- /dev/null +++ b/Config.cpp @@ -0,0 +1,281 @@ +/* + * 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 "Utils.h" +#include "Config.h" +#include "Log.h" + +CConfig::CConfig(const std::string filename) : +m_filename(filename) +{ +} + +CConfig::~CConfig() +{ + for(auto it = m_sections.begin(); it != m_sections.end(); it++) { + delete it->second; + } + m_sections.clear(); +} + +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); + return false; + } + + CConfigSection * currentSection = nullptr; + while(!file.eof()) { + std::string line; + std::getline(file, line); + stripComment(line); + + if(isConfigSection(line)) { + auto it = m_sections.find(line); + currentSection = it != m_sections.end() ? it->second : nullptr; + + if(currentSection == nullptr) { + currentSection = new CConfigSection(line); + m_sections[currentSection->getName()] = currentSection; + } + } else if(isConfigValue(line) && currentSection != nullptr) { + TConfigValue * configValue = readKeyAndValue(line); + if(configValue != nullptr) { + currentSection->getValues()[configValue->m_key] = configValue; + } + } + } + + file.close(); + return true; +} + +void CConfig::stripComment(std::string& s) const +{ + boost::trim(s); + + if(s.length() > 0 && s[0] == '#') { + //we have a lien consisting only of comment, truncate it and leave + s.resize(0); + return; + } + + char * sdup = strdup(s.c_str()); + char * uncommentedPart = strtok(sdup, "#"); + + s = std::string(uncommentedPart != nullptr ? uncommentedPart : ""); + + boost::trim(s); + + free(sdup);// could we use delete sdup here? +} + +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 : ""); + + res = new TConfigValue; + res->m_key = key; + res->m_value = boost::trim_copy(value); + } + + 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 +{ + std::string valueTemp; + std::string dafaultValueStr = defaultValue ? "true" : "false"; + bool ret = getValue(section, key, valueTemp, dafaultValueStr, {"true", "1", "false", "0"}); + if(ret) { + if(isSameNoCase(valueTemp, std::string("true")) || valueTemp == "1") + value = true; + else if(isSameNoCase(valueTemp, std::string("false")) || valueTemp == "0") + value = false; + } + + return ret; +} + +bool CConfig::getValue(const std::string §ion, const std::string& key, int &value, int min, int max, int defaultValue) const +{ + TConfigValue * val = lookupValue(section, key); + if(val == nullptr || val->m_value.empty()) { + value = defaultValue; + return true; + } + + if(!isDecimalInteger(val->m_value)){ + CLog::logError("Configuration error: %s.%s is not a valid number (%s)", section.c_str(), key.c_str(), val->m_value.c_str()); + return false; + } + + int tmpValue = std::strtol(val->m_value.c_str(), nullptr, 10); + + if(tmpValue < min || tmpValue > max) { + CLog::logError("Configuration error: %s.%s is out of range must be between %d and %d, actual %d", section.c_str(), key.c_str(), min, max, tmpValue); + return false; + } + + value = tmpValue; + return true; +} + +bool CConfig::getValue(const std::string §ion, const std::string& key, double &value, double min, double max, double defaultValue) const +{ + TConfigValue * val = lookupValue(section, key); + if(val == nullptr || val->m_value.empty()) { + value = defaultValue; + return true; + } + + if(!isFloatingPoint(val->m_value)){ + CLog::logError("Configuration error: %s.%s is not a valid floating point number (%s)", section.c_str(), key.c_str(), val->m_value.c_str()); + return false; + } + + double tmpValue = std::strtold(val->m_value.c_str(), nullptr); + + if(tmpValue < min || tmpValue > max) { + CLog::logError("Configuration error: %s.%s is out of range must be between %f and %f, actual %f", section.c_str(), key.c_str(), min, max, tmpValue); + return false; + } + + value = tmpValue; + return true; +} + +bool CConfig::getValue(const std::string §ion, const std::string& key, unsigned int &value, unsigned int min, unsigned int max, int defaultValue) const +{ + TConfigValue * val = lookupValue(section, key); + if(val == nullptr || val->m_value.empty()) { + value = defaultValue; + return true; + } + + if(!isDecimalInteger(val->m_value)){ + CLog::logError("Configuration error: %s.%s is not a valid number (%s)", section.c_str(), key.c_str(), val->m_value.c_str()); + return false; + } + + unsigned int tmpValue = std::strtol(val->m_value.c_str(), nullptr, 10); + + if(tmpValue < min || tmpValue > max) { + CLog::logError("Configuration error: %s.%s is out of range must be between %u and %u, actual %u", section.c_str(), key.c_str(), min, max, tmpValue); + return false; + } + + value = tmpValue; + return true; +} + +bool CConfig::getValue(const std::string §ion, const std::string& key, std::string &value, unsigned int minLen, unsigned int maxLen, const std::string defaultValue) const +{ + TConfigValue * val = lookupValue(section, key); + std::string valueTemp = val != nullptr ? val->m_value : defaultValue; + + if(valueTemp.empty() && minLen == 0U) + valueTemp = defaultValue; + + if(valueTemp.length() < minLen || valueTemp.length() > maxLen) { + CLog::logError("Configuration error: %s.%s has an invalid length, length must be between %u and %u, actual %u", section.c_str(), key.c_str(), minLen, maxLen, valueTemp.length()); + return false; + } + + value = valueTemp; + return true; +} + +bool CConfig::getValue(const std::string §ion, const std::string& key, unsigned char &value, unsigned char min, unsigned char max,unsigned char defaultValue) const +{ + unsigned int tempValue; + bool ret = getValue(section, key, tempValue, (unsigned int)min, (unsigned int)max, (unsigned int) defaultValue); + if(ret) { + value = (unsigned char)tempValue; + } + + return ret; +} + +bool CConfig::getValue(const std::string §ion, const std::string& key, std::string &value, const std::string defaultValue, const std::vector& allowedValues) const +{ + std::string valueTemp; + if(getValue(section, key, valueTemp, 0U, 2048, defaultValue)) { + for(auto s : allowedValues) { + if(isSameNoCase(s, valueTemp)) { + value = CUtils::ToLower(valueTemp); + return true; + } + } + } + + // Build error message + std::stringstream allowedValuesStreamStr; + for(auto s : allowedValues) { + allowedValuesStreamStr << s << ", "; + } + + // We want nice looking error message, so get rid of trailing ", " + std::string allowedValuesStr = allowedValuesStreamStr.str(); + allowedValuesStr.resize(allowedValuesStr.length() - 2); + + CLog::logError("Configuration error: %s.%s has an invalid value, valid values are: %s. Actual: %s", section.c_str(), key.c_str(), allowedValuesStr.c_str(), valueTemp.c_str()); + return false; +} + +TConfigValue * CConfig::lookupValue(const std::string& sectionName, const std::string& key) const +{ + TConfigValue * res = nullptr; + std::string sectionNameTemp = sectionName; + + // Make sure we have brackets around section names + if(sectionNameTemp[0] != '[') sectionNameTemp = "[" + sectionNameTemp; + if(sectionNameTemp[sectionNameTemp.length() - 1] != ']') sectionNameTemp.push_back(']'); + + auto itSection = m_sections.find(sectionNameTemp); + + if(itSection != m_sections.end()) { + auto itValue = itSection->second->getValues().find(key); + if(itValue != itSection->second->getValues().end()) { + res = itValue->second; + } + } + + return res; +} \ No newline at end of file diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..d25bca6 --- /dev/null +++ b/Config.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define isConfigSection(s)(!s.empty() && s[0] == '[' && s[s.length() - 1] == ']') +#define isConfigValue(s)(s.find('=') != std::string::npos && s.find('#') > s.find('=')) +#define isSameNoCase(a,b)(strcasecmp(a.c_str(), b.c_str()) == 0) +#define isDecimalInteger(s)(!s.empty() && (s[0] == '-' || std::isdigit(s[0])) && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !(std::isdigit(c) || c == '-'); }) == s.end()) +#define isFloatingPoint(s)(!s.empty() && (s[0] == '-' || std::isdigit(s[0])) && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !(std::isdigit(c) || c == '-' || c == '.'); }) == s.end()) + +// https://www.programmingnotes.org/6504/c-how-to-make-map-unordered-map-keys-case-insensitive-using-c/ +struct case_insensitive_unordered_map { + struct comp { + bool operator() (const std::string& lhs, const std::string& rhs) const { + // On non Windows OS, use the function "strcasecmp" in #include + return strcasecmp(lhs.c_str(), rhs.c_str()) == 0; + } + }; + struct hash { + std::size_t operator() (std::string str) const { + for (std::size_t index = 0; index < str.size(); ++index) { + auto ch = static_cast(str[index]); + str[index] = static_cast(std::tolower(ch)); + } + return std::hash{}(str); + } + }; +}; + +typedef struct { + std::string m_key; + std::string m_value; +} TConfigValue; + +typedef std::unordered_map CConfigValuesMap; + +class CConfigSection { +public: + CConfigSection(const std::string name) : + m_name(name) + { + assert(!name.empty()); + } + + ~CConfigSection() + { + for(auto it = m_values.begin();it != m_values.end();it++) { + delete it->second; + } + m_values.clear(); + } + + std::string getName() const + { + return m_name; + } + CConfigValuesMap & getValues() + { + return m_values; + } +private: + std::string m_name; + CConfigValuesMap m_values; +}; + +typedef std::unordered_map CConfigSectionsMap; + +class CConfig +{ +public: + CConfig(const std::string filename); + ~CConfig(); + + bool load(); + bool getValue(const std::string §ion, const std::string& key, bool &value, bool defaultValue) const; + bool getValue(const std::string §ion, const std::string& key, int &value, int min, int max, int defaultValue) const; + bool getValue(const std::string §ion, const std::string& key, double &value, double min, double max, double defaultValue) const; + bool getValue(const std::string §ion, const std::string& key, unsigned int &value, unsigned int min, unsigned int max,int defaultValue) const; + bool getValue(const std::string §ion, const std::string& key, unsigned char &value, unsigned char min, unsigned char max,unsigned char defaultValue) const; + bool getValue(const std::string §ion, const std::string& key, std::string &value, unsigned int minLen, unsigned int maxLen, const std::string defaultValue) const; + bool getValue(const std::string §ion, const std::string& key, std::string &value, const std::string defaultValue, const std::vector& allowedValues) const; + +private: + void stripComment(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; + CConfigSectionsMap m_sections; +}; diff --git a/DStarGatewayApp.cpp b/DStarGatewayApp.cpp index 3e41146..bfe9e31 100644 --- a/DStarGatewayApp.cpp +++ b/DStarGatewayApp.cpp @@ -98,10 +98,13 @@ bool CDStarGatewayApp::createThread() return false; } + TLog log; + config.getLog(log); + CLog::initialize(log.logDir + "dstargateway.log", LS_INFO, true); + Tpaths paths; config.getPaths(paths); - CLog::initialize(paths.logDir + "dstargateway.log", LS_INFO, true); - m_thread = new CDStarGatewayThread(paths.logDir, paths.dataDir, ""); + m_thread = new CDStarGatewayThread(log.logDir, paths.dataDir, ""); // Setup the gateway TGateway gatewayConfig; diff --git a/DStarGatewayConfig.cpp b/DStarGatewayConfig.cpp index b4c728d..8ee8ecd 100644 --- a/DStarGatewayConfig.cpp +++ b/DStarGatewayConfig.cpp @@ -35,340 +35,274 @@ CDStarGatewayConfig::CDStarGatewayConfig(const std::string &pathname) bool CDStarGatewayConfig::load() { - CLog::logInfo("Loading configuration from %s", m_fileName.c_str()); - Config cfg; - if(open(cfg) - && loadGateway(cfg) - && loadIrcDDB(cfg) - && loadRepeaters(cfg) - && loadPaths(cfg) - && loadAPRS(cfg) - && loadDextra(cfg) - && loadDCS(cfg) - && loadDPlus(cfg) - && loadRemote(cfg) - && loadXLX(cfg)) { + bool ret = false; + CLog::logInfo("Loading Configuration from %s", m_fileName.c_str()); + CConfig cfg(m_fileName); + ret = open(cfg); + if(ret) { + ret = loadGateway(cfg) && ret; + ret = loadIrcDDB(cfg) && ret; + ret = loadRepeaters(cfg) && ret; + ret = loadPaths(cfg) && ret; + ret = loadLog(cfg) && ret; + ret = loadAPRS(cfg) && ret; + ret = loadDextra(cfg) && ret; + ret = loadDCS(cfg) && ret; + ret = loadDPlus(cfg) && ret; + ret = loadRemote(cfg) && ret; + ret = loadXLX(cfg) && ret; + } + + if(ret) { //properly size values m_gateway.callsign.resize(LONG_CALLSIGN_LENGTH - 1U, ' '); m_gateway.callsign.push_back('G'); - - return true; + } + else { + CLog::logError("Loading Configuration from %s failed", m_fileName.c_str()); } - CLog::logError("Loading configuration from %s failed", m_fileName.c_str()); - - return false; + return ret; } -bool CDStarGatewayConfig::loadXLX(const Config & cfg) +bool CDStarGatewayConfig::loadXLX(const CConfig & cfg) { - bool ret = get_value(cfg, "xlx.enabled", m_xlx.enabled, true); - ret = get_value(cfg, "xlx.hostfileUrl", m_xlx.url, 0, 1024, "", false) && ret; + bool ret = cfg.getValue("xlx", "enabled", m_xlx.enabled, true); + ret = cfg.getValue("xlx", "hostfileUrl", m_xlx.url, 0, 1024, "") && ret; + + m_xlx.enabled = m_xlx.enabled && !m_xlx.url.empty(); return ret; } -bool CDStarGatewayConfig::loadRemote(const Config & cfg) +bool CDStarGatewayConfig::loadRemote(const CConfig & cfg) { - bool ret = get_value(cfg, "remote.enabled", m_remote.enabled, false); - ret = get_value(cfg, "remote.port", m_remote.port, 1U, 65535U, 4242U) && ret; - ret = get_value(cfg, "remote.password", m_remote.password, 0, 1024, "", true) && ret; + bool ret = cfg.getValue("remote", "enabled", m_remote.enabled, false); + ret = cfg.getValue("remote", "port", m_remote.port, 1U, 65535U, 4242U) && ret; + ret = cfg.getValue("remote", "password", m_remote.password, 0, 1024, "") && ret; m_remote.enabled = m_remote.enabled && !m_remote.password.empty(); return ret; } -bool CDStarGatewayConfig::loadDextra(const Config & cfg) +bool CDStarGatewayConfig::loadDextra(const CConfig & cfg) { - bool ret = get_value(cfg, "dextra.enabled", m_dextra.enabled, true); - ret = get_value(cfg, "dextra.maxDongles", m_dextra.maxDongles, 1U, 5U, 5U) && ret; + bool ret = cfg.getValue("dextra", "enabled", m_dextra.enabled, true); + ret = cfg.getValue("dextra", "maxDongles", m_dextra.maxDongles, 1U, 5U, 5U) && ret; return ret; } -bool CDStarGatewayConfig::loadDPlus(const Config & cfg) +bool CDStarGatewayConfig::loadDPlus(const CConfig & cfg) { - bool ret = get_value(cfg, "dplus.enabled", m_dplus.enabled, true); - ret = get_value(cfg, "dplus.maxDongles", m_dplus.maxDongles, 1U, 5U, 5U) && ret; - ret = get_value(cfg, "dplus.login", m_dplus.login, 0, LONG_CALLSIGN_LENGTH, m_gateway.callsign, true) && ret; + bool ret = cfg.getValue("dplus", "enabled", m_dplus.enabled, true); + ret = cfg.getValue("dplus", "maxDongles", m_dplus.maxDongles, 1U, 5U, 5U) && ret; + ret = cfg.getValue("dplus", "login", m_dplus.login, 0, LONG_CALLSIGN_LENGTH, m_gateway.callsign) && ret; + m_dplus.enabled = m_dplus.enabled && !m_dplus.login.empty(); m_dplus.login = CUtils::ToUpper(m_dplus.login); return ret; } -bool CDStarGatewayConfig::loadDCS(const Config & cfg) +bool CDStarGatewayConfig::loadDCS(const CConfig & cfg) { - bool ret = get_value(cfg, "dcs.enabled", m_dcs.enabled, true); + bool ret = cfg.getValue("dcs", "enabled", m_dcs.enabled, true); return ret; } -bool CDStarGatewayConfig::loadAPRS(const Config & cfg) +bool CDStarGatewayConfig::loadAPRS(const CConfig & cfg) { - bool ret = get_value(cfg, "aprs.enabled", m_aprs.enabled, false); - ret = get_value(cfg, "aprs.port", m_aprs.port, 1U, 65535U, 14580U) && ret; - ret = get_value(cfg, "aprs.hostname", m_aprs.hostname, 0, 1024, "rotate.aprs2.net", true) && ret; - ret = get_value(cfg, "aprs.password", m_aprs.password, 0U, 30U, "", true) && ret; + bool ret = cfg.getValue("aprs", "enabled", m_aprs.enabled, false); + ret = cfg.getValue("aprs", "port", m_aprs.port, 1U, 65535U, 14580U) && ret; + ret = cfg.getValue("aprs", "hostname", m_aprs.hostname, 0, 1024, "rotate.aprs2.net") && ret; + ret = cfg.getValue("aprs", "password", m_aprs.password, 0U, 30U, "") && ret; + + m_aprs.enabled = m_aprs.enabled && !m_aprs.password.empty(); return ret; } -bool CDStarGatewayConfig::loadPaths(const Config & cfg) +bool CDStarGatewayConfig::loadLog(const CConfig & cfg) { - get_value(cfg, "paths.log", m_paths.logDir, 0, 2048, "/var/log/dstargateway/", true); - get_value(cfg, "paths.data", m_paths.dataDir, 0, 2048, "/var/log/dstargateway/", true); - - if(m_paths.logDir[m_paths.logDir.length() - 1] != '/') { - m_paths.logDir.push_back('/'); - } + bool ret =cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/"); - if(m_paths.dataDir[m_paths.dataDir.length() - 1] != '/') { - m_paths.dataDir.push_back('/'); + if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') { + m_log.logDir.push_back('/'); } //TODO 20211226 check if directory are accessible - return true; + return ret; } -bool CDStarGatewayConfig::loadRepeaters(const Config & cfg) +bool CDStarGatewayConfig::loadPaths(const CConfig & cfg) { - cfg.lookup("repeaters"); - // repeater parameters - for (int i=0; icallsign, 0, 7, m_gateway.callsign, true)) { - CUtils::ToUpper(repeater->callsign); - } - - if(get_value(cfg, key.str() + ".band", repeater->band, 1, 1, "A")) { - CUtils::ToUpper(repeater->band); - } - - if (get_value(cfg, key.str() + ".address", repeater->address, 0, 15, "127.0.0.1", true)) { - // ??? - } + bool ret = cfg.getValue("paths", "data", m_paths.dataDir, 0, 2048, "/usr/local/share/dstargateway.d/"); - if(get_value(cfg, key.str() + ".port", repeater->port, 1U, 65535U, 20011U)) { - // ??? - } + if(ret && m_paths.dataDir[m_paths.dataDir.length() - 1] != '/') { + m_paths.dataDir.push_back('/'); + } - std::string hwType; - if(get_value(cfg, key.str() + ".type", hwType, 1, 5, "", false, {"hb", "icom"} )) { - if(hwType == "hb") repeater->hwType = HW_HOMEBREW; - else if(hwType == "icom") repeater->hwType = HW_ICOM; - } + //TODO 20211226 check if directory are accessible - if (get_value(cfg, key.str() + ".reflector", repeater->reflector, 0, LONG_CALLSIGN_LENGTH, "", true)) { - // ??? - } + return ret; +} - if (get_value(cfg, key.str() + ".reflectorAtStartup", repeater->reflectorAtStartup, true)) { - // ??? - } +bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg) +{ + for(unsigned int i = 0; i < 4; i++) { + std::string section = ((std::stringstream() << "repeater_" << (i + 1))).str(); + bool repeaterEnabled; - std::string reconnect; - if (get_value(cfg, key.str() + ".reflectorReconnect", reconnect, 0, 5, "never", true, - {"never", "fixed", "5", "10", "15", "20", "25", "30", "60", "90", "120", "180"})) { - if(reconnect == "never") repeater->reflectorReconnect = RECONNECT_NEVER; - else if(reconnect == "5") repeater->reflectorReconnect = RECONNECT_5MINS; - else if(reconnect == "10") repeater->reflectorReconnect = RECONNECT_10MINS; - else if(reconnect == "15") repeater->reflectorReconnect = RECONNECT_15MINS; - else if(reconnect == "20") repeater->reflectorReconnect = RECONNECT_20MINS; - else if(reconnect == "25") repeater->reflectorReconnect = RECONNECT_25MINS; - else if(reconnect == "30") repeater->reflectorReconnect = RECONNECT_30MINS; - else if(reconnect == "60") repeater->reflectorReconnect = RECONNECT_60MINS; - else if(reconnect == "90") repeater->reflectorReconnect = RECONNECT_90MINS; - else if(reconnect == "120") repeater->reflectorReconnect = RECONNECT_120MINS; - else if(reconnect == "180") repeater->reflectorReconnect = RECONNECT_180MINS; - else if(reconnect == "fixed") repeater->reflectorReconnect = RECONNECT_FIXED; - } + bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false); + if(!ret || !repeaterEnabled) + continue; - if(get_value(cfg, key.str() + ".frequency", repeater->frequency, 0.0, 1500.0, 434.0)) { - // ??? - } - - if(get_value(cfg, key.str() + ".offset", repeater->offset, -50.0, 50.0, 0.0)) { - // ??? - } - - if(get_value(cfg, key.str() + ".rangeKm", repeater->range, 0.0, 3000.0, 0.0)) { - // ??? - } - - if(get_value(cfg, key.str() + ".latitude", repeater->latitude, -90, 90.0, m_gateway.latitude)) { - // ??? - } - - if(get_value(cfg, key.str() + ".longitude", repeater->longitude, -180, 180.0, m_gateway.longitude)) { - // ??? - } - - if(get_value(cfg, key.str() + ".agl", repeater->agl, 0, 1000.0, 0.0)) { - // ??? - } + TRepeater * repeater = new TRepeater; + ret = cfg.getValue(section, "band", repeater->band, 1, 2, "B") && ret; + ret = cfg.getValue(section, "callsign", repeater->callsign, 0, LONG_CALLSIGN_LENGTH - 1, m_gateway.callsign); + ret = cfg.getValue(section, "address", repeater->address, 0, 15, "127.0.0.1") && ret; + ret = cfg.getValue(section, "port", repeater->port, 1U, 65535U, 20011U) && ret; - if(get_value(cfg, key.str() + ".description1", m_gateway.description1, 0, 1024, "")) { - // ??? + std::string hwType; + ret = cfg.getValue(section, "type", hwType, "", {"hb", "icom"}) && ret; + if(ret) { + if(hwType == "hb") repeater->hwType = HW_HOMEBREW; + else if(hwType == "icom") repeater->hwType = HW_ICOM; } - if(get_value(cfg, key.str() + ".description2", m_gateway.description2, 0, 1024, "")) { - // ??? - } + ret = cfg.getValue(section, "reflector", repeater->reflector, 0, LONG_CALLSIGN_LENGTH, "") && ret; + ret = cfg.getValue(section, "reflectorAtStartup", repeater->reflectorAtStartup, !repeater->reflector.empty()) && ret; - if(get_value(cfg, key.str() + ".url", m_gateway.url, 0, 1024, "")) { - // ??? - } - - int band; - if(get_value(cfg, key.str() + ".band1", band, 0, 255, 0)) { - repeater->band1 = (unsigned char)band; - } - if(get_value(cfg, key.str() + ".band2", band, 0, 255, 0)) { - repeater->band2 = (unsigned char)band; - } - if(get_value(cfg, key.str() + ".band3", band, 0, 255, 0)) { - repeater->band3 = (unsigned char)band; + std::string reconnect; + ret = cfg.getValue(section, "reflectorReconnect", reconnect, "never", {"never", "fixed", "5", "10", "15", "20", "25", "30", "60", "90", "120", "180"}) && ret; + if(ret) { + if(reconnect == "never") repeater->reflectorReconnect = RECONNECT_NEVER; + else if(reconnect == "5") repeater->reflectorReconnect = RECONNECT_5MINS; + else if(reconnect == "10") repeater->reflectorReconnect = RECONNECT_10MINS; + else if(reconnect == "15") repeater->reflectorReconnect = RECONNECT_15MINS; + else if(reconnect == "20") repeater->reflectorReconnect = RECONNECT_20MINS; + else if(reconnect == "25") repeater->reflectorReconnect = RECONNECT_25MINS; + else if(reconnect == "30") repeater->reflectorReconnect = RECONNECT_30MINS; + else if(reconnect == "60") repeater->reflectorReconnect = RECONNECT_60MINS; + else if(reconnect == "90") repeater->reflectorReconnect = RECONNECT_90MINS; + else if(reconnect == "120") repeater->reflectorReconnect = RECONNECT_120MINS; + else if(reconnect == "180") repeater->reflectorReconnect = RECONNECT_180MINS; + else if(reconnect == "fixed") repeater->reflectorReconnect = RECONNECT_FIXED; } - // We have read a complete repeater record, validate it - if(repeater->callsign.length() > 0) { - isOk = true; + ret = cfg.getValue(section, "frequency", repeater->frequency, 0.1, 1500.0, 434.0) && ret; + ret = cfg.getValue(section, "offset", repeater->offset, -50.0, 50.0, 0.0) && ret; + ret = cfg.getValue(section, "rangeKm", repeater->range, 0.0, 3000.0, 0.0) && ret; + ret = cfg.getValue(section, "latitude", repeater->latitude, -90.0, 90.0, m_gateway.latitude) && ret; + ret = cfg.getValue(section, "longitude", repeater->longitude, -180.0, 180.0, m_gateway.longitude) && ret; + ret = cfg.getValue(section, "agl", repeater->agl, 0, 1000.0, 0.0) && ret; + ret = cfg.getValue(section, "description1", m_gateway.description1, 0, 1024, "") && ret; + ret = cfg.getValue(section, "description2", m_gateway.description2, 0, 1024, "") && ret; + ret = cfg.getValue(section, "url", m_gateway.url, 0, 1024, "") && ret;; + ret = cfg.getValue(section, "band1", repeater->band1, 0, 255, 0) && ret; + ret = cfg.getValue(section, "band2", repeater->band2, 0, 255, 0) && ret; + ret = cfg.getValue(section, "band3", repeater->band3, 0, 255, 0) && ret; + + if(ret) { + m_repeaters.push_back(repeater); } else { - CLog::logError("Repeater %d has an empty callsign", i); - } - - if (isOk && !isalpha(repeater->band[0])) { - isOk = false; - CLog::logError("Repeater %s band is not a letter", i); - } - - if (isOk && repeater->address.length() == 0) { - isOk = false; - } - - if(!isOk) { delete repeater; - } else { - m_repeaters.push_back(repeater); } } - return m_repeaters.size() > 0; + if(m_repeaters.size() == 0U) { + CLog::logError("Configuration error: no repeaters configured !"); + return false; + } + + return true; } -bool CDStarGatewayConfig::loadIrcDDB(const Config & cfg) +bool CDStarGatewayConfig::loadIrcDDB(const CConfig & cfg) { - //ircDDB Networks - for(int i = 0; i < cfg.lookup("ircddb").getLength(); i++) { - TircDDB * ircddb = new TircDDB(); - std::stringstream key; - key << "ircddb.[" << i << "].hostname"; - if(! get_value(cfg, key.str(), ircddb->hostname, 5, 30, "") || ircddb->hostname == "") {//do not allow hostname to be empty - delete ircddb; - continue; - } + bool ret = true; + for(unsigned int i = 0; i < 4; i++) { + std::string section = ((std::stringstream() << "ircddb_" << (i + 1))).str(); + bool ircEnabled; - key.str("");key.clear(); - key << "ircddb.[" << i << "].username"; - if (! get_value(cfg, key.str(), ircddb->username, 3, 8, m_gateway.callsign)) {//default user name to callsign - delete ircddb; + ret = cfg.getValue(section, "enabled", ircEnabled, false) && ret; + if(!ircEnabled) continue; - } - CUtils::ToUpper(ircddb->username); + + TircDDB * ircddb = new TircDDB; + ret = cfg.getValue(section, "hostname", ircddb->hostname, 0, 1024, "ircv4.openquad.net") && ret; + ret = cfg.getValue(section, "username", ircddb->username, 0, LONG_CALLSIGN_LENGTH - 1, m_gateway.callsign) && ret; + ret = cfg.getValue(section, "password", ircddb->password, 0, 1024, "") && ret; - key.str("");key.clear(); - key << "ircddb.[" << i << "].password"; - if(!get_value(cfg, key.str(), ircddb->password, 0, 30, "")) { + if(ret) { + m_ircDDB.push_back(ircddb); + } + else { delete ircddb; - continue; } - - ircddb->isQuadNet = ircddb->hostname.find("openquad.net") != std::string::npos; - this->m_ircDDB.push_back(ircddb); - } - - if(this->m_ircDDB.size() == 0) {//no ircddb network specified? Default to openquad! - TircDDB * ircddb = new TircDDB(); - ircddb->hostname = "ircv4.openquad.net"; - ircddb->password = ""; - ircddb->username = m_gateway.callsign; - ircddb->isQuadNet = true; - this->m_ircDDB.push_back(ircddb); - CLog::logInfo("No ircDDB networks configured, defaulting to IRCDDB: host=%s user=%s", ircddb->hostname.c_str(), ircddb->username.c_str()); } - return true; + return ret; } -bool CDStarGatewayConfig::loadGateway(const Config & cfg) +bool CDStarGatewayConfig::loadGateway(const CConfig & cfg) { - bool ret = get_value(cfg, "gateway.callsign", m_gateway.callsign, 3, 8, ""); - get_value(cfg, "gateway.address", m_gateway.address, 0, 20, "0.0.0.0", true) && ret; - get_value(cfg, "gateway.hbAddress", m_gateway.hbAddress, 0, 20, "127.0.0.1", true) && ret; - get_value(cfg, "gateway.hbPort", m_gateway.hbPort, 1U, 65535U, 20010U) && ret; - get_value(cfg, "gateway.icomAddress", m_gateway.icomAddress, 0, 20, "127.0.0.1", true) && ret; - get_value(cfg, "gateway.icomPort", m_gateway.icomPort, 1U, 65535U, 20000U) && ret; - get_value(cfg, "gateway.latitude", m_gateway.latitude, -90.0, 90.0, 0.0) && ret; - get_value(cfg, "gateway.longitude", m_gateway.longitude, -180.0, 180.0, 0.0) && ret; - get_value(cfg, "gateway.description1", m_gateway.description1, 0, 1024, "") && ret; - get_value(cfg, "gateway.description2", m_gateway.description2, 0, 1024, "") && ret; - get_value(cfg, "gateway.url", m_gateway.url, 0, 1024, "") && ret; + bool ret = cfg.getValue("gateway", "callsign", m_gateway.callsign, 3, 8, ""); + ret = cfg.getValue("gateway", "address", m_gateway.address, 0, 20, "0.0.0.0") && ret; + ret = cfg.getValue("gateway", "hbAddress", m_gateway.hbAddress, 0, 20, "127.0.0.1") && ret; + ret = cfg.getValue("gateway", "hbPort", m_gateway.hbPort, 1U, 65535U, 20010U) && ret; + ret = cfg.getValue("gateway", "icomAddress", m_gateway.icomAddress, 0, 20, "127.0.0.1") && ret; + ret = cfg.getValue("gateway", "icomPort", m_gateway.icomPort, 1U, 65535U, 20000U) && ret; + ret = cfg.getValue("gateway", "latitude", m_gateway.latitude, -90.0, 90.0, 0.0) && ret; + ret = cfg.getValue("gateway", "longitude", m_gateway.longitude, -180.0, 180.0, 0.0) && ret; + ret = cfg.getValue("gateway", "description1", m_gateway.description1, 0, 1024, "") && ret; + ret = cfg.getValue("gateway", "description2", m_gateway.description2, 0, 1024, "") && ret; + ret = cfg.getValue("gateway", "url", m_gateway.url, 0, 1024, "") && ret; std::string type; - get_value(cfg, "gateway.type", type, 0, 8, "repeater", true, {"repeater", "hotspot"}) && ret; - if(type == "repeater") m_gateway.type = GT_REPEATER; - if(type == "hotspot") m_gateway.type = GT_HOTSPOT; + ret = cfg.getValue("gateway", "type", type, "repeater", {"repeater", "hotspot"}) && ret; + if(type == "repeater") m_gateway.type = GT_REPEATER; + else if(type == "hotspot") m_gateway.type = GT_HOTSPOT; std::string lang; - get_value(cfg, "gateway.language", lang, 0, 30, "english_uk", true, {"english_uk", "deutsch", "dansk", "francais", "italiano", "polski", "english_us", "espanol", "svenska", "nederlands_nl", "nederlands_be", "norsk", "portugues"}); - if(lang == "english_uk") m_gateway.language = TL_ENGLISH_UK; - else if(lang == "deutsch") m_gateway.language = TL_DEUTSCH; - else if(lang == "dansk") m_gateway.language = TL_DANSK; - else if(lang == "francais") m_gateway.language = TL_FRANCAIS; - else if(lang == "italiano") m_gateway.language = TL_ITALIANO; - else if(lang == "polski") m_gateway.language = TL_POLSKI; - else if(lang == "english_us") m_gateway.language = TL_ENGLISH_US; - else if(lang == "espanol") m_gateway.language = TL_ESPANOL; - else if(lang == "svenska") m_gateway.language = TL_SVENSKA; - else if(lang == "nederlands_nl") m_gateway.language = TL_NEDERLANDS_NL; - else if(lang == "nederlands_be") m_gateway.language = TL_NEDERLANDS_BE; - else if(lang == "norsk") m_gateway.language = TL_NORSK; - else if(lang == "portugues") m_gateway.language = TL_PORTUGUES; + ret = cfg.getValue("gateway", "language", lang, "english_uk", + {"english_uk", "deutsch", "dansk", "francais", "italiano", "polski", + "english_us", "espanol", "svenska", "nederlands_nl", "nederlands_be", "norsk", "portugues"}) && ret;; + if(lang == "english_uk") m_gateway.language = TL_ENGLISH_UK; + else if(lang == "deutsch") m_gateway.language = TL_DEUTSCH; + else if(lang == "dansk") m_gateway.language = TL_DANSK; + else if(lang == "francais") m_gateway.language = TL_FRANCAIS; + else if(lang == "italiano") m_gateway.language = TL_ITALIANO; + else if(lang == "polski") m_gateway.language = TL_POLSKI; + else if(lang == "english_us") m_gateway.language = TL_ENGLISH_US; + else if(lang == "espanol") m_gateway.language = TL_ESPANOL; + else if(lang == "svenska") m_gateway.language = TL_SVENSKA; + else if(lang == "nederlands_nl")m_gateway.language = TL_NEDERLANDS_NL; + else if(lang == "nederlands_be")m_gateway.language = TL_NEDERLANDS_BE; + else if(lang == "norsk") m_gateway.language = TL_NORSK; + else if(lang == "portugues") m_gateway.language = TL_PORTUGUES; CUtils::ToUpper(m_gateway.callsign); CUtils::clean(m_gateway.description1, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,&*()-+=@/?:;"); CUtils::clean(m_gateway.description2, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,&*()-+=@/?:;"); CUtils::clean(m_gateway.url, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,&*()-+=@/?:;"); - return true; + return ret; } -bool CDStarGatewayConfig::open(Config & cfg) +bool CDStarGatewayConfig::open(CConfig & cfg) { - if (m_fileName.size() < 1) { - CLog::logError("Configuration filename too short!\n"); - return false; - } - try { - cfg.readFile(m_fileName.c_str()); + return cfg.load(); } - catch(const FileIOException &fioex) { + catch(...) { CLog::logError("Can't read %s\n", m_fileName.c_str()); return false; } - catch(const ParseException &pex) { - CLog::logError("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return false; - } - return true; } @@ -385,93 +319,6 @@ CDStarGatewayConfig::~CDStarGatewayConfig() } } -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, unsigned int &value, unsigned int min, unsigned int max, unsigned int default_value) -{ - value = default_value; - int valueTmp; - if(get_value(cfg, path, valueTmp, (int)min, (int)max, (int)default_value) - && valueTmp >= 0) { - value = valueTmp; - } - - return true; -} - -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - return true; -} - -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - return true; -} - -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - return true; -} - -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value) -{ - return get_value(cfg, path, value, min, max, default_value, false); -} - -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - CLog::logWarning("%s has an invalid length, must be between %d and %d, actual %d", path.c_str(), min, max, l); - return false; - } - } else - value = default_value; - - if(emptyToDefault && value.length() == 0) { - value = default_value; - } - - return true; -} - -bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault, const std::vector& allowedValues) -{ - bool ret = get_value(cfg, path, value, min, max, default_value, emptyToDefault); - - if(ret) { - for(std::string s : allowedValues) { - if(s == value) { - ret = true; - break; - } - } - } - - if(!ret) { - std::stringstream message; - message << path << "=" << value << " has an invalid value. Valid values are : "; - for(std::string s : allowedValues) { - message << s << ", "; - } - CLog::logWarning(message.str()); - } - - return ret; -} - void CDStarGatewayConfig::getGateway(TGateway & gateway) const { gateway = m_gateway; @@ -497,6 +344,11 @@ void CDStarGatewayConfig::getRepeater(unsigned int index, TRepeater & repeater) repeater = *(m_repeaters[index]); } +void CDStarGatewayConfig::getLog(TLog & log) const +{ + log = m_log; +} + void CDStarGatewayConfig::getPaths(Tpaths & paths) const { paths = m_paths; diff --git a/DStarGatewayConfig.h b/DStarGatewayConfig.h index 2ff6209..56197bf 100644 --- a/DStarGatewayConfig.h +++ b/DStarGatewayConfig.h @@ -20,10 +20,9 @@ #include #include -#include -#include "Defs.h" -using namespace libconfig; +#include "Defs.h" +#include "Config.h" typedef struct { GATEWAY_TYPE type; @@ -62,9 +61,9 @@ typedef struct { std::string description1; std::string description2; std::string url; - char band1; - char band2; - char band3; + unsigned char band1; + unsigned char band2; + unsigned char band3; } TRepeater; typedef struct { @@ -75,10 +74,13 @@ typedef struct { } TircDDB; typedef struct { - std::string logDir; std::string dataDir; } Tpaths; +typedef struct { + std::string logDir; +} TLog; + typedef struct { bool enabled; std::string hostname; @@ -113,6 +115,7 @@ typedef struct { std::string password; } TRemote; + class CDStarGatewayConfig { public: CDStarGatewayConfig(const std::string &pathname); @@ -124,6 +127,7 @@ public: unsigned int getIrcDDBCount() const; void getRepeater(unsigned int repeaterIndex, TRepeater & repeater) const; unsigned int getRepeaterCount() const; + void getLog(TLog& log) const; void getPaths(Tpaths & paths) const; void getAPRS(TAPRS & aprs) const; void getDExtra(TDextra & dextra) const; @@ -133,24 +137,18 @@ public: void getXLX(TXLX & xlx) const; private: - bool open(Config & cfg); - bool loadGateway(const Config & cfg); - bool loadIrcDDB(const Config & cfg); - bool loadRepeaters(const Config & cfg); - bool loadPaths(const Config & cfg); - bool loadAPRS(const Config & cfg); - bool loadDextra(const Config & cfg); - bool loadDPlus(const Config & cfg); - bool loadDCS(const Config & cfg); - bool loadRemote(const Config & cfg); - bool loadXLX(const Config & cfg); - bool get_value(const Config &cfg, const std::string &path, unsigned int &value, unsigned int min, unsigned int max, unsigned int default_value); - bool get_value(const Config &cfg, const std::string &path, int &value, int min, int max, int default_value); - bool get_value(const Config &cfg, const std::string &path, double &value, double min, double max, double default_value); - bool get_value(const Config &cfg, const std::string &path, bool &value, bool default_value); - bool get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value); - bool get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault); - bool get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault, const std::vector& allowedValues); + bool open(CConfig & cfg); + bool loadGateway(const CConfig & cfg); + bool loadIrcDDB(const CConfig & cfg); + bool loadRepeaters(const CConfig & cfg); + bool loadLog(const CConfig & cfg); + bool loadPaths(const CConfig & cfg); + bool loadAPRS(const CConfig & cfg); + bool loadDextra(const CConfig & cfg); + bool loadDPlus(const CConfig & cfg); + bool loadDCS(const CConfig & cfg); + bool loadRemote(const CConfig & cfg); + bool loadXLX(const CConfig & cfg); std::string m_fileName; TGateway m_gateway; @@ -161,6 +159,7 @@ private: TDCS m_dcs; TRemote m_remote; TXLX m_xlx; + TLog m_log; std::vector m_repeaters; std::vector m_ircDDB; }; diff --git a/Makefile b/Makefile index 14752c7..b14d087 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ DEPS = $(SRCS:.cpp=.d) all: dstargateway dstargateway : GitVersion.h $(OBJS) - g++ $(CPPFLAGS) -o dstargateway $(OBJS) -lconfig++ -lcurl -pthread + g++ $(CPPFLAGS) -o dstargateway $(OBJS) -lcurl -pthread %.o : %.cpp g++ $(CPPFLAGS) -MMD -MD -c $< -o $@