2021-02-03 20:00:40 +00:00
|
|
|
// Copyright 2021 Phil Taylor M0VSE
|
2021-02-05 10:45:19 +00:00
|
|
|
// This code is heavily based on "Kappanhang" by HA2NON, ES1AKOS and W6EL!
|
2021-02-03 20:00:40 +00:00
|
|
|
|
|
|
|
#include "udphandler.h"
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
udpHandler::udpHandler(QString ip, quint16 controlPort, quint16 civPort, quint16 audioPort, QString username, QString password,
|
|
|
|
quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec) :
|
|
|
|
controlPort(controlPort),
|
|
|
|
civPort(civPort),
|
|
|
|
audioPort(audioPort)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-07 18:46:47 +00:00
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
this->port = this->controlPort;
|
2021-02-03 20:00:40 +00:00
|
|
|
this->username = username;
|
|
|
|
this->password = password;
|
2021-02-09 12:43:28 +00:00
|
|
|
this->rxBufferSize = buffer;
|
|
|
|
this->rxSampleRate = rxsample;
|
|
|
|
this->txSampleRate = txsample;
|
|
|
|
this->rxCodec = rxcodec;
|
|
|
|
this->txCodec = txcodec;
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
qDebug() << "Starting udpHandler user:" << username << " buffer:" << buffer << " rx sample rate: " << rxsample <<
|
|
|
|
" rx codec: " << rxcodec << " tx sample rate: " << txsample << " tx codec: " << txcodec;
|
2021-02-07 17:40:38 +00:00
|
|
|
|
2021-02-07 18:46:47 +00:00
|
|
|
// Try to set the IP address, if it is a hostname then perform a DNS lookup.
|
|
|
|
if (!radioIP.setAddress(ip))
|
|
|
|
{
|
|
|
|
QHostInfo remote = QHostInfo::fromName(ip);
|
2021-02-07 19:09:19 +00:00
|
|
|
foreach(QHostAddress addr, remote.addresses())
|
2021-02-07 18:46:47 +00:00
|
|
|
{
|
2021-02-07 19:09:19 +00:00
|
|
|
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
|
|
|
radioIP = addr;
|
|
|
|
qDebug() << "Got IP Address :" << ip << ": " << addr.toString();
|
|
|
|
break;
|
|
|
|
}
|
2021-02-07 18:46:47 +00:00
|
|
|
}
|
2021-02-07 19:09:19 +00:00
|
|
|
if (radioIP.isNull())
|
|
|
|
{
|
|
|
|
qDebug() << "Error obtaining IP Address for :" << ip << ": " << remote.errorString();
|
2021-02-07 18:46:47 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
|
|
|
|
// Convoluted way to find the external IP address, there must be a better way????
|
|
|
|
QString localhostname = QHostInfo::localHostName();
|
|
|
|
QList<QHostAddress> hostList = QHostInfo::fromName(localhostname).addresses();
|
2021-02-07 18:46:47 +00:00
|
|
|
foreach(const QHostAddress & address, hostList)
|
2021-02-04 19:53:48 +00:00
|
|
|
{
|
2021-02-07 18:46:47 +00:00
|
|
|
if (address.protocol() == QAbstractSocket::IPv4Protocol && address.isLoopback() == false)
|
2021-02-04 19:53:48 +00:00
|
|
|
{
|
2021-02-04 06:00:13 +00:00
|
|
|
localIP = QHostAddress(address.toString());
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 14:53:42 +00:00
|
|
|
|
|
|
|
// Set my computer name. Should this be configurable?
|
|
|
|
compName = "wfview";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void udpHandler::init()
|
|
|
|
{
|
2021-02-13 23:25:24 +00:00
|
|
|
udpBase::init(); // Perform UDP socket initialization.
|
2021-02-07 18:46:47 +00:00
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
// Connect socket to my dataReceived function.
|
|
|
|
QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpHandler::dataReceived);
|
2021-02-07 18:46:47 +00:00
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
/*
|
|
|
|
Connect various timers
|
|
|
|
*/
|
2021-02-21 14:53:42 +00:00
|
|
|
tokenTimer = new QTimer();
|
|
|
|
areYouThereTimer = new QTimer();
|
|
|
|
pingTimer = new QTimer();
|
|
|
|
idleTimer = new QTimer();
|
2021-02-04 19:53:48 +00:00
|
|
|
|
2021-02-21 14:53:42 +00:00
|
|
|
connect(tokenTimer, &QTimer::timeout, this, std::bind(&udpHandler::sendToken, this, 0x05));
|
|
|
|
connect(areYouThereTimer, &QTimer::timeout, this, QOverload<>::of(&udpHandler::sendAreYouThere));
|
|
|
|
connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing);
|
|
|
|
connect(idleTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, true, 0, 0));
|
2021-02-13 23:25:24 +00:00
|
|
|
|
2021-02-21 14:53:42 +00:00
|
|
|
// Start sending are you there packets - will be stopped once "I am here" received
|
|
|
|
areYouThereTimer->start(AREYOUTHERE_PERIOD);
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
udpHandler::~udpHandler()
|
|
|
|
{
|
2021-02-21 01:18:14 +00:00
|
|
|
if (streamOpened) {
|
2021-02-13 23:25:24 +00:00
|
|
|
if (audio != Q_NULLPTR) {
|
2021-02-03 20:00:40 +00:00
|
|
|
delete audio;
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
if (civ != Q_NULLPTR) {
|
|
|
|
delete civ;
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-13 23:25:24 +00:00
|
|
|
qDebug() << "Sending token removal packet";
|
|
|
|
sendToken(0x01);
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 16:53:26 +00:00
|
|
|
void udpHandler::changeBufferSize(quint16 value)
|
|
|
|
{
|
|
|
|
emit haveChangeBufferSize(value);
|
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpHandler::receiveFromCivStream(QByteArray data)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
emit haveDataFromPort(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void udpHandler::receiveDataFromUserToRig(QByteArray data)
|
|
|
|
{
|
2021-02-13 23:25:24 +00:00
|
|
|
if (civ != Q_NULLPTR)
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-13 23:25:24 +00:00
|
|
|
civ->send(data);
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpHandler::dataReceived()
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
while (udp->hasPendingDatagrams()) {
|
|
|
|
lastReceived = time(0);
|
|
|
|
QNetworkDatagram datagram = udp->receiveDatagram();
|
|
|
|
QByteArray r = datagram.data();
|
|
|
|
|
|
|
|
switch (r.length())
|
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
case (CONTROL_SIZE): // control packet
|
2021-02-05 21:23:00 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
control_packet_t in = (control_packet_t)r.constData();
|
|
|
|
if (in->type == 0x04) {
|
|
|
|
// If timer is active, stop it as they are obviously there!
|
2021-02-21 14:53:42 +00:00
|
|
|
if (areYouThereTimer->isActive()) {
|
|
|
|
areYouThereTimer->stop();
|
2021-02-20 18:29:23 +00:00
|
|
|
// send ping packets every second
|
2021-02-21 14:53:42 +00:00
|
|
|
pingTimer->start(PING_PERIOD);
|
|
|
|
idleTimer->start(IDLE_PERIOD);
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-14 07:15:49 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
// This is "I am ready" in response to "Are you ready" so send login.
|
|
|
|
else if (in->type == 0x06)
|
2021-02-14 07:15:49 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Received I am ready";
|
|
|
|
sendLogin(); // send login packet
|
2021-02-14 07:15:49 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (PING_SIZE): // ping packet
|
|
|
|
{
|
|
|
|
ping_packet_t in = (ping_packet_t)r.constData();
|
|
|
|
if (in->type == 0x07 && in->reply == 0x01 && streamOpened)
|
2021-02-14 07:15:49 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
// This is a response to our ping request so measure latency
|
|
|
|
latency += lastPingSentTime.msecsTo(QDateTime::currentDateTime());
|
|
|
|
latency /= 2;
|
|
|
|
quint32 totalsent = packetsSent;
|
|
|
|
quint32 totallost = packetsLost / 2;
|
|
|
|
if (audio != Q_NULLPTR) {
|
|
|
|
totalsent = totalsent + audio->packetsSent;
|
|
|
|
totallost = totallost + audio->packetsLost / 2;
|
|
|
|
}
|
|
|
|
if (civ != Q_NULLPTR) {
|
|
|
|
totalsent = totalsent + civ->packetsSent;
|
|
|
|
totallost = totallost + civ->packetsLost / 2;
|
|
|
|
}
|
|
|
|
//double perclost = 1.0 * totallost / totalsent * 100.0 ;
|
|
|
|
emit haveNetworkStatus(" rtt: " + QString::number(latency) + " ms, loss: (" + QString::number(packetsLost) + "/" + QString::number(packetsSent) + ")");
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
case (TOKEN_SIZE): // Response to Token request
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
token_packet_t in = (token_packet_t)r.constData();
|
|
|
|
if (in->res == 0x05)
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
if (in->response == 0x0000)
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Token renewal successful";
|
2021-02-21 14:53:42 +00:00
|
|
|
tokenTimer->start(TOKEN_RENEWAL);
|
2021-02-20 18:29:23 +00:00
|
|
|
gotAuthOK = true;
|
|
|
|
if (!streamOpened)
|
|
|
|
{
|
|
|
|
sendRequestStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (in->response == 0xffffffff)
|
|
|
|
{
|
|
|
|
qWarning() << this->metaObject()->className() << ": Radio rejected token renewal, performing login";
|
|
|
|
remoteId = in->sentid;
|
2021-02-21 14:53:42 +00:00
|
|
|
tokRequest = in->tokrequest;
|
|
|
|
token = in->token;
|
|
|
|
// Got new token response
|
|
|
|
sendToken(0x02); // Update it.
|
2021-02-20 18:29:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qWarning() << this->metaObject()->className() << ": Unknown response to token renewal? " << in->response;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (STATUS_SIZE): // Status packet
|
|
|
|
{
|
|
|
|
status_packet_t in = (status_packet_t)r.constData();
|
|
|
|
if (in->error == 0x00ffffff && !streamOpened)
|
|
|
|
{
|
|
|
|
emit haveNetworkError(radioIP.toString(), "Auth failed, try rebooting the radio.");
|
|
|
|
qDebug() << this->metaObject()->className() << ": Auth failed, try rebooting the radio.";
|
|
|
|
}
|
|
|
|
else if (in->error == 0x00000000 && in->disc == 0x01)
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-04 19:53:48 +00:00
|
|
|
emit haveNetworkError(radioIP.toString(), "Got radio disconnected.");
|
2021-02-14 07:15:49 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Got radio disconnected.";
|
2021-02-21 14:53:42 +00:00
|
|
|
if (streamOpened) {
|
|
|
|
// Close stream connections but keep connection open to the radio.
|
|
|
|
if (audio != Q_NULLPTR) {
|
|
|
|
delete audio;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (civ != Q_NULLPTR) {
|
|
|
|
delete civ;
|
|
|
|
}
|
|
|
|
streamOpened = false;
|
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
case(LOGIN_RESPONSE_SIZE): // Response to Login packet.
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
login_response_packet_t in = (login_response_packet_t)r.constData();
|
|
|
|
if (in->error == 0xfeffffff)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-13 23:25:24 +00:00
|
|
|
emit haveNetworkStatus("Invalid Username/Password");
|
2021-02-14 07:15:49 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Invalid Username/Password";
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
else if (!isAuthenticated)
|
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
|
|
|
|
if (in->tokrequest == tokRequest)
|
|
|
|
{
|
|
|
|
emit haveNetworkStatus("Radio Login OK!");
|
|
|
|
qDebug() << this->metaObject()->className() << ": Received matching token response to our request";
|
|
|
|
token = in->token;
|
|
|
|
sendToken(0x02);
|
2021-02-21 14:53:42 +00:00
|
|
|
tokenTimer->start(TOKEN_RENEWAL); // Start token request timer
|
2021-02-20 18:29:23 +00:00
|
|
|
isAuthenticated = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qDebug() << this->metaObject()->className() << ": Token response did not match, sent:" << tokRequest << " got " << in->tokrequest;
|
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
|
|
|
|
if (!strcmp(in->connection, "FTTH"))
|
2021-02-14 07:53:55 +00:00
|
|
|
{
|
|
|
|
highBandwidthConnection = true;
|
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
|
2021-02-21 01:18:14 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Detected connection speed " << in->connection;
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
case (CONNINFO_SIZE):
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
conninfo_packet_t in = (conninfo_packet_t)r.constData();
|
|
|
|
|
2021-02-21 01:18:14 +00:00
|
|
|
devName = in->name;
|
|
|
|
QHostAddress ip = QHostAddress(qToBigEndian(in->ipaddress));
|
|
|
|
if (!streamOpened && in->busy)
|
2021-02-07 17:40:38 +00:00
|
|
|
{
|
2021-02-21 01:18:14 +00:00
|
|
|
if (strcmp(in->computer,compName.toLocal8Bit()))
|
2021-02-20 18:29:23 +00:00
|
|
|
{
|
2021-02-21 01:18:14 +00:00
|
|
|
emit haveNetworkStatus(devName + " in use by: " + in->computer + " (" + ip.toString() + ")");
|
2021-02-20 18:29:23 +00:00
|
|
|
sendControl(false, 0x00, in->seq); // Respond with an idle
|
|
|
|
}
|
|
|
|
else {
|
2021-02-21 01:18:14 +00:00
|
|
|
civ = new udpCivData(localIP, radioIP, civPort);
|
|
|
|
audio = new udpAudio(localIP, radioIP, audioPort, rxBufferSize, rxSampleRate, rxCodec, txSampleRate, txCodec);
|
|
|
|
|
|
|
|
QObject::connect(civ, SIGNAL(receive(QByteArray)), this, SLOT(receiveFromCivStream(QByteArray)));
|
|
|
|
QObject::connect(this, SIGNAL(haveChangeBufferSize(quint16)), audio, SLOT(changeBufferSize(quint16)));
|
|
|
|
|
|
|
|
streamOpened = true;
|
2021-02-05 20:26:18 +00:00
|
|
|
|
2021-02-21 01:18:14 +00:00
|
|
|
emit haveNetworkStatus(devName);
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-21 01:18:14 +00:00
|
|
|
qDebug() << this->metaObject()->className() << "Got serial and audio request success, device name: " << devName;
|
|
|
|
|
|
|
|
// Stuff can change in the meantime because of a previous login...
|
|
|
|
remoteId = in->sentid;
|
|
|
|
myId = in->rcvdid;
|
|
|
|
tokRequest = in->tokrequest;
|
|
|
|
token = in->token;
|
2021-02-20 18:29:23 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-21 01:18:14 +00:00
|
|
|
else if (!streamOpened && !in->busy)
|
2021-02-20 18:29:23 +00:00
|
|
|
{
|
2021-02-21 01:18:14 +00:00
|
|
|
emit haveNetworkStatus(devName + " available");
|
|
|
|
|
|
|
|
identa = in->identa;
|
|
|
|
identb = in->identb;
|
|
|
|
|
|
|
|
sendRequestStream();
|
2021-02-20 18:29:23 +00:00
|
|
|
}
|
|
|
|
break;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
|
|
|
|
case (CAPABILITIES_SIZE):
|
|
|
|
{
|
|
|
|
capabilities_packet_t in = (capabilities_packet_t)r.constData();
|
2021-02-21 01:18:14 +00:00
|
|
|
audioType = in->audio;
|
|
|
|
devName = in->name;
|
2021-02-20 18:29:23 +00:00
|
|
|
//replyId = r.mid(0x42, 16);
|
|
|
|
qDebug() << this->metaObject()->className() << "Received radio capabilities, Name:" <<
|
2021-02-21 01:18:14 +00:00
|
|
|
devName << " Audio:" <<
|
|
|
|
audioType;
|
2021-02-20 18:29:23 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-13 23:25:24 +00:00
|
|
|
udpBase::dataReceived(r); // Call parent function to process the rest.
|
2021-02-04 20:09:09 +00:00
|
|
|
r.clear();
|
|
|
|
datagram.clear();
|
|
|
|
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
void udpHandler::sendRequestStream()
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
|
2021-02-15 19:28:17 +00:00
|
|
|
QByteArray usernameEncoded;
|
|
|
|
passcode(username, usernameEncoded);
|
2021-02-20 18:29:23 +00:00
|
|
|
int txSeqBufLengthMs = 300;
|
|
|
|
|
|
|
|
conninfo_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.code = 0x0180;
|
|
|
|
p.res = 0x03;
|
|
|
|
p.resb = 0x8010;
|
|
|
|
p.identa = identa;
|
|
|
|
p.identb = identb;
|
|
|
|
p.innerseq = authInnerSendSeq;
|
|
|
|
p.tokrequest = tokRequest;
|
|
|
|
p.token = token;
|
2021-02-21 01:18:14 +00:00
|
|
|
memcpy(&p.name, devName.toLocal8Bit().constData(), devName.length());
|
2021-02-20 18:29:23 +00:00
|
|
|
p.rxenable = 1;
|
|
|
|
p.txenable = 1;
|
|
|
|
p.rxcodec = rxCodec;
|
|
|
|
p.txcodec = txCodec;
|
|
|
|
memcpy(&p.ident, QByteArrayLiteral("&96D7").constData(), 5);
|
|
|
|
p.rxsample = qToBigEndian((quint32)rxSampleRate);
|
|
|
|
p.txsample = qToBigEndian((quint32)txSampleRate);
|
|
|
|
p.civport = qToBigEndian((quint32)civPort);
|
|
|
|
p.audioport = qToBigEndian((quint32)audioPort);
|
|
|
|
p.txbuffer = qToBigEndian((quint32)txSeqBufLengthMs);
|
2021-02-14 15:30:34 +00:00
|
|
|
|
2021-02-03 20:00:40 +00:00
|
|
|
authInnerSendSeq++;
|
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p)));
|
2021-02-13 23:25:24 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-14 07:53:55 +00:00
|
|
|
void udpHandler::sendAreYouThere()
|
|
|
|
{
|
|
|
|
if (areYouThereCounter == 20)
|
|
|
|
{
|
|
|
|
qDebug() << this->metaObject()->className() << ": Radio not responding.";
|
|
|
|
emit haveNetworkStatus("Radio not responding!");
|
|
|
|
}
|
2021-02-18 15:54:26 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Sending Are You There...";
|
|
|
|
|
2021-02-14 07:53:55 +00:00
|
|
|
areYouThereCounter++;
|
2021-02-18 15:54:26 +00:00
|
|
|
udpBase::sendControl(false,0x03,0x00);
|
2021-02-14 07:53:55 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpHandler::sendLogin() // Only used on control stream.
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
qDebug() << this->metaObject()->className() << ": Sending login packet";
|
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
tokRequest = static_cast<quint16>(rand() | rand() << 8); // Generate random token request.
|
|
|
|
|
2021-02-15 19:28:17 +00:00
|
|
|
QByteArray usernameEncoded;
|
|
|
|
QByteArray passwordEncoded;
|
|
|
|
passcode(username, usernameEncoded);
|
|
|
|
passcode(password, passwordEncoded);
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
login_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.code = 0x0170; // Not sure what this is?
|
|
|
|
p.innerseq = authInnerSendSeq;
|
|
|
|
p.tokrequest = tokRequest;
|
|
|
|
memcpy(p.username, usernameEncoded.constData(), usernameEncoded.length());
|
|
|
|
memcpy(p.password, passwordEncoded.constData(), passwordEncoded.length());
|
2021-02-21 01:18:14 +00:00
|
|
|
memcpy(p.name, compName.toLocal8Bit().constData(), compName.length());
|
2021-02-03 20:00:40 +00:00
|
|
|
|
|
|
|
authInnerSendSeq++;
|
2021-02-20 18:29:23 +00:00
|
|
|
sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p)));
|
2021-02-13 23:25:24 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpHandler::sendToken(uint8_t magic)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-14 16:23:25 +00:00
|
|
|
qDebug() << this->metaObject()->className() << "Sending Token request: " << magic;
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
token_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.code = 0x0130; // Not sure what this is?
|
|
|
|
p.res = magic;
|
|
|
|
p.innerseq = authInnerSendSeq;
|
|
|
|
p.tokrequest = tokRequest;
|
|
|
|
p.token = token;
|
2021-02-14 15:30:34 +00:00
|
|
|
|
2021-02-03 20:00:40 +00:00
|
|
|
authInnerSendSeq++;
|
2021-02-20 18:29:23 +00:00
|
|
|
sendTrackedPacket(QByteArray::fromRawData((const char *)p.packet, sizeof(p)));
|
2021-02-21 14:53:42 +00:00
|
|
|
tokenTimer->start(100); // Set 100ms timer for retry (this will be cancelled if a response is received)
|
2021-02-13 23:25:24 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
// Class that manages all Civ Data to/from the rig
|
|
|
|
udpCivData::udpCivData(QHostAddress local, QHostAddress ip, quint16 civPort)
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-13 23:25:24 +00:00
|
|
|
qDebug() << "Starting udpCivData";
|
2021-02-04 19:53:48 +00:00
|
|
|
localIP = local;
|
2021-02-13 23:25:24 +00:00
|
|
|
port = civPort;
|
2021-02-03 20:00:40 +00:00
|
|
|
radioIP = ip;
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
udpBase::init(); // Perform connection
|
|
|
|
|
|
|
|
QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpCivData::dataReceived);
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-18 15:54:26 +00:00
|
|
|
sendControl(false, 0x03, 0x00); // First connect packet
|
2021-02-13 23:25:24 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Connect various timers
|
|
|
|
*/
|
2021-02-21 14:53:42 +00:00
|
|
|
pingTimer = new QTimer();
|
|
|
|
idleTimer = new QTimer();
|
|
|
|
|
|
|
|
connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing);
|
|
|
|
connect(idleTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, true, 0, 0));
|
2021-02-13 23:25:24 +00:00
|
|
|
|
|
|
|
// send ping packets every 100 ms (maybe change to less frequent?)
|
2021-02-21 14:53:42 +00:00
|
|
|
pingTimer->start(PING_PERIOD);
|
2021-02-13 23:25:24 +00:00
|
|
|
// Send idle packets every 100ms, this timer will be reset everytime a non-idle packet is sent.
|
2021-02-21 14:53:42 +00:00
|
|
|
idleTimer->start(IDLE_PERIOD);
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
udpCivData::~udpCivData() {
|
|
|
|
sendOpenClose(true);
|
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpCivData::send(QByteArray d)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
// qDebug() << "Sending: (" << d.length() << ") " << d;
|
2021-02-20 18:29:23 +00:00
|
|
|
data_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.reply = (char)0xc1;
|
|
|
|
p.datalen = d.length();
|
|
|
|
p.sendseq = qToBigEndian(sendSeqB); // THIS IS BIG ENDIAN!
|
|
|
|
|
|
|
|
QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p));
|
2021-02-03 20:00:40 +00:00
|
|
|
t.append(d);
|
2021-02-13 23:25:24 +00:00
|
|
|
sendTrackedPacket(t);
|
2021-02-03 20:00:40 +00:00
|
|
|
sendSeqB++;
|
2021-02-13 23:25:24 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpCivData::sendOpenClose(bool close)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
uint8_t magic = 0x05;
|
|
|
|
|
2021-02-05 17:40:58 +00:00
|
|
|
if (close)
|
|
|
|
{
|
2021-02-03 20:00:40 +00:00
|
|
|
magic = 0x00;
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-21 01:18:14 +00:00
|
|
|
openclose_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.data = 0x01c0; // Not sure what other values are available:
|
|
|
|
p.sendseq = qToBigEndian(sendSeqB);
|
|
|
|
p.magic = magic;
|
2021-02-03 20:00:40 +00:00
|
|
|
|
|
|
|
sendSeqB++;
|
|
|
|
|
2021-02-21 01:18:14 +00:00
|
|
|
sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p)));
|
2021-02-13 23:25:24 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpCivData::dataReceived()
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
while (udp->hasPendingDatagrams())
|
|
|
|
{
|
2021-02-03 20:00:40 +00:00
|
|
|
QNetworkDatagram datagram = udp->receiveDatagram();
|
|
|
|
//qDebug() << "Received: " << datagram.data();
|
|
|
|
QByteArray r = datagram.data();
|
|
|
|
|
|
|
|
switch (r.length())
|
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
case (CONTROL_SIZE): // Control packet
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
control_packet_t in = (control_packet_t)r.constData();
|
|
|
|
if (in->type == 0x06)
|
|
|
|
{
|
|
|
|
// Update remoteId
|
|
|
|
remoteId = in->sentid;
|
|
|
|
sendOpenClose(false);
|
2021-02-05 20:26:18 +00:00
|
|
|
}
|
2021-02-20 20:23:21 +00:00
|
|
|
break;
|
2021-02-20 18:29:23 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
if (r.length() > 21) {
|
|
|
|
// First check if we are missing any packets?
|
|
|
|
uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
|
|
|
|
if (lastReceivedSeq == 0 || lastReceivedSeq > gotSeq) {
|
|
|
|
lastReceivedSeq = gotSeq;
|
|
|
|
}
|
2021-02-05 20:26:18 +00:00
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
for (uint16_t f = lastReceivedSeq + 1; f < gotSeq; f++) {
|
|
|
|
// Do we need to request a retransmit?
|
|
|
|
qDebug() << this->metaObject()->className() << ": Missing Sequence: (" << r.length() << ") " << f;
|
|
|
|
}
|
2021-02-05 20:26:18 +00:00
|
|
|
|
2021-02-14 16:14:56 +00:00
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
lastReceivedSeq = gotSeq;
|
2021-02-05 20:26:18 +00:00
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
quint8 temp = r[0] - 0x15;
|
|
|
|
if ((quint8)r[16] == 0xc1 && (quint8)r[17] == temp)
|
|
|
|
{
|
|
|
|
emit receive(r.mid(0x15));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-13 23:25:24 +00:00
|
|
|
udpBase::dataReceived(r); // Call parent function to process the rest.
|
2021-02-04 20:09:09 +00:00
|
|
|
r.clear();
|
|
|
|
datagram.clear();
|
|
|
|
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Audio stream
|
2021-02-13 23:25:24 +00:00
|
|
|
udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint16 buffer, quint16 rxsample, quint8 rxcodec, quint16 txsample, quint8 txcodec)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
qDebug() << "Starting udpAudio";
|
2021-02-09 12:43:28 +00:00
|
|
|
this->localIP = local;
|
2021-02-13 23:25:24 +00:00
|
|
|
this->port = audioPort;
|
2021-02-09 12:43:28 +00:00
|
|
|
this->radioIP = ip;
|
|
|
|
this->bufferSize = buffer;
|
|
|
|
this->rxSampleRate = rxsample;
|
|
|
|
this->txSampleRate = txsample;
|
|
|
|
this->rxCodec = rxcodec;
|
|
|
|
this->txCodec = txcodec;
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-04 20:20:23 +00:00
|
|
|
init(); // Perform connection
|
2021-02-04 19:53:48 +00:00
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpAudio::dataReceived);
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-12 14:28:55 +00:00
|
|
|
/*
|
|
|
|
0x72 is RX audio codec
|
|
|
|
0x73 is TX audio codec (only single channel options)
|
|
|
|
0x01 uLaw 1ch 8bit
|
|
|
|
0x02 PCM 1ch 8bit
|
|
|
|
0x04 PCM 1ch 16bit
|
|
|
|
0x08 PCM 2ch 8bit
|
|
|
|
0x10 PCM 2ch 16bit
|
|
|
|
0x20 uLaw 2ch 8bit
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (rxCodec == 0x01 || rxCodec == 0x20) {
|
2021-02-09 12:43:28 +00:00
|
|
|
rxIsUlawCodec = true;
|
2021-02-12 14:28:55 +00:00
|
|
|
}
|
|
|
|
if (rxCodec == 0x08 || rxCodec == 0x10 || rxCodec == 0x20) {
|
2021-02-09 12:43:28 +00:00
|
|
|
rxChannelCount = 2;
|
2021-02-12 14:28:55 +00:00
|
|
|
}
|
|
|
|
if (rxCodec == 0x04 || rxCodec == 0x10) {
|
|
|
|
rxNumSamples = 16;
|
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-11 19:18:35 +00:00
|
|
|
rxaudio = new audioHandler();
|
2021-02-08 08:31:48 +00:00
|
|
|
rxAudioThread = new QThread(this);
|
|
|
|
|
|
|
|
rxaudio->moveToThread(rxAudioThread);
|
|
|
|
|
2021-02-12 11:04:42 +00:00
|
|
|
connect(this, SIGNAL(setupRxAudio(quint8, quint8, quint16, quint16, bool, bool)), rxaudio, SLOT(init(quint8, quint8, quint16, quint16, bool, bool)));
|
2021-02-12 23:56:02 +00:00
|
|
|
//connect(this, SIGNAL(haveAudioData(QByteArray)), rxaudio, SLOT(incomingAudio(QByteArray)));
|
2021-02-08 16:53:26 +00:00
|
|
|
connect(this, SIGNAL(haveChangeBufferSize(quint16)), rxaudio, SLOT(changeBufferSize(quint16)));
|
2021-02-08 10:22:20 +00:00
|
|
|
connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater()));
|
2021-02-08 08:31:48 +00:00
|
|
|
|
2021-02-11 19:18:35 +00:00
|
|
|
if (txCodec == 0x01)
|
|
|
|
txIsUlawCodec = true;
|
2021-02-12 14:28:55 +00:00
|
|
|
else if (txCodec == 0x04)
|
|
|
|
txNumSamples = 16;
|
2021-02-11 19:18:35 +00:00
|
|
|
|
|
|
|
txChannelCount = 1; // Only 1 channel is supported.
|
|
|
|
|
|
|
|
txaudio = new audioHandler();
|
|
|
|
txAudioThread = new QThread(this);
|
|
|
|
|
|
|
|
txaudio->moveToThread(txAudioThread);
|
|
|
|
|
|
|
|
connect(this, SIGNAL(setupTxAudio(quint8, quint8, quint16, quint16, bool, bool)), txaudio, SLOT(init(quint8, quint8, quint16, quint16, bool, bool)));
|
2021-02-12 23:56:02 +00:00
|
|
|
//connect(txaudio, SIGNAL(haveAudioData(QByteArray)), this, SLOT(sendTxAudio(QByteArray)));
|
2021-02-11 19:18:35 +00:00
|
|
|
connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater()));
|
|
|
|
|
2021-02-08 11:14:17 +00:00
|
|
|
rxAudioThread->start();
|
2021-02-11 19:18:35 +00:00
|
|
|
|
|
|
|
txAudioThread->start();
|
|
|
|
|
2021-02-18 15:54:26 +00:00
|
|
|
sendControl(false, 0x03, 0x00); // First connect packet
|
2021-02-13 23:25:24 +00:00
|
|
|
|
2021-02-21 14:53:42 +00:00
|
|
|
pingTimer = new QTimer();
|
|
|
|
connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing);
|
|
|
|
pingTimer->start(PING_PERIOD); // send ping packets every 100ms
|
2021-02-13 23:25:24 +00:00
|
|
|
|
2021-02-14 07:15:49 +00:00
|
|
|
emit setupTxAudio(txNumSamples, txChannelCount, txSampleRate, bufferSize, txIsUlawCodec, true);
|
|
|
|
emit setupRxAudio(rxNumSamples, rxChannelCount, rxSampleRate, bufferSize, rxIsUlawCodec, false);
|
|
|
|
|
2021-02-21 14:53:42 +00:00
|
|
|
txAudioTimer = new QTimer();
|
|
|
|
txAudioTimer->setTimerType(Qt::PreciseTimer);
|
|
|
|
connect(txAudioTimer, &QTimer::timeout, this, &udpAudio::sendTxAudio);
|
|
|
|
txAudioTimer->start(TXAUDIO_PERIOD);
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-05 17:40:58 +00:00
|
|
|
udpAudio::~udpAudio()
|
|
|
|
{
|
2021-02-21 14:53:42 +00:00
|
|
|
if (txAudioTimer != Q_NULLPTR)
|
2021-02-13 23:25:24 +00:00
|
|
|
{
|
2021-02-21 14:53:42 +00:00
|
|
|
txAudioTimer->stop();
|
|
|
|
delete txAudioTimer;
|
2021-02-13 23:25:24 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 10:22:20 +00:00
|
|
|
if (rxAudioThread) {
|
|
|
|
rxAudioThread->quit();
|
|
|
|
rxAudioThread->wait();
|
2021-02-08 08:31:48 +00:00
|
|
|
}
|
2021-02-13 23:25:24 +00:00
|
|
|
|
2021-02-11 19:18:35 +00:00
|
|
|
if (txAudioThread) {
|
|
|
|
txAudioThread->quit();
|
|
|
|
txAudioThread->wait();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-12 20:42:56 +00:00
|
|
|
|
|
|
|
|
2021-02-12 23:56:02 +00:00
|
|
|
void udpAudio::sendTxAudio()
|
2021-02-11 19:18:35 +00:00
|
|
|
{
|
2021-02-18 15:54:26 +00:00
|
|
|
|
2021-02-21 14:53:42 +00:00
|
|
|
if (txaudio->isChunkAvailable()) {
|
2021-02-13 11:04:26 +00:00
|
|
|
QByteArray audio;
|
|
|
|
txaudio->getNextAudioChunk(audio);
|
2021-02-20 20:19:18 +00:00
|
|
|
int counter = 1;
|
|
|
|
int len = 0;
|
2021-02-21 14:53:42 +00:00
|
|
|
|
2021-02-20 20:19:18 +00:00
|
|
|
while (len < audio.length()) {
|
|
|
|
QByteArray partial = audio.mid(len, 1364);
|
|
|
|
txaudio_packet p;
|
2021-02-20 18:29:23 +00:00
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
2021-02-20 20:19:18 +00:00
|
|
|
p.len = sizeof(p)+partial.length();
|
2021-02-20 18:29:23 +00:00
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
2021-02-21 14:53:42 +00:00
|
|
|
p.ident = 0x0080; // TX audio is always this?
|
2021-02-20 20:19:18 +00:00
|
|
|
p.datalen = (quint16)qToBigEndian((quint16)partial.length());
|
2021-02-21 14:53:42 +00:00
|
|
|
p.sendseq = (quint16)qToBigEndian((quint16)sendAudioSeq); // THIS IS BIG ENDIAN!
|
2021-02-20 18:29:23 +00:00
|
|
|
QByteArray tx = QByteArray::fromRawData((const char*)p.packet, sizeof(p));
|
2021-02-12 23:56:02 +00:00
|
|
|
tx.append(partial);
|
2021-02-20 20:19:18 +00:00
|
|
|
len = len + partial.length();
|
2021-02-12 23:56:02 +00:00
|
|
|
//qDebug() << "Sending audio packet length: " << tx.length();
|
2021-02-13 23:25:24 +00:00
|
|
|
sendTrackedPacket(tx);
|
2021-02-12 23:56:02 +00:00
|
|
|
sendAudioSeq++;
|
2021-02-20 20:19:18 +00:00
|
|
|
counter++;
|
2021-02-12 23:56:02 +00:00
|
|
|
}
|
2021-02-13 11:04:26 +00:00
|
|
|
}
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-08 16:53:26 +00:00
|
|
|
void udpAudio::changeBufferSize(quint16 value)
|
|
|
|
{
|
|
|
|
emit haveChangeBufferSize(value);
|
|
|
|
}
|
|
|
|
|
2021-02-12 20:42:56 +00:00
|
|
|
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpAudio::dataReceived()
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
while (udp->hasPendingDatagrams()) {
|
|
|
|
QNetworkDatagram datagram = udp->receiveDatagram();
|
|
|
|
//qDebug() << "Received: " << datagram.data();
|
|
|
|
QByteArray r = datagram.data();
|
|
|
|
|
|
|
|
switch (r.length())
|
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
case (16): // Response to control packet handled in udpBase
|
2021-02-03 20:00:40 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2021-02-20 18:29:23 +00:00
|
|
|
{
|
2021-02-09 12:43:28 +00:00
|
|
|
/* Audio packets start as follows:
|
|
|
|
PCM 16bit and PCM8/uLAW stereo: 0x44,0x02 for first packet and 0x6c,0x05 for second.
|
|
|
|
uLAW 8bit/PCM 8bit 0xd8,0x03 for all packets
|
|
|
|
PCM 16bit stereo 0x6c,0x05 first & second 0x70,0x04 third
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2021-02-20 18:29:23 +00:00
|
|
|
if (r.mid(0, 2) == QByteArrayLiteral("\x6c\x05") ||
|
2021-02-09 12:43:28 +00:00
|
|
|
r.mid(0, 2) == QByteArrayLiteral("\x44\x02") ||
|
|
|
|
r.mid(0, 2) == QByteArrayLiteral("\xd8\x03") ||
|
|
|
|
r.mid(0, 2) == QByteArrayLiteral("\x70\x04"))
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-14 16:14:56 +00:00
|
|
|
// First check if we are missing any packets as seq should be sequential.
|
2021-02-03 20:00:40 +00:00
|
|
|
uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
|
2021-02-05 20:26:18 +00:00
|
|
|
if (lastReceivedSeq == 0 || lastReceivedSeq > gotSeq) {
|
|
|
|
lastReceivedSeq = gotSeq;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-20 18:29:23 +00:00
|
|
|
for (uint16_t f = lastReceivedSeq + 1; f < gotSeq; f++) {
|
2021-02-05 20:26:18 +00:00
|
|
|
// Do we need to request a retransmit?
|
|
|
|
qDebug() << this->metaObject()->className() << ": Missing Sequence: (" << r.length() << ") " << f;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-05 20:26:18 +00:00
|
|
|
|
|
|
|
lastReceivedSeq = gotSeq;
|
|
|
|
|
2021-02-12 23:56:02 +00:00
|
|
|
rxaudio->incomingAudio(r.mid(24));
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
}
|
2021-02-04 18:52:00 +00:00
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
udpBase::dataReceived(r); // Call parent function to process the rest.
|
2021-02-04 20:09:09 +00:00
|
|
|
r.clear();
|
|
|
|
datagram.clear();
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-04 20:20:23 +00:00
|
|
|
|
|
|
|
void udpBase::init()
|
2021-02-04 19:53:48 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
timeStarted.start();
|
2021-02-04 19:53:48 +00:00
|
|
|
udp = new QUdpSocket(this);
|
|
|
|
udp->bind(); // Bind to random port.
|
|
|
|
localPort = udp->localPort();
|
|
|
|
qDebug() << "UDP Stream bound to local port:" << localPort << " remote port:" << port;
|
|
|
|
uint32_t addr = localIP.toIPv4Address();
|
2021-02-13 23:25:24 +00:00
|
|
|
myId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (localPort & 0xffff);
|
2021-02-04 19:53:48 +00:00
|
|
|
}
|
2021-02-13 23:25:24 +00:00
|
|
|
|
2021-02-04 19:53:48 +00:00
|
|
|
udpBase::~udpBase()
|
|
|
|
{
|
|
|
|
qDebug() << "Closing UDP stream :" << radioIP.toString() << ":" << port;
|
2021-02-07 12:59:41 +00:00
|
|
|
if (udp != Q_NULLPTR) {
|
2021-02-18 15:54:26 +00:00
|
|
|
sendControl(false, 0x05, 0x00); // Send disconnect
|
2021-02-04 19:53:48 +00:00
|
|
|
udp->close();
|
|
|
|
delete udp;
|
|
|
|
}
|
2021-02-21 14:53:42 +00:00
|
|
|
if (pingTimer != Q_NULLPTR)
|
2021-02-07 12:59:41 +00:00
|
|
|
{
|
2021-02-21 14:53:42 +00:00
|
|
|
pingTimer->stop();
|
|
|
|
delete pingTimer;
|
2021-02-07 12:59:41 +00:00
|
|
|
}
|
2021-02-21 14:53:42 +00:00
|
|
|
if (idleTimer != Q_NULLPTR)
|
2021-02-07 12:59:41 +00:00
|
|
|
{
|
2021-02-21 14:53:42 +00:00
|
|
|
idleTimer->stop();
|
|
|
|
delete idleTimer;
|
2021-02-07 12:59:41 +00:00
|
|
|
}
|
2021-02-21 14:53:42 +00:00
|
|
|
pingTimer = Q_NULLPTR;
|
|
|
|
idleTimer = Q_NULLPTR;
|
|
|
|
|
2021-02-04 19:53:48 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
|
|
|
|
// Base class!
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpBase::dataReceived(QByteArray r)
|
2021-02-04 18:52:00 +00:00
|
|
|
{
|
|
|
|
switch (r.length())
|
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
case (CONTROL_SIZE): // Empty response used for simple comms and retransmit requests.
|
|
|
|
{
|
|
|
|
control_packet_t in = (control_packet_t)r.constData();
|
|
|
|
// We should check for missing packets here
|
|
|
|
// for now just store received seq.
|
|
|
|
lastReceivedSeq = in->seq;
|
|
|
|
if (in->type == 0x04) {
|
|
|
|
qDebug() << this->metaObject()->className() << ": Received I am here";
|
|
|
|
areYouThereCounter = 0;
|
|
|
|
// I don't think that we will ever receive an "I am here" other than in response to "Are you there?"
|
|
|
|
remoteId = in->sentid;
|
|
|
|
sendControl(false, 0x06, 0x01); // Send Are you ready - untracked.
|
2021-02-04 18:52:00 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
else if (in->type == 0x06)
|
2021-02-04 18:52:00 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
// Just get the seqnum and ignore the rest.
|
2021-02-04 18:52:00 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
else if (in->type == 0x01) // retransmit request
|
|
|
|
{
|
|
|
|
// retransmit request
|
|
|
|
// Send an idle with the requested seqnum if not found.
|
|
|
|
bool found = false;
|
|
|
|
for (int f = txSeqBuf.length() - 1; f >= 0; f--)
|
2021-02-04 18:52:00 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
packetsLost++;
|
|
|
|
if (txSeqBuf[f].seqNum == in->seq) {
|
|
|
|
//qDebug() << this->metaObject()->className() << ": retransmitting packet :" << gotSeq << " (len=" << txSeqBuf[f].data.length() << ")";
|
|
|
|
QMutexLocker locker(&mutex);
|
|
|
|
udp->writeDatagram(txSeqBuf[f].data, radioIP, port);
|
|
|
|
found = true;
|
|
|
|
break;
|
2021-02-05 13:16:48 +00:00
|
|
|
}
|
2021-02-04 18:52:00 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
if (!found)
|
|
|
|
{
|
|
|
|
// Packet was not found in buffer
|
|
|
|
//qDebug() << this->metaObject()->className() << ": Could not find requested packet " << gotSeq << ", sending idle.";
|
|
|
|
sendControl(false, 0, in->seq);
|
|
|
|
}
|
2021-02-04 18:52:00 +00:00
|
|
|
}
|
2021-02-20 20:27:35 +00:00
|
|
|
break;
|
2021-02-04 18:52:00 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
case (PING_SIZE): // ping packet
|
2021-02-04 18:52:00 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
ping_packet_t in = (ping_packet_t)r.constData();
|
|
|
|
if (in->type == 0x07)
|
2021-02-04 18:52:00 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
// It is a ping request/response
|
|
|
|
//uint16_t gotSeq = qFromLittleEndian<quint16>(r.mid(6, 2));
|
|
|
|
if (in->reply == 0x00)
|
|
|
|
{
|
|
|
|
ping_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.type = 0x07;
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.reply = 0x01;
|
|
|
|
p.seq = in->seq;
|
|
|
|
p.time = in->time;
|
|
|
|
QMutexLocker locker(&mutex);
|
|
|
|
udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port);
|
|
|
|
}
|
|
|
|
else if (r[0x10] == (char)0x01) {
|
|
|
|
if (in->seq == pingSendSeq)
|
|
|
|
{
|
|
|
|
// This is response to OUR request so increment counter
|
|
|
|
pingSendSeq++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Not sure what to do here, need to spend more time with the protocol but try sending ping with same seq next time?
|
|
|
|
//qDebug() << "Received out-of-sequence ping response. Sent:" << pingSendSeq << " received " << gotSeq;
|
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-02-20 18:29:23 +00:00
|
|
|
qDebug() << "Unhandled response to ping. I have never seen this! 0x10=" << r[16];
|
2021-02-13 23:25:24 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
|
2021-02-15 19:28:17 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (0x18):
|
|
|
|
{
|
|
|
|
if (r.mid(0, 6) == QByteArrayLiteral("\x18\x00\x00\x00\x01\x00"))
|
|
|
|
{ // retransmit range request, can contain multiple ranges.
|
|
|
|
for (int f = 16; f < r.length() - 4; f = f + 4)
|
|
|
|
{
|
|
|
|
quint16 start = qFromLittleEndian<quint16>(r.mid(f, 2));
|
|
|
|
quint16 end = qFromLittleEndian<quint16>(r.mid(f + 2, 2));
|
|
|
|
packetsLost = packetsLost + (end - start);
|
|
|
|
qDebug() << this->metaObject()->className() << ": Retransmit range request for:" << start << " to " << end;
|
|
|
|
for (quint16 gotSeq = start; gotSeq <= end; gotSeq++)
|
|
|
|
{
|
|
|
|
bool found = false;
|
|
|
|
for (int h = txSeqBuf.length() - 1; h >= 0; h--)
|
|
|
|
if (txSeqBuf[h].seqNum == gotSeq) {
|
|
|
|
//qDebug() << this->metaObject()->className() << ": retransmitting packet :" << gotSeq << " (len=" << txSeqBuf[f].data.length() << ")";
|
|
|
|
QMutexLocker locker(&mutex);
|
|
|
|
udp->writeDatagram(txSeqBuf[h].data, radioIP, port);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!found)
|
|
|
|
{
|
|
|
|
//qDebug() << this->metaObject()->className() << ": Could not find requested packet " << gotSeq << ", sending idle.";
|
|
|
|
sendControl(false, 0, gotSeq);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-13 23:25:24 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
break;
|
2021-02-04 18:52:00 +00:00
|
|
|
}
|
2021-02-20 18:29:23 +00:00
|
|
|
default:
|
|
|
|
break;
|
2021-02-04 18:52:00 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-02-18 15:54:26 +00:00
|
|
|
// Used to send idle and other "control" style messages
|
2021-02-20 18:29:23 +00:00
|
|
|
void udpBase::sendControl(bool tracked=true, quint8 type=0, quint16 seq=0)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
control_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.type = type;
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
2021-02-03 20:00:40 +00:00
|
|
|
|
2021-02-04 13:12:08 +00:00
|
|
|
if (!tracked) {
|
2021-02-20 18:29:23 +00:00
|
|
|
p.seq = seq;
|
2021-02-13 23:25:24 +00:00
|
|
|
QMutexLocker locker(&mutex);
|
2021-02-20 18:29:23 +00:00
|
|
|
udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port);
|
2021-02-04 13:12:08 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-02-20 18:29:23 +00:00
|
|
|
sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p)));
|
2021-02-04 13:12:08 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
// Send periodic ping packets
|
|
|
|
void udpBase::sendPing()
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-20 18:29:23 +00:00
|
|
|
ping_packet p;
|
|
|
|
memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00!
|
|
|
|
p.len = sizeof(p);
|
|
|
|
p.type = 0x07;
|
|
|
|
p.sentid = myId;
|
|
|
|
p.rcvdid = remoteId;
|
|
|
|
p.seq = pingSendSeq;
|
|
|
|
p.time = timeStarted.msecsSinceStartOfDay();
|
2021-02-13 23:25:24 +00:00
|
|
|
lastPingSentTime = QDateTime::currentDateTime();
|
|
|
|
QMutexLocker locker(&mutex);
|
2021-02-20 18:29:23 +00:00
|
|
|
udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port);
|
2021-02-06 10:54:20 +00:00
|
|
|
innerSendSeq++;
|
2021-02-03 20:00:40 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpBase::sendTrackedPacket(QByteArray d)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
// As the radio can request retransmission of these packets, store them in a buffer (eventually!)
|
|
|
|
d[6] = sendSeq & 0xff;
|
|
|
|
d[7] = (sendSeq >> 8) & 0xff;
|
2021-02-04 13:12:08 +00:00
|
|
|
SEQBUFENTRY s;
|
|
|
|
s.seqNum = sendSeq;
|
2021-02-05 13:16:48 +00:00
|
|
|
s.timeSent = time(NULL);
|
2021-02-04 13:12:08 +00:00
|
|
|
s.data = (d);
|
|
|
|
txSeqBuf.append(s);
|
2021-02-13 23:25:24 +00:00
|
|
|
purgeOldEntries(); // Delete entries older than PURGE_SECONDS seconds (currently 5)
|
2021-02-03 20:00:40 +00:00
|
|
|
sendSeq++;
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
QMutexLocker locker(&mutex);
|
|
|
|
udp->writeDatagram(d, radioIP, port);
|
2021-02-21 14:53:42 +00:00
|
|
|
if (idleTimer != Q_NULLPTR && idleTimer->isActive()) {
|
|
|
|
idleTimer->start(IDLE_PERIOD); // Reset idle counter if it's running
|
2021-02-13 23:25:24 +00:00
|
|
|
}
|
2021-02-14 10:40:47 +00:00
|
|
|
packetsSent++;
|
2021-02-13 23:25:24 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
void udpBase::purgeOldEntries()
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
for (int f = txSeqBuf.length() - 1; f >= 0; f--)
|
|
|
|
{
|
2021-02-13 23:25:24 +00:00
|
|
|
if (difftime(time(NULL), txSeqBuf[f].timeSent) > PURGE_SECONDS)
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-03 20:00:40 +00:00
|
|
|
txSeqBuf.removeAt(f);
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// passcode function used to generate secure (ish) code
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="str"></param>
|
|
|
|
/// <returns>pointer to encoded username or password</returns>
|
2021-02-15 19:28:17 +00:00
|
|
|
void passcode(QString in, QByteArray& out)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-09 12:43:28 +00:00
|
|
|
const quint8 sequence[] =
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0x47,0x5d,0x4c,0x42,0x66,0x20,0x23,0x46,0x4e,0x57,0x45,0x3d,0x67,0x76,0x60,0x41,0x62,0x39,0x59,0x2d,0x68,0x7e,
|
|
|
|
0x7c,0x65,0x7d,0x49,0x29,0x72,0x73,0x78,0x21,0x6e,0x5a,0x5e,0x4a,0x3e,0x71,0x2c,0x2a,0x54,0x3c,0x3a,0x63,0x4f,
|
|
|
|
0x43,0x75,0x27,0x79,0x5b,0x35,0x70,0x48,0x6b,0x56,0x6f,0x34,0x32,0x6c,0x30,0x61,0x6d,0x7b,0x2f,0x4b,0x64,0x38,
|
|
|
|
0x2b,0x2e,0x50,0x40,0x3f,0x55,0x33,0x37,0x25,0x77,0x24,0x26,0x74,0x6a,0x28,0x53,0x4d,0x69,0x22,0x5c,0x44,0x31,
|
|
|
|
0x36,0x58,0x3b,0x7a,0x51,0x5f,0x52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2021-02-15 19:28:17 +00:00
|
|
|
QByteArray ba = in.toLocal8Bit();
|
2021-02-03 20:00:40 +00:00
|
|
|
uchar* ascii = (uchar*)ba.constData();
|
2021-02-15 19:28:17 +00:00
|
|
|
for (int i = 0; i < in.length() && i < 16; i++)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
|
|
|
int p = ascii[i] + i;
|
2021-02-05 17:40:58 +00:00
|
|
|
if (p > 126)
|
|
|
|
{
|
2021-02-03 20:00:40 +00:00
|
|
|
p = 32 + p % 127;
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-15 19:28:17 +00:00
|
|
|
out.append(sequence[p]);
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
2021-02-15 19:28:17 +00:00
|
|
|
return;
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 23:25:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// returns a QByteArray of a null terminated string
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="c"></param>
|
|
|
|
/// <param name="s"></param>
|
|
|
|
/// <returns></returns>
|
|
|
|
QByteArray parseNullTerminatedString(QByteArray c, int s)
|
2021-02-03 20:00:40 +00:00
|
|
|
{
|
2021-02-07 17:40:38 +00:00
|
|
|
//QString res = "";
|
|
|
|
QByteArray res;
|
2021-02-03 20:00:40 +00:00
|
|
|
for (int i = s; i < c.length(); i++)
|
|
|
|
{
|
|
|
|
if (c[i] != '\0')
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-07 17:40:38 +00:00
|
|
|
res.append(c[i]);
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
else
|
2021-02-05 17:40:58 +00:00
|
|
|
{
|
2021-02-03 20:00:40 +00:00
|
|
|
break;
|
2021-02-05 17:40:58 +00:00
|
|
|
}
|
2021-02-03 20:00:40 +00:00
|
|
|
}
|
|
|
|
return res;
|
2021-02-04 06:00:13 +00:00
|
|
|
}
|
2021-02-14 18:32:58 +00:00
|
|
|
|