Files
qt6-migration-pipeline/TetraIF/PrtCnv/PrtCnvRadioThread.cpp
T
2026-07-14 12:07:36 +02:00

2268 lines
82 KiB
C++

#include "PrtCnvRadioThread.hpp"
#include "PrtCnvRadioTrafficThread.hpp"
#include "PrtCnvCentralThread.hpp"
#include "PrtCnvMember.hpp"
#include "PrtCnvTelegramLogThread.hpp"
#include "PrtCnvInjectionThread.hpp"
#include "PrtCnvThreadStarter.hpp"
#include "PrtCnvDbThread.hpp"
#include <CoPrtCnv/CoPrtCnvCentralTelegram.hpp>
#include <CoPrtCnv/CoPrtCnvTelegramConverter.hpp>
#include <CoPrtCnv/CoPrtCnvHexDumpHelper.hpp>
#include <TrainingAreaBase/training_area_telegrams.h>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QNetworkInterface>
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QDateTime>
#include <QtCore/QCoreApplication>
#include <QtCore/QStringList>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <time.h>
#include <iostream>
#include <math.h>
namespace PrtCnv
{
static BiteError getBiteError(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_radioTelegram,
size_t iOffset)
{
BiteError ret;
ret.canDeviceNumber = a_radioTelegram.getPayloadDataUInt8(iOffset + 0);
ret.subdeviceNumber = a_radioTelegram.getPayloadDataUInt8(iOffset + 1);
ret.uniqueId = a_radioTelegram.getPayloadDataUInt32(iOffset + 2, 4);
ret.errorNumber = a_radioTelegram.getPayloadDataUInt8(iOffset + 6);
return ret;
}
PrtCnvRadioThread::PrtCnvRadioThread(int exerciseId, PrtCnvThreadStarter *a_parent)
: PrtCnvSocketThread(a_parent, "PrtCnvRadioThread"),
m_CentralThread(nullptr),
m_radioTrafficThread(nullptr),
m_SendCounter(0),
m_HostISSI(0),
START_COUNTER(0),
END_COUNTER(256),
m_EnvironmentTelegramTimer(nullptr),
m_BroadcastISSI(0),
m_LoginPathWithPrefix(QString()),
m_ExerciseId(exerciseId),
m_DoLicenseDespiteSelftestOrConfigFailure(false),
m_DoCheckIdentity(true),
m_DoDumpReceivedTelegrams(false),
m_DoDumpSentTelegrams(false),
m_DoUseSDSTL(false),
m_EmergencyCallISSI(0),
m_EnvironmentTelegram(nullptr),
m_SendLoad(E_TrafficLoad::Unknown),
m_SendQueueFullLoad(0),
m_SendQueueHighLoad(0),
m_SendTelegramsNormalLoad(100),
m_SendTelegramsHighLoad(50),
m_SendTelegramsFullLoad(10),
m_SentTelegrams(0),
m_SentBytes(0),
m_TickCounter(0),
m_TicksPerSecond(0),
m_AverageIndex(0),
m_ReceivedTelegrams(0),
m_ReceivedBytes(0),
m_bUseScrambling(false),
m_loggerStats(getLogger("PrtCnvRadioThread.statistic")),
m_exerciseEquipmentAssignmentMutex(QMutex::Recursive)
{
m_Name = QStringLiteral("Radio");
for (int i = 0; i < AVERAGE_COUNT; ++i)
{
m_SentBytesAverage[i] = 0;
m_ReceivedBytesAverage[i] = 0;
}
qRegisterMetaType<N_CoPrtCnv::E_LicensingKind>("N_CoPrtCnv::E_LicensingKind");
}
int32_t PrtCnvRadioThread::create(NGL::Settings *settings)
{
INFO_LOG(m_logger) << "create";
PrtCnvSocketThread::create(settings);
m_BroadcastManager.create(settings);
m_CentralThread = qobject_cast<PrtCnvCentralThread *>(m_Parent->getOtherThread(PrtCnvApplication::e_Central));
connect(this, &PrtCnvRadioThread::checkSendToRadio,
this, &PrtCnvRadioThread::checkSend, Qt::QueuedConnection);
connect(this, &PrtCnvRadioThread::checkMemberForValidLogin,
this, &PrtCnvRadioThread::onCheckMemberForValidLogin, Qt::QueuedConnection);
connect(this, &PrtCnvRadioThread::checkSendToCentral,
m_CentralThread, &PrtCnvCentralThread::checkSend, Qt::QueuedConnection);
connect(this, &PrtCnvRadioThread::stopped,
m_CentralThread, &PrtCnvCentralThread::radioStopped, Qt::QueuedConnection);
connect(this, &PrtCnvRadioThread::cleant,
m_CentralThread, &PrtCnvCentralThread::radioCleant, Qt::QueuedConnection);
connect(this, &PrtCnvRadioThread::tetraConnectionState,
m_CentralThread, &PrtCnvCentralThread::onTetraConnectionState, Qt::QueuedConnection);
connect(this, &PrtCnvRadioThread::playerLicencedChanged,
m_CentralThread, &PrtCnvCentralThread::onPlayerLicencedChanged, Qt::QueuedConnection);
connect(&m_BroadcastManager, &PrtCnvBroadcastMgr::areaEffectSimulationDeactivated,
this, &PrtCnvRadioThread::areaEffectSimulationDeactivated);
connect(&m_BroadcastManager, &PrtCnvBroadcastMgr::areaEffectSimulationActivated,
this, &PrtCnvRadioThread::areaEffectSimulationActivated);
auto mainApp = m_Parent->getMainApp();
connect(mainApp, &PrtCnvApplication::outgoingBroadcastTelegramsRequest,
this, &PrtCnvRadioThread::onBroadcastTelegramsRequestFromCentral);
connect(this, &PrtCnvRadioThread::warehouseMessageFromPtrCnv,
mainApp, &PrtCnvApplication::warehouseMessageFromPtrCnv);
static_assert(sizeof(N_CoPrtCnv::CoPrtCnvRadioTelegram::PrtCnvRadioGwHeader) == 4,
"This code expects a size of 4 for CoPrtCnvRadioTelegram::PrtCnvRadioGwHeader (otherwise it will not work correct)"
);
return 0;
}
int32_t PrtCnvRadioThread::destroy()
{
INFO_LOG(m_logger) << "Destroy";
PrtCnvSocketThread::destroy();
delete m_EnvironmentTelegramTimer; m_EnvironmentTelegramTimer = nullptr;
return 0;
}
int32_t PrtCnvRadioThread::init()
{
INFO_LOG(m_logger) << "init";
PrtCnvSocketThread::init();
m_BroadcastManager.setDbThread(m_DbThread);
m_TickTimeout = m_Config->getInteger(QStringLiteral("Broadcast"), QStringLiteral("SendGap"), 500);
m_TicksPerSecond = 1000 / m_TickTimeout;
m_BroadcastManager.init();
N_CoPrtCnv::CoPrtCnvRadioTelegram::setEtsMode(m_Config->getBoolean(getName(), QStringLiteral("EtsMode"), false));
m_HostISSI = m_Config->getInteger(getName(), QStringLiteral("HostISSI"), 9998);
m_BroadcastISSI = m_Config->getInteger(getName(), QStringLiteral("BroadcastISSI"), 5000);
m_EmergencyCallISSI = m_Config->getInteger(getName(), QStringLiteral("EmergencyCallISSI"), 9000);
if (m_DoCleanDb)
m_DbThread->licenseMember(m_DbThread->addMember(0, m_BroadcastISSI));
QString groupString = m_Config->getString(getName(), QStringLiteral("ListenGroups"), QString());
const QVector<QStringRef> groups = groupString.splitRef(QLatin1Char(','), QString::SkipEmptyParts);
for (const QStringRef &sr : groups)
{
uint32_t group = sr.toUInt();
if (group > 0)
m_ListenGroups.insert(group);
}
if (m_ListenGroups.size() > 5)
{
while (m_ListenGroups.size() > 5)
m_ListenGroups.erase(m_ListenGroups.end());
WARNING_LOG(m_logger) << "More than 5 ListenGroups defined - ignoring all except the first 5";
}
m_LoginPathWithPrefix = m_Config->getString(QStringLiteral("Login"), QStringLiteral("LoginFilePathWithPrefix"), QStringLiteral("login"));
if (!QDir::isAbsolutePath(m_LoginPathWithPrefix))
m_LoginPathWithPrefix = QCoreApplication::applicationDirPath() + QLatin1String("/../") + m_LoginPathWithPrefix;
m_LoginPathWithPrefix = QDir::cleanPath(QDir::fromNativeSeparators(m_LoginPathWithPrefix));
QString path = m_LoginPathWithPrefix.left(m_LoginPathWithPrefix.lastIndexOf(QLatin1Char('/')));
QDir().mkpath(path);
m_DoLicenseDespiteSelftestOrConfigFailure = m_Config->getBoolean(QStringLiteral("Login"), QStringLiteral("DoLicenseDespiteSelftestOrConfigFailure"), false);
initIdentityArray();
m_bUseScrambling = m_Config->getBoolean(getName(), QStringLiteral("UseScrambling"), false);
m_DoDumpReceivedTelegrams = m_Config->getBoolean(getName(), QStringLiteral("DumpReceivedTelegrams"), false);
m_DoDumpSentTelegrams = m_Config->getBoolean(getName(), QStringLiteral("DumpSentTelegrams"), false);
m_DoUseSDSTL = m_Config->getBoolean(getName(), QStringLiteral("UseSDS_TL"), false);
const QString trafficControl(QStringLiteral("TrafficControl"));
m_SendQueueHighLoad = m_Config->getInteger(trafficControl, QStringLiteral("SendQueueHighLoad"), 1000);
m_SendQueueFullLoad = m_Config->getInteger(trafficControl, QStringLiteral("SendQueueFullLoad"), 2000);
m_SendTelegramsNormalLoad = m_Config->getInteger(trafficControl, QStringLiteral("SendTelegramsNormalLoad"), 100);
m_SendTelegramsHighLoad = m_Config->getInteger(trafficControl, QStringLiteral("SendTelegramsHighLoad"), 50);
m_SendTelegramsFullLoad = m_Config->getInteger(trafficControl, QStringLiteral("SendTelegramsFullLoad"), 10);
m_SendLoad = E_TrafficLoad::Unknown;
if (!m_DoCleanDb)
m_BroadcastManager.restoreAfterCrash();
emit initDone();
return 0;
}
int32_t PrtCnvRadioThread::start()
{
INFO_LOG(m_logger) << "Start";
PrtCnvSocketThread::start();
connectSocket();
return 0;
}
void PrtCnvRadioThread::connected()
{
INFO_LOG(m_logger) << "Radio-Socket connected m_Socket=" << m_Socket;
m_ReceiveCounter = END_COUNTER - 1;
m_SendCounter = END_COUNTER;
m_SentTelegrams = 0;
m_SentBytes = 0;
m_ReceivedTelegrams = 0;
m_ReceivedBytes = 0;
m_TickCounter = 0;
for (int i = 0; i < AVERAGE_COUNT; i++)
{
m_SentBytesAverage[i] = 0;
m_ReceivedBytesAverage[i] = 0;
}
m_AverageIndex = 0;
doRegister();
startTickTimer();
m_ConnectionTimer->stop();
m_State = PrtCnvSocketThread::e_REGISTERING;
}
void PrtCnvRadioThread::prepareCleanUp()
{
m_DoCleanup = true;
}
void PrtCnvRadioThread::doStop()
{
m_DoCleanup = true;
if (m_EnvironmentTelegramTimer)
m_EnvironmentTelegramTimer->stop();
m_TickTimer->stop();
disconnectSocket();
}
void PrtCnvRadioThread::onBroadcastTelegramsRequestFromCentral(int equipmentNumber, int broadcastIndex, int resetCounter)
{
m_BroadcastManager.handleNachforderungBroadcastTelegram(equipmentNumber, broadcastIndex, resetCounter);
}
void PrtCnvRadioThread::doClean()
{
INFO_LOG(m_logger) << "doClean";
m_BroadcastManager.cleanAndInit(true);
m_DbThread->setResetCounter(m_DbThread->getResetCounter() + 1);
emit cleant();
}
void PrtCnvRadioThread::doStart()
{
m_DoCleanup = false;
connectSocket();
if (m_EnvironmentTelegramTimer && m_EnvironmentTelegramTimer->interval() > 0)
m_EnvironmentTelegramTimer->start();
}
void PrtCnvRadioThread::disconnected()
{
INFO_LOG(m_logger) << "Radio-Socket disconnected m_Socket=" << m_Socket;
if (e_WORKING == m_State || e_REGISTERING == m_State)
{
m_Socket->disconnectFromHost();
if (m_Socket->state() != QAbstractSocket::UnconnectedState)
m_Socket->waitForDisconnected();
}
m_State = e_DISCONNECTING;
if (m_DoCleanup)
{
m_ConnectionTimer->stop();
emit stopped();
}
else
connectSocket();
emit tetraConnectionState(false);
}
void PrtCnvRadioThread::readyRead()
{
N_CoPrtCnv::CoPrtCnvRadioTelegram::PrtCnvRadioGwHeader header;
while (true)
{
if (m_DoCleanup)
break;
qint64 bytesRead = m_Socket->peek(reinterpret_cast<char *>(&header), sizeof(header));
if (0 == bytesRead)
break;
if (sizeof(header) != bytesRead)
break;
if (N_CoPrtCnv::CoPrtCnvRadioTelegram::MAGIC_NUMBER != header.m_MagicNumber)
{
ERROR_LOG(m_logger) << "Reconnect because radio-Telegram does not begin with magic-number 0xA5: I got 0x"
<< QString::number(header.m_MagicNumber, 16);
disconnectSocket();
break;
}
if (!N_CoPrtCnv::CoPrtCnvRadioTelegram::isTelegramLengthOk(header.m_Length))
WARNING_LOG(m_logger) << "Radio-Telegram length to big: " << header.m_Length
<< ", but I proceed with this telegram.";
uint16_t dataSize = header.m_Length + sizeof(header);
if (m_Socket->bytesAvailable() < dataSize)
{
INFO_LOG(m_logger) << "not enough bytes available - waiting for more data";
break;
}
QByteArray buf(dataSize, Qt::Uninitialized);
bytesRead = 0;
while (bytesRead < dataSize)
{
qint64 br = m_Socket->read(&buf.data()[bytesRead], dataSize - bytesRead);
bytesRead += br;
}
if (!checkAndUpdateReceiveCounter(header.m_Counter, END_COUNTER, START_COUNTER))
{
m_LogThread->writeRadioTelegramLog(buf, true, 1);
ERROR_LOG(m_logger) << "Reconnect because frame-counter of received radio-telegram was wrong";
disconnectSocket();
break;
}
handleReceivedTelegram(buf);
m_ReceivedTelegrams++;
m_ReceivedBytes += dataSize;
}
}
void PrtCnvRadioThread::handleReceivedTelegram(const QByteArray &data)
{
if (m_DoDumpReceivedTelegrams)
{
INFO_LOG(m_logger) << "Received radio-telegram:";
NGL::NGLLoggerHelper::hexDumpHelper(m_logger, NGL::LogLevel::Info, data);
}
switch (data.at(4))
{
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_RegisterRsp:
handleRegisterRsp(data.at(5));
m_State = e_WORKING;
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_DataIndReq:
handleDataIndReq(data, true);
emit checkSendToCentral();
return;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_ReportIndReq:
handleReportIndReq(data);
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_StatusIndReq:
handleStatusIndReq(data);
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_AirInterfaceLoadReq:
handleAirInterfaceLoadReq(data);
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_AirLoadHyterra:
break;
default:
ERROR_LOG(m_logger) << "Radio-Telegram with PDU-Type=" << data.at(4)
<< " not implemented!";
break;
}
m_LogThread->writeRadioTelegramLog(data, true, 1);
}
void PrtCnvRadioThread::handleDataIndReq(const QByteArray &data, bool bIsMaybeScrambled)
{
N_CoPrtCnv::CoPrtCnvRadioTelegram radioTelegram(data);
const uint32_t dstISSI = radioTelegram.getDestISSI();
if (m_bUseScrambling && bIsMaybeScrambled && m_EmergencyCallISSI != dstISSI)
{
if (!unscrambleContent(radioTelegram))
return;
}
m_LogThread->writeRadioTelegramLog(radioTelegram.data(), true, 1);
if (radioTelegram.isNormalTelegram() && radioTelegram.getTelID() == GEF_ESW_MELDUNG)
return;
if (m_HostISSI == dstISSI || m_ListenGroups.find(dstISSI) != m_ListenGroups.end())
{
if (!m_radioTrafficThread)
m_radioTrafficThread = qobject_cast<PrtCnvRadioTrafficThread *>(m_Parent->getOtherThread(PrtCnvApplication::e_RadioTraffic));
if (m_radioTrafficThread)
m_radioTrafficThread->newTelegramArrived(radioTelegram.getPayloadSize());
PrtCnvMember *member = m_DbThread->getMemberByISSI(radioTelegram.getSrcISSI());
if (radioTelegram.isLoginTelegram() || (member != nullptr))
{
switch (radioTelegram.getFragmentStatus())
{
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_FirstFragment:
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_MiddleFragment:
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LastFragment:
if (member->m_Licensed)
handleFragmentedTelegram(radioTelegram);
else
{
sendLizenzierungTneToRadio(member->m_Id, member->m_ISSI,
N_CoPrtCnv::E_LicensingKind::Unlicense,
N_CoPrtCnv::E_LicensingErrorCode::Unlicense);
sendFehlermeldungGefechtsfeldToCentral(member->m_Id,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorNotLicensed,
N_CoPrtCnv::CoPrtCnvRadioTelegram::e_FirstFragment == radioTelegram.getFragmentStatus() ?
radioTelegram.getTelID() : GEF_BESTAETIGUNG);
WARNING_LOG(m_logger) << "TNE with id=" << member->m_Id
<< " tries to send a fragmented telegramm without beeing licensed";
}
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_NotFragmented:
if (radioTelegram.isLoginTelegram())
handleLoginTelegram(radioTelegram);
else if (radioTelegram.isNormalTelegram())
{
if (radioTelegram.isCrcOk())
{
radioTelegram.fixupTelegramIdForEts(false);
gef_telegramm_id_et telId = radioTelegram.getTelID();
bool bIsBite = (GEF_BITE_MELDUNG2 == telId);
if (member->m_Licensed || bIsBite)
{
if (radioTelegram.isContentValid())
{
if (m_BroadcastManager.handleRadioTelegram(radioTelegram))
{
std::shared_ptr<N_CoPrtCnv::CoPrtCnvCentralTelegram>
centralTelegram(N_CoPrtCnv::CoPrtCnvTelegramConverter::convertToCentral(&radioTelegram));
if (centralTelegram)
{
if (sendAnswer(radioTelegram))
{
m_DbThread->storeCentralTelegram(centralTelegram, radioTelegram);
}
}
}
}
else
{
sendFehlermeldungGefechtsfeldToCentral(member->m_Id,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::InvalidByteCount,
telId);
ERROR_LOG(m_logger) << "The content of received telegram is wrong => thrown away";
}
}
else
{
sendLizenzierungTneToRadio(member->m_Id, member->m_ISSI,
N_CoPrtCnv::E_LicensingKind::Unlicense,
N_CoPrtCnv::E_LicensingErrorCode::Unlicense);
sendFehlermeldungGefechtsfeldToCentral(member->m_Id,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorNotLicensed,
telId);
WARNING_LOG(m_logger) << "TNE with id=" << member->m_Id
<< " tries to send a telegramm without beeing licensed";
}
}
else
{
ERROR_LOG(m_logger) << "CRC of received telegram is wrong => thrown away";
sendFehlermeldungGefechtsfeldToCentral(radioTelegram.getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::WrongCrc,
radioTelegram.getTelID());
}
}
else
{
ERROR_LOG(m_logger) << "Unknown non-fragmented telegram-type: 0x"
<< QString::number(radioTelegram.getFragmentCounter(), 16);
sendFehlermeldungGefechtsfeldToCentral(radioTelegram.getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::FragmentationError,
radioTelegram.getTelID());
}
break;
default:
ERROR_LOG(m_logger) << "Unknown payload-status: " << radioTelegram.getFragmentStatus();
sendFehlermeldungGefechtsfeldToCentral(radioTelegram.getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::FragmentationError,
radioTelegram.getTelID());
break;
}
}
else
{
ERROR_LOG(m_logger) << "The TPU with id=" << radioTelegram.getIdentity()
<< " and ISSI=" << radioTelegram.getSrcISSI()
<< " has sent a telegram without be logged in";
bool hasId = !(radioTelegram.getFragmentStatus() == N_CoPrtCnv::CoPrtCnvRadioTelegram::e_MiddleFragment || radioTelegram.getFragmentStatus() == N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LastFragment);
sendLoginResponse(N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LoginReject, hasId ? radioTelegram.getIdentity() : 0, radioTelegram.getSrcISSI());
}
}
else
{
if (m_EmergencyCallISSI == dstISSI)
{
uint32_t arr[10];
for (int i = 0; i < 10; ++i)
arr[i] = static_cast<uint32_t>(radioTelegram.getPayloadDataUInt8(i - 1));
for (int i = 0; i < 10; ++i)
DEBUG_LOG(m_logger) << "arr[" << i << "] = 0x" << QString::number(arr[i], 16);
uint32_t x, y;
x = (arr[0] << 24) + (arr[1] << 16) + (arr[2] << 8) + arr[3];
x = (x >> 3) & 0x1FFFFFF;
y = (arr[3] << 24) + (arr[4] << 16) + (arr[5] << 8) + arr[6];
y = (y >> 3) & 0xFFFFFF;
TrainingAreaBase::TetraIcd::EmergencyCall data;
double longitude = x * (360 / pow(2, 25));
double latitude = y * (180 / pow(2, 24));
data.absPosition.lon = longitude * (M_PI / 180.0);
data.absPosition.lat = latitude * (M_PI / 180.0);
data.issi = radioTelegram.getSrcISSI();
data.timeStamp = QDateTime::currentMSecsSinceEpoch();
auto tele(std::make_shared<N_CoPrtCnv::CoPrtCnvCentralTelegram>(data));
DEBUG_LOG(m_logger) << "Notruf lon: " << data.absPosition.lon << ", lat: " << data.absPosition.lat << ", issi: " << data.issi;
m_DbThread->storeCentralTelegram(tele, radioTelegram);
}
else
{
ERROR_LOG(m_logger) << "Radio-Telegram with PDU-Type=" << QString::number(data.at(4))
<< " and telId=" << radioTelegram.getTelID()
<< " is for another host: " << radioTelegram.getDestISSI()
<< " (we are " << m_HostISSI << ")";
}
}
}
void PrtCnvRadioThread::handleLoginTelegram(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_loginTelegram)
{
using namespace N_CoPrtCnv;
uint16_t identity = a_loginTelegram.getIdentity();
uint32_t issi = a_loginTelegram.getSrcISSI();
if (CoPrtCnvRadioTelegram::e_LoginRequest != (a_loginTelegram.getPayloadDataUInt8(2) & CoPrtCnvRadioTelegram::e_LoginStatusMask))
{
ERROR_LOG(m_logger) << QStringLiteral("Unexpected login-status: 0x%1").arg(a_loginTelegram.getPayloadDataUInt8(2), 2, 16, QLatin1Char('0'));
return;
}
if (0 == identity || m_BroadcastISSI == issi)
{
sendLoginResponse(CoPrtCnvRadioTelegram::e_LoginReject, identity, issi);
sendFehlermeldungGefechtsfeldToCentral(identity, TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorLoginEquipmentNumber);
writeLoginLog(a_loginTelegram,
QStringLiteral("Login-failure: New TPU with Id=%1 and ISSI=%2 tries to use broadcast-id or -issi => rejected").arg(identity).arg(issi));
return;
}
const bool bIsMasteringFlag = (a_loginTelegram.getPayloadDataUInt8(2) & CoPrtCnvRadioTelegram::e_LoginMastering) == CoPrtCnvRadioTelegram::e_LoginMastering;
PrtCnvMember *member = m_DbThread->getMemberById(identity);
PrtCnvMember *issiMember = m_DbThread->getMemberByISSI(issi);
DEBUG_LOG(m_logger) << "TPU with id=" << identity << " and ISSI="
<< issi << " tries to log in, mastering: " << bIsMasteringFlag;
if (bIsMasteringFlag && member != issiMember)
{
removeMasteringMember(a_loginTelegram, member, issiMember, identity, issi);
member = nullptr;
issiMember = nullptr;
}
const int tpuExId = exerciseIdForIdentity(identity);
TRACE_LOG(m_logger) << "Exercise Id for EqId " << identity << " is: " << tpuExId;
if (member || issiMember)
{
if (member && (issi == uint32_t(member->m_ISSI)))
{
if (member->m_Licensed)
m_DbThread->licenseMember(member, false);
m_DbThread->updateExerciseIdForMember(member, m_ExerciseId);
sendLoginAccept(identity, issi, tpuExId, bIsMasteringFlag);
}
else
{
if (bIsMasteringFlag)
{
m_DbThread->addMember(identity, issi);
sendLoginAccept(identity, issi, tpuExId, bIsMasteringFlag);
sendFehlermeldungGefechtsfeldToCentral(identity, TrainingAreaBase::TetraIcd::CtaDataError::Reason::InfoLoginMasteringFlag);
}
else
{
int rejectCause = 0;
if (issiMember)
rejectCause |= CoPrtCnvRadioTelegram::e_LoginOtherISSI;
else if (member)
rejectCause |= CoPrtCnvRadioTelegram::e_LoginOtherEqId;
else
rejectCause |= CoPrtCnvRadioTelegram::e_LoginMastering;
rejectCause |= CoPrtCnvRadioTelegram::e_LoginReject;
sendLoginResponse(CoPrtCnvRadioTelegram::E_LoginStatus(rejectCause),
identity, issi);
sendFehlermeldungGefechtsfeldToCentral(identity, TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorLoginEquipmentNumber);
writeLoginLog(a_loginTelegram,
QStringLiteral("Login-failure: Another TPU with Id=%1 and ISSI=%2 without mastering tried to log in => rejected").arg(identity).arg(issi));
}
}
}
else
{
m_DbThread->addMember(identity, issi);
m_DbThread->updateExerciseIdForMember(issiMember, m_ExerciseId);
sendLoginAccept(identity, issi, tpuExId, bIsMasteringFlag);
}
}
void PrtCnvRadioThread::handleFragmentedTelegram(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_fragmentedTelegram)
{
PrtCnvMember *member = m_DbThread->getMemberByISSI(a_fragmentedTelegram.getSrcISSI());
if (member)
{
if (a_fragmentedTelegram.isCrcOk())
{
bool doAddFragment = true;
if (a_fragmentedTelegram.getFragmentStatus() == N_CoPrtCnv::CoPrtCnvRadioTelegram::e_FirstFragment)
{
if (!member->isFragmentedListEmpty())
{
ERROR_LOG(m_logger) << "Did not get the last radio-telegram-fragment";
sendFehlermeldungGefechtsfeldToCentral(member->m_Id,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::FragmentationError,
member->getFragmentedTelegramId());
member->clearFragmentedList();
}
}
else if (a_fragmentedTelegram.getFragmentStatus() == N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LastFragment)
{
if (member->isFragmentedListEmpty())
{
ERROR_LOG(m_logger) << "Got a lonely last radio-telegram-fragment";
sendFehlermeldungGefechtsfeldToCentral(member->m_Id,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::FragmentationError,
member->getFragmentedTelegramId());
}
else
{
member->addFragmentedTelegram(a_fragmentedTelegram);
const QByteArray unfragmentedRadioTelegramData = member->getUnfragmentedTelegramData();
if (!unfragmentedRadioTelegramData.isEmpty())
{
handleDataIndReq(unfragmentedRadioTelegramData, false);
}
else
sendFehlermeldungGefechtsfeldToCentral(member->m_Id,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::FragmentationError,
member->getFragmentedTelegramId());
}
member->clearFragmentedList();
doAddFragment = false;
}
else
{
if (member->isFragmentedListEmpty())
{
ERROR_LOG(m_logger) << "Did not get the first radio-telegram-fragment";
member->clearFragmentedList();
doAddFragment = false;
}
}
if (doAddFragment)
{
member->addFragmentedTelegram(a_fragmentedTelegram);
}
}
else
ERROR_LOG(m_logger) << "CRC of received fragmented telegram is wrong => thorw away";
}
else
ERROR_LOG(m_logger) << "Internal error: Did not found the TNE with ISSI=" << a_fragmentedTelegram.getSrcISSI() << " in member-list";
}
bool PrtCnvRadioThread::handleBITEEXT_Telegram(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_BITE_RadioTelegram)
{
auto anmeldeart = static_cast<TrainingAreaBase::TetraIcd::BiteReport::LoginType>(a_BITE_RadioTelegram.getPayloadDataUInt32(7, 1));
uint32_t ergebnisCAN = a_BITE_RadioTelegram.getPayloadDataUInt32(8, 3);
uint32_t ergebnisAusstattung = a_BITE_RadioTelegram.getPayloadDataUInt8(11);
uint32_t konfigurationsvergleich = a_BITE_RadioTelegram.getPayloadDataUInt8(23);
uint32_t aktuelleKonfiguration = a_BITE_RadioTelegram.getPayloadDataUInt32(24, 3);
QList<PrtCnv::BiteError> fehlerListe;
const auto nonDynamic = getBiteError(a_BITE_RadioTelegram, 12);
if (nonDynamic.errorNumber != 0 ||
nonDynamic.canDeviceNumber != 0 ||
nonDynamic.subdeviceNumber != 0 ||
nonDynamic.uniqueId != 0)
fehlerListe.append(nonDynamic);
const uint8_t fehlerLen = a_BITE_RadioTelegram.getPayloadDataUInt8(19);
for (uint8_t i = 0; i < fehlerLen; ++i)
{
const uint8_t iPos = 27 + i * 7;
fehlerListe.append(getBiteError(a_BITE_RadioTelegram, iPos));
}
return handleBITE_Telegram(a_BITE_RadioTelegram,
anmeldeart,
ergebnisCAN,
ergebnisAusstattung,
aktuelleKonfiguration,
konfigurationsvergleich,
fehlerListe
);
}
bool PrtCnvRadioThread::handleBITE_Telegram(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_BITE_RadioTelegram,
TrainingAreaBase::TetraIcd::BiteReport::LoginType anmeldeart,
uint32_t ergebnisCAN,
uint32_t ergebnisAusstattung,
uint32_t aktuelleKonfiguration,
uint32_t konfigurationsvergleich,
const QList<PrtCnv::BiteError> &fehlerListe
)
{
switch (anmeldeart)
{
case TrainingAreaBase::TetraIcd::BiteReport::LoginType::InvalidLogin:
return true;
case TrainingAreaBase::TetraIcd::BiteReport::LoginType::NoLogin:
case TrainingAreaBase::TetraIcd::BiteReport::LoginType::NewLogin:
case TrainingAreaBase::TetraIcd::BiteReport::LoginType::Relogin:
{
const uint16_t identity = a_BITE_RadioTelegram.getIdentity();
bool doForwardBiteMeldung = true;
QString logText(QStringLiteral("Licensing: The TPU with Id=%1 and ISSI=%2").arg(identity).arg(a_BITE_RadioTelegram.getSrcISSI()));
PrtCnvMember *member = m_DbThread->getMemberById(identity);
if (member)
{
if (a_BITE_RadioTelegram.getSrcISSI() == uint32_t(member->m_ISSI))
{
if (isIdentityValid(identity))
{
m_DbThread->updateExerciseIdForMember(member, m_ExerciseId);
if (!member->m_Licensed)
{
m_DbThread->licenseMember(member);
if (TrainingAreaBase::TetraIcd::BiteReport::LoginType::NewLogin == anmeldeart)
logText += QStringLiteral(" is now licensed");
else
logText += QStringLiteral(" is now licensed (WIEDER_ANMELDUNG instead NEUE_ANMELDUNG used!)");
}
else
{
if (TrainingAreaBase::TetraIcd::BiteReport::LoginType::NewLogin == anmeldeart)
logText += QStringLiteral(" is now relicensed (NEUE_ANMELDUNG instead WIEDER_ANMELDUNG used!)");
else
logText += QStringLiteral(" is now relicensed");
}
N_CoPrtCnv::E_LicensingErrorCode errorCode = N_CoPrtCnv::E_LicensingErrorCode::Ok;
if (ergebnisAusstattung != 1)
{
errorCode = N_CoPrtCnv::E_LicensingErrorCode::Battery;
logText += QStringLiteral(", has battery-Warning");
}
if ((ergebnisCAN & aktuelleKonfiguration) != aktuelleKonfiguration)
{
errorCode = N_CoPrtCnv::E_LicensingErrorCode::Selftest;
logText += QStringLiteral(", but selftest is failed");
}
if (konfigurationsvergleich != 0)
{
errorCode = N_CoPrtCnv::E_LicensingErrorCode::Configuration;
logText += QStringLiteral(", but has configuration-failure: ");
}
bool selftestOrConfigError = (errorCode == N_CoPrtCnv::E_LicensingErrorCode::Configuration || errorCode == N_CoPrtCnv::E_LicensingErrorCode::Selftest);
bool doLicense = !(selftestOrConfigError && !m_DoLicenseDespiteSelftestOrConfigFailure);
prepareWareHouseMessage(identity, doLicense, ergebnisCAN, ergebnisAusstattung, aktuelleKonfiguration, konfigurationsvergleich, fehlerListe);
if (anmeldeart == TrainingAreaBase::TetraIcd::BiteReport::LoginType::NoLogin)
return true;
sendLizenzierungTneToRadio(identity, member->m_ISSI,
doLicense ? N_CoPrtCnv::E_LicensingKind::Success : N_CoPrtCnv::E_LicensingKind::Rejected,
errorCode);
if (doLicense)
m_DoNeedActPosResp.insert(identity);
if (selftestOrConfigError)
sendFehlermeldungGefechtsfeldToCentral(identity,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorConfigSelfTest,
a_BITE_RadioTelegram.getTelID());
if (selftestOrConfigError && !m_DoLicenseDespiteSelftestOrConfigFailure)
{
logText = QStringLiteral("Licensing: The TPU with Id=%1 and ISSI=%2").arg(identity).arg(a_BITE_RadioTelegram.getSrcISSI());
logText += QStringLiteral(" could not be licensed, because ");
if (errorCode == N_CoPrtCnv::E_LicensingErrorCode::Configuration)
logText += QStringLiteral("of configuration-failure");
else
logText += QStringLiteral("selftest failed");
}
}
else
{
sendLizenzierungTneToRadio(identity, member->m_ISSI, N_CoPrtCnv::E_LicensingKind::Rejected, N_CoPrtCnv::E_LicensingErrorCode::IdentityDenied);
sendFehlermeldungGefechtsfeldToCentral(identity,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorSendingNamelist,
a_BITE_RadioTelegram.getTelID());
logText += QStringLiteral(" could not be licensed because PlayerID is not allowed");
}
}
else
{
sendLoginResponse(N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LoginReject, identity, a_BITE_RadioTelegram.getSrcISSI());
logText += QStringLiteral(" could not be licensed because ISSI is wrong");
doForwardBiteMeldung = false;
}
}
else
{
sendLoginResponse(N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LoginReject, identity, a_BITE_RadioTelegram.getSrcISSI());
logText += QStringLiteral(" wants to be licensed but is not logged in");
doForwardBiteMeldung = false;
}
writeLoginLog(a_BITE_RadioTelegram, logText);
return doForwardBiteMeldung;
}
}
ERROR_LOG(m_logger) << "BITE-Meldung contains unknown anmeldeart: " << static_cast<int>(anmeldeart);
return true;
}
void PrtCnvRadioThread::checkSend()
{
if (m_DoCleanup)
return;
if (PrtCnvSocketThread::e_WORKING != m_State)
return;
if (isSendLimitReached())
return;
PrtCnvStoredRadioTelegram *handle = nullptr;
const auto radioTelegram = m_DbThread->getNextRadioTelegram(handle);
if (radioTelegram)
{
if (radioTelegram->isNormalTelegram())
{
switch (radioTelegram->getTelID())
{
case GEF_UMGEBUNGS_MELDUNG2:
{
const int exerciseId = radioTelegram->getPayloadDataUInt32(5, 2);
if (m_ExerciseId != exerciseId)
{
ERROR_LOG(m_logger) << "Started with exercise Id " << m_ExerciseId << " but got environment telegram for exercise " << exerciseId;
exit(5);
}
if (m_ExerciseId > 0 && !m_EnvironmentTelegramTimer)
{
m_EnvironmentTelegramTimer = new QTimer(this);
connect(m_EnvironmentTelegramTimer, &QTimer::timeout, this, &PrtCnvRadioThread::timeoutEnvironmentTelegram, Qt::QueuedConnection);
}
if (m_EnvironmentTelegramTimer)
{
INFO_LOG(m_logger) << "I got the Umgebungsmeldung-Telegram => I start now sending Umgebungsmeldung periodically";
m_EnvironmentTelegramTimer->setInterval(m_Config->getInteger(getName(), QStringLiteral("CyclicEnvironmentTime"), 30) * 1000);
m_EnvironmentTelegramTimer->start();
}
radioTelegram->setPayloadDataWithIndex(7, m_DbThread->getResetCounter(), 1);
break;
}
case GEF_WIRKFELD_STATUS_MINENSPERRE:
{
uint16_t wirkfeldKennung = radioTelegram->getPayloadDataUInt32(5, 2);
if (m_BroadcastManager.isEffectSimulationIdDeactivated(wirkfeldKennung))
{
WARNING_LOG(m_logger) << "Special Handling for GEF_WIRKFELD_STATUS_MINENSPERRE with id " << wirkfeldKennung
<< " -> do not send to CTA";
m_DbThread->radioTelegramSent(m_SendCounter, handle, 0);
return;
}
break;
}
default:
break;
}
}
sendTelegram(radioTelegram, handle);
emit checkSendToRadio();
}
}
void PrtCnvRadioThread::timeoutTick()
{
if (m_DoCleanup) return;
startTickTimer();
checkSend();
if (!isSendLimitReached())
{
time_t currentTime = time(nullptr);
auto broadcastTelegram = m_BroadcastManager.getNextBroadcastTelegramToSend(0, currentTime);
while (broadcastTelegram)
{
uint16_t broadcastIndex = broadcastTelegram->getBroadcastIndex();
sendTelegram(broadcastTelegram, nullptr, false);
if (isSendLimitReached())
broadcastTelegram = nullptr;
else
broadcastTelegram = m_BroadcastManager.getNextBroadcastTelegramToSend(broadcastIndex, currentTime);
}
}
else
INFO_LOG(m_logger) << "send limit reached";
m_BroadcastManager.incTickAndSetTrafficLoad(E_TrafficLoad::HighLoad == m_SendLoad, E_TrafficLoad::FullLoad == m_SendLoad);
if (!isSendLimitReached())
{
auto radioTelegram = m_BroadcastManager.getNextNachforderungTelegramToSend();
if (radioTelegram)
sendTelegram(radioTelegram, nullptr, false);
}
try
{
N_CoPrtCnv::CoPrtCnvCentralTelegram *radioInjectTelegram;
while ((radioInjectTelegram = m_InjectionThread->getNextRadioInjectionTelegram()))
{
if (radioInjectTelegram->telegramId() == TrainingAreaBase::TetraIcd::TelegramId::NACHFORDERUNG_BROADCAST_TELEGRAMME)
{
const auto tele = TrainingAreaBase::TetraIcd::RequestBroadcastTelegrams::fromJson(radioInjectTelegram->jsonData());
onBroadcastTelegramsRequestFromCentral(tele.header.equipmentNumber, tele.broadcastIndex, tele.resetCounter);
delete radioInjectTelegram;
continue;
}
std::shared_ptr<N_CoPrtCnv::CoPrtCnvRadioTelegram>
radioTelegram(N_CoPrtCnv::CoPrtCnvTelegramConverter::convertToRadio(radioInjectTelegram));
if (radioTelegram)
sendTelegram(radioTelegram, nullptr, false);
delete radioInjectTelegram;
}
}
catch (std::exception &e)
{
ERROR_LOG(m_logger) << "Catched an unexpected exception: " << e.what() << " => could not inject radio-telegram, but we proceed...";
}
if (m_DbThread->isEnvironmentResetCounterOld())
timeoutEnvironmentTelegram();
computeAndLogTrafficStatistics();
}
void PrtCnvRadioThread::timeoutAliveAndConnection()
{
INFO_LOG(m_logger) << "timeoutAliveAndConnection socketState=" << m_Socket->state() << " m_State=" << m_State;
switch (m_State)
{
case e_CONNECTING:
connectSocket();
break;
case e_REGISTERING:
case e_WORKING:
break;
case e_DISCONNECTING:
m_Socket->abort();
break;
default:
ERROR_LOG(m_logger) << "timeoutAlive for m_State=" << m_State << " not implemented";
}
}
void PrtCnvRadioThread::sendTelegram(const std::shared_ptr<N_CoPrtCnv::CoPrtCnvRadioTelegram> &a_telegram,
PrtCnvStoredRadioTelegram *a_handle,
const bool a_doCheckWhetherExpired)
{
uint16_t broadcastIndex = 0;
bool isSpecialTelegram = !a_telegram->isNormalTelegram();
bool isLicensing = false;
PrtCnvMember *member = nullptr;
if (!isSpecialTelegram)
{
member = m_DbThread->getMemberById(a_telegram->getIdentity());
isLicensing = (GEF_LIZENSIERUNG_TNE == a_telegram->getTelID());
}
if (member || isSpecialTelegram || isLicensing)
{
if (!a_telegram->needsLicensing() || (member && member->m_Licensed))
{
if (!a_telegram->hasExpired() || !a_doCheckWhetherExpired)
{
if (a_telegram->isContentValid())
{
if (a_telegram->mustBeFragmented())
sendFragmentedTelegram(a_telegram);
else
broadcastIndex = sendToSocket(a_telegram);
m_LogThread->writeRadioTelegramLog(a_telegram->data(), false, a_telegram->getPriority());
}
else
{
sendFehlermeldungGefechtsfeldToCentral(a_telegram->getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::WrongParameter,
a_telegram->getTelID());
INFO_LOG(m_logger) << "Telegram with telId=" << a_telegram->getTelID() << " is invalid => thrown away";
}
}
else
{
if (a_telegram->getBroadcastIndex() == 0)
sendFehlermeldungGefechtsfeldToCentral(a_telegram->getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::TelegramOutdated,
a_telegram->getTelID());
INFO_LOG(m_logger) << "Telegram with telId=" << a_telegram->getTelID() << " has expired => thrown away";
}
}
else
{
sendFehlermeldungGefechtsfeldToCentral(a_telegram->getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorNotLicensed,
a_telegram->getTelID());
ERROR_LOG(m_logger) << "Central tries to send a telegram with telId=" << a_telegram->getTelID() << " to TNE with id=" << a_telegram->getIdentity() << ", but the TNE is not licensed!";
}
}
else
{
sendFehlermeldungGefechtsfeldToCentral(a_telegram->getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorNotLoggedIn,
a_telegram->getTelID());
ERROR_LOG(m_logger) << "Central tries to send a telegram to TNE with id=" << a_telegram->getIdentity() << ", but the TNE is not logged in!";
}
if (a_handle)
m_DbThread->radioTelegramSent(m_SendCounter, a_handle, broadcastIndex);
if (broadcastIndex >= MAX_BROADCAST_INDEX)
{
m_BroadcastManager.handleOverrunOfBroadcastCounter();
sendFehlermeldungGefechtsfeldToCentral(0, TrainingAreaBase::TetraIcd::CtaDataError::Reason::BroadcastCounterOverflow);
timeoutEnvironmentTelegram();
}
}
void PrtCnvRadioThread::doRegister()
{
auto registerIndTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(m_HostISSI));
sendTelegram(registerIndTelegram, nullptr);
INFO_LOG(m_logger) << "Send Radio-Telegram Register-Ind with hostISSI=" << m_HostISSI;
}
void PrtCnvRadioThread::registerGroups()
{
if (m_ListenGroups.empty())
return;
auto registerGroupIndTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(m_HostISSI, m_ListenGroups));
sendTelegram(registerGroupIndTelegram, nullptr);
}
void PrtCnvRadioThread::handleRegisterRsp(const uint8_t a_regsitrationStatus)
{
INFO_LOG(m_logger) << "Got Radio-Telegram Register-Rsp with status=" << std::to_string(a_regsitrationStatus);
std::stringstream logText;
switch (a_regsitrationStatus)
{
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_IssiSuccessfulRegistered:
emit tetraConnectionState(true);
registerGroups();
return;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_DuplicateISSI:
logText << "TETRA: Register-Response: Status=Duplicate ISSI";
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_InvalidISSI:
logText << "TETRA: Register-Response: Status=Invalid ISSI";
break;
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_InvalidIpAddress:
logText << "TETRA: Register-Response: Status=Invalid IP address => Configure TETRA for accepting our IP-address (";
{
const QList<QHostAddress> ipList = QNetworkInterface::allAddresses();
for (const QHostAddress &addr : ipList)
logText << addr.toString().toStdString() << ", ";
logText << ")...";
}
break;
default:
logText << "Unknown registration status: " << std::to_string(a_regsitrationStatus);
break;
}
emit tetraConnectionState(false);
FATAL_LOG(m_logger) << logText.str();
std::cerr << logText.str() << std::endl;
exit(1);
}
std::ostream &operator<<(std::ostream &os, const PrtCnvRadioThread::E_TrafficLoad tl)
{
switch (tl)
{
case PrtCnvRadioThread::E_TrafficLoad::FullLoad:
os << "E_TrafficLoad::FullLoad";
return os;
case PrtCnvRadioThread::E_TrafficLoad::HighLoad:
os << "E_TrafficLoad::HighLoad";
return os;
case PrtCnvRadioThread::E_TrafficLoad::NormalLoad:
os << "E_TrafficLoad::NormalLoad";
return os;
case PrtCnvRadioThread::E_TrafficLoad::Unknown:
os << "E_TrafficLoad::Unknown";
return os;
}
Q_ASSERT(false);
os << std::to_string(static_cast<int>(tl));
return os;
}
bool PrtCnvRadioThread::sendAnswer(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_radioTelegram)
{
switch (a_radioTelegram.getTelID())
{
case GEF_BITE_MELDUNG2:
return handleBITEEXT_Telegram(a_radioTelegram);
case GEF_DEAKTIVIERUNG_EINZELMINE:
{
auto radioTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(GEF_DEAKTIVIERUNG_EINZELMINE, 13, 0));
radioTelegram->setPayload2Byte(0);
radioTelegram->setPayloadData(a_radioTelegram.getPayloadDataPtr(5), 8);
storeRadioTelegram(radioTelegram, 0);
emit checkSendToRadio();
break;
}
case GEF_POSITIONS_MELDUNG:
case GEF_POSITIONS_MELDUNG_LFZ:
case GEF_POSITIONS_MELDUNG_MIT_TEILNEHMER:
case GEF_IPS_POSITIONSDATEN:
{
auto it = m_DoNeedActPosResp.find(a_radioTelegram.getIdentity());
if (it != m_DoNeedActPosResp.end())
{
auto radioTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(GEF_ALLGEMEINE_NACHRICHT, 9, a_radioTelegram.getIdentity()));
radioTelegram->setPayload2Byte(0);
radioTelegram->setPayload1Byte(3);
radioTelegram->setPayload1Byte(0);
radioTelegram->setPayload2Byte(8);
storeRadioTelegram(radioTelegram, 0);
emit checkSendToRadio();
m_DoNeedActPosResp.erase(it);
}
break;
}
default:
break;
}
return true;
}
uint32_t PrtCnvRadioThread::getDestinationISSI(const uint16_t a_TNE_Id) const
{
PrtCnvMember *member = m_DbThread->getMemberById(a_TNE_Id);
if (member)
return member->m_ISSI;
ERROR_LOG(m_logger) << "TPU with id=" << a_TNE_Id << " not logged in.";
return 0;
}
void PrtCnvRadioThread::timeoutEnvironmentTelegram()
{
if (m_DoCleanup) return;
if (PrtCnvSocketThread::e_WORKING != m_State) return;
auto envTelegram = m_DbThread->getEnvironmentTelegram();
if (envTelegram)
{
INFO_LOG(m_logger) << "It is time to send the Umgebungsmeldung";
sendTelegram(envTelegram, nullptr);
}
else
{
WARNING_LOG(m_logger) << "Got no Umgebungsmeldung from DB-Thread...";
}
}
void PrtCnvRadioThread::sendFehlermeldungGefechtsfeldToCentral(const uint16_t a_identity,
const TrainingAreaBase::TetraIcd::CtaDataError::Reason a_errorCause,
const gef_telegramm_id_et a_telegramId)
{
N_CoPrtCnv::CoPrtCnvRadioTelegram radioTelegram(GEF_FEHLERMELDUNG_GFD, 9, a_identity);
radioTelegram.setPayload4Byte(time(nullptr));
radioTelegram.setPayload1Byte(static_cast<uint8_t>(a_errorCause));
radioTelegram.setPayload1Byte(a_telegramId);
std::shared_ptr<N_CoPrtCnv::CoPrtCnvCentralTelegram>
centralTelegram(N_CoPrtCnv::CoPrtCnvTelegramConverter::convertToCentral(&radioTelegram));
if (centralTelegram)
{
m_DbThread->storeCentralTelegram(centralTelegram, radioTelegram);
emit checkSendToCentral();
}
}
void PrtCnvRadioThread::writeLoginLog(uint32_t identity, uint32_t issi, const QString &a_prosaText)
{
const QDateTime dtCur(QDateTime::currentDateTimeUtc());
QString txt =
QString::number(dtCur.toSecsSinceEpoch()) + QLatin1Char(';') +
QString::number(issi) + QLatin1Char(';') +
QString::number(identity) + QLatin1Char(';') +
QLatin1Char(';') +
a_prosaText + QLatin1Char(';') + dtCur.toString();
writeLoginLog(nullptr, txt);
}
void PrtCnvRadioThread::writeLoginLog(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_radioTelegram, const QString &a_prosaText)
{
writeLoginLog(&a_radioTelegram, a_prosaText);
}
void PrtCnvRadioThread::writeLoginLog(const N_CoPrtCnv::CoPrtCnvRadioTelegram *a_radioTelegram, const QString &a_prosaText)
{
QByteArray logData;
if (a_radioTelegram)
{
int len = 3;
if (a_radioTelegram->getTelID() == GEF_BITE_MELDUNG2)
len = 27;
QStringList sl;
sl.reserve(len);
for (int i = 0; i < len; i++)
{
uint8_t data = a_radioTelegram->getPayloadDataUInt8(i);
sl += QStringLiteral("0x%1").arg(data, 2, 16, QLatin1Char('0'));
}
const QDateTime dtCur(QDateTime::currentDateTimeUtc());
logData =
QByteArray::number(dtCur.toSecsSinceEpoch()) + ";" +
QByteArray::number(a_radioTelegram->getSrcISSI()) + ";" +
QByteArray::number(a_radioTelegram->getIdentity()) + ";" +
sl.join(QLatin1Char(',')).toUtf8() + ";" +
a_prosaText.toUtf8() + ";" + dtCur.toString().toUtf8();
}
else
{
logData = a_prosaText.toUtf8();
}
logData += "\n";
INFO_LOG(m_logger) << logData;
QString date = QDate::currentDate().toString(QLatin1String("yyyyMMdd"));
QString fileName = m_LoginPathWithPrefix + date + QLatin1Char('_') + QString::number(m_ExerciseId) + QLatin1String(".log");
QFile file(fileName);
file.open(QIODevice::Append);
file.write(logData);
file.close();
}
void PrtCnvRadioThread::sendLoginResponse(const N_CoPrtCnv::CoPrtCnvRadioTelegram::E_LoginStatus a_loginTelegramStatus,
const uint16_t a_identity, const uint32_t a_ISSI)
{
auto answerTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(a_loginTelegramStatus, a_identity, a_ISSI));
storeRadioTelegram(answerTelegram, 0);
emit checkSendToRadio();
}
void PrtCnvRadioThread::sendLoginResponse(const N_CoPrtCnv::CoPrtCnvRadioTelegram::E_LoginStatus a_loginTelegramStatus,
const uint16_t a_identity, const uint32_t a_ISSI, const TrainingAreaBase::TetraIcd::GroupIssiInformation &group)
{
using namespace N_CoPrtCnv;
auto answerTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(a_loginTelegramStatus, a_identity, a_ISSI));
if ((a_loginTelegramStatus & CoPrtCnvRadioTelegram::e_LoginOtherExercise) == CoPrtCnvRadioTelegram::e_LoginOtherExercise)
{
NOTICE_LOG(m_logger) << "sendLoginResponse for " << a_identity << " with issi " << a_ISSI
<< " and groups " << group.serverIssi << "/" << group.broadcastIssi1
<< "/" << group.broadcastIssi2;
answerTelegram->setLoginIssis(group.serverIssi, group.broadcastIssi1, group.broadcastIssi2);
}
storeRadioTelegram(answerTelegram, 0);
emit checkSendToRadio();
}
void PrtCnvRadioThread::sendLizenzierungTneToRadio(const uint16_t a_identity, const uint32_t a_issi,
const N_CoPrtCnv::E_LicensingKind a_licensingKind,
const N_CoPrtCnv::E_LicensingErrorCode a_errorCause)
{
auto radioTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(GEF_LIZENSIERUNG_TNE, 7, a_identity));
radioTelegram->setPayload1Byte(static_cast<uint8_t>(a_licensingKind));
radioTelegram->setPayload1Byte(static_cast<uint8_t>(a_errorCause));
radioTelegram->setPayload2Byte(0x0013);
radioTelegram->setDestISSI(a_issi);
storeRadioTelegram(radioTelegram, 0);
emit checkSendToRadio();
emit playerLicencedChanged(a_identity, a_licensingKind);
}
void PrtCnvRadioThread::initIdentityArray()
{
const QString loginGroup(QStringLiteral("login"));
if (m_Config->getBoolean(loginGroup, QStringLiteral("DoCheckPlayerId"), true))
{
m_AllowedIdentities.clear();
QString identifyFileName = m_Config->getString(loginGroup, QStringLiteral("PlayerIdFilePath"), QString());
if (identifyFileName.isEmpty())
{
uint32_t lowest = m_Config->getInteger(loginGroup, QStringLiteral("LowestPlayerId"), 1);
uint32_t highest = m_Config->getInteger(loginGroup, QStringLiteral("HighestPlayerId"), MAX_IDENTITY);
for (uint32_t i = lowest; i <= highest; i++)
setIdentityVaild(i);
INFO_LOG(m_logger) << "PlayerIds from " << lowest << " .. " << highest << " will be used.";
}
else
{
INFO_LOG(m_logger) << "PlayerId-file '" << identifyFileName << "' will be used.";
QString path = identifyFileName;
if (!QDir::isAbsolutePath(path))
path = QCoreApplication::applicationDirPath() + QLatin1Char('/') + path;
QFile file(path);
if (file.open(QIODevice::ReadOnly))
{
uint32_t lineCount = 0;
while (!file.atEnd())
{
lineCount++;
QByteArray line(file.readLine());
int hashPos = line.indexOf('#');
if (-1 != hashPos)
line = line.left(hashPos);
if (!line.trimmed().isEmpty())
{
int pos = line.indexOf('-');
if (-1 == pos)
{
setIdentityVaild(readIdentity(line, lineCount, identifyFileName));
}
else
{
QByteArray playerId = line.left(pos).trimmed();
uint16_t left = readIdentity(playerId, lineCount, identifyFileName);
playerId = line.right(line.length() - pos - 1).trimmed();
uint16_t right = readIdentity(playerId, lineCount, identifyFileName);
for (int i = left; i <= right; i++)
setIdentityVaild(i);
}
}
}
file.close();
}
else
{
ERROR_LOG(m_logger) << "Could not open the PlayerId-file '" << identifyFileName << "'!";
std::cout << std::endl << "Error: Could not open the PlayerId-file '" << qPrintable(identifyFileName) << "'!" << std::endl;
exit(-1);
}
}
}
else
{
for (uint32_t i = 0; i <= MAX_IDENTITY; i++)
setIdentityVaild(i);
}
}
uint16_t PrtCnvRadioThread::readIdentity(const QByteArray &a_idAsText, const uint32_t a_line, const QString &a_fileName)
{
ulong playerId = a_idAsText.toULong();
if (0 == playerId || playerId > MAX_IDENTITY)
{
if (0 == playerId)
{
FATAL_LOG(m_logger) << a_fileName << ":" << a_line << ": could not read playerID - '"
<< a_idAsText << "'";
}
else
{
FATAL_LOG(m_logger) << a_fileName << ":" << a_line << ": PlayerID too big - '"
<< a_idAsText << "'";
}
exit(1);
}
return playerId;
}
bool PrtCnvRadioThread::isIdentityValid(uint16_t a_identity) const
{
return m_AllowedIdentities.find(a_identity) != m_AllowedIdentities.end();
}
void PrtCnvRadioThread::setIdentityVaild(uint16_t a_identity)
{
m_AllowedIdentities.insert(a_identity);
}
void PrtCnvRadioThread::sendFragmentedTelegram(const std::shared_ptr<N_CoPrtCnv::CoPrtCnvRadioTelegram> &a_telegram)
{
uint8_t fragmentCounter = 0;
N_CoPrtCnv::CoPrtCnvRadioTelegram::E_FragmentStatus fragmentStatus;
do
{
bool error = false;
auto fragmentedTelegram(std::make_shared<N_CoPrtCnv::CoPrtCnvRadioTelegram>(a_telegram, fragmentCounter++, error));
if (!error)
{
fragmentedTelegram->setDestISSI(getDestinationISSI(a_telegram->getIdentity()));
sendTelegram(fragmentedTelegram, nullptr);
fragmentStatus = fragmentedTelegram->getFragmentStatus();
}
else
{
sendFehlermeldungGefechtsfeldToCentral(a_telegram->getIdentity(),
TrainingAreaBase::TetraIcd::CtaDataError::Reason::FragmentationError,
a_telegram->getTelID());
return;
}
}
while (N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LastFragment != fragmentStatus);
}
uint16_t PrtCnvRadioThread::sendToSocket(const std::shared_ptr<N_CoPrtCnv::CoPrtCnvRadioTelegram> &a_telegram)
{
if (++m_SendCounter >= END_COUNTER)
m_SendCounter = START_COUNTER;
a_telegram->setZaehler(m_SendCounter);
a_telegram->setSrcISSI(m_HostISSI);
uint16_t broadcastIndex = 0;
gef_telegramm_id_et telId = a_telegram->isNormalTelegram() ? a_telegram->getTelID() : GEF_BESTAETIGUNG;
if (a_telegram->isNormalTelegram())
{
if (N_CoPrtCnv::CoPrtCnvRadioTelegram::e_DataIndReq == a_telegram->getPDU_Type())
{
if (GEF_LIZENSIERUNG_TNE != a_telegram->getTelID())
a_telegram->setDestISSI(getDestinationISSI(a_telegram->getIdentity()));
}
if (GEF_TEILNEHMERMODE_SETZEN == telId && a_telegram->getDestISSI() != m_BroadcastISSI)
{
DEBUG_LOG(m_logger) << "Got MODE with bci: " << a_telegram->getBroadcastIndex();
if (a_telegram->getBroadcastIndex()<= 0)
a_telegram->setBroadcastIndex(m_BroadcastManager.getBroadcastIndex());
DEBUG_LOG(m_logger) << "MODE with bci is now: " << a_telegram->getBroadcastIndex();
}
if (GEF_UMGEBUNGS_MELDUNG2 == telId)
{
a_telegram->setBroadcastIndex(m_BroadcastManager.getBroadcastIndex());
a_telegram->setPayloadDataWithIndex(7, m_DbThread->getResetCounter(), 1);
a_telegram->setPayloadDataWithIndex(8, m_DbThread->getBattleFieldOperatingState(), 1);
a_telegram->setPayloadDataWithIndex(16, (uint32_t)time(nullptr), 4);
m_DbThread->setResetCounterOnEnvironment();
}
else
{
if (GEF_INIT != telId &&
a_telegram->mustBroadcastBeSet())
{
broadcastIndex = m_BroadcastManager.getNextBroadcastIndex();
a_telegram->setBroadcastIndex(broadcastIndex);
}
}
a_telegram->fixupTelegramIdForEts(true);
}
DEBUG_LOG(m_logger) << "Send telegram to radio: telId=" << telId << " wcounter=" << int(a_telegram->zaehler()) << " size=" << a_telegram->getSize();
if (m_DoUseSDSTL && N_CoPrtCnv::CoPrtCnvRadioTelegram::e_DataIndReq == a_telegram->getPDU_Type())
{
a_telegram->setProtocolIdentifier(N_CoPrtCnv::CoPrtCnvRadioTelegram::e_SimpleTextMessagingUsingSDS_TL);
a_telegram->setMessageReference(telId);
}
if (m_DoDumpSentTelegrams)
{
INFO_LOG(m_logger) << "Send radio-telegram with size " << a_telegram->getSize()
<< " and payload size " << a_telegram->getPayloadSize();
NGL::NGLLoggerHelper::hexDumpHelper(m_logger, NGL::LogLevel::Info, a_telegram->data());
}
Q_ASSERT(QThread::currentThreadId() == m_currentThreadId);
if (m_bUseScrambling && a_telegram->getPDU_Type() == N_CoPrtCnv::CoPrtCnvRadioTelegram::E_PDU_Type::e_DataIndReq)
sendScrambledContent(a_telegram);
else
m_Socket->write(a_telegram->data());
m_SentTelegrams++;
m_SentBytes += a_telegram->getSize();
if (a_telegram->isNormalTelegram())
a_telegram->fixupTelegramIdForEts(false);
return broadcastIndex;
}
static inline void scramble(const unsigned char *dataIn, unsigned char *dataOut, size_t len, uint32_t initial, uint32_t polynom)
{
uint32_t lfsr = initial;
for (size_t j = 0; j < len; ++j)
{
for (int i = 0; i < 8; ++i)
{
unsigned char lsbBit = (lfsr & 0x01);
lfsr >>= 1;
if (lsbBit)
lfsr ^= polynom;
}
dataOut[len - j - 1] = dataIn[len - j - 1] ^ static_cast<unsigned char>(lfsr);
}
}
void PrtCnvRadioThread::sendScrambledContent(const std::shared_ptr<N_CoPrtCnv::CoPrtCnvRadioTelegram> &a_telegram) const
{
const uint32_t initial = (a_telegram->getSrcISSI() % 0xFFFF) + 1;
constexpr uint32_t polynom = 0x8016;
unsigned char dataOut[N_CoPrtCnv::CoPrtCnvRadioTelegram::MAX_RADIO_TELEGRAM_PAYLOAD_LENGTH];
const uint32_t payloadOffset = N_CoPrtCnv::CoPrtCnvRadioTelegram::DATA_IND_REQ_HEADER_LENGTH - 1;
const uint32_t scrambleLen = a_telegram->getSize() - payloadOffset;
const unsigned char *dataIn = reinterpret_cast<const unsigned char *>(a_telegram->getData() + payloadOffset);
scramble(dataIn, dataOut, scrambleLen, initial, polynom);
QByteArray tmp (reinterpret_cast<char *>(dataOut), scrambleLen);
DEBUG_LOG(m_logger) << "Raw scrambled data: " << tmp.toHex(' ');
m_Socket->write(a_telegram->getData(), payloadOffset);
m_Socket->write(reinterpret_cast<char *>(dataOut), scrambleLen);
}
bool PrtCnvRadioThread::unscrambleContent(N_CoPrtCnv::CoPrtCnvRadioTelegram &a_telegram) const
{
const uint32_t initial = (a_telegram.getSrcISSI() % 0xFFFF) + 1;
constexpr uint32_t polynom = 0x8016;
const uint32_t payloadOffset = N_CoPrtCnv::CoPrtCnvRadioTelegram::DATA_IND_REQ_HEADER_LENGTH - 1;
const uint32_t scrambleLen = a_telegram.getSize() - payloadOffset;
unsigned char *dataIn = reinterpret_cast<unsigned char *>(a_telegram.getData() + payloadOffset);
unsigned char dataOut[N_CoPrtCnv::CoPrtCnvRadioTelegram::MAX_RADIO_TELEGRAM_PAYLOAD_LENGTH];
if (scrambleLen > N_CoPrtCnv::CoPrtCnvRadioTelegram::MAX_RADIO_TELEGRAM_PAYLOAD_LENGTH)
{
ERROR_LOG(m_logger) << "Telegram length does not match - can't unscramble content, len: " << scrambleLen;
NGL::NGLLoggerHelper::hexDumpHelper(m_logger, NGL::LogLevel::Error, a_telegram.data());
Q_ASSERT(false);
return false;
}
scramble(dataIn, dataOut, scrambleLen, initial, polynom);
memcpy(dataIn, dataOut, scrambleLen);
return true;
}
void PrtCnvRadioThread::handleReportIndReq(const QByteArray &data)
{
const uint8_t *a_data = reinterpret_cast<const uint8_t *>(data.data());
uint32_t srcISSI = *(uint32_t *)&a_data[5];
uint32_t destISSI = *(uint32_t *)&a_data[9];
if (m_HostISSI == destISSI)
{
N_CoPrtCnv::CoPrtCnvRadioTelegram::E_DeliveryStatusForTL_Report delivery = (N_CoPrtCnv::CoPrtCnvRadioTelegram::E_DeliveryStatusForTL_Report)a_data[16];
switch (delivery)
{
case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_DeliveryFailed:
{
uint8_t messageReference = a_data[14];
uint16_t identity = 0;
PrtCnvMember *member = m_DbThread->getMemberByISSI(srcISSI);
if (member)
identity = member->m_Id;
sendFehlermeldungGefechtsfeldToCentral(identity,
TrainingAreaBase::TetraIcd::CtaDataError::Reason::ErrorSendingToExerciseControl,
static_cast<gef_telegramm_id_et>(messageReference));
break;
}
default:
ERROR_LOG(m_logger) << "reaction to delivery " << (int)a_data[16] << " not implemented";
break;
}
}
else
ERROR_LOG(m_logger) << "Radio-Ack-Telegram from " << srcISSI << " is for another host: " << destISSI << " (we are " << m_HostISSI << ")";
}
void PrtCnvRadioThread::prepareWareHouseMessage(qint32 equipNr, bool doLicence, qint64 ergebnisCAN, qint64 ergebnisAusstattung, qint64 aktuelleKonfiguration, qint64 konfigurationsvergleich, const QList<BiteError> &fehlerListe)
{
QJsonObject obj;
obj.insert(QStringLiteral("equipNo"), equipNr);
obj.insert(QStringLiteral("doLicence"), doLicence);
obj.insert(QStringLiteral("biteState"), ergebnisCAN);
obj.insert(QStringLiteral("batteryState"), ergebnisAusstattung);
obj.insert(QStringLiteral("configState"), aktuelleKonfiguration);
obj.insert(QStringLiteral("configError"), konfigurationsvergleich);
QJsonArray errorArr;
for (const auto &error: fehlerListe)
{
QJsonObject errorObj;
errorObj.insert(QStringLiteral("errorNumber"), error.errorNumber);
errorObj.insert(QStringLiteral("canDeviceNumber"), error.canDeviceNumber);
errorObj.insert(QStringLiteral("subdeviceNumber"), error.subdeviceNumber);
errorObj.insert(QStringLiteral("uniqueId"), error.uniqueId);
errorArr.append(errorObj);
}
obj.insert(QStringLiteral("biteError"), errorArr);
QJsonDocument doc(obj);
emit warehouseMessageFromPtrCnv(doc.toJson());
}
void PrtCnvRadioThread::handleStatusIndReq(const QByteArray &data)
{
const uint8_t *a_data = reinterpret_cast<const uint8_t *>(data.data());
uint32_t srcISSI = *(uint32_t *)&a_data[5];
uint32_t dstISSI = *(uint32_t *)&a_data[9];
uint16_t status = *(uint16_t *)&a_data[13];
INFO_LOG(m_logger) << "Received Status from " << srcISSI << " for "
<< dstISSI << " - status: " << status;
}
void PrtCnvRadioThread::handleAirInterfaceLoadReq(const QByteArray &data)
{
const uint8_t *a_data = reinterpret_cast<const uint8_t *>(data.data());
uint16_t nbrOfReports = *(uint16_t *)&a_data[5];
INFO_LOG(m_logger) << "Number of AirInterface-reports: " << nbrOfReports;
uint16_t sdsQueueLength = 0;
uint16_t index = 7;
for (int i = 0; i < nbrOfReports; i++)
{
uint8_t networkIndex = a_data[index++];
uint16_t baseStationID = *(uint16_t *)&a_data[index];
index += 2;
uint8_t channelID = a_data[index++];
uint8_t queueLength = a_data[index++];
INFO_LOG(m_logger) << "AirInterface-report[" << i << "]: networkIndex=" << (int)networkIndex << " baseStationID=" << baseStationID << " channelID=" << (int)channelID << " queueLength=" << (int)queueLength;
if (1 == networkIndex)
sdsQueueLength = queueLength;
}
E_TrafficLoad newLoad;
if (sdsQueueLength > m_SendQueueFullLoad)
{
newLoad = E_TrafficLoad::FullLoad;
INFO_LOG(m_logger) << "Set send-load to Full";
}
else if (sdsQueueLength > m_SendQueueHighLoad)
{
newLoad = E_TrafficLoad::HighLoad;
INFO_LOG(m_logger) << "Set send-load to High";
}
else
{
newLoad = E_TrafficLoad::NormalLoad;
INFO_LOG(m_logger) << "Set send-load to Normal";
}
m_SendLoad = newLoad;
}
void PrtCnvRadioThread::computeAndLogTrafficStatistics()
{
if (++m_TickCounter >= m_TicksPerSecond)
{
m_TickCounter = 0;
m_SentBytesAverage[m_AverageIndex] = m_SentBytes;
m_ReceivedBytesAverage[m_AverageIndex] = m_ReceivedBytes;
if (++m_AverageIndex >= AVERAGE_COUNT) m_AverageIndex = 0;
uint32_t sendAverage = 0;
uint32_t receiveAverage = 0;
for (int i = 0; i < AVERAGE_COUNT; i++)
{
sendAverage += m_SentBytesAverage[i];
receiveAverage += m_ReceivedBytesAverage[i];
}
DEBUG_LOG(m_loggerStats) << "Send-Statistics: " << m_SentTelegrams << " telegrams (" << m_SentBytes << "/" << sendAverage << " bytes) sent; " << (sendAverage / 10) << " kB/s";
DEBUG_LOG(m_loggerStats) << "Receive-Statistics: " << m_ReceivedTelegrams << " telegrams (" << m_ReceivedBytes << "/" << receiveAverage << " bytes) received; " << (receiveAverage / 10) << " kB/s";
m_SentBytes = 0;
m_SentTelegrams = 0;
m_ReceivedBytes = 0;
m_ReceivedTelegrams = 0;
}
}
bool PrtCnvRadioThread::isSendLimitReached() const
{
switch (m_SendLoad)
{
case E_TrafficLoad::NormalLoad:
return m_SentTelegrams >= m_SendTelegramsNormalLoad;
case E_TrafficLoad::HighLoad:
return m_SentTelegrams >= m_SendTelegramsHighLoad;
case E_TrafficLoad::FullLoad:
return m_SentTelegrams >= m_SendTelegramsFullLoad;
case E_TrafficLoad::Unknown:
return false;
}
Q_ASSERT(false);
return false;
}
void PrtCnvRadioThread::sendEnvironmentTelegramTimerNow()
{
QTimer::singleShot(0, this, &PrtCnvRadioThread::onSendEnvironmentTelegramTimerNow);
}
void PrtCnvRadioThread::onSendEnvironmentTelegramTimerNow()
{
if (!m_EnvironmentTelegramTimer || !m_EnvironmentTelegramTimer->isActive())
return;
timeoutEnvironmentTelegram();
}
template <typename T>
void removeFromContainer(T &cont, int id)
{
auto it = cont.find(id);
if (it != cont.end())
cont.erase(it);
}
void PrtCnvRadioThread::exerciseEquipmentChanged(const TrainingAreaBase::TetraIcd::ExerciseEquipmentAssignmentComplete &data)
{
QMutexLocker l(&m_exerciseEquipmentAssignmentMutex);
m_exerciseEquipmentAssignments = data.exerciseEquipmentAssignments;
for (const auto &info : data.groupIssiInformation)
m_groupIssiInformation[info.exerciseId] = info;
emit checkMemberForValidLogin(-1, -1);
}
void PrtCnvRadioThread::onCheckMemberForValidLogin(int identity, int issi)
{
QMutexLocker l(&m_exerciseEquipmentAssignmentMutex);
if (identity == -1)
{
const auto knownIdentities = m_DbThread->getClaimStatistics();
for (const auto &p : knownIdentities)
{
if (p.first <= 0)
continue;
onCheckMemberForValidLogin(p.first, -1);
}
}
else
{
if (isIdentityInCurrentExercise(identity))
{
DEBUG_LOG(m_logger) << "onCheckMemberForValidLogin: eqid: " << identity << " is in current exercise " << m_ExerciseId;
return;
}
PrtCnv::PrtCnvMember *member = m_DbThread->getMemberById(identity);
if (!member)
return;
issi = member->m_ISSI;
const int newExerciseId = exerciseIdForIdentity(identity);
if (newExerciseId != 0 && newExerciseId != member->m_loggedInExerciseId)
sendLoginAccept(identity, issi, newExerciseId, false);
}
}
void PrtCnvRadioThread::sendLoginAccept(int identity, int issi, int exerciseId, bool bWithMastering)
{
using namespace N_CoPrtCnv;
auto response = 0;
if (bWithMastering)
response |= CoPrtCnvRadioTelegram::e_LoginMastering;
PrtCnvMember *member = m_DbThread->getMemberById(identity);
const auto it = m_groupIssiInformation.find(exerciseId);
if (it == m_groupIssiInformation.end() || exerciseId == 0)
{
QString log;
if (exerciseId == 0)
log = QStringLiteral("Denied Login of TPU with Id=%1 and ISSI=%2 - Id is not assigned in any exercise.")
.arg(identity).arg(issi);
else
log = QStringLiteral("TPU with Id=%1 and ISSI=%2 should be in exercise %3 but could not find valid group information for this exercise!")
.arg(identity).arg(issi).arg(exerciseId);
if (bWithMastering)
log += QStringLiteral(" Mastering-Bit is set");
WARNING_LOG(m_logger) << log;
writeLoginLog(identity, issi, log);
if (m_ExerciseId != exerciseId)
response |= CoPrtCnvRadioTelegram::e_LoginNoExerciseAssignment;
response |= CoPrtCnvRadioTelegram::e_LoginReject;
sendLoginResponse(CoPrtCnvRadioTelegram::E_LoginStatus(response), identity, issi);
if (m_ExerciseId == 0)
m_DbThread->updateExerciseIdForMember(member, exerciseId);
else
m_DbThread->removeMember(member);
return;
}
const TrainingAreaBase::TetraIcd::GroupIssiInformation &gi = it->second;
QString log;
if (m_ExerciseId != exerciseId)
log = QStringLiteral("TPU with Id=%1 and ISSI=%2 should switch to exercise %3 (%4/%5).")
.arg(identity).arg(issi).arg(exerciseId).arg(gi.broadcastIssi1).arg(gi.broadcastIssi2);
else
log = QStringLiteral("(Re)Login of TPU with Id=%1 and ISSI=%2.")
.arg(identity).arg(issi);
if (bWithMastering)
log += QStringLiteral(" Mastering-Bit is set");
DEBUG_LOG(m_logger) << log;
writeLoginLog(identity, issi, log);
if (m_ExerciseId != exerciseId)
response |= CoPrtCnvRadioTelegram::e_LoginOtherExercise;
response |= CoPrtCnvRadioTelegram::e_LoginAccept;
sendLoginResponse(CoPrtCnvRadioTelegram::E_LoginStatus(response), identity, issi, gi);
if (m_ExerciseId == 0)
m_DbThread->updateExerciseIdForMember(member, exerciseId);
}
bool PrtCnvRadioThread::isIdentityInCurrentExercise(int identity) const
{
QMutexLocker l(&m_exerciseEquipmentAssignmentMutex);
if (m_exerciseEquipmentAssignments.empty())
return true;
const auto it = m_exerciseEquipmentAssignments.find(m_ExerciseId);
if (it == m_exerciseEquipmentAssignments.end())
return false;
const auto itIdentity = it->second.find(identity);
return (itIdentity != it->second.end());
}
int PrtCnvRadioThread::exerciseIdForIdentity(int identity) const
{
QMutexLocker l(&m_exerciseEquipmentAssignmentMutex);
for (const auto &s : m_exerciseEquipmentAssignments)
{
auto it = s.second.find(identity);
if (it != s.second.end())
return s.first;
}
return 0;
}
void PrtCnvRadioThread::removeMasteringMember(const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_loginTelegram,
PrtCnvMember *member,
PrtCnvMember *issiMember,
int masterIdentity,
int masterIssi)
{
using namespace N_CoPrtCnv;
if (member)
{
m_DbThread->updateExerciseIdForMember(member, 0);
if (member->m_Licensed)
{
sendLizenzierungTneToRadio(member->m_Id, member->m_ISSI, N_CoPrtCnv::E_LicensingKind::Unlicense, N_CoPrtCnv::E_LicensingErrorCode::Unlicense);
INFO_LOG(m_logger) << "Unlicense TPU with already used ID. Id="
<< member->m_Id << " ISSI=" << member->m_ISSI;
}
sendLoginResponse(CoPrtCnvRadioTelegram::E_LoginStatus(CoPrtCnvRadioTelegram::e_LoginReject | CoPrtCnvRadioTelegram::e_LoginOtherEqId),
member->m_Id, member->m_ISSI);
INFO_LOG(m_logger) << "Reject TPU with already used ID. Id="
<< member->m_Id << " ISSI=" << member->m_ISSI;
writeLoginLog(a_loginTelegram,
QStringLiteral("Login-failure: TPU with Id=%1 and ISSI=%2 was removed from TETRA due to Mastering-Bit from Id=%3 and ISSI=%4")
.arg(member->m_Id).arg(member->m_ISSI).arg(masterIdentity).arg(masterIssi));
m_DbThread->removeMember(member);
}
if (issiMember)
{
m_DbThread->updateExerciseIdForMember(issiMember, 0);
if (issiMember->m_Licensed)
{
sendLizenzierungTneToRadio(issiMember->m_Id, issiMember->m_ISSI, N_CoPrtCnv::E_LicensingKind::Unlicense, N_CoPrtCnv::E_LicensingErrorCode::Unlicense);
INFO_LOG(m_logger) << "Unlicense TPU with already used ISSI. Id="
<< issiMember->m_Id << " ISSI=" << issiMember->m_ISSI;
}
sendLoginResponse(CoPrtCnvRadioTelegram::E_LoginStatus(CoPrtCnvRadioTelegram::e_LoginReject | CoPrtCnvRadioTelegram::e_LoginOtherISSI),
issiMember->m_Id, issiMember->m_ISSI);
INFO_LOG(m_logger) << "Reject TPU with already used ISSI. Id="
<< issiMember->m_Id << " ISSI=" << issiMember->m_ISSI;
writeLoginLog(a_loginTelegram,
QStringLiteral("Login-failure: TPU with Id=%1 and ISSI=%2 was removed from TETRA due to Mastering-Bit from Id=%3 and ISSI=%4")
.arg(issiMember->m_Id).arg(issiMember->m_ISSI).arg(masterIdentity).arg(masterIssi));
m_DbThread->removeMember(issiMember);
}
}
}