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.
1124 lines
28 KiB
1124 lines
28 KiB
/*
|
|
* Copyright (C) 2011-2014 by Jonathan Naylor G4KLX
|
|
* Copyright (c) 2017,2018 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 <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "SlowDataEncoder.h"
|
|
#include "GroupHandler.h"
|
|
#include "DExtraHandler.h" // DEXTRA_LINK
|
|
#include "DCSHandler.h" // DCS_LINK
|
|
#include "Utils.h"
|
|
|
|
const unsigned int MESSAGE_DELAY = 4U;
|
|
|
|
// define static members
|
|
CG2ProtocolHandler *CGroupHandler::m_g2Handler = NULL;
|
|
CIRCDDB *CGroupHandler::m_irc = NULL;
|
|
CCacheManager *CGroupHandler::m_cache = NULL;
|
|
std::string CGroupHandler::m_gateway;
|
|
std::list<CGroupHandler *> CGroupHandler::m_Groups;
|
|
|
|
|
|
CSGSXLUser::CSGSXLUser(const std::string &callsign, unsigned int timeout) :
|
|
m_callsign(callsign),
|
|
m_timer(1000U, timeout)
|
|
{
|
|
m_timer.start();
|
|
}
|
|
|
|
CSGSXLUser::~CSGSXLUser()
|
|
{
|
|
}
|
|
|
|
bool CSGSXLUser::clock(unsigned int ms)
|
|
{
|
|
m_timer.clock(ms);
|
|
|
|
return m_timer.isRunning() && m_timer.hasExpired();
|
|
}
|
|
|
|
bool CSGSXLUser::hasExpired()
|
|
{
|
|
return m_timer.isRunning() && m_timer.hasExpired();
|
|
}
|
|
|
|
void CSGSXLUser::reset()
|
|
{
|
|
m_timer.start();
|
|
}
|
|
|
|
std::string CSGSXLUser::getCallsign() const
|
|
{
|
|
return m_callsign;
|
|
}
|
|
|
|
CTimer CSGSXLUser::getTimer() const
|
|
{
|
|
return m_timer;
|
|
}
|
|
|
|
CSGSXLId::CSGSXLId(unsigned int id, unsigned int timeout, CSGSXLUser *user) :
|
|
m_id(id),
|
|
m_timer(1000U, timeout),
|
|
m_login(false),
|
|
m_info(false),
|
|
m_logoff(false),
|
|
m_end(false),
|
|
m_user(user),
|
|
m_textCollector()
|
|
{
|
|
assert(user != NULL);
|
|
|
|
m_timer.start();
|
|
}
|
|
|
|
CSGSXLId::~CSGSXLId()
|
|
{
|
|
}
|
|
|
|
unsigned int CSGSXLId::getId() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
void CSGSXLId::reset()
|
|
{
|
|
m_timer.start();
|
|
}
|
|
|
|
void CSGSXLId::setLogin()
|
|
{
|
|
m_login = true;
|
|
}
|
|
|
|
void CSGSXLId::setInfo()
|
|
{
|
|
if (!m_login && !m_logoff)
|
|
m_info = true;
|
|
}
|
|
|
|
void CSGSXLId::setLogoff()
|
|
{
|
|
if (!m_login && !m_info)
|
|
m_logoff = true;
|
|
}
|
|
|
|
void CSGSXLId::setEnd()
|
|
{
|
|
m_end = true;
|
|
}
|
|
|
|
bool CSGSXLId::clock(unsigned int ms)
|
|
{
|
|
m_timer.clock(ms);
|
|
|
|
return m_timer.isRunning() && m_timer.hasExpired();
|
|
}
|
|
|
|
bool CSGSXLId::hasExpired()
|
|
{
|
|
return m_timer.isRunning() && m_timer.hasExpired();
|
|
}
|
|
|
|
bool CSGSXLId::isLogin() const
|
|
{
|
|
return m_login;
|
|
}
|
|
|
|
bool CSGSXLId::isInfo() const
|
|
{
|
|
return m_info;
|
|
}
|
|
|
|
bool CSGSXLId::isLogoff() const
|
|
{
|
|
return m_logoff;
|
|
}
|
|
|
|
bool CSGSXLId::isEnd() const
|
|
{
|
|
return m_end;
|
|
}
|
|
|
|
CSGSXLUser* CSGSXLId::getUser() const
|
|
{
|
|
return m_user;
|
|
}
|
|
|
|
CTextCollector& CSGSXLId::getTextCollector()
|
|
{
|
|
return m_textCollector;
|
|
}
|
|
|
|
void CGroupHandler::add(const std::string &callsign, const std::string &logoff, const std::string &repeater, const std::string &infoText, const std::string &permanent,
|
|
unsigned int userTimeout, CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch, const std::string &reflector)
|
|
{
|
|
CGroupHandler *group = new CGroupHandler(callsign, logoff, repeater, infoText, permanent, userTimeout, callsignSwitch, txMsgSwitch, reflector);
|
|
|
|
if (group)
|
|
m_Groups.push_back(group);
|
|
else
|
|
printf("Cannot allocate Smart Group with callsign %s\n", callsign.c_str());
|
|
}
|
|
|
|
void CGroupHandler::setG2Handler(CG2ProtocolHandler *handler)
|
|
{
|
|
assert(handler != NULL);
|
|
|
|
m_g2Handler = handler;
|
|
}
|
|
|
|
void CGroupHandler::setIRC(CIRCDDB *irc)
|
|
{
|
|
assert(irc != NULL);
|
|
|
|
m_irc = irc;
|
|
}
|
|
|
|
void CGroupHandler::setCache(CCacheManager *cache)
|
|
{
|
|
assert(cache != NULL);
|
|
|
|
m_cache = cache;
|
|
}
|
|
|
|
void CGroupHandler::setGateway(const std::string &gateway)
|
|
{
|
|
m_gateway = gateway;
|
|
}
|
|
|
|
CGroupHandler *CGroupHandler::findGroup(const std::string &callsign)
|
|
{
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++) {
|
|
if (0 == (*it)->m_groupCallsign.compare(callsign))
|
|
return *it;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CGroupHandler *CGroupHandler::findGroup(const CHeaderData &header)
|
|
{
|
|
std::string your = header.getYourCall();
|
|
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++) {
|
|
if (0 == (*it)->m_groupCallsign.compare(your))
|
|
return *it;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void CGroupHandler::findGroupsByLogoff(const CHeaderData &header, std::list<CGroupHandler*> & groups)
|
|
{
|
|
std::string your = header.getYourCall();
|
|
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++) {
|
|
if (0 == (*it)->m_offCallsign.compare(your))
|
|
groups.push_back(*it);
|
|
}
|
|
}
|
|
|
|
CGroupHandler *CGroupHandler::findGroup(const CAMBEData &data)
|
|
{
|
|
unsigned int id = data.getId();
|
|
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++) {
|
|
if ((*it)->m_id == id)
|
|
return *it;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
std::list<std::string> CGroupHandler::listGroups()
|
|
{
|
|
std::list<std::string> groups;
|
|
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++)
|
|
groups.push_back((*it)->m_groupCallsign);
|
|
|
|
return groups;
|
|
}
|
|
|
|
CRemoteGroup *CGroupHandler::getInfo() const
|
|
{
|
|
CRemoteGroup *data = new CRemoteGroup(m_groupCallsign, m_offCallsign, m_repeater, m_infoText, m_linkReflector, m_linkStatus, m_userTimeout);
|
|
|
|
for (auto it=m_users.begin(); it!=m_users.end(); ++it) {
|
|
CSGSXLUser* user = it->second;
|
|
data->addUser(user->getCallsign(), user->getTimer().getTimer(), user->getTimer().getTimeout());
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
void CGroupHandler::finalise()
|
|
{
|
|
while (m_Groups.size()) {
|
|
delete m_Groups.front();
|
|
m_Groups.pop_front();
|
|
}
|
|
}
|
|
|
|
void CGroupHandler::clock(unsigned int ms)
|
|
{
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++)
|
|
(*it)->clockInt(ms);
|
|
}
|
|
|
|
void CGroupHandler::link()
|
|
{
|
|
for (auto it=m_Groups.begin(); it!=m_Groups.end(); it++)
|
|
(*it)->linkInt();
|
|
}
|
|
|
|
CGroupHandler::CGroupHandler(const std::string &callsign, const std::string &logoff, const std::string &repeater, const std::string &infoText, const std::string &permanent,
|
|
unsigned int userTimeout, CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch, const std::string &reflector) :
|
|
m_groupCallsign(callsign),
|
|
m_offCallsign(logoff),
|
|
m_shortCallsign("SMRT"),
|
|
m_repeater(repeater),
|
|
m_infoText(infoText),
|
|
m_permanent(),
|
|
m_linkReflector(reflector),
|
|
m_linkGateway(),
|
|
m_linkStatus(LS_NONE),
|
|
m_oldlinkStatus(LS_INIT),
|
|
m_linkTimer(1000U, NETWORK_TIMEOUT),
|
|
m_audioUnit(NULL),
|
|
m_id(0x00U),
|
|
m_announceTimer(1000U, 2U * 60U), // 2 minutes
|
|
m_userTimeout(userTimeout),
|
|
m_callsignSwitch(callsignSwitch),
|
|
m_txMsgSwitch(txMsgSwitch),
|
|
m_ids(),
|
|
m_users(),
|
|
m_repeaters()
|
|
{
|
|
m_announceTimer.start();
|
|
|
|
// set link type
|
|
if (m_linkReflector.size())
|
|
m_linkType = (0 == m_linkReflector.compare(0, 3, "XRF")) ? LT_DEXTRA : LT_DCS;
|
|
else
|
|
m_linkType = LT_NONE;
|
|
|
|
// Create the short version of the Smart Group callsign
|
|
if (0 == m_groupCallsign.compare(0, 3, "SGS")) {
|
|
if (' ' == m_groupCallsign[7])
|
|
m_shortCallsign = std::string("S") + m_groupCallsign.substr(3, 3);
|
|
else
|
|
m_shortCallsign = m_groupCallsign.substr(3, 3) + m_groupCallsign[7];
|
|
}
|
|
if (permanent.size() < 4)
|
|
return;
|
|
char *buf = (char *)calloc(permanent.size() + 1, 1);
|
|
if (buf) {
|
|
strcpy(buf, permanent.c_str());
|
|
char *token = strtok(buf, ",");
|
|
while (token) {
|
|
if (strlen(token)) {
|
|
std::string newcall(token);
|
|
if (newcall.size() > 3) {
|
|
CUtils::ToUpper(newcall);
|
|
newcall.resize(LONG_CALLSIGN_LENGTH, ' ');
|
|
m_permanent.insert(newcall);
|
|
token = strtok(NULL, ",");
|
|
}
|
|
}
|
|
}
|
|
free(buf);
|
|
}
|
|
}
|
|
|
|
CGroupHandler::~CGroupHandler()
|
|
{
|
|
for (auto it = m_ids.begin(); it != m_ids.end(); it++)
|
|
delete it->second;
|
|
m_ids.clear();
|
|
|
|
for (auto it = m_users.begin(); it != m_users.end(); ++it)
|
|
delete it->second;
|
|
m_users.clear();
|
|
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
m_repeaters.clear();
|
|
m_permanent.erase(m_permanent.begin(), m_permanent.end());
|
|
}
|
|
|
|
void CGroupHandler::process(CHeaderData &header)
|
|
{
|
|
std::string my = header.getMyCall1();
|
|
std::string your = header.getYourCall();
|
|
unsigned int id = header.getId();
|
|
|
|
CSGSXLUser *group_user = m_users[my]; // if not found, m_user[my] will be created and its value will be set to NULL
|
|
bool islogin = false;
|
|
|
|
// Ensure that this user is in the cache.
|
|
CUserData *userData = m_cache->findUser(my); // userData is a new record (or NULL), so we have to delete or save it
|
|
// to prevent a memory leak
|
|
if (NULL == userData)
|
|
m_irc->findUser(my);
|
|
|
|
if (0 == your.compare(m_groupCallsign)) {
|
|
// This is a normal message for logging in/relaying
|
|
if (group_user == NULL) {
|
|
printf("Adding %s to Smart Group %s\n", my.c_str(), your.c_str());
|
|
// This is a new user, add him to the list
|
|
group_user = new CSGSXLUser(my, m_userTimeout * 60U);
|
|
m_users[my] = group_user;
|
|
|
|
logUser(LU_ON, your, my); // inform Quadnet
|
|
|
|
// add a new Id for this message
|
|
CSGSXLId* tx = new CSGSXLId(id, MESSAGE_DELAY, group_user);
|
|
tx->setLogin();
|
|
m_ids[id] = tx;
|
|
islogin = true;
|
|
} else {
|
|
group_user->reset();
|
|
|
|
// Check that it isn't a duplicate header
|
|
CSGSXLId* tx = m_ids[id];
|
|
if (tx) {
|
|
//printf("Duplicate header from %s, deleting userData...\n", my.c_str());
|
|
delete userData;
|
|
return;
|
|
}
|
|
//printf("Updating %s on Smart Group %s\n", my.c_str(), your.c_str());
|
|
logUser(LU_ON, your, my); // this will be an update
|
|
m_ids[id] = new CSGSXLId(id, MESSAGE_DELAY, group_user);
|
|
}
|
|
} else {
|
|
// unsubscribe was sent by someone
|
|
if (userData) {
|
|
delete userData;
|
|
userData = NULL;
|
|
}
|
|
|
|
// This is a logoff message
|
|
if (NULL == group_user) { // Not a known user, ignore
|
|
m_users.erase(my); // we created it, now we don't need it
|
|
return;
|
|
}
|
|
|
|
printf("Removing %s from Smart Group %s\n", group_user->getCallsign().c_str(), m_groupCallsign.c_str());
|
|
logUser(LU_OFF, m_groupCallsign, my); // inform Quadnet
|
|
// Remove the user from the user list
|
|
m_users.erase(my);
|
|
|
|
CSGSXLId* tx = new CSGSXLId(id, MESSAGE_DELAY, group_user);
|
|
tx->setLogoff();
|
|
m_ids[id] = tx;
|
|
|
|
return;
|
|
}
|
|
|
|
if (m_id != 0x00U) {
|
|
delete userData;
|
|
return;
|
|
}
|
|
|
|
m_id = id;
|
|
|
|
// Change the Your callsign to CQCQCQ
|
|
header.setCQCQCQ();
|
|
|
|
header.setFlag1(0x00);
|
|
header.setFlag2(0x00);
|
|
header.setFlag3(0x00);
|
|
|
|
if (LT_DEXTRA == m_linkType) {
|
|
if (!islogin) {
|
|
header.setRepeaters(m_linkGateway, m_linkReflector);
|
|
CDExtraHandler::writeHeader(this, header, DIR_OUTGOING);
|
|
}
|
|
} else if (LT_DCS == m_linkType) {
|
|
if (!islogin) {
|
|
header.setRepeaters(m_linkGateway, m_linkReflector);
|
|
CDCSHandler::writeHeader(this, header, DIR_OUTGOING);
|
|
}
|
|
}
|
|
|
|
// Get the home repeater of the user, because we don't want to route this incoming back to him
|
|
std::string exclude;
|
|
if (userData) {
|
|
exclude = userData->getRepeater();
|
|
delete userData; // it's gone now
|
|
}
|
|
|
|
// Build new repeater list, based on users that are currently logged in
|
|
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
|
|
CSGSXLUser *user = it->second;
|
|
if (user != NULL) {
|
|
// Find the user in the cache
|
|
userData = m_cache->findUser(user->getCallsign());
|
|
|
|
if (userData) {
|
|
// Check for the excluded repeater
|
|
if (userData->getRepeater().compare(exclude)) {
|
|
// Find the users repeater in the repeater list, add it otherwise
|
|
CSGSRepeater *repeater = m_repeaters[userData->getRepeater()];
|
|
if (repeater == NULL) {
|
|
// Add a new repeater entry
|
|
repeater = new CSGSRepeater;
|
|
// we zone rroute to all the repeaters, except for the sender who transmitted it
|
|
repeater->m_destination = std::string("/") + userData->getRepeater().substr(0, 6) + userData->getRepeater().back();
|
|
repeater->m_repeater = userData->getRepeater();
|
|
repeater->m_gateway = userData->getGateway();
|
|
repeater->m_address = userData->getAddress();
|
|
m_repeaters[userData->getRepeater()] = repeater;
|
|
}
|
|
}
|
|
|
|
delete userData;
|
|
userData = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (m_callsignSwitch) {
|
|
case SCS_GROUP_CALLSIGN:
|
|
header.setMyCall1(m_groupCallsign);
|
|
header.setMyCall2("SMRT");
|
|
break;
|
|
case SCS_USER_CALLSIGN:
|
|
header.setMyCall1(my);
|
|
header.setMyCall2(m_shortCallsign);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (!islogin)
|
|
sendToRepeaters(header);
|
|
|
|
if (m_txMsgSwitch)
|
|
sendFromText(my);
|
|
}
|
|
|
|
void CGroupHandler::process(CAMBEData &data)
|
|
{
|
|
unsigned int id = data.getId();
|
|
|
|
CSGSXLId* tx = m_ids[id];
|
|
if (tx == NULL)
|
|
return;
|
|
|
|
tx->reset();
|
|
|
|
CSGSXLUser* user = tx->getUser();
|
|
user->reset();
|
|
|
|
// If we've just logged in, the LOGOFF and INFO commands are disabled
|
|
if (! tx->isLogin()) {
|
|
// If we've already found some slow data, then don't look again
|
|
if (! tx->isLogoff() && ! tx->isInfo()) {
|
|
tx->getTextCollector().writeData(data);
|
|
bool hasText = tx->getTextCollector().hasData();
|
|
if (hasText) {
|
|
std::string text = tx->getTextCollector().getData();
|
|
std::string TEMP(text.substr(0,6));
|
|
CUtils::ToUpper(TEMP);
|
|
if (0 == TEMP.compare("LOGOFF")) {
|
|
printf("Removing %s from Smart Group %s, logged off\n", user->getCallsign().c_str(), m_groupCallsign.c_str());
|
|
logUser(LU_OFF, m_groupCallsign, user->getCallsign()); // inform quadnet
|
|
|
|
tx->setLogoff();
|
|
|
|
// Ensure that this user is in the cache in time for the logoff ack
|
|
CUserData* cacheUser = m_cache->findUser(user->getCallsign());
|
|
if (cacheUser == NULL)
|
|
m_irc->findUser(user->getCallsign());
|
|
|
|
delete cacheUser;
|
|
cacheUser = NULL;
|
|
}
|
|
TEMP = text.substr(0, 4);
|
|
CUtils::ToUpper(TEMP);
|
|
if (0 == TEMP.compare("INFO")) {
|
|
tx->setInfo();
|
|
|
|
// Ensure that this user is in the cache in time for the info text
|
|
CUserData* cacheUser = m_cache->findUser(user->getCallsign());
|
|
if (cacheUser == NULL)
|
|
m_irc->findUser(user->getCallsign());
|
|
|
|
delete cacheUser;
|
|
cacheUser = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (id == m_id && !tx->isLogin()) {
|
|
if (LT_DEXTRA == m_linkType)
|
|
CDExtraHandler::writeAMBE(this, data, DIR_OUTGOING);
|
|
else if (LT_DCS == m_linkType)
|
|
CDCSHandler::writeAMBE(this, data, DIR_OUTGOING);
|
|
sendToRepeaters(data);
|
|
}
|
|
|
|
if (data.isEnd()) {
|
|
if (id == m_id) {
|
|
// Clear the repeater list if we're the relayed id
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
m_repeaters.clear();
|
|
m_id = 0x00U;
|
|
}
|
|
|
|
if (tx->isLogin()) {
|
|
tx->reset();
|
|
tx->setEnd();
|
|
} else if (tx->isLogoff()) {
|
|
m_users.erase(user->getCallsign());
|
|
tx->reset();
|
|
tx->setEnd();
|
|
} else if (tx->isInfo()) {
|
|
tx->reset();
|
|
tx->setEnd();
|
|
} else {
|
|
m_ids.erase(tx->getId());
|
|
delete tx;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CGroupHandler::logoff(const std::string &callsign)
|
|
{
|
|
if (0 == callsign.compare("ALL ")) {
|
|
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
|
|
CSGSXLUser* user = it->second;
|
|
if (user) {
|
|
printf("Removing %s from Smart Group %s, logged off by remote control\n", user->getCallsign().c_str(), m_groupCallsign.c_str());
|
|
logUser(LU_OFF, m_groupCallsign, user->getCallsign()); // inform Quadnet
|
|
delete user;
|
|
}
|
|
}
|
|
|
|
for (auto it = m_ids.begin(); it != m_ids.end(); ++it)
|
|
delete it->second;
|
|
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
|
|
m_users.clear();
|
|
m_ids.clear();
|
|
m_repeaters.clear();
|
|
|
|
m_id = 0x00U;
|
|
|
|
return true;
|
|
} else {
|
|
CSGSXLUser* user = m_users[callsign];
|
|
if (user == NULL) {
|
|
printf("Invalid callsign asked to logoff");
|
|
return false;
|
|
}
|
|
printf("Removing %s from Smart Group %s, logged off by remote control\n", user->getCallsign().c_str(), m_groupCallsign.c_str());
|
|
logUser(LU_OFF, m_groupCallsign, user->getCallsign()); // inform Quadnet
|
|
|
|
// Find any associated id structure associated with this use, and the logged off user is the
|
|
// currently relayed one, remove his id.
|
|
for (auto it = m_ids.begin(); it != m_ids.end(); ++it) {
|
|
CSGSXLId* id = it->second;
|
|
if (id != NULL && id->getUser() == user) {
|
|
if (id->getId() == m_id)
|
|
m_id = 0x00U;
|
|
|
|
m_ids.erase(it);
|
|
delete id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_users.erase(callsign);
|
|
delete user;
|
|
|
|
// Check to see if we have any users left
|
|
unsigned int count = m_users.size();
|
|
|
|
// If none then clear all the data structures
|
|
if (count == 0U) {
|
|
for (auto it = m_ids.begin(); it != m_ids.end(); ++it)
|
|
delete it->second;
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
|
|
m_ids.clear();
|
|
m_repeaters.clear();
|
|
|
|
m_id = 0x00U;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool CGroupHandler::process(CHeaderData &header, DIRECTION, AUDIO_SOURCE)
|
|
{
|
|
if (m_id != 0x00U)
|
|
return false;
|
|
|
|
std::string my = header.getMyCall1();
|
|
m_id = header.getId();
|
|
|
|
m_linkTimer.start();
|
|
|
|
// Change the Your callsign to CQCQCQ
|
|
header.setCQCQCQ();
|
|
|
|
header.setFlag1(0x00);
|
|
header.setFlag2(0x00);
|
|
header.setFlag3(0x00);
|
|
|
|
// Build new repeater list
|
|
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
|
|
CSGSXLUser* user = it->second;
|
|
if (user) {
|
|
// Find the user in the cache
|
|
CUserData* userData = m_cache->findUser(user->getCallsign());
|
|
|
|
if (userData) {
|
|
// Find the users repeater in the repeater list, add it otherwise
|
|
CSGSRepeater* repeater = m_repeaters[userData->getRepeater()];
|
|
if (repeater == NULL) {
|
|
// Add a new repeater entry
|
|
repeater = new CSGSRepeater;
|
|
repeater->m_destination = std::string("/") + userData->getRepeater().substr(0, 6) + userData->getRepeater().back();
|
|
repeater->m_repeater = userData->getRepeater();
|
|
repeater->m_gateway = userData->getGateway();
|
|
repeater->m_address = userData->getAddress();
|
|
m_repeaters[userData->getRepeater()] = repeater;
|
|
}
|
|
|
|
delete userData;
|
|
userData = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (m_callsignSwitch) {
|
|
case SCS_GROUP_CALLSIGN:
|
|
header.setMyCall1(m_groupCallsign);
|
|
header.setMyCall2("SMRT");
|
|
break;
|
|
case SCS_USER_CALLSIGN:
|
|
header.setMyCall1(my);
|
|
header.setMyCall2(m_shortCallsign);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
CSGSXLId *tx = m_ids[m_id];
|
|
if (tx) {
|
|
if (!tx->isLogin())
|
|
sendToRepeaters(header);
|
|
} else
|
|
sendToRepeaters(header);
|
|
|
|
if (m_txMsgSwitch)
|
|
sendFromText(my);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CGroupHandler::process(CAMBEData &data, DIRECTION, AUDIO_SOURCE)
|
|
{
|
|
unsigned int id = data.getId();
|
|
if (id != m_id)
|
|
return false;
|
|
|
|
m_linkTimer.start();
|
|
|
|
CSGSXLId *tx = m_ids[id];
|
|
if (tx) {
|
|
if (!tx->isLogin())
|
|
sendToRepeaters(data);
|
|
} else
|
|
sendToRepeaters(data);
|
|
|
|
if (data.isEnd()) {
|
|
m_linkTimer.stop();
|
|
m_id = 0x00U;
|
|
|
|
// Clear the repeater list
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
m_repeaters.clear();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CGroupHandler::remoteLink(const std::string &reflector)
|
|
{
|
|
if (LT_NONE != m_linkType)
|
|
return false;
|
|
|
|
if (LONG_CALLSIGN_LENGTH != reflector.size())
|
|
return false;
|
|
if (0 == reflector.compare(0, 3, "XRF"))
|
|
m_linkType = LT_DEXTRA;
|
|
else if (0 == reflector.compare(0, 3, "DCS"))
|
|
m_linkType = LT_DCS;
|
|
else
|
|
return false;
|
|
|
|
m_linkReflector.assign(reflector);
|
|
return linkInt();
|
|
}
|
|
|
|
bool CGroupHandler::linkInt()
|
|
{
|
|
if (LT_NONE == m_linkType)
|
|
return false;
|
|
|
|
printf("Linking %s to %s reflector %s\n", m_repeater.c_str(), (LT_DEXTRA==m_linkType)?"DExtra":"DCS", m_linkReflector.c_str());
|
|
|
|
// Find the repeater to link to
|
|
CRepeaterData* data = m_cache->findRepeater(m_linkReflector);
|
|
if (data == NULL) {
|
|
printf("Cannot find the reflector in the cache, not linking\n");
|
|
return false;
|
|
}
|
|
|
|
m_linkGateway = data->getGateway();
|
|
bool rtv = true;
|
|
switch (m_linkType) {
|
|
case LT_DEXTRA:
|
|
m_linkStatus = LS_LINKING_DEXTRA;
|
|
CDExtraHandler::link(this, m_repeater, m_linkReflector, data->getAddress());
|
|
break;
|
|
case LT_DCS:
|
|
m_linkStatus = LS_LINKING_DCS;
|
|
CDCSHandler::link(this, m_repeater, m_linkReflector, data->getAddress());
|
|
break;
|
|
default:
|
|
rtv = false;
|
|
break;
|
|
}
|
|
delete data;
|
|
return rtv;
|
|
}
|
|
|
|
void CGroupHandler::clockInt(unsigned int ms)
|
|
{
|
|
if(m_audioUnit != NULL)
|
|
{
|
|
m_audioUnit->clock(ms);
|
|
}
|
|
m_linkTimer.clock(ms);
|
|
if (m_linkTimer.isRunning() && m_linkTimer.hasExpired()) {
|
|
m_linkTimer.stop();
|
|
m_id = 0x00U;
|
|
|
|
// Clear the repeater list
|
|
for (std::map<std::string, CSGSRepeater *>::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
m_repeaters.clear();
|
|
}
|
|
m_announceTimer.clock(ms);
|
|
if (m_announceTimer.hasExpired()) {
|
|
m_irc->sendHeardWithTXMsg(m_groupCallsign, " ", "CQCQCQ ", m_repeater, m_gateway, 0x00U, 0x00U, 0x00U, std::string(""), m_infoText);
|
|
if (m_offCallsign.size() && m_offCallsign.compare(" "))
|
|
m_irc->sendHeardWithTXMsg(m_offCallsign, " ", "CQCQCQ ", m_repeater, m_gateway, 0x00U, 0x00U, 0x00U, std::string(""), m_infoText);
|
|
m_announceTimer.start(60U * 60U); // 1 hour
|
|
|
|
}
|
|
if (m_oldlinkStatus!=m_linkStatus && 7==m_irc->getConnectionState()) {
|
|
updateReflectorInfo();
|
|
m_oldlinkStatus = m_linkStatus;
|
|
}
|
|
|
|
// For each incoming id
|
|
for (auto it = m_ids.begin(); it != m_ids.end(); ++it) {
|
|
CSGSXLId* tx = it->second;
|
|
|
|
if (tx != NULL && tx->clock(ms)) {
|
|
std::string callsign = tx->getUser()->getCallsign();
|
|
|
|
if (tx->isEnd()) {
|
|
CUserData* user = m_cache->findUser(callsign);
|
|
if (user) {
|
|
if (tx->isLogin()) {
|
|
sendAck(*user, AT_LOGIN);
|
|
} else if (tx->isInfo()) {
|
|
//TODO F4FXL 2020-03-15 Audio Info ?
|
|
//sendAck(*user, m_infoText);
|
|
} else if (tx->isLogoff()) {
|
|
sendAck(*user, AT_LOGOFF);
|
|
}
|
|
|
|
delete user;
|
|
user = NULL;
|
|
} else {
|
|
printf("Cannot find %s in the cache", callsign.c_str());
|
|
}
|
|
|
|
delete tx;
|
|
m_ids.erase(it);
|
|
|
|
// The iterator is now invalid, so we'll find the next expiry on the next clock tick with a
|
|
// new iterator
|
|
break;
|
|
} else {
|
|
if (tx->getId() == m_id) {
|
|
// Clear the repeater list if we're the relayed id
|
|
for (std::map<std::string, CSGSRepeater *>::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
|
|
delete it->second;
|
|
m_repeaters.clear();
|
|
m_id = 0x00U;
|
|
}
|
|
|
|
if (tx->isLogin()) {
|
|
tx->reset();
|
|
tx->setEnd();
|
|
} else if (tx->isLogoff()) {
|
|
m_users.erase(callsign);
|
|
tx->reset();
|
|
tx->setEnd();
|
|
} else if (tx->isInfo()) {
|
|
tx->reset();
|
|
tx->setEnd();
|
|
} else {
|
|
delete tx;
|
|
m_ids.erase(it);
|
|
// The iterator is now invalid, so we'll find the next expiry on the next clock tick with a
|
|
// new iterator
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Individual user expiry, but not for the permanent entries
|
|
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
|
|
CSGSXLUser* user = it->second;
|
|
if (user && m_permanent.find(user->getCallsign()) == m_permanent.end())
|
|
user->clock(ms);
|
|
}
|
|
|
|
// Don't do timeouts when relaying audio
|
|
if (m_id != 0x00U)
|
|
return;
|
|
|
|
// Individual user expiry
|
|
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
|
|
CSGSXLUser* user = it->second;
|
|
if (user && user->hasExpired()) {
|
|
printf("Removing %s from Smart Group %s, user timeout\n", user->getCallsign().c_str(), m_groupCallsign.c_str());
|
|
|
|
logUser(LU_OFF, m_groupCallsign, user->getCallsign()); // inform QuadNet
|
|
delete user;
|
|
m_users.erase(it);
|
|
// The iterator is now invalid, so we'll find the next expiry on the next clock tick with a
|
|
// new iterator
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGroupHandler::updateReflectorInfo()
|
|
{
|
|
std::string subcommand("REFLECTOR");
|
|
std::vector<std::string> parms;
|
|
std::string callsign(m_groupCallsign);
|
|
CUtils::ReplaceChar(callsign, ' ', '_');
|
|
parms.push_back(callsign);
|
|
std::string reflector(m_linkReflector);
|
|
if (reflector.size() < 8)
|
|
reflector.assign("________");
|
|
else
|
|
CUtils::ReplaceChar(reflector, ' ', '_');
|
|
parms.push_back(reflector);
|
|
switch (m_linkStatus) {
|
|
case LS_LINKING_DCS:
|
|
case LS_LINKING_DEXTRA:
|
|
case LS_PENDING_IRCDDB:
|
|
parms.push_back(std::string("LINKING"));
|
|
break;
|
|
case LS_LINKED_DCS:
|
|
case LS_LINKED_DEXTRA:
|
|
parms.push_back(std::string("LINKED"));
|
|
break;
|
|
case LS_NONE:
|
|
parms.push_back(std::string("UNLINKED"));
|
|
break;
|
|
default:
|
|
parms.push_back(std::string("FAILED"));
|
|
break;
|
|
}
|
|
parms.push_back(std::to_string(m_userTimeout));
|
|
std::string info(m_infoText);
|
|
info.resize(20, '_');
|
|
CUtils::ReplaceChar(info, ' ', '_');
|
|
parms.push_back(info);
|
|
|
|
m_irc->sendSGSInfo(subcommand, parms);
|
|
}
|
|
|
|
void CGroupHandler::logUser(LOGUSER lu, const std::string channel, const std::string user)
|
|
{
|
|
std::string cmd(LU_OFF==lu ? "LOGOFF" : "LOGON");
|
|
std::string chn(channel);
|
|
std::string usr(user);
|
|
CUtils::ReplaceChar(chn, ' ', '_');
|
|
CUtils::ReplaceChar(usr, ' ', '_');
|
|
std::vector<std::string> parms;
|
|
parms.push_back(chn);
|
|
parms.push_back(usr);
|
|
m_irc->sendSGSInfo(cmd, parms);
|
|
}
|
|
|
|
void CGroupHandler::sendToRepeaters(CHeaderData& header) const
|
|
{
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it) {
|
|
CSGSRepeater* repeater = it->second;
|
|
if (repeater != NULL) {
|
|
header.setYourCall(repeater->m_destination);
|
|
header.setDestination(repeater->m_address, G2_DV_PORT);
|
|
header.setRepeaters(repeater->m_gateway, repeater->m_repeater);
|
|
m_g2Handler->writeHeader(header);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGroupHandler::sendToRepeaters(CAMBEData &data) const
|
|
{
|
|
for (auto it = m_repeaters.begin(); it != m_repeaters.end(); ++it) {
|
|
CSGSRepeater* repeater = it->second;
|
|
if (repeater != NULL) {
|
|
data.setDestination(repeater->m_address, G2_DV_PORT);
|
|
m_g2Handler->writeAMBE(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGroupHandler::sendFromText(const std::string &my) const
|
|
{
|
|
std::string text;
|
|
switch (m_callsignSwitch) {
|
|
case SCS_GROUP_CALLSIGN:
|
|
text = std::string("FROM %") + my;
|
|
break;
|
|
case SCS_USER_CALLSIGN:
|
|
text = std::string("VIA SMARTGP ") + m_groupCallsign;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
CSlowDataEncoder slowData;
|
|
slowData.setTextData(text);
|
|
|
|
CAMBEData data;
|
|
data.setId(m_id);
|
|
|
|
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
|
|
::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
|
|
|
|
for (unsigned int i = 0U; i < 21U; i++) {
|
|
if (i == 0U) {
|
|
// The first AMBE packet is a sync
|
|
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
|
|
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
|
|
data.setSeq(i);
|
|
} else {
|
|
// The packets containing the text data
|
|
unsigned char slowDataBuffer[DATA_FRAME_LENGTH_BYTES];
|
|
slowData.getTextData(slowDataBuffer);
|
|
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, slowDataBuffer, DATA_FRAME_LENGTH_BYTES);
|
|
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
|
|
data.setSeq(i);
|
|
}
|
|
|
|
sendToRepeaters(data);
|
|
}
|
|
}
|
|
|
|
void CGroupHandler::sendAck(const CUserData &user, ACK_TYPE ackType)
|
|
{
|
|
if(m_audioUnit == NULL)
|
|
{
|
|
m_audioUnit = new CAudioUnit(m_g2Handler);
|
|
}
|
|
m_audioUnit->setAck(ackType, m_groupCallsign, user.getUser(), user.getRepeater(), user.getGateway(), user.getAddress());
|
|
m_audioUnit->sendAck();
|
|
}
|
|
|
|
void CGroupHandler::linkUp(DSTAR_PROTOCOL, const std::string &callsign)
|
|
{
|
|
printf("%s link to %s established\n", (LT_DEXTRA==m_linkType)?"DExtra":"DCS", callsign.c_str());
|
|
|
|
m_linkStatus = (LT_DEXTRA == m_linkType) ? LS_LINKED_DEXTRA : LS_LINKED_DCS;
|
|
}
|
|
|
|
bool CGroupHandler::linkFailed(DSTAR_PROTOCOL, const std::string &callsign, bool isRecoverable)
|
|
{
|
|
if (!isRecoverable) {
|
|
if (m_linkStatus != LS_NONE) {
|
|
printf("%s link to %s has failed\n", (LT_DEXTRA==m_linkType)?"DExtra":"DCS", callsign.c_str());
|
|
m_linkStatus = LS_NONE;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (m_linkStatus == LS_LINKING_DEXTRA || m_linkStatus == LS_LINKED_DEXTRA || m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS) {
|
|
printf("%s link to %s has failed, relinking\n", (LT_DEXTRA==m_linkType)?"DExtra":"DCS", callsign.c_str());
|
|
m_linkStatus = (LT_DEXTRA == m_linkType) ? LS_LINKING_DEXTRA : LS_LINKING_DCS;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CGroupHandler::linkRefused(DSTAR_PROTOCOL, const std::string &callsign)
|
|
{
|
|
if (m_linkStatus != LS_NONE) {
|
|
printf("%s link to %s was refused\n", (LT_DEXTRA==m_linkType)?"DExtra":"DCS", callsign.c_str());
|
|
m_linkStatus = LS_NONE;
|
|
}
|
|
}
|
|
|
|
bool CGroupHandler::singleHeader()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
DSTAR_LINKTYPE CGroupHandler::getLinkType()
|
|
{
|
|
return m_linkType;
|
|
}
|
|
|
|
void CGroupHandler::setLinkType(DSTAR_LINKTYPE linkType)
|
|
{
|
|
m_linkType = linkType;
|
|
}
|
|
|
|
void CGroupHandler::clearReflector()
|
|
{
|
|
m_linkReflector.clear();
|
|
}
|