1
0
Fork 0

Merge branch 'master' into IPv6

ycs232-kbc
Jonathan Naylor 4 years ago
commit cef5ff8e31

@ -37,7 +37,7 @@ m_height(0),
m_desc(),
m_suffix(suffix),
m_aprsAddr(),
m_aprsAddrLen(),
m_aprsAddrLen(0U),
m_aprsSocket()
#if defined(USE_GPSD)
,m_gpsdEnabled(false),
@ -55,7 +55,8 @@ m_gpsdData()
m_callsign.append(rptSuffix.substr(0U, 1U));
}
CUDPSocket::lookup(address, port, m_aprsAddr, m_aprsAddrLen);
if (CUDPSocket::lookup(address, port, m_aprsAddr, m_aprsAddrLen) != 0)
m_aprsAddrLen = 0U;
}
CAPRSWriter::~CAPRSWriter()
@ -90,6 +91,11 @@ void CAPRSWriter::setGPSDLocation(const std::string& address, const std::string&
bool CAPRSWriter::open()
{
if (m_aprsAddrLen == 0U) {
LogError("Unable to lookup the adress of the APRS-IS server");
return false;
}
#if defined(USE_GPSD)
if (m_gpsdEnabled) {
int ret = ::gps_open(m_gpsdAddress.c_str(), m_gpsdPort.c_str(), &m_gpsdData);
@ -103,7 +109,7 @@ bool CAPRSWriter::open()
LogMessage("Connected to GPSD");
}
#endif
bool ret = m_aprsSocket.open();
bool ret = m_aprsSocket.open(m_aprsAddr);
if (!ret)
return false;

@ -145,6 +145,26 @@ bool CConf::read()
continue;
char* value = ::strtok(NULL, "\r\n");
if (value == NULL)
continue;
// Remove quotes from the value
size_t len = ::strlen(value);
if (len > 1U && *value == '"' && value[len - 1U] == '"') {
value[len - 1U] = '\0';
value++;
} else {
char *p;
// if value is not quoted, remove after # (to make comment)
if ((p = strchr(value, '#')) != NULL)
*p = '\0';
// remove trailing tab/space
for (p = value + strlen(value) - 1U; p >= value && (*p == '\t' || *p == ' '); p--)
*p = '\0';
}
if (section == SECTION_GENERAL) {
if (::strcmp(key, "Callsign") == 0) {
// Convert the callsign to upper case

@ -36,6 +36,7 @@ m_addr(),
m_addrLen(),
m_ping(NULL),
m_options(NULL),
m_opt(),
m_info(NULL),
m_reflector(),
m_print(),
@ -123,8 +124,6 @@ bool CFCSNetwork::writeLink(const std::string& reflector)
char url[30U];
::sprintf(url, "%s.xreflector.net", name.c_str());
sockaddr_storage addr;
unsigned int addrLen;
if (CUDPSocket::lookup(url, FCS_PORT, m_addr, m_addrLen) != 0) {
LogWarning("Unknown FCS reflector - %s", name.c_str());
return false;
@ -299,5 +298,5 @@ void CFCSNetwork::writeOptions()
if (m_debug)
CUtils::dump(1U, "FCS Network Options Sent", m_options, 50U);
m_socket.write(m_options, 50U, m_address, FCS_PORT);
m_socket.write(m_options, 50U, m_addr, m_addrLen);
}

@ -41,6 +41,8 @@ public:
bool open();
void setOptions(const std::string& options);
void clearDestination();
void write(const unsigned char* data);
@ -61,6 +63,8 @@ private:
sockaddr_storage m_addr;
unsigned int m_addrLen;
unsigned char* m_ping;
unsigned char* m_options;
std::string m_opt;
unsigned char* m_info;
std::string m_reflector;
std::string m_print;
@ -71,6 +75,7 @@ private:
CTimer m_resetTimer;
FCS_STATE m_state;
void writeOptions();
void writeInfo();
void writePing();
};

@ -96,40 +96,59 @@ int CUDPSocket::lookup(const std::string& hostname, unsigned int port, sockaddr_
return 0;
}
bool CUDPSocket::match(const sockaddr_storage& addr1, const sockaddr_storage& addr2)
bool CUDPSocket::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));
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));
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;
}
}
bool CUDPSocket::isnone(const sockaddr_storage& addr)
bool CUDPSocket::isNone(const sockaddr_storage& addr)
{
struct sockaddr_in *in = (struct sockaddr_in *)&addr;
return ((addr.ss_family == AF_INET) && (in->sin_addr.s_addr == htonl(INADDR_NONE)));
}
bool CUDPSocket::open()
bool CUDPSocket::open(const sockaddr_storage& address)
{
return open(AF_UNSPEC);
return open(address.ss_family);
}
bool CUDPSocket::open(const unsigned int af)
bool CUDPSocket::open(unsigned int af)
{
sockaddr_storage addr;
unsigned int addrlen;
@ -235,6 +254,7 @@ int CUDPSocket::read(unsigned char* buffer, unsigned int length, sockaddr_storag
}
address_length = size;
return len;
}

