#include "tetra_if.h" #include #include #include #include #include #include #include #include #include #include #include #include #include TetraIF::TetraIF(const QHostAddress &addr, unsigned short port, unsigned short issi) : QObject() , m_addr(addr) , m_port(port) , m_issi(issi) , m_socket(new QTcpSocket(this)) , m_count(0) , m_telegramsPerSecond(0) , m_telegramsInCurSecond(0) , m_secondFragment(0) , m_timer(nullptr) , m_SendCounter(0) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger()) { connect(m_socket, &QTcpSocket::connected, this, &TetraIF::onConnected); connect(m_socket, &QTcpSocket::disconnected, this, &TetraIF::onDisconnected); connect(m_socket, &QTcpSocket::readyRead, this, &TetraIF::onReadyRead); connect(m_socket, QOverload::of(&QTcpSocket::error), this, &TetraIF::onError); m_outputDir = QCoreApplication::applicationDirPath(); m_outputDir += QLatin1Char('/') + QString::number(m_issi) + QLatin1Char('-'); } void TetraIF::start() { m_socket->connectToHost(m_addr, m_port); } void TetraIF::listenMode(const QStringList &args) { if (args.count() > 1) usage(); m_outputDir = QCoreApplication::applicationDirPath(); if (args.count() == 1) m_outputDir = args[0]; if (!QDir().mkpath(m_outputDir)) { ERROR_LOG(m_logger) << "Error creating directory " << m_outputDir; usage(); } m_outputDir += QLatin1Char('/') + QString::number(m_issi) + QLatin1Char('-'); } void TetraIF::randomMode(const QStringList &args) { if (args.count() < 3) { ERROR_LOG(m_logger) << "Invalid argument count"; usage(); } bool bOk; int dataSize = args[0].toInt(&bOk); if (!bOk || dataSize > 256 || dataSize < 0) { ERROR_LOG(m_logger) << "Invalid data size " << args[0]; usage(); } m_count = args[1].toInt(&bOk); if (!bOk || m_count == 0 || m_count < -1) { ERROR_LOG(m_logger) << "Invalid count " << args[1]; usage(); } m_telegramsPerSecond = args[2].toInt(&bOk); if (!bOk || m_telegramsPerSecond < 1) { ERROR_LOG(m_logger) << "Invalid telegrams/second " << args[2]; usage(); } m_data.reserve(dataSize); for (int i = 0; i < dataSize; ++i) m_data += (i % 256); parseISSIs(args.mid(3)); INFO_LOG(m_logger) << "Sending " << m_data.size() << " bytes, " << m_telegramsPerSecond << " telegrams/second for " << m_count << " time(s) to issi(s) " << getIssisAsString(); setupTimer(); } void TetraIF::fileMode(const QStringList &args) { if (args.count() < 3) { ERROR_LOG(m_logger) << "Invalid argument count"; usage(); } QFile f(args[0]); if (!f.exists() || f.size() > 256) { ERROR_LOG(m_logger) << "Invalid file: " << args[0]; usage(); } bool bOk; m_count = args[1].toInt(&bOk); if (!bOk || m_count == 0 || m_count < -1) { ERROR_LOG(m_logger) << "Invalid count " << args[1]; usage(); } m_telegramsPerSecond = args[2].toInt(&bOk); if (!bOk || m_telegramsPerSecond < 1) { ERROR_LOG(m_logger) << "Invalid telegrams/second " << args[2]; usage(); } if (!f.open(QIODevice::ReadOnly)) { ERROR_LOG(m_logger) << "Error opening file " << f.fileName() << " for reading - error: " << f.errorString(); } m_data = f.readAll(); parseISSIs(args.mid(3)); INFO_LOG(m_logger) << "Sending " << m_data.size() << " bytes, " << m_telegramsPerSecond << " telegrams/second for " << m_count << " time(s) to issi(s) " << getIssisAsString(); setupTimer(); } void TetraIF::usage() { NGL::INGLLogger &logger(NGL::NGLLoggerBootstrap::getNGLRootLogger()); ERROR_LOG(logger) << "Usage : " << QCoreApplication::applicationFilePath() << " "; ERROR_LOG(logger) << " : listen|send_random|send_file"; ERROR_LOG(logger) << " : []"; ERROR_LOG(logger) << " : [ .. ]"; ERROR_LOG(logger) << " : [ .. ]"; exit(1); } void TetraIF::onConnected() { INFO_LOG(m_logger) << "Connected to " << m_socket->peerName() << ":" << m_socket->peerPort(); doInitialLogin(); } void TetraIF::onDisconnected() { INFO_LOG(m_logger) << "Disconnected - exiting application"; QCoreApplication::quit(); } void TetraIF::onReadyRead() { do { DEBUG_LOG(m_logger) << "Got " << m_socket->bytesAvailable() << " bytes from socket"; N_CoPrtCnv::CoPrtCnvRadioTelegram::PrtCnvRadioGwHeader header; if (m_socket->bytesAvailable() < int(sizeof(header))) return; m_socket->peek(reinterpret_cast(&header), sizeof(header)); if (N_CoPrtCnv::CoPrtCnvRadioTelegram::MAGIC_NUMBER != header.m_MagicNumber) { ERROR_LOG(m_logger) << "Incorrect magic number (0x" << QString::number(header.m_MagicNumber, 16) << " - aborting"; exit(2); } if (!N_CoPrtCnv::CoPrtCnvRadioTelegram::isTelegramLengthOk(header.m_Length)) { ERROR_LOG(m_logger) << "Radio-Telegram length to big: " << header.m_Length << ", but I proceed with this telegram."; } const quint16 dataSize = header.m_Length + sizeof(header); if (m_socket->bytesAvailable() < dataSize) { INFO_LOG(m_logger) << "not enough bytes available - waiting for more data"; return; } QByteArray buf(dataSize, Qt::Uninitialized); m_socket->read(buf.data(), dataSize); handleReceivedTelegram(buf); } while (m_socket->bytesAvailable()); } void TetraIF::onError(QAbstractSocket::SocketError err) { ERROR_LOG(m_logger) << "Socket error " << err << " (" << m_socket->errorString() << ") - aborting"; exit(2); } void TetraIF::doInitialLogin() { N_CoPrtCnv::CoPrtCnvRadioTelegram tele(m_issi); m_socket->write(tele.data()); m_socket->flush(); } void TetraIF::handleReceivedTelegram(const QByteArray &data) { const auto type = N_CoPrtCnv::CoPrtCnvRadioTelegram::E_PDU_Type(data.at(4)); switch (type) { case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_RegisterRsp: handleRegisterRsp(data.at(5)); break; case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_DataIndReq: handleDataIndReq(data); break; case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_ReportIndReq: break; case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_AirInterfaceLoadReq: break; default: return; } INFO_LOG(m_logger) << "PDU Type: " << type; dumpData(data); } void TetraIF::handleRegisterRsp(const uint8_t a_registrationStatus) { QByteArray logText; switch (a_registrationStatus) { case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_IssiSuccessfulRegistered: INFO_LOG(m_logger) << "Valid register response from Tetra "; if (m_timer) m_timer->start(); 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().toUtf8() + ", "; logText += ")..."; } break; default: logText = "Unknown registration status: " + QByteArray::number(a_registrationStatus); break; } ERROR_LOG(m_logger) << logText; exit(2); } void TetraIF::dumpData(const QByteArray &data) { if (m_outputDir.isEmpty()) return; const qint64 msecs = QDateTime::currentMSecsSinceEpoch(); const QString timeStr = QStringLiteral("%1.%2").arg(msecs / 1000).arg(msecs % 1000); { QFile f(m_outputDir + timeStr + QLatin1String(".dat")); if (!f.open(QIODevice::WriteOnly)) { ERROR_LOG(m_logger) << "Error opening file " << f.fileName() << " for writing - error: " << f.errorString(); } f.write(data); f.close(); } if (data.size() > 27) { N_CoPrtCnv::CoPrtCnvRadioTelegram tele(data); INFO_LOG(m_logger) << "Got telegram from " << tele.getSrcISSI() << " with size " << data.size() << " and payload size " << tele.getPayloadSize(); NGL::NGLLoggerHelper::hexDumpHelper(m_logger, NGL::LogLevel::Info, tele.data()); QFile f(m_outputDir + QStringLiteral("payload-%1-%2.dat").arg(QString::number(tele.getSrcISSI()), timeStr)); if (!f.open(QIODevice::WriteOnly)) { ERROR_LOG(m_logger) << "Error opening file " << f.fileName() << " for writing - error: " << f.errorString(); } f.write(&data.data()[27], data.size() - 27); f.close(); } } void TetraIF::onSendData() { ++m_secondFragment; if (m_secondFragment == 100) { m_secondFragment = 0; m_telegramsInCurSecond = 0; } int telegramsToSendNow = (m_telegramsPerSecond - m_telegramsInCurSecond) / (100 - m_secondFragment); NOTICE_LOG(m_logger) << "telegramsToSendNow: " << telegramsToSendNow << ", " << m_telegramsPerSecond << ", " << m_telegramsInCurSecond << ", " << m_secondFragment; m_telegramsInCurSecond += telegramsToSendNow; static uint8_t s_counter = 1; static char s_randomChar = 0; for (int i = 0; i < telegramsToSendNow; ++i) { for (unsigned short dest_issi : std::as_const(m_issiList)) { m_data[0] = s_randomChar++; N_CoPrtCnv::CoPrtCnvRadioTelegram tele(GEF_BESTAETIGUNG, m_data.size(), dest_issi); tele.setPayloadDataComplete(m_data); tele.setSrcISSI(m_issi); tele.setDestISSI(dest_issi); tele.setZaehler(s_counter++); DEBUG_LOG(m_logger) << "Sending " << tele.getSize() << " bytes to Tetra (ISSI: " << dest_issi << ")"; m_socket->write(tele.data()); } if (m_count > 0) { if (--m_count <= 0) { INFO_LOG(m_logger) << "Finsihed"; m_socket->disconnectFromHost(); } } m_socket->flush(); } } void TetraIF::parseISSIs(const QStringList &issiList) { m_issiList.clear(); if (issiList.count() == 0) { m_issiList += 0; return; } for (const QString &issiStr : issiList) { bool bOk; unsigned short issi = issiStr.toUShort(&bOk); if (!bOk) { ERROR_LOG(m_logger) << "Not a valid issi number: " << issiStr; usage(); } m_issiList += issi; } } QString TetraIF::getIssisAsString() const { QString ret; for (unsigned short issi : std::as_const(m_issiList)) ret += QString::number(issi) + QLatin1String(", "); return ret.left(ret.size() - 2); } void TetraIF::handleDataIndReq(const QByteArray &data) { N_CoPrtCnv::CoPrtCnvRadioTelegram *radioTelegram = new N_CoPrtCnv::CoPrtCnvRadioTelegram(data); uint32_t m_HostISSI = 2; if (m_HostISSI == radioTelegram->getDestISSI()) { switch (radioTelegram->getFragmentStatus()) { case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_FirstFragment: case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_MiddleFragment: case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LastFragment: case N_CoPrtCnv::CoPrtCnvRadioTelegram::e_NotFragmented: handleLoginTelegram(radioTelegram); break; default: ERROR_LOG(m_logger) << "Unknown payload-status!: " << radioTelegram->getFragmentStatus(); break; } delete radioTelegram; } } void TetraIF::handleLoginTelegram(N_CoPrtCnv::CoPrtCnvRadioTelegram *a_loginTelegram) { uint16_t identity = a_loginTelegram->getIdentity(); uint32_t issi = a_loginTelegram->getSrcISSI(); if (N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LoginRequest == (a_loginTelegram->getPayloadDataUInt8(2) & N_CoPrtCnv::CoPrtCnvRadioTelegram::e_LoginStatusMask)) { bool sent = sendLoginResponse(issi); if (sent) INFO_LOG(m_logger) << "Login: New TPU with Id=" << identity << " and ISSI=" << issi << " logged in"; else INFO_LOG(m_logger) << "Login: New TPU with Id=" << identity << " and ISSI=" << issi << " is NOT logged in"; } } bool TetraIF::sendLoginResponse(const uint32_t a_ISSI) { ++m_SendCounter; N_CoPrtCnv::CoPrtCnvRadioTelegram *a_telegram = new N_CoPrtCnv::CoPrtCnvRadioTelegram(GEF_BESTAETIGUNG, 20, a_ISSI); a_telegram->setZaehler(m_SendCounter); a_telegram->setDestISSI(a_ISSI); a_telegram->setSrcISSI(2); gef_telegramm_id_et telId = a_telegram->isNormalTelegram() ? a_telegram->getTelID() : GEF_BESTAETIGUNG; INFO_LOG(m_logger) << "Send telegram to radio: telId=" << telId << " wcounter=" << a_telegram->zaehler() << " size= 28"; a_telegram->setProtocolIdentifier(N_CoPrtCnv::CoPrtCnvRadioTelegram::e_TDSMessageWithoutSDS_TL); a_telegram->setPayloadData(0x01); a_telegram->setPayloadData(0x04); a_telegram->getData()[22] = 0x0d; char *data = a_telegram->getData(); data[02] = 0x18; data[22] = 0x0d; data[23] = 0x00; data[24] = 0x02; data[25] = 0x00; data[13] = 0x00; data[26] = 0x01; data[27] = 0x40; m_socket->write(data, 28); delete a_telegram; return m_socket->flush(); } void TetraIF::setupTimer() { delete m_timer; m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &TetraIF::onSendData); m_timer->setInterval(10); }