You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

965 lines
30 KiB

/*
* Copyright (c) 2023 by Thomas A. Early N7TAE
*
* 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 <sys/stat.h>
#include <stdlib.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <vector>
#include <list>
#include <algorithm>
#include <regex>
#include "Global.h"
#include "CurlGet.h"
// ini file keywords
#define JAUTOLINKMODULE "AutoLinkModule"
#define JBLACKLISTPATH "BlacklistPath"
#define JBRANDMEISTER "Brandmeister"
#define JCALLSIGN "Callsign"
#define JCOUNTRY "Country"
#define JDCS "DCS"
#define JDEFAULTID "DefaultId"
#define JDEFAULTRXFREQ "DefaultRxFreq"
#define JDEFAULTTXFREQ "DefaultTxFreq"
#define JDESCRIPTION "Description"
#define JDEXTRA "DExtra"
#define JDMRIDDB "DMR ID DB"
#define JDMRPLUS "DMRPlus"
#define JDPLUS "DPlus"
#define JENABLE "Enable"
#define JFILES "Files"
#define JFILEPATH "FilePath"
#define JG3 "G3"
#define JG3TERMINALPATH "G3TerminalPath"
#define JINTERLINKPATH "InterlinkPath"
#define JIPADDRESSES "IpAddresses"
#define JIPV4BINDING "IPv4Binding"
#define JIPV4EXTERNAL "IPv4External"
#define JIPV6BINDING "IPv6Binding"
#define JIPV6EXTERNAL "IPv6External"
#define JM17 "M17"
#define JMMDVM "MMDVM"
#define JMODE "Mode"
#define JMODULE "Module"
#define JMODULES "Modules"
#define JNAMES "Names"
#define JNXDNIDDB "NXDN ID DB"
#define JNXDN "NXDN"
#define JP25 "P25"
#define JPIDPATH "PidPath"
#define JPORT "Port"
#define JREFLECTORID "ReflectorID"
#define JREFRESHMIN "RefreshMin"
#define JREGISTRATIONDESCRIPTION "RegistrationDescription"
#define JREGISTRATIONID "RegistrationID"
#define JREGISTRATIONNAME "RegistrationName"
#define JSPONSOR "Sponsor"
#define JREFLSTATEPATH "ReflStatePath"
#define JSYSOPEMAIL "SysopEmail"
#define JTRANSCODED "Transcoded"
#define JTRANSCODER "Transcoder"
#define JURF "URF"
#define JURL "URL"
#define JUSRP "USRP"
#define JWHITELISTPATH "WhitelistPath"
#define JYSF "YSF"
#define JYSFTXRXDB "YSF TX/RX DB"
static inline void split(const std::string &s, char delim, std::vector<std::string> &v)
{
std::istringstream iss(s);
std::string item;
while (std::getline(iss, item, delim))
v.push_back(item);
}
// trim from start (in place)
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}
CConfigure::CConfigure()
{
IPv4RegEx = std::regex("^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3,3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]){1,1}$", std::regex::extended);
IPv6RegEx = std::regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1,1}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|([0-9a-fA-F]{1,4}:){1,1}(:[0-9a-fA-F]{1,4}){1,6}|:((:[0-9a-fA-F]{1,4}){1,7}|:))$", std::regex::extended);
}
bool CConfigure::ReadData(const std::string &path)
// returns true on failure
{
bool rval = false;
ESection section = ESection::none;
counter = 0;
SJsonKeys::DB *pdb;
//data.ysfalmodule = 0;
//data.DPlusPort = data.DCSPort = data.DExtraPort = data.BMPort = data.DMRPlusPort = 0;
std::ifstream cfgfile(path.c_str(), std::ifstream::in);
if (! cfgfile.is_open()) {
std::cerr << "ERROR: '" << path << "' was not found!" << std::endl;
return true;
}
std::string ipv4, ipv6;
{
CCurlGet curl;
std::stringstream ss;
if (CURLE_OK == curl.GetURL("https://ipv4.icanhazip.com", ss))
{
ipv4.assign(ss.str());
trim(ipv4);
}
ss.str(std::string());
if (CURLE_OK == curl.GetURL("https://ipv6.icanhazip.com", ss))
{
ipv6.assign(ss.str());
trim(ipv6);
}
}
std::string line;
while (std::getline(cfgfile, line))
{
counter++;
trim(line);
if (3 > line.size())
continue; // can't be anything
if ('#' == line.at(0))
continue; // skip comments
// check for next section
if ('[' == line.at(0))
{
std::string hname(line.substr(1));
auto pos = hname.find(']');
if (std::string::npos != pos)
hname.resize(pos);
section = ESection::none;
if (0 == hname.compare(JNAMES))
section = ESection::names;
else if (0 == hname.compare(JIPADDRESSES))
section = ESection::ip;
else if (0 == hname.compare(JMODULES))
section = ESection::modules;
else if (0 == hname.compare(JDPLUS))
section = ESection::dplus;
else if (0 == hname.compare(JDEXTRA))
section = ESection::dextra;
else if (0 == hname.compare(JG3))
section = ESection::g3;
else if (0 == hname.compare(JDMRPLUS))
section = ESection::dmrplus;
else if (0 == hname.compare(JMMDVM))
section = ESection::mmdvm;
else if (0 == hname.compare(JNXDN))
section = ESection::nxdn;
else if (0 == hname.compare(JBRANDMEISTER))
section = ESection::bm;
else if (0 == hname.compare(JYSF))
section = ESection::ysf;
else if (0 == hname.compare(JDCS))
section = ESection::dcs;
else if (0 == hname.compare(JP25))
section = ESection::p25;
else if (0 == hname.compare(JM17))
section = ESection::m17;
else if (0 == hname.compare(JUSRP))
section = ESection::usrp;
else if (0 == hname.compare(JURF))
section = ESection::urf;
else if (0 == hname.compare(JDMRIDDB))
section = ESection::dmrid;
else if (0 == hname.compare(JNXDNIDDB))
section = ESection::nxdnid;
else if (0 == hname.compare(JYSFTXRXDB))
section = ESection::ysffreq;
else if (0 == hname.compare(JFILES))
section = ESection::files;
else
{
std::cerr << "WARNING: unknown ini file section: " << line << std::endl;
}
continue;
}
std::vector<std::string> tokens;
split(line, '=', tokens);
// check value for end-of-line comment
if (2 > tokens.size())
{
std::cout << "WARNING: line #" << counter << ": '" << line << "' does not contain an equal sign, skipping" << std::endl;
continue;
}
auto pos = tokens[1].find('#');
if (std::string::npos != pos)
{
tokens[1].assign(tokens[1].substr(0, pos));
rtrim(tokens[1]); // whitespace between the value and the end-of-line comment
}
// trim whitespace from around the '='
rtrim(tokens[0]);
ltrim(tokens[1]);
const std::string key(tokens[0]);
const std::string value(tokens[1]);
if (key.empty() || value.empty())
{
std::cout << "WARNING: line #" << counter << ": missing key or value: '" << line << "'" << std::endl;
continue;
}
switch (section)
{
case ESection::names:
if (0 == key.compare(JCALLSIGN))
data[g_Keys.names.callsign] = value;
else if (0 == key.compare(JSYSOPEMAIL))
data[g_Keys.names.email] = value;
else if (0 == key.compare(JCOUNTRY))
data[g_Keys.names.country] = value.substr(0,2);
else if (0 == key.compare(JSPONSOR))
data[g_Keys.names.sponsor] = value;
else
badParam(key);
break;
case ESection::ip:
if (0 == key.compare(JIPV4BINDING))
{
data[g_Keys.ip.ipv4bind] = value;
}
else if (0 == key.compare(JIPV6BINDING))
{
data[g_Keys.ip.ipv6bind] = value;
}
else if (0 == key.compare(JIPV4EXTERNAL))
{
data[g_Keys.ip.ipv4address] = value;
}
else if (0 == key.compare(JIPV6EXTERNAL))
{
data[g_Keys.ip.ipv6address] = value;
}
else if (0 == key.compare(JTRANSCODER))
{
if (value.compare("local"))
{
std::cout << "WARNING: Line #" << counter << ": malformed transcoder address, '" << value << "', resetting..." << std::endl;
}
data[g_Keys.ip.transcoder] = "local";
}
else
badParam(key);
break;
case ESection::modules:
if (0 == key.compare(JMODULES))
{
std::string m(value);
if (checkModules(m))
{
std::cerr << "ERROR: line #" << counter << ": no letters found in Modules: '" << m << "'" << std::endl;
rval = true;
} else
data[g_Keys.modules.modules] = m;
}
else if (0 == key.compare(JTRANSCODED))
{
std::string m(value);
if (checkModules(m))
{
std::cerr << "ERROR: line #" << counter << ": no letters found in Transcoded: '" << m << "'" << std::endl;
rval = true;
} else
data[g_Keys.modules.tcmodules] = m;
}
else if (0 == key.compare(0, 11, "Description"))
{
if (12 == key.size() && isupper(key[11]))
data[key] = value;
else
badParam(key);
}
else
badParam(key);
break;
case ESection::bm:
if (0 == key.compare(JPORT))
data[g_Keys.bm.port] = getUnsigned(value, "Brandmeister Port", 1024, 65535, 10002);
else if (0 == key.compare(JENABLE))
data[g_Keys.bm.enable] = IS_TRUE(value[0]);
else
badParam(key);
break;
case ESection::dcs:
if (0 == key.compare(JPORT))
data[g_Keys.dcs.port] = getUnsigned(value, "DCS Port", 1024, 65535, 30051);
else
badParam(key);
break;
case ESection::dextra:
if (0 == key.compare(JPORT))
data[g_Keys.dextra.port] = getUnsigned(value, "DExtra Port", 1024, 65535, 30001);
else
badParam(key);
break;
case ESection::g3:
if (0 == key.compare(JENABLE))
data[g_Keys.g3.enable] = IS_TRUE(value[0]);
else
badParam(key);
break;
case ESection::dmrplus:
if (0 == key.compare(JPORT))
data[g_Keys.dmrplus.port] = getUnsigned(value, "DMRPlus Port", 1024, 65535, 8880);
else
badParam(key);
break;
case ESection::dplus:
if (0 == key.compare(JPORT))
data[g_Keys.dplus.port] = getUnsigned(value, "DPlus Port", 1024, 65535, 20001);
else
badParam(key);
break;
case ESection::m17:
if (0 == key.compare(JPORT))
data[g_Keys.m17.port] = getUnsigned(value, "M17 Port", 1024, 65535, 17000);
else
badParam(key);
break;
case ESection::mmdvm:
if (0 == key.compare(JPORT))
data[g_Keys.mmdvm.port] = getUnsigned(value, "MMDVM Port", 1024, 65535, 62030);
else if (0 == key.compare(JDEFAULTID))
data[g_Keys.mmdvm.defaultid] = getUnsigned(value, "MMDVM DefaultID", 0, 9999999, 0);
else
badParam(key);
break;
case ESection::nxdn:
if (0 == key.compare(JPORT))
data[g_Keys.nxdn.port] = getUnsigned(value, "NDXN Port", 1024, 65535, 41400);
else if (0 == key.compare(JAUTOLINKMODULE))
setAutolink(JNXDN, g_Keys.nxdn.autolinkmod, value);
else if (0 == key.compare(JREFLECTORID))
data[g_Keys.nxdn.reflectorid] = getUnsigned(value, "NXDN ReflectorID", 0, 65535, 0);
else
badParam(key);
break;
case ESection::p25:
if (0 == key.compare(JPORT))
data[g_Keys.p25.port] = getUnsigned(value, "P25 Port", 1024, 65535, 41000);
else if (0 == key.compare(JAUTOLINKMODULE))
setAutolink(JP25, g_Keys.p25.autolinkmod, value);
else if (0 == key.compare(JREFLECTORID))
data[g_Keys.p25.reflectorid] = getUnsigned(value, "P25 ReflectorID", 0, 16777215, 0);
else
badParam(key);
break;
case ESection::urf:
if (0 == key.compare(JPORT))
data[g_Keys.urf.port] = getUnsigned(value, "URF Port", 1024, 65535, 10017);
else
badParam(key);
break;
case ESection::usrp:
if (0 == key.compare(JENABLE))
data[g_Keys.usrp.enable] = IS_TRUE(value[0]);
else if (0 == key.compare(JPORT))
data[g_Keys.usrp.port] = getUnsigned(value, "USRP Port", 1024, 65535, 32000);
else if (0 == key.compare(JMODULE))
data[g_Keys.usrp.module] = value.substr(0, 1);
else if (0 == key.compare(JCALLSIGN))
data[g_Keys.usrp.callsign] = value;
else if (0 == key.compare(JFILEPATH))
data[g_Keys.usrp.filepath] = value;
else
badParam(key);
break;
case ESection::ysf:
if (0 == key.compare(JPORT))
data[g_Keys.ysf.port] = getUnsigned(value, "YSF Port", 1024, 65535, 42000);
else if (0 == key.compare(JAUTOLINKMODULE))
setAutolink(JYSF, g_Keys.ysf.autolinkmod, value);
else if (0 == key.compare(JDEFAULTTXFREQ))
data[g_Keys.ysf.defaulttxfreq] = getUnsigned(value, "YSF DefaultTxFreq", 40000000, 2600000000, 439000000);
else if (0 == key.compare(JDEFAULTRXFREQ))
data[g_Keys.ysf.defaultrxfreq] = getUnsigned(value, "YSF DefaultRxFreq", 40000000, 2600000000, 439000000);
else if (0 == key.compare(JREGISTRATIONID))
data[g_Keys.ysf.ysfreflectordb.id] = getUnsigned(value, "YSF RegistrationID", 0, 9999999, 0);
else if (0 == key.compare(JREGISTRATIONNAME))
{
std::string name(value);
if (name.size() > 13) name.resize(13);
data[g_Keys.ysf.ysfreflectordb.name] = name;
}
else if (0 == key.compare(JREGISTRATIONDESCRIPTION))
{
std::string desc(value);
if (desc.size() > 16) desc.resize(16);
data[g_Keys.ysf.ysfreflectordb.description] = desc;
}
else
badParam(key);
break;
case ESection::dmrid:
case ESection::nxdnid:
case ESection::ysffreq:
switch (section)
{
case ESection::dmrid: pdb = &g_Keys.dmriddb; break;
case ESection::nxdnid: pdb = &g_Keys.nxdniddb; break;
case ESection::ysffreq: pdb = &g_Keys.ysftxrxdb; break;
}
if (0 == key.compare(JURL))
data[pdb->url] = value;
else if (0 == key.compare(JMODE))
{
if ((0==value.compare("file")) || (0==value.compare("http")) || (0==value.compare("both")))
data[pdb->mode] = value;
else
{
std::cout << "WARNING: line #" << counter << ": Mode, '" << value << "' not recognized. Setting to 'http'" << std::endl;
data[pdb->mode] = "http";
}
}
else if (0 == key.compare(JREFRESHMIN))
data[pdb->refreshmin] = getUnsigned(value, JREFRESHMIN, 15, 14400, 180);
else if (0 == key.compare(JFILEPATH))
data[pdb->filepath] = value;
else
badParam(key);
break;
case ESection::files:
if (0 == key.compare(JPIDPATH))
data[g_Keys.files.pid] = value;
else if (0 == key.compare(JREFLSTATEPATH))
data[g_Keys.files.state] = value;
else if (0 == key.compare(JWHITELISTPATH))
data[g_Keys.files.white] = value;
else if (0 == key.compare(JBLACKLISTPATH))
data[g_Keys.files.black] = value;
else if (0 == key.compare(JINTERLINKPATH))
data[g_Keys.files.interlink] = value;
else if (0 == key.compare(JG3TERMINALPATH))
data[g_Keys.files.terminal] = value;
else
badParam(key);
break;
default:
std::cout << "WARNING: parameter '" << line << "' defined befor any [section]" << std::endl;
}
}
cfgfile.close();
////////////////////////////// check the input
// Names section
if (isDefined(ErrorLevel::fatal, JNAMES, JCALLSIGN, g_Keys.names.callsign, rval))
{
const auto cs = data[g_Keys.names.callsign].get<std::string>();
auto RefRegEx = std::regex("^URF([A-Z0-9]){3,3}$", std::regex::extended);
if (! std::regex_match(cs, RefRegEx))
{
std::cerr << "ERROR: [" << JNAMES << ']' << JCALLSIGN << " '" << cs << "' is malformed" << std::endl;
rval = true;
}
}
isDefined(ErrorLevel::mild, JNAMES, JSYSOPEMAIL, g_Keys.names.email, rval);
isDefined(ErrorLevel::mild, JNAMES, JCOUNTRY, g_Keys.names.country, rval);
isDefined(ErrorLevel::mild, JNAMES, JSPONSOR, g_Keys.names.sponsor, rval);
// IP Address section
// ipv4 bind and external
if (isDefined(ErrorLevel::fatal, JIPADDRESSES, JIPV4BINDING, g_Keys.ip.ipv4bind, rval))
{
if (std::regex_match(data[g_Keys.ip.ipv4bind].get<std::string>(), IPv4RegEx))
{
if (data.contains(g_Keys.ip.ipv4address))
{
auto v4 = data[g_Keys.ip.ipv4address].get<std::string>();
if (std::regex_match(v4, IPv4RegEx))
{
if (ipv4.compare(v4))
std::cout << "WARNING: specified IPv4 external address, " << v4 << ", is different than detected address, " << ipv4 << std::endl;
}
else
{
std::cerr << "ERROR: specifed IPv4 external address, " << v4 << ", is malformed" << std::endl;
rval = true;
}
}
else
{
// make sure curl worked!
if (std::regex_match(ipv4, IPv4RegEx))
data[g_Keys.ip.ipv4address] = ipv4;
else
{
std::cerr << "ERROR: could not detect IPv4 address at this time" << std::endl;
rval = true;
}
}
}
else
{
std::cerr << "ERROR: IPv4 binding address, " << data[g_Keys.ip.ipv4address].get<std::string>() << ", is malformed" << std::endl;
rval = true;
}
}
// ipv6 bind and external
if (data.contains(g_Keys.ip.ipv6bind))
{
if (std::regex_match(data[g_Keys.ip.ipv6bind].get<std::string>(), IPv6RegEx))
{
if (data.contains(g_Keys.ip.ipv6address))
{
auto v6 = data[g_Keys.ip.ipv6address].get<std::string>();
if (std::regex_match(v6, IPv6RegEx))
{
if (ipv6.compare(v6))
std::cout << "WARNING: specified IPv6 external address [" << v6 << "], is different than detected address [" << ipv6 << ']' << std::endl;
}
else
{
std::cerr << "ERROR: the specifed IPv6 address [" << v6 << "] is malformed" << std::endl;
rval = true;
}
}
else
{
// make sure curl worked!
if (std::regex_match(ipv6, IPv6RegEx))
data[g_Keys.ip.ipv6address] = ipv6;
else
{
std::cerr << "ERROR: could not detect IPv6 address at this time" << std::endl;
rval = true;
}
}
}
else
{
std::cerr << "ERROR: IPv6 binding address, " << data[g_Keys.ip.ipv6address].get<std::string>() << ", is malformed" << std::endl;
rval = true;
}
}
else
{
data[g_Keys.ip.ipv6bind] = nullptr;
data[g_Keys.ip.ipv6address] = nullptr;
}
// Modules section
if (isDefined(ErrorLevel::fatal, JMODULES, JMODULES, g_Keys.modules.modules, rval))
{
const auto mods(data[g_Keys.modules.modules].get<std::string>());
if (data.contains(g_Keys.modules.tcmodules))
{
const auto tcmods(data[g_Keys.modules.tcmodules].get<std::string>());
// how many transcoded modules
auto size = tcmods.size();
if (3 != size && 1 != size)
std::cout << "WARNING: [" << JMODULES << ']' << JTRANSCODED << " doesn't define three (or one) modules" << std::endl;
// make sure each transcoded module is configured
for (auto c : data[g_Keys.modules.tcmodules])
{
if (std::string::npos == mods.find(c))
{
std::cerr << "ERROR: transcoded module '" << c << "' not found in defined modules" << std::endl;
rval = true;
}
}
}
else
data[g_Keys.modules.tcmodules] = nullptr;
// finally, check the module descriptions
for (unsigned i=0; i<26; i++)
{
if (std::string::npos == mods.find('A'+i))
{
if (data.contains(g_Keys.modules.descriptor[i]))
{
std::cout << "WARNING: " << g_Keys.modules.descriptor[i] << " defined for an unconfigured module. Deleting..." << std::endl;
data.erase(g_Keys.modules.descriptor[i]);
}
}
else
{
if (! data.contains(g_Keys.modules.descriptor[i]))
{
std::string value("Module ");
value.append(1, 'A'+i);
std::cout << "WARNING: " << g_Keys.modules.descriptor[i] << " not found. Setting to " << value << std::endl;
data[g_Keys.modules.descriptor[i]] = value;
}
}
}
}
// "simple" protocols with only a Port
isDefined(ErrorLevel::fatal, JDCS, JPORT, g_Keys.dcs.port, rval);
isDefined(ErrorLevel::fatal, JDEXTRA, JPORT, g_Keys.dextra.port, rval);
isDefined(ErrorLevel::fatal, JDMRPLUS, JPORT, g_Keys.dmrplus.port, rval);
isDefined(ErrorLevel::fatal, JDPLUS, JPORT, g_Keys.dplus.port, rval);
isDefined(ErrorLevel::fatal, JM17, JPORT, g_Keys.m17.port, rval);
isDefined(ErrorLevel::fatal, JURF, JPORT, g_Keys.urf.port, rval);
// BM
if (isDefined(ErrorLevel::fatal, JBRANDMEISTER, JENABLE, g_Keys.bm.enable, rval))
{
if (GetBoolean(g_Keys.bm.enable))
{
isDefined(ErrorLevel::fatal, JBRANDMEISTER, JPORT, g_Keys.bm.port, rval);
}
}
// G3
isDefined(ErrorLevel::fatal, JG3, JENABLE, g_Keys.g3.enable, rval);
// MMDVM
isDefined(ErrorLevel::fatal, JMMDVM, JPORT, g_Keys.mmdvm.port, rval);
isDefined(ErrorLevel::fatal, JMMDVM, JDEFAULTID, g_Keys.mmdvm.defaultid, rval);
// NXDN
isDefined(ErrorLevel::fatal, JNXDN, JPORT, g_Keys.nxdn.port, rval);
checkAutoLink(JNXDN, JAUTOLINKMODULE, g_Keys.nxdn.autolinkmod, rval);
isDefined(ErrorLevel::fatal, JNXDN, JREFLECTORID, g_Keys.nxdn.reflectorid, rval);
// P25
isDefined(ErrorLevel::fatal, JP25, JPORT, g_Keys.p25.port, rval);
checkAutoLink(JP25, JAUTOLINKMODULE, g_Keys.p25.autolinkmod, rval);
isDefined(ErrorLevel::fatal, JP25, JREFLECTORID, g_Keys.p25.reflectorid, rval);
// USRP
if (isDefined(ErrorLevel::fatal, JUSRP, JENABLE, g_Keys.usrp.enable, rval))
{
if (GetBoolean(g_Keys.usrp.enable))
{
if (IsString(g_Keys.modules.tcmodules))
{
if (isDefined(ErrorLevel::fatal, JUSRP, JMODULE, g_Keys.usrp.module, rval))
{
if (std::string::npos == GetString(g_Keys.modules.tcmodules).find(GetString(g_Keys.usrp.module).at(0)))
{
std::cerr << "ERROR: [" << JUSRP << ']' << JMODULE << " is not a transcoded module" << std::endl;
rval = true;
}
}
isDefined(ErrorLevel::fatal, JUSRP, JPORT, g_Keys.usrp.port, rval);
isDefined(ErrorLevel::fatal, JUSRP, JCALLSIGN, g_Keys.usrp.callsign, rval);
//if (isDefined(ErrorLevel::fatal, JUSRP, JFILEPATH, g_Keys.usrp.filepath, rval))
if (data.contains(g_Keys.usrp.filepath))
checkFile(JUSRP, JFILEPATH, data[g_Keys.usrp.filepath]);
}
else
{
std::cerr << "ERROR: " << JUSRP << " requires a transoder" << std::endl;
rval = true;
}
}
}
// YSF
isDefined(ErrorLevel::fatal, JYSF, JPORT, g_Keys.ysf.port, rval);
checkAutoLink(JYSF, JAUTOLINKMODULE, g_Keys.ysf.autolinkmod, rval);
isDefined(ErrorLevel::fatal, JYSF, JDEFAULTRXFREQ, g_Keys.ysf.defaultrxfreq, rval);
isDefined(ErrorLevel::fatal, JYSF, JDEFAULTTXFREQ, g_Keys.ysf.defaulttxfreq, rval);
isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONID, g_Keys.ysf.ysfreflectordb.id, rval);
isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONNAME, g_Keys.ysf.ysfreflectordb.name, rval);
isDefined(ErrorLevel::mild, JYSF, JREGISTRATIONDESCRIPTION, g_Keys.ysf.ysfreflectordb.description, rval);
// Databases
std::list<std::pair<const std::string, const struct SJsonKeys::DB *>> dbs = {
{ JDMRIDDB, &g_Keys.dmriddb },
{ JNXDNIDDB, &g_Keys.nxdniddb },
{ JYSFTXRXDB, &g_Keys.ysftxrxdb }
};
for ( auto &item : dbs )
{
if (isDefined(ErrorLevel::fatal, item.first, JMODE, item.second->mode, rval))
{
if (ERefreshType::file != GetRefreshType(item.second->mode))
{
isDefined(ErrorLevel::fatal, item.first, JURL, item.second->url, rval);
isDefined(ErrorLevel::fatal, item.first, JREFRESHMIN, item.second->refreshmin, rval);
}
if (ERefreshType::http != GetRefreshType(item.second->mode))
{
if (isDefined(ErrorLevel::fatal, item.first, JFILEPATH, item.second->filepath, rval))
checkFile(item.first, JFILEPATH, data[item.second->filepath]);
}
}
}
// Other files
isDefined(ErrorLevel::fatal, JFILES, JPIDPATH, g_Keys.files.pid, rval);
isDefined(ErrorLevel::fatal, JFILES, JREFLSTATEPATH, g_Keys.files.state, rval);
if (isDefined(ErrorLevel::fatal, JFILES, JWHITELISTPATH, g_Keys.files.white, rval))
checkFile(JFILES, JWHITELISTPATH, data[g_Keys.files.white]);
if (isDefined(ErrorLevel::fatal, JFILES, JBLACKLISTPATH, g_Keys.files.black, rval))
checkFile(JFILES, JBLACKLISTPATH, data[g_Keys.files.black]);
if (isDefined(ErrorLevel::fatal, JFILES, JINTERLINKPATH, g_Keys.files.interlink, rval))
checkFile(JFILES, JINTERLINKPATH, data[g_Keys.files.interlink]);
if (data.contains(g_Keys.g3.enable) && GetBoolean(g_Keys.g3.enable))
{
if (isDefined(ErrorLevel::fatal, JFILES, JG3TERMINALPATH, g_Keys.files.terminal, rval))
checkFile(JFILES, JG3TERMINALPATH, data[g_Keys.files.terminal]);
}
return rval;
}
bool CConfigure::isDefined(ErrorLevel level, const std::string &section, const std::string &pname, const std::string &key, bool &rval)
{
if (data.contains(key))
return true;
if (ErrorLevel::mild == level)
{
std::cout << "WARNING: [" << section << ']' << pname << " is not defined" << std::endl;
data[key] = nullptr;
}
else
{
std::cerr << "ERROR: [" << section << ']' << pname << " is not defined" << std::endl;
rval = true;
}
return false;
}
void CConfigure::checkAutoLink(const std::string &section, const std::string &pname, const std::string &key, bool &rval)
{
if (data.contains(key))
{
auto ismods = data.contains(g_Keys.modules.modules);
const auto mods(ismods ? data[g_Keys.modules.modules].get<std::string>() : "");
const auto c = data[key].get<std::string>().at(0);
if (std::string::npos == mods.find(c))
{
std::cerr << "ERROR: [" << section << ']' << pname << " module '" << c << "' not a configured module" << std::endl;
rval = true;
}
}
else
data[key] = nullptr;
}
std::string CConfigure::getDataRefreshType(ERefreshType type) const
{
if (ERefreshType::both == type)
return std::string("both");
else if (ERefreshType::file == type)
return std::string("file");
else
return std::string("http");
}
unsigned CConfigure::getUnsigned(const std::string &valuestr, const std::string &label, unsigned min, unsigned max, unsigned def) const
{
auto i = unsigned(std::stoul(valuestr.c_str()));
if ( i < min || i > max )
{
std::cout << "WARNING: line #" << counter << ": " << label << " is out of range. Reset to " << def << std::endl;
i = def;
}
return (unsigned)i;
}
void CConfigure::badParam(const std::string &key) const
{
std::cout << "WARNING: line #" << counter << ": Unexpected parameter: '" << key << "'" << std::endl;
}
bool CConfigure::checkModules(std::string &m) const
{
bool rval = false; // return true on error
for(unsigned i=0; i<m.size(); i++)
if (islower(m[i]))
m[i] = toupper(m[i]);
const std::string all("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
std::string found;
for (auto c : all)
if (std::string::npos != m.find(c) && std::string::npos == found.find(c))
found.append(1, c);
m.assign(found);
return m.empty();
}
void CConfigure::setAutolink(const std::string &section, const std::string &key, const std::string &value)
{
auto c = toupper(value.at(0));
if (isupper(c))
data[key] = std::string(1, c);
else
std::cout << "WARNING: line #" << counter << ": " << section << " AutoLinkModule is invalid: '" << value.substr(0, 1) << "'" << std::endl;
}
void CConfigure::checkFile(const std::string &section, const std::string &key, const std::string &filepath) const
{
struct stat sstat;
auto rval = stat(filepath.c_str(), &sstat);
if (rval)
{
std::cout << "WARNING: [" << section << ']' << key << " \"" << filepath << "\": " << strerror(errno) << std::endl;
}
}
void CConfigure::Dump(bool justpublic) const
{
nlohmann::json tmpjson = data;
if (justpublic)
{
for (auto &it : data.items())
{
if (islower(it.key().at(0)))
tmpjson.erase(it.key());
}
}
std::cout << tmpjson.dump(4) << std::endl;
}
ERefreshType CConfigure::GetRefreshType(const std::string &key) const
{
ERefreshType type = ERefreshType::http;
if (data.contains(key))
{
if (data[key].is_string())
{
auto s = data[key].get<std::string>();
if (0 == s.compare("both"))
type = ERefreshType::both;
else if (0 == s.compare("file"))
type = ERefreshType::file;
else
type = ERefreshType::http;
}
}
return type;
}
std::string CConfigure::GetString(const std::string &key) const
{
std::string str;
if (data.contains(key))
{
if (data[key].is_null())
{
// null is the same thing as an empty string
return str;
}
else if (data[key].is_string())
{
str.assign(data[key].get<std::string>());
}
else
std::cerr << "ERROR: GetString(): '" << key << "' is not a string" << std::endl;
}
else
{
std::cerr << "ERROR: GetString(): item at '" << key << "' is not defined" << std::endl;
}
return str;
}
unsigned CConfigure::GetUnsigned(const std::string &key) const
{
unsigned u = 0;
if (data.contains(key))
{
if (data[key].is_number_unsigned())
{
u = data[key].get<unsigned>();
}
else
std::cerr << "ERROR: GetString(): '" << key << "' is not an unsigned value" << std::endl;
}
else
{
std::cerr << "ERROR: GetString(): item at '" << key << "' is not defined" << std::endl;
}
return u;
}
bool CConfigure::GetBoolean(const std::string &key) const
{
if (data[key].is_boolean())
return data[key];
else
return false;
}
char CConfigure::GetAutolinkModule(const std::string &key) const
{
char c = 0;
if (data.contains(key))
{
if (data[key].is_string())
{
c = data[key].get<std::string>().at(0);
}
}
return c;
}
bool CConfigure::IsString(const std::string &key) const
{
if (data.contains(key))
return data[key].is_string();
return false;
}
#ifdef INICHECK
SJsonKeys g_Keys;
int main(int argc, char *argv[])
{
if (3 == argc && strlen(argv[1]) > 1 && '-' == argv[1][0])
{
CConfigure d;
auto rval = d.ReadData(argv[2]);
if ('q' != argv[1][1])
d.Dump(('n' == argv[1][1]) ? true : false);
return rval ? EXIT_FAILURE : EXIT_SUCCESS;
}
std::cerr << "Usage: " << argv[0] << " -(q|n|v) FILENAME\nWhere:\n\t-q just prints warnings and errors.\n\t-n also prints keys that begin with an uppercase letter.\n\t-v prints all keys, warnings and errors." << std::endl;
return EXIT_SUCCESS;
}
#endif

Powered by TurnKey Linux.