@ -35,14 +35,19 @@
#include <ws2tcpip.h>
#endif
enum IPMATCHTYPE {
IMT_ADDRESS_AND_PORT,
IMT_ADDRESS_ONLY
};
class CUDPSocket {
public:
CUDPSocket(const std::string& address, unsigned int port = 0U);
CUDPSocket(unsigned int port = 0U);
~CUDPSocket();
bool open();
bool open(const unsigned int af);
bool open(unsigned int af = AF_UNSPEC);
bool open(const sockaddr_storage& address);
int read(unsigned char* buffer, unsigned int length, sockaddr_storage& address, unsigned int &address_length);
bool write(const unsigned char* buffer, unsigned int length, const sockaddr_storage& address, unsigned int address_length);
@ -51,8 +56,10 @@ public:
static int lookup(const std::string& hostName, unsigned int port, sockaddr_storage& address, unsigned int& address_length);
static int lookup(const std::string& hostName, unsigned int port, sockaddr_storage& address, unsigned int& address_length, struct addrinfo& hints);
static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2);
static bool isnone(const sockaddr_storage& addr);
static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type = IMT_ADDRESS_AND_PORT);
static bool isNone(const sockaddr_storage& addr);
private:
std::string m_address;
@ -61,3 +68,4 @@ private:
};
#endif

@ -19,6 +19,6 @@
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20200903";
const char* VERSION = "20200905";
#endif

@ -187,7 +187,10 @@ int CYSFGateway::run()
bool debug = m_conf.getNetworkDebug();
sockaddr_storage rptAddr;
unsigned int rptAddrLen;
CUDPSocket::lookup(m_conf.getRptAddress(), m_conf.getRptPort(), rptAddr, rptAddrLen);
if (CUDPSocket::lookup(m_conf.getRptAddress(), m_conf.getRptPort(), rptAddr, rptAddrLen) != 0) {
::fprintf(stderr, "YSFGateway: cannot find the address of the MMDVM Host");
return 1;
}
std::string myAddress = m_conf.getMyAddress();
unsigned int myPort = m_conf.getMyPort();

