504 lines
14 KiB
C++
504 lines
14 KiB
C++
|
|
|
|
#include "tetra_if.h"
|
|
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDateTime>
|
|
#include <QDir>
|
|
#include <QNetworkInterface>
|
|
#include <QStringList>
|
|
#include <QTcpSocket>
|
|
#include <QTimer>
|
|
|
|
#include <CoPrtCnv/CoPrtCnvRadioTelegram.hpp>
|
|
#include <CoPrtCnv/CoPrtCnvTelegramConverter.hpp>
|
|
#include <CoPrtCnv/CoPrtCnvHexDumpHelper.hpp>
|
|
#include <PrtCnv/PrtCnvRadioThread.hpp>
|
|
#include <PrtCnv/PrtCnvThreadStarter.hpp>
|
|
|
|
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<QAbstractSocket::SocketError>::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() << " <ip> <port> <src_issi> <mode> <options>";
|
|
ERROR_LOG(logger) << " <mode> : listen|send_random|send_file";
|
|
ERROR_LOG(logger) << " <listen> : [<output_dir>]";
|
|
ERROR_LOG(logger) << " <send_random> : <size> <count> <telegrams_per_second_and_dest> [<dest_issi1> .. <dest_issiN>]";
|
|
ERROR_LOG(logger) << " <send_file> : <file> <count> <telegrams_per_second_and_dest> [<dest_issi1> .. <dest_issiN>]";
|
|
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<char *>(&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<QHostAddress> 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 : qAsConst(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 : qAsConst(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);
|
|
}
|