Add Remote handler

master
Geoffrey Merck 4 years ago
parent 016ac2b34f
commit 45dade6f6b

@ -1,6 +1,6 @@
/*
* Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX
* Copyright (c) 2017-2018 by Thomas A. Early N7TAE
* Copyright (C) 2010,2012 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
@ -18,23 +18,26 @@
*/
#include <cassert>
#include <cstdlib>
#include <list>
#include <vector>
#include "GroupHandler.h"
#include "RepeaterHandler.h"
#ifdef USE_STARNET
#include "StarNetHandler.h"
#endif
#include "RemoteHandler.h"
#include "DExtraHandler.h"
#include "DPlusHandler.h"
#include "DStarDefines.h"
#include "DCSHandler.h"
#include "Utils.h"
#include "Log.h"
CRemoteHandler::CRemoteHandler(const std::string &password, unsigned int port, const std::string &address) :
CRemoteHandler::CRemoteHandler(const std::string& password, unsigned int port, const std::string& address) :
m_password(password),
m_handler(port, address),
m_random(0U)
{
assert(port > 0U);
assert(password.size());
assert(!password.empty());
}
CRemoteHandler::~CRemoteHandler()
@ -52,51 +55,79 @@ void CRemoteHandler::process()
switch (type) {
case RPHT_LOGOUT:
m_handler.setLoggedIn(false);
printf("Remote control user has logged out\n");
wxLogMessage("Remote control user has logged out");
break;
case RPHT_LOGIN:
m_random = (uint32_t)rand();
m_random = ::rand();
m_handler.sendRandom(m_random);
break;
case RPHT_HASH: {
bool valid = m_handler.readHash(m_password, m_random);
if (valid) {
printf("Remote control user has logged in\n");
wxLogMessage("Remote control user has logged in");
m_handler.setLoggedIn(true);
m_handler.sendACK();
} else {
printf("Remote control user has failed login authentication\n");
wxLogMessage("Remote control user has failed login authentication");
m_handler.setLoggedIn(false);
m_handler.sendNAK("Invalid password");
}
}
break;
case RPHT_SMARTGROUP: {
std::string callsign = m_handler.readGroup();
sendGroup(callsign);
case RPHT_CALLSIGNS:
sendCallsigns();
break;
case RPHT_REPEATER: {
std::string callsign = m_handler.readRepeater();
sendRepeater(callsign);
}
break;
#ifdef USE_STARNET
case RPHT_STARNET: {
std::string callsign = m_handler.readStarNetGroup();
sendStarNetGroup(callsign);
}
break;
#endif
case RPHT_LINK: {
std::string callsign, reflector;
m_handler.readLink(callsign, reflector);
printf("Remote control user has linked \"%s\" to \"%s\"\n", callsign.c_str(), reflector.c_str());
link(callsign, reflector);
RECONNECT reconnect;
m_handler.readLink(callsign, reconnect, reflector);
if (reflector.empty())
wxLogMessage("Remote control user has linked \"%s\" to \"None\" with reconnect %d", callsign.c_str(), int(reconnect));
else
wxLogMessage("Remote control user has linked \"%s\" to \"%s\" with reconnect %d", callsign.c_str(), reflector.c_str(), int(reconnect));
link(callsign, reconnect, reflector, true);
}
break;
case RPHT_UNLINK: {
std::string callsign;
m_handler.readUnlink(callsign);
printf("Remote control user has unlinked \"%s\"\n", callsign.c_str());
unlink(callsign);
std::string callsign, reflector;
PROTOCOL protocol;
m_handler.readUnlink(callsign, protocol, reflector);
wxLogMessage("Remote control user has unlinked \"%s\" from \"%s\" for protocol %d", callsign.c_str(), reflector.c_str(), int(protocol));
unlink(callsign, protocol, reflector);
}
break;
case RPHT_LINKSCR: {
std::string callsign, reflector;
RECONNECT reconnect;
m_handler.readLinkScr(callsign, reconnect, reflector);
if (reflector.empty())
wxLogMessage("Remote control user has linked \"%s\" to \"None\" with reconnect %d from localhost", callsign.c_str(), reconnect);
else
wxLogMessage("Remote control user has linked \"%s\" to \"%s\" with reconnect %d from localhost", callsign.c_str(), reflector.c_str(), reconnect);
link(callsign, reconnect, reflector, false);
}
break;
#ifdef USE_STARNET
case RPHT_LOGOFF: {
std::string callsign, user;
m_handler.readLogoff(callsign, user);
printf("Remote control user has logged off \"%s\" from \"%s\"\n", user.c_str(), callsign.c_str());
wxLogMessage("Remote control user has logged off \"%s\" from \"%s\"", user.c_str(), callsign.c_str());
logoff(callsign, user);
}
break;
#endif
default:
break;
}
@ -107,78 +138,98 @@ void CRemoteHandler::close()
m_handler.close();
}
void CRemoteHandler::sendGroup(const std::string &callsign)
void CRemoteHandler::sendCallsigns()
{
std::vector<std::string> repeaters = CRepeaterHandler::listDVRepeaters();
#if USE_STARNET
std::vector<std::string> starNets = CStarNetHandler::listStarNets();
#else
std::vector<std::string> starNets;
#endif
m_handler.sendCallsigns(repeaters, starNets);
}
void CRemoteHandler::sendRepeater(const std::string& callsign)
{
CGroupHandler *group = CGroupHandler::findGroup(callsign);
if (group == NULL) {
m_handler.sendNAK("Invalid Smart Group callsign");
CRepeaterHandler* repeater = CRepeaterHandler::findDVRepeater(callsign);
if (repeater == NULL) {
m_handler.sendNAK("Invalid repeater callsign");
return;
}
CRemoteGroup *data = group->getInfo();
if (data != NULL)
m_handler.sendGroup(*data);
CRemoteRepeaterData* data = repeater->getInfo();
if (data != NULL) {
CDExtraHandler::getInfo(repeater, *data);
CDPlusHandler::getInfo(repeater, *data);
CDCSHandler::getInfo(repeater, *data);
#ifdef USE_CCS
CCCSHandler::getInfo(repeater, *data);
#endif
m_handler.sendRepeater(*data);
}
delete data;
}
void CRemoteHandler::link(const std::string &callsign, const std::string &reflector)
#ifdef USE_STARNET
void CRemoteHandler::sendStarNetGroup(const std::string& callsign)
{
CGroupHandler *smartGroup = CGroupHandler::findGroup(callsign);
if (NULL == smartGroup) {
m_handler.sendNAK(std::string("Invalid Smart Group subscribe call ") + callsign);
CStarNetHandler* starNet = CStarNetHandler::findStarNet(callsign);
if (starNet == NULL) {
m_handler.sendNAK("Invalid STARnet Group callsign");
return;
}
if (smartGroup->remoteLink(reflector))
m_handler.sendACK();
else
m_handler.sendNAK("link failed");
CRemoteStarNetGroup* data = starNet->getInfo();
if (data != NULL)
m_handler.sendStarNetGroup(*data);
delete data;
}
#endif
void CRemoteHandler::unlink(const std::string &callsign)
void CRemoteHandler::link(const std::string& callsign, RECONNECT reconnect, const std::string& reflector, bool respond)
{
CGroupHandler *smartGroup = CGroupHandler::findGroup(callsign);
if (NULL == smartGroup) {
m_handler.sendNAK(std::string("Invalid Smart Group subscribe call ") + callsign);
CRepeaterHandler* repeater = CRepeaterHandler::findDVRepeater(callsign);
if (repeater == NULL) {
m_handler.sendNAK("Invalid repeater callsign");
return;
}
CRemoteGroup *data = smartGroup->getInfo();
if (data) {
switch (smartGroup->getLinkType()) {
case LT_DEXTRA:
CDExtraHandler::unlink(smartGroup, data->getReflector(), false);
break;
case LT_DCS:
CDCSHandler::unlink(smartGroup, data->getReflector(), false);
break;
default:
delete data;
m_handler.sendNAK("alread unlinked");
return;
}
delete data;
} else {
m_handler.sendNAK("could not get Smart Group info");
repeater->link(reconnect, reflector);
if (respond)
m_handler.sendACK();
}
void CRemoteHandler::unlink(const std::string& callsign, PROTOCOL protocol, const std::string& reflector)
{
CRepeaterHandler* repeater = CRepeaterHandler::findDVRepeater(callsign);
if (repeater == NULL) {
m_handler.sendNAK("Invalid repeater callsign");
return;
}
smartGroup->setLinkType(LT_NONE);
smartGroup->clearReflector();
repeater->unlink(protocol, reflector);
m_handler.sendACK();
}
void CRemoteHandler::logoff(const std::string &callsign, const std::string &user)
#if USE_STARNET
void CRemoteHandler::logoff(const std::string& callsign, const std::string& user)
{
CGroupHandler *pGroup = CGroupHandler::findGroup(callsign);
if (pGroup == NULL) {
m_handler.sendNAK("Invalid Smart Group callsign");
CStarNetHandler* starNet = CStarNetHandler::findStarNet(callsign);
if (starNet == NULL) {
m_handler.sendNAK("Invalid STARnet group callsign");
return;
}
bool res = pGroup->logoff(user);
bool res = starNet->logoff(user);
if (!res)
m_handler.sendNAK("Invalid Smart Group user callsign");
m_handler.sendNAK("Invalid STARnet user callsign");
else
m_handler.sendACK();
}
#endif

@ -1,6 +1,6 @@
/*
* Copyright (C) 2011,2013 by Jonathan Naylor G4KLX
* Copyright (c) 2017-2018 by Thomas A. Early
* Copyright (C) 2010,2012 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
@ -17,17 +17,18 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#ifndef RemoteHandler_H
#define RemoteHandler_H
#include <string>
#include <cstdint>
#include "RemoteProtocolHandler.h"
#include "Timer.h"
class CRemoteHandler {
public:
CRemoteHandler(const std::string &password, unsigned int port, const std::string &address = std::string(""));
CRemoteHandler(const std::string& password, unsigned int port, const std::string& address = "");
~CRemoteHandler();
bool open();
@ -37,12 +38,20 @@ public:
void close();
private:
std::string m_password;
std::string m_password;
CRemoteProtocolHandler m_handler;
uint32_t m_random;
void sendGroup(const std::string &callsign);
void link(const std::string &callsign, const std::string &reflector);
void unlink(const std::string &callsign);
void logoff(const std::string &callsign, const std::string &user);
unsigned int m_random;
void sendCallsigns();
void sendRepeater(const std::string& callsign);
#if USE_STARNET
void sendStarNetGroup(const std::string& callsign);
#endif
void link(const std::string& callsign, RECONNECT reconnect, const std::string& reflector, bool respond);
void unlink(const std::string& callsign, PROTOCOL protocol, const std::string& reflector);
#if USE_STARNET
void logoff(const std::string& callsign, const std::string& user);
#endif
};
#endif

@ -1,6 +1,6 @@
/*
* Copyright (C) 2011,2013 by Jonathan Naylor G4KLX
* Copyright (c) 2017,2018 by Thomas A. Early N7TAE
* 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
@ -17,20 +17,18 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <cassert>
#include <cstring>
#include <cassert>
#include "RemoteProtocolHandler.h"
#include "DStarDefines.h"
#include "SHA256.h"
#include "Utils.h"
#define wxINT32_SWAP_ON_BE(x) x
#define wxUINT32_SWAP_ON_BE(x) x
#include "StringUtils.h"
const unsigned int BUFFER_LENGTH = 2000U;
CRemoteProtocolHandler::CRemoteProtocolHandler(unsigned int port, const std::string &address) :
CRemoteProtocolHandler::CRemoteProtocolHandler(unsigned int port, const std::string& address) :
m_socket(address, port),
m_address(),
m_port(0U),
@ -68,9 +66,9 @@ RPH_TYPE CRemoteProtocolHandler::readType()
if (length <= 0)
return m_type;
// CUtils::dump("Incoming", m_inBuffer, length);
// CUtils::dump(wxT("Incoming"), m_inBuffer, length);
if (memcmp(m_inBuffer, "LIN", 3U) == 0) {
if (::memcmp(m_inBuffer, "LIN", 3U) == 0) {
m_loggedIn = false;
m_address = address;
m_port = port;
@ -79,7 +77,7 @@ RPH_TYPE CRemoteProtocolHandler::readType()
}
if (address.s_addr == inet_addr("127.0.0.1")) {
if (memcmp(m_inBuffer, "LKS", 3U) == 0) {
if (::memcmp(m_inBuffer, "LKS", 3U) == 0) {
m_inLength = length;
m_type = RPHT_LINKSCR;
return m_type;
@ -88,70 +86,70 @@ RPH_TYPE CRemoteProtocolHandler::readType()
if (m_loggedIn) {
if (address.s_addr != m_address.s_addr || port != m_port) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
}
m_inLength = length;
if (memcmp(m_inBuffer, "SHA", 3U) == 0) {
if (::memcmp(m_inBuffer, "SHA", 3U) == 0) {
if (m_loggedIn) {
sendNAK("Someone is already logged in");
sendNAK(wxT("Someone is already logged in"));
return m_type;
}
m_type = RPHT_HASH;
return m_type;
} else if (memcmp(m_inBuffer, "GCS", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "GCS", 3U) == 0) {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_CALLSIGNS;
return m_type;
} else if (memcmp(m_inBuffer, "GRP", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "GRP", 3U) == 0) {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_REPEATER;
return m_type;
} else if (memcmp(m_inBuffer, "GSN", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "GSN", 3U) == 0) {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_SMARTGROUP;
m_type = RPHT_STARNET;
return m_type;
} else if (memcmp(m_inBuffer, "LNK", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "LNK", 3U) == 0) {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_LINK;
return m_type;
} else if (memcmp(m_inBuffer, "UNL", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "UNL", 3U) == 0) {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_UNLINK;
return m_type;
} else if (memcmp(m_inBuffer, "LGO", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "LGO", 3U) == 0) {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_LOGOFF;
return m_type;
} else if (memcmp(m_inBuffer, "LOG", 3U) == 0) {
} else if (::memcmp(m_inBuffer, "LOG", 3U) == 0) {
if (!m_loggedIn)
return m_type;
m_type = RPHT_LOGOUT;
return m_type;
} else {
if (!m_loggedIn) {
sendNAK("You are not logged in");
sendNAK(wxT("You are not logged in"));
return m_type;
}
m_type = RPHT_UNKNOWN;
@ -159,25 +157,25 @@ RPH_TYPE CRemoteProtocolHandler::readType()
}
}
bool CRemoteProtocolHandler::readHash(const std::string &password, uint32_t random)
bool CRemoteProtocolHandler::readHash(const std::string& password, uint32_t random)
{
if (m_type != RPHT_HASH)
return false;
unsigned char *hash = m_inBuffer + 3U;
unsigned char* hash = m_inBuffer + 3U;
unsigned int len = password.size() + sizeof(uint32_t);
unsigned char *in = new unsigned char[len];
unsigned char *out = new unsigned char[32U];
unsigned int len = password.length() + sizeof(uint32_t);
unsigned char* in = new unsigned char[len];
unsigned char* out = new unsigned char[32U];
memcpy(in, &random, sizeof(uint32_t));
for (unsigned int i = 0U; i < password.size(); i++)
in[i + sizeof(unsigned int)] = password.at(i);
::memcpy(in, &random, sizeof(uint32_t));
for (unsigned int i = 0U; i < password.length(); i++)
in[i + sizeof(unsigned int)] = password[i];
CSHA256 sha256;
sha256.buffer(in, len, out);
bool res = memcmp(out, hash, 32U) == 0;
bool res = ::memcmp(out, hash, 32U) == 0;
delete[] in;
delete[] out;
@ -188,82 +186,115 @@ bool CRemoteProtocolHandler::readHash(const std::string &password, uint32_t rand
std::string CRemoteProtocolHandler::readRepeater()
{
if (m_type != RPHT_REPEATER)
return std::string("");
return "";
std::string callsign((char *)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
std::string callsign((char*)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
return callsign;
}
std::string CRemoteProtocolHandler::readGroup()
#ifdef USE_STARNET
std::string CRemoteProtocolHandler::readStarNetGroup()
{
if (m_type != RPHT_SMARTGROUP)
return std::string("");
if (m_type != RPHT_STARNET)
return "";
std::string callsign((char *)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
std::string callsign((char*)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
return callsign;
}
#endif
bool CRemoteProtocolHandler::readLogoff(std::string &callsign, std::string &user)
bool CRemoteProtocolHandler::readLogoff(std::string& callsign, std::string& user)
{
if (m_type != RPHT_LOGOFF)
return false;
callsign = std::string((char *)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
user = std::string((char *)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH), LONG_CALLSIGN_LENGTH);
callsign = std::string((char*)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
user = std::string((char*)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH), LONG_CALLSIGN_LENGTH);
return true;
}
bool CRemoteProtocolHandler::readLink(std::string &callsign, std::string &reflector)
bool CRemoteProtocolHandler::readLink(std::string& callsign, RECONNECT& reconnect, std::string& reflector)
{
if (m_type != RPHT_LINK)
return false;
callsign = std::string((char *)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
callsign = std::string((char*)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
reflector = std::string((char *)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH), LONG_CALLSIGN_LENGTH);
int32_t temp;
::memcpy(&temp, m_inBuffer + 3U + LONG_CALLSIGN_LENGTH, sizeof(int32_t));
reconnect = RECONNECT(CUtils::swap_endian(temp));
if (0==reflector.compare(" "))
return false;
reflector = std::string((char*)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH + sizeof(int32_t)),LONG_CALLSIGN_LENGTH);
if (reflector == (" "))
reflector.clear();
return true;
}
bool CRemoteProtocolHandler::readUnlink(std::string &callsign)
bool CRemoteProtocolHandler::readUnlink(std::string& callsign, PROTOCOL& protocol, std::string& reflector)
{
if (m_type != RPHT_UNLINK)
return false;
callsign = std::string((char *)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
callsign = std::string((char*)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
int32_t temp;
::memcpy(&temp, m_inBuffer + 3U + LONG_CALLSIGN_LENGTH, sizeof(int32_t));
protocol = PROTOCOL(CUtils::swap_endian(temp));
reflector = std::string((char*)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH + sizeof(int32_t)),LONG_CALLSIGN_LENGTH);
return true;
}
bool CRemoteProtocolHandler::sendCallsigns(const std::list<std::string> &repeaters, const std::list<std::string> &groups)
bool CRemoteProtocolHandler::readLinkScr(std::string& callsign, RECONNECT& reconnect, std::string& reflector)
{
unsigned char *p = m_outBuffer;
if (m_type != RPHT_LINKSCR)
return false;
callsign = std::string((char*)(m_inBuffer + 3U), LONG_CALLSIGN_LENGTH);
reflector = std::string((char*)(m_inBuffer + 3U + LONG_CALLSIGN_LENGTH), LONG_CALLSIGN_LENGTH);
std::string rec = std::string((char*)(m_inBuffer + 3U + 2U * LONG_CALLSIGN_LENGTH),1U);
unsigned long val = std::stoul(rec);
reconnect = RECONNECT(val);
if (reflector == " ")
reflector.clear();
return true;
}
memcpy(p, "CAL", 3U);
bool CRemoteProtocolHandler::sendCallsigns(const std::vector<std::string> & repeaters, const std::vector<std::string>& starNets)
{
unsigned char* p = m_outBuffer;
::memcpy(p, "CAL", 3U);
p += 3U;
for (auto it=repeaters.cbegin(); it!=repeaters.cend(); it++) {
for (unsigned int n = 0U; n < repeaters.size(); n++) {
*p++ = 'R';
memset(p, ' ' , LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < it->size(); i++)
p[i] = it->at(i);
::memset(p, ' ' , LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < repeaters[n].length(); i++)
p[i] = repeaters[n][i];
p += LONG_CALLSIGN_LENGTH;
}
for (auto it=groups.cbegin(); it!=groups.cend(); it++) {
for (unsigned int n = 0U; n < starNets.size(); n++) {
*p++ = 'S';
memset(p, ' ' , LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < it->size(); i++)
p[i] = it->at(i);
::memset(p, ' ' , LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < starNets[n].length(); i++)
p[i] = starNets[n][i];
p += LONG_CALLSIGN_LENGTH;
}
@ -272,118 +303,105 @@ bool CRemoteProtocolHandler::sendCallsigns(const std::list<std::string> &repeate
return m_socket.write(m_outBuffer, p - m_outBuffer, m_address, m_port);
}
bool CRemoteProtocolHandler::sendRepeater(const CRemoteRepeaterData &data)
bool CRemoteProtocolHandler::sendRepeater(const CRemoteRepeaterData& data)
{
unsigned char *p = m_outBuffer;
unsigned char* p = m_outBuffer;
memcpy(p, "RPT", 3U);
::memcpy(p, "RPT", 3U);
p += 3U;
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getCallsign().size(); i++)
p[i] = data.getCallsign().at(i);
::memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getCallsign().length(); i++)
p[i] = data.getCallsign()[i];
p += LONG_CALLSIGN_LENGTH;
uint32_t reconnect = wxINT32_SWAP_ON_BE(data.getReconnect());
memcpy(p, &reconnect, sizeof(uint32_t));
p += sizeof(uint32_t);
int32_t reconnect = CUtils::swap_endian(data.getReconnect());
::memcpy(p, &reconnect, sizeof(int32_t));
p += sizeof(int32_t);
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getReflector().size(); i++)
p[i] = data.getReflector().at(i);
::memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getReflector().length(); i++)
p[i] = data.getReflector()[i];
p += LONG_CALLSIGN_LENGTH;
for (unsigned int n = 0U; n < data.getLinkCount(); n++) {
CRemoteLinkData *link = data.getLink(n);
CRemoteLinkData* link = data.getLink(n);
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < link->getCallsign().size(); i++)
p[i] = link->getCallsign().at(i);
::memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < link->getCallsign().length(); i++)
p[i] = link->getCallsign()[i];
p += LONG_CALLSIGN_LENGTH;
uint32_t protocol = wxINT32_SWAP_ON_BE(link->getProtocol());
memcpy(p, &protocol, sizeof(uint32_t));
p += sizeof(uint32_t);
int32_t protocol = CUtils::swap_endian(link->getProtocol());
::memcpy(p, &protocol, sizeof(int32_t));
p += sizeof(int32_t);
uint32_t linked = wxINT32_SWAP_ON_BE(link->isLinked());
memcpy(p, &linked, sizeof(uint32_t));
p += sizeof(uint32_t);
int32_t linked = CUtils::swap_endian(link->isLinked());
::memcpy(p, &linked, sizeof(int32_t));
p += sizeof(int32_t);
uint32_t direction = wxINT32_SWAP_ON_BE(link->getDirection());
memcpy(p, &direction, sizeof(uint32_t));
p += sizeof(uint32_t);
int32_t direction = CUtils::swap_endian(link->getDirection());
::memcpy(p, &direction, sizeof(int32_t));
p += sizeof(int32_t);
uint32_t dongle = wxINT32_SWAP_ON_BE(link->isDongle());
memcpy(p, &dongle, sizeof(uint32_t));
p += sizeof(uint32_t);
int32_t dongle = CUtils::swap_endian(link->isDongle());
::memcpy(p, &dongle, sizeof(int32_t));
p += sizeof(int32_t);
}
// CUtils::dump("Outgoing", m_outBuffer, p - m_outBuffer);
// CUtils::dump(wxT("Outgoing"), m_outBuffer, p - m_outBuffer);
return m_socket.write(m_outBuffer, p - m_outBuffer, m_address, m_port);
}
bool CRemoteProtocolHandler::sendGroup(const CRemoteGroup &data)
#if USE_STARNET
bool CRemoteProtocolHandler::sendStarNetGroup(const CRemoteStarNetGroup& data)
{
unsigned char *p = m_outBuffer;
unsigned char* p = m_outBuffer;
memcpy(p, "SNT", 3U);
::memcpy(p, "SNT", 3U);
p += 3U;
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getCallsign().size(); i++)
p[i] = data.getCallsign().at(i);
::memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getCallsign().length(); i++)
p[i] = data.getCallsign().GetChar(i);
p += LONG_CALLSIGN_LENGTH;
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getLogoff().size(); i++)
p[i] = data.getLogoff().at(i);
::memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getLogoff().length(); i++)
p[i] = data.getLogoff().GetChar(i);
p += LONG_CALLSIGN_LENGTH;
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getRepeater().size(); i++)
p[i] = data.getRepeater().at(i);
p += LONG_CALLSIGN_LENGTH;
memset(p, ' ', 20);
for (unsigned int i = 0U; i < data.getInfoText().size(); i++)
p[i] = data.getInfoText().at(i);
p += 20;
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < data.getReflector().size(); i++)
p[i] = data.getReflector().at(i);
p += LONG_CALLSIGN_LENGTH;
LINK_STATUS ls = data.getLinkStatus();
memcpy(p, &ls, sizeof(enum LINK_STATUS));
p += sizeof(enum LINK_STATUS);
uint32_t timer = wxUINT32_SWAP_ON_BE(data.getTimer());
::memcpy(p, &timer, sizeof(uint32_t));
p += sizeof(uint32_t);
unsigned int ut = data.getUserTimeout();
memcpy(p, &ut, sizeof(unsigned int));
p += sizeof(unsigned int);
uint32_t timeout = wxUINT32_SWAP_ON_BE(data.getTimeout());
::memcpy(p, &timeout, sizeof(uint32_t));
p += sizeof(uint32_t);
for (unsigned int n = 0U; n < data.getUserCount(); n++) {
CRemoteUser *user = data.getUser(n);
CRemoteStarNetUser& user = data.getUser(n);
memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < user->getCallsign().size(); i++)
p[i] = user->getCallsign().at(i);
::memset(p, ' ', LONG_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < user.getCallsign().length(); i++)
p[i] = user.getCallsign().GetChar(i);
p += LONG_CALLSIGN_LENGTH;
uint32_t timer = wxUINT32_SWAP_ON_BE(user->getTimer());
memcpy(p, &timer, sizeof(uint32_t));
timer = wxUINT32_SWAP_ON_BE(user.getTimer());
::memcpy(p, &timer, sizeof(uint32_t));
p += sizeof(uint32_t);
uint32_t timeout = wxUINT32_SWAP_ON_BE(user->getTimeout());
memcpy(p, &timeout, sizeof(uint32_t));
timeout = wxUINT32_SWAP_ON_BE(user.getTimeout());
::memcpy(p, &timeout, sizeof(uint32_t));
p += sizeof(uint32_t);
}
// CUtils::dump("Outgoing", m_outBuffer, p - m_outBuffer);
// CUtils::dump(wxT("Outgoing"), m_outBuffer, p - m_outBuffer);
return m_socket.write(m_outBuffer, p - m_outBuffer, m_address, m_port);
}
#endif
void CRemoteProtocolHandler::setLoggedIn(bool set)
{
@ -397,35 +415,35 @@ void CRemoteProtocolHandler::close()
bool CRemoteProtocolHandler::sendACK()
{
memcpy(m_outBuffer + 0U, "ACK", 3U);
::memcpy(m_outBuffer + 0U, "ACK", 3U);
// CUtils::dump("Outgoing", m_outBuffer, 3U);
// CUtils::dump(wxT("Outgoing"), m_outBuffer, 3U);
return m_socket.write(m_outBuffer, 3U, m_address, m_port);
}
bool CRemoteProtocolHandler::sendNAK(const std::string &text)
bool CRemoteProtocolHandler::sendNAK(const std::string& text)
{
memcpy(m_outBuffer + 0U, "NAK", 3U);
::memcpy(m_outBuffer + 0U, "NAK", 3U);
memset(m_outBuffer + 3U, 0x00U, text.size() + 1U);
::memset(m_outBuffer + 3U, 0x00U, text.length() + 1U);
for (unsigned int i = 0U; i < text.size(); i++)
m_outBuffer[i + 3U] = text.at(i);
for (unsigned int i = 0U; i < text.length(); i++)
m_outBuffer[i + 3U] = text[i];
// CUtils::dump("Outgoing", m_outBuffer, 3U + text.size() + 1U);
// CUtils::dump(wxT("Outgoing"), m_outBuffer, 3U + text.length() + 1U);
return m_socket.write(m_outBuffer, 3U + text.size() + 1U, m_address, m_port);
return m_socket.write(m_outBuffer, 3U + text.length() + 1U, m_address, m_port);
}
bool CRemoteProtocolHandler::sendRandom(uint32_t random)
{
memcpy(m_outBuffer + 0U, "RND", 3U);
::memcpy(m_outBuffer + 0U, "RND", 3U);
uint32_t temp = wxUINT32_SWAP_ON_BE(random);
memcpy(m_outBuffer + 3U, &temp, sizeof(uint32_t));
uint32_t temp = CUtils::swap_endian(random);
::memcpy(m_outBuffer + 3U, &temp, sizeof(uint32_t));
// CUtils::dump("Outgoing", m_outBuffer, 3U + sizeof(uint32_t));
// CUtils::dump(wxT("Outgoing"), m_outBuffer, 3U + sizeof(uint32_t));
return m_socket.write(m_outBuffer, 3U + sizeof(uint32_t), m_address, m_port);
}

@ -1,6 +1,6 @@
/*
* Copyright (C) 2011,2013 by Jonathan Naylor G4KLX
* Copyright (c) 2017,2018 by Thomas A. Early N7TAE
* 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
@ -17,24 +17,27 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#ifndef RemoteProtocolHandler_H
#define RemoteProtocolHandler_H
#include <string>
#include <cstdint>
#include <list>
#include <vector>
#include "RemoteGroup.h"
#ifdef USE_STARNET
#include "RemoteStarNetGroup.h"
#endif
#include "RemoteRepeaterData.h"
#include "UDPReaderWriter.h"
#include "Defs.h"
enum RPH_TYPE {
RPHT_NONE,
RPHT_LOGIN,
RPHT_HASH,
RPHT_CALLSIGNS,
RPHT_REPEATER,
RPHT_SMARTGROUP,
RPHT_STARNET,
RPHT_LINK,
RPHT_UNLINK,
RPHT_LINKSCR,
@ -45,26 +48,29 @@ enum RPH_TYPE {
class CRemoteProtocolHandler {
public:
CRemoteProtocolHandler(unsigned int port, const std::string &address = std::string(""));
CRemoteProtocolHandler(unsigned int port, const std::string& address = "");
~CRemoteProtocolHandler();
bool open();
RPH_TYPE readType();
bool readHash(const std::string& password, uint32_t random);
std::string readRepeater();
std::string readGroup();
bool readHash(const std::string &password, uint32_t random);
bool readLink(std::string &callsign, std::string &reflector);
bool readUnlink(std::string &callsign);
bool readLogoff(std::string &callsign, std::string &user);
std::string readStarNetGroup();
bool readLink(std::string& callsign, RECONNECT& reconnect, std::string& reflector);
bool readUnlink(std::string& callsign, PROTOCOL& protocol, std::string& reflector);
bool readLinkScr(std::string& callsign, RECONNECT& reconnect, std::string& reflector);
bool readLogoff(std::string& callsign, std::string& user);
bool sendACK();
bool sendNAK(const std::string &text);
bool sendRandom(uint32_t random);
bool sendCallsigns(const std::list<std::string> &repeaters, const std::list<std::string> &groups);
bool sendRepeater(const CRemoteRepeaterData &data);
bool sendGroup(const CRemoteGroup &data);
bool sendACK();
bool sendNAK(const std::string& text);
bool sendRandom(uint32_t random);
bool sendCallsigns(const std::vector<std::string> & repeaters, const std::vector<std::string>& starNets);
bool sendRepeater(const CRemoteRepeaterData& data);
#ifdef USE_STARNET
bool sendStarNetGroup(const CRemoteStarNetGroup& data);
#endif
void setLoggedIn(bool set);
@ -76,7 +82,9 @@ private:
unsigned int m_port;
bool m_loggedIn;
RPH_TYPE m_type;
unsigned char *m_inBuffer;
unsigned char* m_inBuffer;
unsigned int m_inLength;
unsigned char *m_outBuffer;
unsigned char* m_outBuffer;
};
#endif

@ -2523,38 +2523,38 @@ void CRepeaterHandler::writeLinkingTo(const std::string &callsign)
switch (m_language) {
case TL_DEUTSCH:
text = string_format(wxT("Verbinde mit %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Verbinde mit %s"), callsign.c_str());
break;
case TL_DANSK:
text = string_format(wxT("Linker til %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linker til %s"), callsign.c_str());
break;
case TL_FRANCAIS:
text = string_format(wxT("Connexion a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Connexion a %s"), callsign.c_str());
break;
case TL_ITALIANO:
text = string_format(wxT("In conn con %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("In conn con %s"), callsign.c_str());
break;
case TL_POLSKI:
text = string_format(wxT("Linkuje do %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linkuje do %s"), callsign.c_str());
break;
case TL_ESPANOL:
text = string_format(wxT("Enlazando %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Enlazando %s"), callsign.c_str());
break;
case TL_SVENSKA:
text = string_format(wxT("Lankar till %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Lankar till %s"), callsign.c_str());
break;
case TL_NEDERLANDS_NL:
case TL_NEDERLANDS_BE:
text = string_format(wxT("Linken naar %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linken naar %s"), callsign.c_str());
break;
case TL_NORSK:
text = string_format(wxT("Kobler til %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Kobler til %s"), callsign.c_str());
break;
case TL_PORTUGUES:
text = string_format(wxT("Conectando, %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Conectando, %s"), callsign.c_str());
break;
default:
text = string_format(wxT("Linking to %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linking to %s"), callsign.c_str());
break;
}
@ -2575,38 +2575,38 @@ void CRepeaterHandler::writeLinkedTo(const std::string &callsign)
switch (m_language) {
case TL_DEUTSCH:
text = string_format(wxT("Verlinkt zu %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Verlinkt zu %s"), callsign.c_str());
break;
case TL_DANSK:
text = string_format(wxT("Linket til %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linket til %s"), callsign.c_str());
break;
case TL_FRANCAIS:
text = string_format(wxT("Connecte a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Connecte a %s"), callsign.c_str());
break;
case TL_ITALIANO:
text = string_format(wxT("Connesso a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Connesso a %s"), callsign.c_str());
break;
case TL_POLSKI:
text = string_format(wxT("Polaczony z %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Polaczony z %s"), callsign.c_str());
break;
case TL_ESPANOL:
text = string_format(wxT("Enlazado %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Enlazado %s"), callsign.c_str());
break;
case TL_SVENSKA:
text = string_format(wxT("Lankad till %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Lankad till %s"), callsign.c_str());
break;
case TL_NEDERLANDS_NL:
case TL_NEDERLANDS_BE:
text = string_format(wxT("Gelinkt met %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Gelinkt met %s"), callsign.c_str());
break;
case TL_NORSK:
text = string_format(wxT("Tilkoblet %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Tilkoblet %s"), callsign.c_str());
break;
case TL_PORTUGUES:
text = string_format(wxT("Conectado a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Conectado a %s"), callsign.c_str());
break;
default:
text = string_format(wxT("Linked to %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linked to %s"), callsign.c_str());
break;
}
@ -2681,48 +2681,48 @@ void CRepeaterHandler::writeIsBusy(const std::string& callsign)
switch (m_language) {
case TL_DEUTSCH:
text = wxT("Nicht verbunden");
tempText = string_format(wxT("%s ist belegt"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s ist belegt"), callsign.c_str());
break;
case TL_DANSK:
text = wxT("Ikke forbundet");
tempText = string_format(wxT("Optaget fra %s"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("Optaget fra %s"), callsign.c_str());
break;
case TL_FRANCAIS:
text = wxT("Non connecte");
tempText = string_format(wxT("Occupe par %s"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("Occupe par %s"), callsign.c_str());
break;
case TL_ITALIANO:
text = wxT("Non connesso");
tempText = string_format(wxT("Occupado da%s"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("Occupado da%s"), callsign.c_str());
break;
case TL_POLSKI:
text = wxT("Nie polaczony");
tempText = string_format(wxT("%s jest zajety"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s jest zajety"), callsign.c_str());
break;
case TL_ESPANOL:
text = wxT("No enlazado");
tempText = string_format(wxT("%s ocupado"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s ocupado"), callsign.c_str());
break;
case TL_SVENSKA:
text = wxT("Ej lankad");
tempText = string_format(wxT("%s ar upptagen"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s ar upptagen"), callsign.c_str());
break;
case TL_NEDERLANDS_NL:
case TL_NEDERLANDS_BE:
text = wxT("Niet gelinkt");
tempText = string_format(wxT("%s is bezet"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s is bezet"), callsign.c_str());
break;
case TL_NORSK:
text = wxT("Ikke linket");
tempText = string_format(wxT("%s er opptatt"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s er opptatt"), callsign.c_str());
break;
case TL_PORTUGUES:
text = wxT("Desconectado");
tempText = string_format(wxT("%s, ocupado"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s, ocupado"), callsign.c_str());
break;
default:
text = wxT("Not linked");
tempText = string_format(wxT("%s is busy"), callsign.c_str());
tempText = CStringUtils::string_format(wxT("%s is busy"), callsign.c_str());
break;
}
@ -2747,38 +2747,38 @@ void CRepeaterHandler::ccsLinkMade(const std::string& callsign, DIRECTION direct
switch (m_language) {
case TL_DEUTSCH:
text = string_format(wxT("Verlinkt zu %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Verlinkt zu %s"), callsign.c_str());
break;
case TL_DANSK:
text = string_format(wxT("Linket til %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linket til %s"), callsign.c_str());
break;
case TL_FRANCAIS:
text = string_format(wxT("Connecte a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Connecte a %s"), callsign.c_str());
break;
case TL_ITALIANO:
text = string_format(wxT("Connesso a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Connesso a %s"), callsign.c_str());
break;
case TL_POLSKI:
text = string_format(wxT("Polaczony z %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Polaczony z %s"), callsign.c_str());
break;
case TL_ESPANOL:
text = string_format(wxT("Enlazado %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Enlazado %s"), callsign.c_str());
break;
case TL_SVENSKA:
text = string_format(wxT("Lankad till %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Lankad till %s"), callsign.c_str());
break;
case TL_NEDERLANDS_NL:
case TL_NEDERLANDS_BE:
text = string_format(wxT("Gelinkt met %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Gelinkt met %s"), callsign.c_str());
break;
case TL_NORSK:
text = string_format(wxT("Tilkoblet %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Tilkoblet %s"), callsign.c_str());
break;
case TL_PORTUGUES:
text = string_format(wxT("Conectado a %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Conectado a %s"), callsign.c_str());
break;
default:
text = string_format(wxT("Linked to %s"), callsign.c_str());
text = CStringUtils::string_format(wxT("Linked to %s"), callsign.c_str());
break;
}
@ -2890,48 +2890,48 @@ void CRepeaterHandler::ccsLinkFailed(const std::string& dtmf, DIRECTION directio
switch (m_language) {
case TL_DEUTSCH:
text = wxT("Nicht verbunden");
tempText = string_format(wxT("%s unbekannt"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s unbekannt"), dtmf.c_str());
break;
case TL_DANSK:
text = wxT("Ikke forbundet");
tempText = string_format(wxT("%s unknown"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s unknown"), dtmf.c_str());
break;
case TL_FRANCAIS:
text = wxT("Non connecte");
tempText = string_format(wxT("%s inconnu"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s inconnu"), dtmf.c_str());
break;
case TL_ITALIANO:
text = wxT("Non connesso");
tempText = string_format(wxT("Sconosciuto %s"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("Sconosciuto %s"), dtmf.c_str());
break;
case TL_POLSKI:
text = wxT("Nie polaczony");
tempText = string_format(wxT("%s nieznany"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s nieznany"), dtmf.c_str());
break;
case TL_ESPANOL:
text = wxT("No enlazado");
tempText = string_format(wxT("Desconocido %s"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("Desconocido %s"), dtmf.c_str());
break;
case TL_SVENSKA:
text = wxT("Ej lankad");
tempText = string_format(wxT("%s okand"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s okand"), dtmf.c_str());
break;
case TL_NEDERLANDS_NL:
case TL_NEDERLANDS_BE:
text = wxT("Niet gelinkt");
tempText = string_format(wxT("%s bekend"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s bekend"), dtmf.c_str());
break;
case TL_NORSK:
text = wxT("Ikke linket");
tempText = string_format(wxT("%s ukjent"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s ukjent"), dtmf.c_str());
break;
case TL_PORTUGUES:
text = wxT("Desconectado");
tempText = string_format(wxT("%s desconhecido"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s desconhecido"), dtmf.c_str());
break;
default:
text = wxT("Not linked");
tempText = string_format(wxT("%s unknown"), dtmf.c_str());
tempText = CStringUtils::string_format(wxT("%s unknown"), dtmf.c_str());
break;
}

Loading…
Cancel
Save

Powered by TurnKey Linux.