@ -62,7 +62,9 @@ m_addrLen(0U),
m_poll(NULL),
m_unlink(NULL),
m_buffer(1000U, "YSF Network Buffer"),
m_pollTimer(1000U, 5U)
m_pollTimer(1000U, 5U),
m_name(),
m_linked(false)
{
m_poll = new unsigned char[14U];
::memcpy(m_poll + 0U, "YSFP", 4U);

@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2019 by Jonathan Naylor G4KLX
* Copyright (C) 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
@ -130,13 +130,16 @@ bool CYSFReflectors::load()
std::string host = std::string(p4);
unsigned int port = (unsigned int)::atoi(p5);
sockaddr_storage addr;
unsigned int addrLen;
if (CUDPSocket::lookup(host, port, addr, addrLen) == 0) {
CYSFReflector* refl = new CYSFReflector;
CUDPSocket::lookup(host, port, refl->m_addr, refl->m_addrLen);
refl->m_id = std::string(p1);
refl->m_name = std::string(p2);
refl->m_desc = std::string(p3);
refl->m_addr = addr;
refl->m_addrLen = addrLen;
refl->m_count = std::string(p6);
refl->m_type = YT_YSF;
refl->m_wiresX = (refl->m_name.compare(0, 3, "XLX") == 0);
@ -145,6 +148,9 @@ bool CYSFReflectors::load()
refl->m_desc.resize(14U, ' ');
m_newReflectors.push_back(refl);
} else {
LogWarning("Unable to resolve the address of YSF reflector - %s", p2);
}
}
}
@ -156,11 +162,15 @@ bool CYSFReflectors::load()
// Add the Parrot entry
if (m_parrotPort > 0U) {
sockaddr_storage addr;
unsigned int addrLen;
if (CUDPSocket::lookup(m_parrotAddress, m_parrotPort, addr, addrLen) == 0) {
CYSFReflector* refl = new CYSFReflector;
refl->m_id = "00001";
refl->m_name = "ZZ Parrot ";
refl->m_desc = "Parrot ";
CUDPSocket::lookup(m_parrotAddress, m_parrotPort, refl->m_addr, refl->m_addrLen);
refl->m_addr = addr;
refl->m_addrLen = addrLen;
refl->m_count = "000";
refl->m_type = YT_YSF;
refl->m_wiresX = false;
@ -168,15 +178,22 @@ bool CYSFReflectors::load()
m_newReflectors.push_back(refl);
LogInfo("Loaded YSF parrot");
} else {
LogWarning("Unable to resolve the address of the YSF Parrot");
}
}
// Add the YSF2DMR entry
if (m_YSF2DMRPort > 0U) {
sockaddr_storage addr;
unsigned int addrLen;
if (CUDPSocket::lookup(m_YSF2DMRAddress, m_YSF2DMRPort, addr, addrLen) == 0) {
CYSFReflector* refl = new CYSFReflector;
refl->m_id = "00002";
refl->m_name = "YSF2DMR ";
refl->m_desc = "Link YSF2DMR ";
CUDPSocket::lookup(m_YSF2DMRAddress, m_YSF2DMRPort, refl->m_addr, refl->m_addrLen);
refl->m_addr = addr;
refl->m_addrLen = addrLen;
refl->m_count = "000";
refl->m_type = YT_YSF;
refl->m_wiresX = true;
@ -184,15 +201,22 @@ bool CYSFReflectors::load()
m_newReflectors.push_back(refl);
LogInfo("Loaded YSF2DMR");
} else {
LogWarning("Unable to resolve the address of YSF2DMR");
}
}
// Add the YSF2NXDN entry
if (m_YSF2NXDNPort > 0U) {
sockaddr_storage addr;
unsigned int addrLen;
if (CUDPSocket::lookup(m_YSF2NXDNAddress, m_YSF2NXDNPort, addr, addrLen) == 0) {
CYSFReflector* refl = new CYSFReflector;
refl->m_id = "00003";
refl->m_name = "YSF2NXDN ";
refl->m_desc = "Link YSF2NXDN ";
CUDPSocket::lookup(m_YSF2NXDNAddress, m_YSF2NXDNPort, refl->m_addr, refl->m_addrLen);
refl->m_addr = addr;
refl->m_addrLen = addrLen;
refl->m_count = "000";
refl->m_type = YT_YSF;
refl->m_wiresX = true;
@ -200,15 +224,22 @@ bool CYSFReflectors::load()
m_newReflectors.push_back(refl);
LogInfo("Loaded YSF2NXDN");
} else {
LogWarning("Unable to resolve the address of YSF2NXDN");
}
}
// Add the YSF2P25 entry
if (m_YSF2P25Port > 0U) {
sockaddr_storage addr;
unsigned int addrLen;
if (CUDPSocket::lookup(m_YSF2P25Address, m_YSF2P25Port, addr, addrLen) == 0) {
CYSFReflector* refl = new CYSFReflector;
refl->m_id = "00004";
refl->m_name = "YSF2P25 ";
refl->m_desc = "Link YSF2P25 ";
CUDPSocket::lookup(m_YSF2P25Address, m_YSF2P25Port, refl->m_addr, refl->m_addrLen);
refl->m_addr = addr;
refl->m_addrLen = addrLen;
refl->m_count = "000";
refl->m_type = YT_YSF;
refl->m_wiresX = true;
@ -216,6 +247,9 @@ bool CYSFReflectors::load()
m_newReflectors.push_back(refl);
LogInfo("Loaded YSF2P25");
} else {
LogWarning("Unable to resolve the address of YSF2P25");
}
}
unsigned int id = 10U;

@ -95,40 +95,56 @@ int CUDPSocket::lookup(const std::string& hostname, unsigned int port, sockaddr_
return 0;
}
bool CUDPSocket::match(const sockaddr_storage &addr1, const sockaddr_storage &addr2)
bool CUDPSocket::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) );
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) );
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;
}
}
bool CUDPSocket::isnone(const sockaddr_storage &addr)
bool CUDPSocket::isNone(const sockaddr_storage& addr)
{
struct sockaddr_in *in = (struct sockaddr_in *)&addr;
return ( (addr.ss_family == AF_INET) &&
(in->sin_addr.s_addr == htonl(INADDR_NONE)) );
return ((addr.ss_family == AF_INET) && (in->sin_addr.s_addr == htonl(INADDR_NONE)));
}
bool CUDPSocket::open()
bool CUDPSocket::open(const sockaddr_storage& address)
{
return open(AF_UNSPEC);
return open(address.ss_family);
}
bool CUDPSocket::open(const unsigned int af)

