ircDDBGateway/Common/StarNetHandler.cpp

1339 lines
34 KiB
C++
Raw Normal View History

2018-05-09 20:23:17 +02:00
/*
* Copyright (C) 2011-2014 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
* 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 "SlowDataEncoder.h"
#include "RepeaterHandler.h"
#include "StarNetHandler.h"
#include "DExtraHandler.h" // DEXTRA_LINK
#include "DStarDefines.h"
#include "DCSHandler.h" // DCS_LINK
#include <wx/filename.h>
#include <wx/tokenzr.h>
#include <vector>
const unsigned int MESSAGE_DELAY = 4U;
unsigned int CStarNetHandler::m_maxStarNets = 0U;
CStarNetHandler** CStarNetHandler::m_starNets = NULL;
CG2ProtocolHandler* CStarNetHandler::m_g2Handler = NULL;
CIRCDDB* CStarNetHandler::m_irc = NULL;
CCacheManager* CStarNetHandler::m_cache = NULL;
wxString CStarNetHandler::m_gateway;
wxString CStarNetHandler::m_name;
wxFFile* CStarNetHandler::m_logFile = NULL;
CStarNetUser::CStarNetUser(const wxString &callsign, unsigned int timeout) :
m_callsign(callsign),
m_timer(1000U, timeout)
{
m_timer.start();
}
CStarNetUser::~CStarNetUser()
{
}
bool CStarNetUser::clock(unsigned int ms)
{
m_timer.clock(ms);
return m_timer.isRunning() && m_timer.hasExpired();
}
bool CStarNetUser::hasExpired()
{
return m_timer.isRunning() && m_timer.hasExpired();
}
void CStarNetUser::reset()
{
m_timer.start();
}
wxString CStarNetUser::getCallsign() const
{
return m_callsign;
}
CTimer CStarNetUser::getTimer() const
{
return m_timer;
}
CStarNetId::CStarNetId(unsigned int id, unsigned int timeout, CStarNetUser* 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()
{
wxASSERT(user != NULL);
m_timer.start();
}
CStarNetId::~CStarNetId()
{
}
unsigned int CStarNetId::getId() const
{
return m_id;
}
void CStarNetId::reset()
{
m_timer.start();
}
void CStarNetId::setLogin()
{
m_login = true;
}
void CStarNetId::setInfo()
{
if (!m_login && !m_logoff)
m_info = true;
}
void CStarNetId::setLogoff()
{
if (!m_login && !m_info)
m_logoff = true;
}
void CStarNetId::setEnd()
{
m_end = true;
}
bool CStarNetId::clock(unsigned int ms)
{
m_timer.clock(ms);
return m_timer.isRunning() && m_timer.hasExpired();
}
bool CStarNetId::hasExpired()
{
return m_timer.isRunning() && m_timer.hasExpired();
}
bool CStarNetId::isLogin() const
{
return m_login;
}
bool CStarNetId::isInfo() const
{
return m_info;
}
bool CStarNetId::isLogoff() const
{
return m_logoff;
}
bool CStarNetId::isEnd() const
{
return m_end;
}
CStarNetUser* CStarNetId::getUser() const
{
return m_user;
}
CTextCollector& CStarNetId::getTextCollector()
{
return m_textCollector;
}
void CStarNetHandler::initialise(unsigned int maxStarNets, const wxString& name)
{
wxASSERT(maxStarNets > 0U);
m_maxStarNets = maxStarNets;
m_name = name;
m_starNets = new CStarNetHandler*[maxStarNets];
for (unsigned int i = 0U; i < maxStarNets; i++)
m_starNets[i] = NULL;
}
#if defined(DEXTRA_LINK) || defined(DCS_LINK)
void CStarNetHandler::add(const wxString& callsign, const wxString& logoff, const wxString& repeater, const wxString& infoText, const wxString& permanent, unsigned int userTimeout, unsigned int groupTimeout, STARNET_CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch, const wxString& reflector)
{
CStarNetHandler* starNet = new CStarNetHandler(callsign, logoff, repeater, infoText, permanent, userTimeout, groupTimeout, callsignSwitch, txMsgSwitch, reflector);
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
if (m_starNets[i] == NULL) {
m_starNets[i] = starNet;
return;
}
}
wxLogError(wxT("Cannot add StarNet group with callsign %s, no space"), callsign.c_str());
delete starNet;
}
#else
void CStarNetHandler::add(const wxString& callsign, const wxString& logoff, const wxString& repeater, const wxString& infoText, const wxString& permanent, unsigned int userTimeout, unsigned int groupTimeout, STARNET_CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch)
{
CStarNetHandler* starNet = new CStarNetHandler(callsign, logoff, repeater, infoText, permanent, userTimeout, groupTimeout, callsignSwitch, txMsgSwitch);
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
if (m_starNets[i] == NULL) {
m_starNets[i] = starNet;
return;
}
}
wxLogError(wxT("Cannot add StarNet group with callsign %s, no space"), callsign.c_str());
delete starNet;
}
#endif
void CStarNetHandler::setG2Handler(CG2ProtocolHandler* handler)
{
wxASSERT(handler != NULL);
m_g2Handler = handler;
}
void CStarNetHandler::setIRC(CIRCDDB* irc)
{
wxASSERT(irc != NULL);
m_irc = irc;
}
void CStarNetHandler::setCache(CCacheManager* cache)
{
wxASSERT(cache != NULL);
m_cache = cache;
}
void CStarNetHandler::setGateway(const wxString& gateway)
{
m_gateway = gateway;
}
void CStarNetHandler::setLogging(bool enable, const wxString& dir)
{
if (!enable)
return;
wxString fullName = STARNET_BASE_NAME;
if (!m_name.IsEmpty()) {
fullName.Append(wxT("_"));
fullName.Append(m_name);
}
wxFileName fileName(dir, fullName, wxT("log"));
m_logFile = new wxFFile;
bool ret = m_logFile->Open(fileName.GetFullPath(), wxT("wt"));
if (!ret) {
wxLogError(wxT("Unable to open %s for writing"), fileName.GetFullPath().c_str());
delete m_logFile;
m_logFile = NULL;
}
}
CStarNetHandler* CStarNetHandler::findStarNet(const wxString& callsign)
{
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
CStarNetHandler* starNet = m_starNets[i];
if (starNet != NULL) {
if (starNet->m_groupCallsign.IsSameAs(callsign))
return starNet;
}
}
return NULL;
}
CStarNetHandler* CStarNetHandler::findStarNet(const CHeaderData& header)
{
wxString your = header.getYourCall();
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
CStarNetHandler* starNet = m_starNets[i];
if (starNet != NULL) {
if (starNet->m_groupCallsign.IsSameAs(your))
return starNet;
if (starNet->m_offCallsign.IsSameAs(your))
return starNet;
}
}
return NULL;
}
CStarNetHandler* CStarNetHandler::findStarNet(const CAMBEData& data)
{
unsigned int id = data.getId();
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
CStarNetHandler* starNet = m_starNets[i];
if (starNet != NULL) {
if (starNet->m_id == id)
return starNet;
}
}
return NULL;
}
wxArrayString CStarNetHandler::listStarNets()
{
wxArrayString starNets;
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
CStarNetHandler* starNet = m_starNets[i];
if (starNet != NULL)
starNets.Add(starNet->m_groupCallsign);
}
return starNets;
}
CRemoteStarNetGroup* CStarNetHandler::getInfo() const
{
CRemoteStarNetGroup* data = new CRemoteStarNetGroup(m_groupCallsign, m_offCallsign, m_groupTimer.getTimer(), m_groupTimer.getTimeout());
for (CStarNetUsersHashMap::const_iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
data->addUser(user->getCallsign(), user->getTimer().getTimer(), user->getTimer().getTimeout());
}
return data;
}
void CStarNetHandler::finalise()
{
for (unsigned int i = 0U; i < m_maxStarNets; i++)
delete m_starNets[i];
delete[] m_starNets;
if (m_logFile != NULL) {
m_logFile->Close();
delete m_logFile;
}
}
void CStarNetHandler::clock(unsigned int ms)
{
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
if (m_starNets[i] != NULL)
m_starNets[i]->clockInt(ms);
}
}
#if defined(DEXTRA_LINK) || defined(DCS_LINK)
void CStarNetHandler::link()
{
for (unsigned int i = 0U; i < m_maxStarNets; i++) {
if (m_starNets[i] != NULL)
m_starNets[i]->linkInt();
}
}
#endif
#if defined(DEXTRA_LINK) || defined(DCS_LINK)
CStarNetHandler::CStarNetHandler(const wxString& callsign, const wxString& logoff, const wxString& repeater, const wxString& infoText, const wxString& permanent, unsigned int userTimeout, unsigned int groupTimeout, STARNET_CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch, const wxString& reflector) :
m_groupCallsign(callsign),
m_offCallsign(logoff),
m_shortCallsign(wxT("SNET")),
m_repeater(repeater),
m_infoText(infoText),
m_permanent(),
m_linkReflector(reflector),
m_linkGateway(),
m_linkStatus(LS_NONE),
m_linkTimer(1000U, NETWORK_TIMEOUT),
m_id(0x00U),
m_groupTimer(1000U, groupTimeout * 60U),
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();
// Create the short version of the STARnet Group callsign
wxString rest;
if (m_groupCallsign.StartsWith(wxT("STN"), &rest)) {
wxChar c = m_groupCallsign.GetChar(7U);
if (c == wxT(' '))
m_shortCallsign.Printf(wxT("S%s"), rest.Left(3U).c_str());
else
m_shortCallsign.Printf(wxT("%s%c"), rest.Left(3U).c_str(), c);
}
wxStringTokenizer tkn(permanent, wxT(","));
while (tkn.HasMoreTokens()) {
wxString callsign = tkn.GetNextToken();
callsign.resize(LONG_CALLSIGN_LENGTH, wxT(' '));
m_permanent.Add(callsign);
}
}
#else
CStarNetHandler::CStarNetHandler(const wxString& callsign, const wxString& logoff, const wxString& repeater, const wxString& infoText, const wxString& permanent, unsigned int userTimeout, unsigned int groupTimeout, STARNET_CALLSIGN_SWITCH callsignSwitch, bool txMsgSwitch) :
m_groupCallsign(callsign),
m_offCallsign(logoff),
m_shortCallsign(wxT("SNET")),
m_repeater(repeater),
m_infoText(infoText),
m_permanent(),
m_id(0x00U),
m_groupTimer(1000U, groupTimeout * 60U),
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();
// Create the short version of the STARnet Group callsign
wxString rest;
if (m_groupCallsign.StartsWith(wxT("STN"), &rest)) {
wxChar c = m_groupCallsign.GetChar(7U);
if (c == wxT(' '))
m_shortCallsign.Printf(wxT("S%s"), rest.Left(3U).c_str());
else
m_shortCallsign.Printf(wxT("%s%c"), rest.Left(3U).c_str(), c);
}
wxStringTokenizer tkn(permanent, wxT(","));
while (tkn.HasMoreTokens()) {
wxString callsign = tkn.GetNextToken();
callsign.resize(LONG_CALLSIGN_LENGTH, wxT(' '));
m_permanent.Add(callsign);
}
}
#endif
CStarNetHandler::~CStarNetHandler()
{
for (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it)
delete it->second;
for (CStarNetRepeatersHashMap::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
delete it->second;
m_users.clear();
m_repeaters.clear();
}
void CStarNetHandler::process(CHeaderData &header)
{
wxString my = header.getMyCall1();
wxString your = header.getYourCall();
unsigned int id = header.getId();
CStarNetUser* user = m_users[my];
// Ensure that this user is in the cache
CUserData* userData = m_cache->findUser(my);
if (userData == NULL)
m_irc->findUser(my);
if (your.IsSameAs(m_groupCallsign)) {
// This is a normal message for logging in/relaying
if (user == NULL) {
// This is a new user, add them to the list
if (m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Adding %s to StarNet group %s\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
my.c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
m_logFile->Flush();
}
// Start the StarNet group timer if not already running
if (!m_groupTimer.isRunning())
m_groupTimer.start();
user = new CStarNetUser(my, m_userTimeout * 60U);
m_users[my] = user;
CStarNetId* tx = new CStarNetId(id, MESSAGE_DELAY, user);
tx->setLogin();
m_ids[id] = tx;
} else {
user->reset();
// Check that it isn't a duplicate header
CStarNetId* tx = m_ids[id];
if (tx != NULL) {
delete userData;
return;
}
m_ids[id] = new CStarNetId(id, MESSAGE_DELAY, user);
}
} else {
delete userData;
userData = NULL;
// This is a logoff message
if (user == NULL) // Not a known user, ignore
return;
if (m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Removing %s from StarNet group %s, logged off\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
user->getCallsign().c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
m_logFile->Flush();
}
// Remove the user from the user list
m_users.erase(my);
CStarNetId* tx = new CStarNetId(id, MESSAGE_DELAY, user);
tx->setLogoff();
m_ids[id] = tx;
return;
}
m_groupTimer.start();
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 defined(DEXTRA_LINK)
header.setRepeaters(m_linkGateway, m_linkReflector);
CDExtraHandler::writeHeader(this, header, DIR_OUTGOING);
#endif
#if defined(DCS_LINK)
header.setRepeaters(m_linkGateway, m_linkReflector);
CDCSHandler::writeHeader(this, header, DIR_OUTGOING);
#endif
// Get the home repeater of the user
wxString exclude;
if (userData != NULL) {
exclude = userData->getRepeater();
delete userData;
userData = NULL;
}
// Build new repeater list
for (CStarNetUsersHashMap::const_iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
if (user != NULL) {
// Find the user in the cache
CUserData* userData = m_cache->findUser(user->getCallsign());
if (userData != NULL) {
// Check for the excluded repeater
if (!userData->getRepeater().IsSameAs(exclude)) {
// Find the users repeater in the repeater list, add it otherwise
CStarNetRepeater* repeater = m_repeaters[userData->getRepeater()];
if (repeater == NULL) {
// Add a new repeater entry
repeater = new CStarNetRepeater;
repeater->m_destination = wxT("/") + userData->getRepeater().Left(6U) + userData->getRepeater().Right(1U);
repeater->m_repeater = userData->getRepeater();
repeater->m_gateway = userData->getGateway();
repeater->m_address = userData->getAddress();
repeater->m_local = CRepeaterHandler::findDVRepeater(userData->getRepeater());
m_repeaters[userData->getRepeater()] = repeater;
}
}
delete userData;
userData = NULL;
}
}
}
switch (m_callsignSwitch) {
case SCS_GROUP_CALLSIGN:
// Change the My Callsign 1 to be that of the StarNet group
header.setMyCall1(m_groupCallsign);
header.setMyCall2(wxT("SNET"));
break;
case SCS_USER_CALLSIGN:
// Change the My Callsign 2 to be that of the StarNet group
header.setMyCall1(my);
header.setMyCall2(m_shortCallsign);
break;
default:
break;
}
sendToRepeaters(header);
if (m_txMsgSwitch)
sendFromText(my);
}
void CStarNetHandler::process(CAMBEData &data)
{
unsigned int id = data.getId();
CStarNetId* tx = m_ids[id];
if (tx == NULL)
return;
tx->reset();
CStarNetUser* user = tx->getUser();
user->reset();
m_groupTimer.start();
// 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) {
wxString text = tx->getTextCollector().getData();
if (text.Left(6U).IsSameAs(wxT("LOGOFF"), false)) {
if (m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Removing %s from StarNet group %s, logged off\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
user->getCallsign().c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
m_logFile->Flush();
}
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;
}
if (text.Left(4U).IsSameAs(wxT("INFO"), false)) {
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) {
#if defined(DEXTRA_LINK)
CDExtraHandler::writeAMBE(this, data, DIR_OUTGOING);
#endif
#if defined(DCS_LINK)
CDCSHandler::writeAMBE(this, data, DIR_OUTGOING);
#endif
sendToRepeaters(data);
}
if (data.isEnd()) {
if (id == m_id) {
// Clear the repeater list if we're the relayed id
for (CStarNetRepeatersHashMap::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(user->getCallsign());
tx->reset();
tx->setEnd();
} else if (tx->isInfo()) {
tx->reset();
tx->setEnd();
} else {
m_ids.erase(tx->getId());
delete tx;
}
}
}
bool CStarNetHandler::logoff(const wxString &callsign)
{
if (callsign.IsSameAs(wxT("ALL "))) {
for (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
if (user != NULL && m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Removing %s from StarNet group %s, logged off by remote control\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
user->getCallsign().c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
}
delete user;
}
if (m_logFile != NULL)
m_logFile->Flush();
for (CStarNetIdsHashMap::iterator it = m_ids.begin(); it != m_ids.end(); ++it)
delete it->second;
for (CStarNetRepeatersHashMap::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
delete it->second;
m_users.clear();
m_ids.clear();
m_repeaters.end();
m_groupTimer.stop();
m_id = 0x00U;
return true;
} else {
CStarNetUser* user = m_users[callsign];
if (user == NULL) {
wxLogMessage(wxT("Invalid callsign asked to logoff"));
return false;
}
if (m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Removing %s from StarNet group %s, logged off by remote control\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
user->getCallsign().c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
m_logFile->Flush();
}
// Find any associated id structure associated with this use, and the logged off user is the
// currently relayed one, remove his id.
for (CStarNetIdsHashMap::iterator it = m_ids.begin(); it != m_ids.end(); ++it) {
CStarNetId* 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 = 0U;
for (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it) {
if (it->second != NULL)
count++;
}
// If none then clear all the data structures
if (count == 0U) {
for (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it)
delete it->second;
for (CStarNetIdsHashMap::iterator it = m_ids.begin(); it != m_ids.end(); ++it)
delete it->second;
for (CStarNetRepeatersHashMap::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
delete it->second;
m_users.clear();
m_ids.clear();
m_repeaters.end();
m_groupTimer.stop();
m_id = 0x00U;
}
return true;
}
}
#if defined(DEXTRA_LINK) || defined(DCS_LINK)
bool CStarNetHandler::process(CHeaderData &header, DIRECTION, AUDIO_SOURCE)
{
if (m_id != 0x00U)
return false;
wxString 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 (CStarNetUsersHashMap::const_iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
if (user != NULL) {
// Find the user in the cache
CUserData* userData = m_cache->findUser(user->getCallsign());
if (userData != NULL) {
// Find the users repeater in the repeater list, add it otherwise
CStarNetRepeater* repeater = m_repeaters[userData->getRepeater()];
if (repeater == NULL) {
// Add a new repeater entry
repeater = new CStarNetRepeater;
repeater->m_destination = wxT("/") + userData->getRepeater().Left(6U) + userData->getRepeater().Right(1U);
repeater->m_repeater = userData->getRepeater();
repeater->m_gateway = userData->getGateway();
repeater->m_address = userData->getAddress();
repeater->m_local = CRepeaterHandler::findDVRepeater(userData->getRepeater());
m_repeaters[userData->getRepeater()] = repeater;
}
delete userData;
userData = NULL;
}
}
}
switch (m_callsignSwitch) {
case SCS_GROUP_CALLSIGN:
// Change the My Callsign 1 to be that of the StarNet group
header.setMyCall1(m_groupCallsign);
header.setMyCall2(wxT("SNET"));
break;
case SCS_USER_CALLSIGN:
// Change the My Callsign 2 to be that of the StarNet group
header.setMyCall1(my);
header.setMyCall2(m_shortCallsign);
break;
default:
break;
}
sendToRepeaters(header);
if (m_txMsgSwitch)
sendFromText(my);
return true;
}
bool CStarNetHandler::process(CAMBEData &data, DIRECTION, AUDIO_SOURCE)
{
unsigned int id = data.getId();
if (id != m_id)
return false;
m_linkTimer.start();
sendToRepeaters(data);
if (data.isEnd()) {
m_linkTimer.stop();
m_id = 0x00U;
// Clear the repeater list
for (CStarNetRepeatersHashMap::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
delete it->second;
m_repeaters.clear();
}
return true;
}
#endif
#if defined(DEXTRA_LINK)
void CStarNetHandler::linkInt()
{
if (m_linkReflector.IsEmpty())
return;
wxLogMessage(wxT("Linking %s at startup to DExtra reflector %s"), m_repeater.c_str(), m_linkReflector.c_str());
// Find the repeater to link to
CRepeaterData* data = m_cache->findRepeater(m_linkReflector);
if (data == NULL) {
wxLogError(wxT("Cannot find the reflector in the cache, not linking"));
return;
}
m_linkGateway = data->getGateway();
m_linkStatus = LS_LINKING_DEXTRA;
CDExtraHandler::link(this, m_repeater, m_linkReflector, data->getAddress());
delete data;
}
#endif
#if defined(DCS_LINK)
void CStarNetHandler::linkInt()
{
if (m_linkReflector.IsEmpty())
return;
wxLogMessage(wxT("Linking %s at startup to DCS reflector %s"), m_repeater.c_str(), m_linkReflector.c_str());
// Find the repeater to link to
CRepeaterData* data = m_cache->findRepeater(m_linkReflector);
if (data == NULL) {
wxLogError(wxT("Cannot find the reflector in the cache, not linking"));
return;
}
m_linkGateway = data->getGateway();
m_linkStatus = LS_LINKING_DCS;
CDCSHandler::link(this, m_repeater, m_linkReflector, data->getAddress());
delete data;
}
#endif
void CStarNetHandler::clockInt(unsigned int ms)
{
#if defined(DEXTRA_LINK) || defined(DCS_LINK)
m_linkTimer.clock(ms);
if (m_linkTimer.isRunning() && m_linkTimer.hasExpired()) {
m_linkTimer.stop();
m_id = 0x00U;
// Clear the repeater list
for (CStarNetRepeatersHashMap::iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it)
delete it->second;
m_repeaters.clear();
}
#endif
m_announceTimer.clock(ms);
if (m_announceTimer.hasExpired()) {
m_irc->sendHeardWithTXMsg(m_groupCallsign, wxT(" "), wxT("CQCQCQ "), m_repeater, m_gateway, 0x00U, 0x00U, 0x00U, wxEmptyString, m_infoText);
if (!m_offCallsign.IsEmpty() && !m_offCallsign.IsSameAs(wxT(" ")))
m_irc->sendHeardWithTXMsg(m_offCallsign, wxT(" "), wxT("CQCQCQ "), m_repeater, m_gateway, 0x00U, 0x00U, 0x00U, wxEmptyString, m_infoText);
m_announceTimer.start(60U * 60U); // 1 hour
}
// For each incoming id
for (CStarNetIdsHashMap::iterator it = m_ids.begin(); it != m_ids.end(); ++it) {
CStarNetId* tx = it->second;
if (tx != NULL && tx->clock(ms)) {
wxString callsign = tx->getUser()->getCallsign();
if (tx->isEnd()) {
CUserData* user = m_cache->findUser(callsign);
if (user != NULL) {
if (tx->isLogin())
sendAck(*user, wxT("Logged in"));
else if (tx->isInfo())
sendAck(*user, m_infoText);
else if (tx->isLogoff())
sendAck(*user, wxT("Logged off"));
delete user;
user = NULL;
} else {
wxLogError(wxT("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 (CStarNetRepeatersHashMap::iterator it1 = m_repeaters.begin(); it1 != m_repeaters.end(); ++it1)
delete it1->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 (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
if (user != NULL && m_permanent.Index(user->getCallsign()) == wxNOT_FOUND)
user->clock(ms);
}
// Handle the group expiry timer
m_groupTimer.clock(ms);
// Don't do timeouts when relaying audio
if (m_id != 0x00U)
return;
if (m_groupTimer.isRunning() && m_groupTimer.hasExpired()) {
std::vector<CStarNetUser*> permanent;
// Clear all the users, except the permenent one
for (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
if (user != NULL) {
if (m_permanent.Index(user->getCallsign()) != wxNOT_FOUND) {
permanent.push_back(user);
} else {
if (m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Removing %s from StarNet group %s, group timeout\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
user->getCallsign().c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
m_logFile->Flush();
}
delete user;
}
}
}
m_users.clear();
// Re-insert the permenent users
for (std::vector<CStarNetUser*>::const_iterator it = permanent.begin(); it != permanent.end(); ++it) {
CStarNetUser* user = *it;
wxString callsign = user->getCallsign();
m_users[callsign] = user;
}
m_groupTimer.stop();
}
// Individual user expiry
for (CStarNetUsersHashMap::iterator it = m_users.begin(); it != m_users.end(); ++it) {
CStarNetUser* user = it->second;
if (user != NULL && user->hasExpired()) {
if (m_logFile != NULL) {
time_t timeNow = ::time(NULL);
struct tm* tm = ::gmtime(&timeNow);
wxString text;
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: Removing %s from StarNet group %s, user timeout\n"),
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
user->getCallsign().c_str(), m_groupCallsign.c_str());
m_logFile->Write(text);
m_logFile->Flush();
}
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 CStarNetHandler::sendToRepeaters(CHeaderData& header) const
{
for (CStarNetRepeatersHashMap::const_iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it) {
CStarNetRepeater* 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);
if (repeater->m_local != NULL)
repeater->m_local->process(header, DIR_INCOMING, AS_G2);
else
m_g2Handler->writeHeader(header);
}
}
}
void CStarNetHandler::sendToRepeaters(CAMBEData& data) const
{
for (CStarNetRepeatersHashMap::const_iterator it = m_repeaters.begin(); it != m_repeaters.end(); ++it) {
CStarNetRepeater* repeater = it->second;
if (repeater != NULL) {
data.setDestination(repeater->m_address, G2_DV_PORT);
if (repeater->m_local != NULL)
repeater->m_local->process(data, DIR_INCOMING, AS_G2);
else
m_g2Handler->writeAMBE(data);
}
}
}
void CStarNetHandler::sendFromText(const wxString& my) const
{
wxString text;
switch (m_callsignSwitch) {
case SCS_GROUP_CALLSIGN:
text.Printf(wxT("FROM %s"), my.c_str());
break;
case SCS_USER_CALLSIGN:
text.Printf(wxT("VIA STARnet %s"), m_groupCallsign.c_str());
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 CStarNetHandler::sendAck(const CUserData& user, const wxString& text) const
{
unsigned int id = CHeaderData::createId();
CHeaderData header(m_groupCallsign, wxT(" "), user.getUser(), user.getGateway(), user.getRepeater());
header.setDestination(user.getAddress(), G2_DV_PORT);
header.setId(id);
m_g2Handler->writeHeader(header);
CSlowDataEncoder slowData;
slowData.setTextData(text);
CAMBEData data;
data.setId(id);
data.setDestination(user.getAddress(), G2_DV_PORT);
unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES];
::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
for (unsigned int i = 0U; i < 20U; 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 if (i == 19U) {
// The last packet of the ack
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES);
data.setData(buffer, DV_FRAME_MAX_LENGTH_BYTES);
data.setSeq(i);
data.setEnd(true);
} 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);
}
m_g2Handler->writeAMBE(data);
}
}
#if defined(DEXTRA_LINK)
void CStarNetHandler::linkUp(DSTAR_PROTOCOL, const wxString& callsign)
{
wxLogMessage(wxT("DExtra link to %s established"), callsign.c_str());
m_linkStatus = LS_LINKED_DEXTRA;
}
bool CStarNetHandler::linkFailed(DSTAR_PROTOCOL, const wxString& callsign, bool isRecoverable)
{
if (!isRecoverable) {
if (m_linkStatus != LS_NONE) {
wxLogMessage(wxT("DExtra link to %s has failed"), callsign.c_str());
m_linkStatus = LS_NONE;
}
return false;
}
if (m_linkStatus == LS_LINKING_DEXTRA || m_linkStatus == LS_LINKED_DEXTRA) {
wxLogMessage(wxT("DExtra link to %s has failed, relinking"), callsign.c_str());
m_linkStatus = LS_LINKING_DEXTRA;
return true;
}
return false;
}
void CStarNetHandler::linkRefused(DSTAR_PROTOCOL, const wxString& callsign)
{
if (m_linkStatus != LS_NONE) {
wxLogMessage(wxT("DExtra link to %s was refused"), callsign.c_str());
m_linkStatus = LS_NONE;
}
}
bool CStarNetHandler::singleHeader()
{
return true;
}
#endif
#if defined(DCS_LINK)
void CStarNetHandler::linkUp(DSTAR_PROTOCOL, const wxString& callsign)
{
wxLogMessage(wxT("DCS link to %s established"), callsign.c_str());
m_linkStatus = LS_LINKED_DCS;
}
void CStarNetHandler::linkRefused(DSTAR_PROTOCOL, const wxString& callsign)
{
if (m_linkStatus != LS_NONE) {
wxLogMessage(wxT("DCS link to %s was refused"), callsign.c_str());
m_linkStatus = LS_NONE;
}
}
bool CStarNetHandler::linkFailed(DSTAR_PROTOCOL, const wxString& callsign, bool isRecoverable)
{
if (!isRecoverable) {
if (m_linkStatus != LS_NONE) {
wxLogMessage(wxT("DCS link to %s has failed"), callsign.c_str());
m_linkStatus = LS_NONE;
}
return false;
}
if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS) {
wxLogMessage(wxT("DCS link to %s has failed, relinking"), callsign.c_str());
m_linkStatus = LS_LINKING_DCS;
return true;
}
return false;
}
bool CStarNetHandler::singleHeader()
{
return true;
}
#endif