#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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"); } int32_t PrtCnvRadioThread::create(NGL::Settings *settings) { INFO_LOG(m_logger) << "create"; PrtCnvSocketThread::create(settings); m_BroadcastManager.create(settings); m_CentralThread = qobject_cast(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 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(&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(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 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(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(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(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 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 &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(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 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 &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(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(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 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(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(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(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(a_errorCause)); radioTelegram.setPayload1Byte(a_telegramId); std::shared_ptr 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(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(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(GEF_LIZENSIERUNG_TNE, 7, a_identity)); radioTelegram->setPayload1Byte(static_cast(a_licensingKind)); radioTelegram->setPayload1Byte(static_cast(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 &a_telegram) { uint8_t fragmentCounter = 0; N_CoPrtCnv::CoPrtCnvRadioTelegram::E_FragmentStatus fragmentStatus; do { bool error = false; auto fragmentedTelegram(std::make_shared(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 &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(lfsr); } } void PrtCnvRadioThread::sendScrambledContent(const std::shared_ptr &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(a_telegram->getData() + payloadOffset); scramble(dataIn, dataOut, scrambleLen, initial, polynom); QByteArray tmp (reinterpret_cast(dataOut), scrambleLen); DEBUG_LOG(m_logger) << "Raw scrambled data: " << tmp.toHex(' '); m_Socket->write(a_telegram->getData(), payloadOffset); m_Socket->write(reinterpret_cast(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(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(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(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 &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(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(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 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); } } }