@ -35,14 +35,19 @@
#include <ws2tcpip.h>
#endif
enum IPMATCHTYPE {
IMT_ADDRESS_AND_PORT,
IMT_ADDRESS_ONLY
};
class CUDPSocket {
public:
CUDPSocket(const std::string& address, unsigned int port = 0U);
CUDPSocket(unsigned int port = 0U);
~CUDPSocket();
bool open();
bool open(const unsigned int af);
bool open(const sockaddr_storage& addr);
bool open(const unsigned int af = AF_UNSPEC);
int read(unsigned char* buffer, unsigned int length, sockaddr_storage& address, unsigned int &address_length);
bool write(const unsigned char* buffer, unsigned int length, const sockaddr_storage& address, unsigned int address_length);
@ -51,8 +56,11 @@ public:
static int lookup(const std::string& hostName, unsigned int port, sockaddr_storage &address, unsigned int &address_length);
static int lookup(const std::string& hostName, unsigned int port, sockaddr_storage &address, unsigned int &address_length, struct addrinfo &hints);
static bool match(const sockaddr_storage &addr1, const sockaddr_storage &addr2);
static bool isnone(const sockaddr_storage &addr);
static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type = IMT_ADDRESS_AND_PORT);
static bool isNone(const sockaddr_storage& addr);
private:
std::string m_address;

@ -19,6 +19,6 @@
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20200903";
const char* VERSION = "20200905";
#endif

@ -88,6 +88,26 @@ bool CConf::read()
continue;
char* value = ::strtok(NULL, "\r\n");
if (value == NULL)
continue;
// Remove quotes from the value
size_t len = ::strlen(value);
if (len > 1U && *value == '"' && value[len - 1U] == '"') {
value[len - 1U] = '\0';
value++;
} else {
char *p;
// if value is not quoted, remove after # (to make comment)
if ((p = strchr(value, '#')) != NULL)
*p = '\0';
// remove trailing tab/space
for (p = value + strlen(value) - 1U; p >= value && (*p == '\t' || *p == ' '); p--)
*p = '\0';
}
if (section == SECTION_GENERAL) {
if (::strcmp(key, "Daemon") == 0)
m_daemon = ::atoi(value) == 1;

@ -96,40 +96,59 @@ int CUDPSocket::lookup(const std::string& hostname, unsigned int port, sockaddr_
return 0;
}
bool CUDPSocket::match(const sockaddr_storage& addr1, const sockaddr_storage& addr2)
bool CUDPSocket::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));
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));
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;
}
}
bool CUDPSocket::isnone(const sockaddr_storage& addr)
bool CUDPSocket::isNone(const sockaddr_storage& addr)
{
struct sockaddr_in *in = (struct sockaddr_in *)&addr;
return ((addr.ss_family == AF_INET) && (in->sin_addr.s_addr == htonl(INADDR_NONE)));
}
bool CUDPSocket::open()
bool CUDPSocket::open(const sockaddr_storage& address)
{
return open(AF_UNSPEC);
return open(address.ss_family);
}
bool CUDPSocket::open(const unsigned int af)
bool CUDPSocket::open(unsigned int af)
{
sockaddr_storage addr;
unsigned int addrlen;
@ -235,6 +254,7 @@ int CUDPSocket::read(unsigned char* buffer, unsigned int length, sockaddr_storag
}
address_length = size;
return len;
}

@ -35,14 +35,19 @@
#include <ws2tcpip.h>
#endif
enum IPMATCHTYPE {
IMT_ADDRESS_AND_PORT,
IMT_ADDRESS_ONLY
};
class CUDPSocket {
public:
CUDPSocket(const std::string& address, unsigned int port = 0U);
CUDPSocket(unsigned int port = 0U);
~CUDPSocket();
bool open();
bool open(const unsigned int af);
bool open(unsigned int af = AF_UNSPEC);
bool open(const sockaddr_storage& address);
int read(unsigned char* buffer, unsigned int length, sockaddr_storage& address, unsigned int &address_length);
bool write(const unsigned char* buffer, unsigned int length, const sockaddr_storage& address, unsigned int address_length);
@ -51,8 +56,10 @@ public:
static int lookup(const std::string& hostName, unsigned int port, sockaddr_storage& address, unsigned int& address_length);
static int lookup(const std::string& hostName, unsigned int port, sockaddr_storage& address, unsigned int& address_length, struct addrinfo& hints);
static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2);
static bool isnone(const sockaddr_storage& addr);
static bool match(const sockaddr_storage& addr1, const sockaddr_storage& addr2, IPMATCHTYPE type = IMT_ADDRESS_AND_PORT);
static bool isNone(const sockaddr_storage& addr);
private:
std::string m_address;
@ -61,3 +68,4 @@ private:
};
#endif

@ -19,6 +19,6 @@
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20200903";
const char* VERSION = "20200905";
#endif

Loading…
Cancel
Save