#include "udpserver.h" #include "logcategories.h" #define STALE_CONNECTION 15 udpServer::udpServer(SERVERCONFIG config) : config(config) { qDebug(logUdpServer()) << "Starting udp server"; } void udpServer::init() { srand(time(NULL)); // Generate random key timeStarted.start(); // Convoluted way to find the external IP address, there must be a better way???? QString localhostname = QHostInfo::localHostName(); QList hostList = QHostInfo::fromName(localhostname).addresses(); foreach(const QHostAddress & address, hostList) { if (address.protocol() == QAbstractSocket::IPv4Protocol && address.isLoopback() == false) { localIP = QHostAddress(address.toString()); } } foreach(QNetworkInterface netInterface, QNetworkInterface::allInterfaces()) { // Return only the first non-loopback MAC Address if (!(netInterface.flags() & QNetworkInterface::IsLoopBack)) { macAddress = netInterface.hardwareAddress(); } } uint32_t addr = localIP.toIPv4Address(); qDebug(logUdpServer()) << " My IP Address: " << QHostAddress(addr).toString() << " My MAC Address: " << macAddress; controlId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (config.controlPort & 0xffff); civId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (config.civPort & 0xffff); audioId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (config.audioPort & 0xffff); qDebug(logUdpServer()) << "Server Binding Control to: " << config.controlPort; udpControl = new QUdpSocket(this); udpControl->bind(config.controlPort); QUdpSocket::connect(udpControl, &QUdpSocket::readyRead, this, &udpServer::controlReceived); qDebug(logUdpServer()) << "Server Binding CIV to: " << config.civPort; udpCiv = new QUdpSocket(this); udpCiv->bind(config.civPort); QUdpSocket::connect(udpCiv, &QUdpSocket::readyRead, this, &udpServer::civReceived); qDebug(logUdpServer()) << "Server Binding Audio to: " << config.audioPort; udpAudio = new QUdpSocket(this); udpAudio->bind(config.audioPort); QUdpSocket::connect(udpAudio, &QUdpSocket::readyRead, this, &udpServer::audioReceived); } udpServer::~udpServer() { qDebug(logUdpServer()) << "Closing udpServer"; foreach(CLIENT * client, controlClients) { if (client->idleTimer != Q_NULLPTR) { client->idleTimer->stop(); delete client->idleTimer; } if (client->pingTimer != Q_NULLPTR) { client->pingTimer->stop(); delete client->pingTimer; } if (client->wdTimer != Q_NULLPTR) { client->wdTimer->stop(); delete client->wdTimer; } if (client->retransmitTimer != Q_NULLPTR) { client->retransmitTimer->stop(); delete client->retransmitTimer; } delete client; controlClients.removeAll(client); } foreach(CLIENT * client, civClients) { if (client->idleTimer != Q_NULLPTR) { client->idleTimer->stop(); delete client->idleTimer; } if (client->pingTimer != Q_NULLPTR) { client->pingTimer->stop(); delete client->pingTimer; } if (client->retransmitTimer != Q_NULLPTR) { client->retransmitTimer->stop(); delete client->retransmitTimer; } delete client; civClients.removeAll(client); } foreach(CLIENT * client, audioClients) { if (client->idleTimer != Q_NULLPTR) { client->idleTimer->stop(); delete client->idleTimer; } if (client->pingTimer != Q_NULLPTR) { client->pingTimer->stop(); delete client->pingTimer; } if (client->retransmitTimer != Q_NULLPTR) { client->retransmitTimer->stop(); delete client->retransmitTimer; } delete client; audioClients.removeAll(client); } if (rxAudioTimer != Q_NULLPTR) { rxAudioTimer->stop(); delete rxAudioTimer; rxAudioTimer = Q_NULLPTR; } if (rxAudioThread != Q_NULLPTR) { rxAudioThread->quit(); rxAudioThread->wait(); } if (txAudioThread != Q_NULLPTR) { txAudioThread->quit(); txAudioThread->wait(); } if (udpControl != Q_NULLPTR) { udpControl->close(); delete udpControl; } if (udpCiv != Q_NULLPTR) { udpCiv->close(); delete udpCiv; } if (udpAudio != Q_NULLPTR) { udpAudio->close(); delete udpAudio; } } void udpServer::receiveRigCaps(rigCapabilities caps) { this->rigCaps = caps; } #define RETRANSMIT_PERIOD 100 void udpServer::controlReceived() { // Received data on control port. while (udpControl->hasPendingDatagrams()) { QNetworkDatagram datagram = udpControl->receiveDatagram(); QByteArray r = datagram.data(); CLIENT* current = Q_NULLPTR; if (datagram.senderAddress().isNull() || datagram.senderPort() == 65535 || datagram.senderPort() == 0) return; foreach(CLIENT * client, controlClients) { if (client != Q_NULLPTR) { if (client->ipAddress == datagram.senderAddress() && client->port == datagram.senderPort()) { current = client; } } } if (current == Q_NULLPTR) { current = new CLIENT(); current->connected = true; current->isStreaming = false; current->timeConnected = QDateTime::currentDateTime(); current->ipAddress = datagram.senderAddress(); current->port = datagram.senderPort(); current->civPort = config.civPort; current->audioPort = config.audioPort; current->myId = controlId; current->remoteId = qFromLittleEndian(r.mid(8, 4)); current->socket = udpControl; current->pingSeq = (quint8)rand() << 8 | (quint8)rand(); current->pingTimer = new QTimer(); connect(current->pingTimer, &QTimer::timeout, this, std::bind(&udpServer::sendPing, this, &controlClients, current, (quint16)0x00, false)); current->pingTimer->start(100); current->idleTimer = new QTimer(); connect(current->idleTimer, &QTimer::timeout, this, std::bind(&udpServer::sendControl, this, current, (quint8)0x00, (quint16)0x00)); current->idleTimer->start(100); current->wdTimer = new QTimer(); connect(current->wdTimer, &QTimer::timeout, this, std::bind(&udpServer::watchdog, this, current)); current->wdTimer->start(1000); current->retransmitTimer = new QTimer(); connect(current->retransmitTimer, &QTimer::timeout, this, std::bind(&udpServer::sendRetransmitRequest, this, current)); current->retransmitTimer->start(RETRANSMIT_PERIOD); current->commonCap = 0x8010; qDebug(logUdpServer()) << "New Control connection created from :" << current->ipAddress.toString() << ":" << QString::number(current->port); controlClients.append(current); } current->lastHeard = QDateTime::currentDateTime(); switch (r.length()) { case (CONTROL_SIZE): { control_packet_t in = (control_packet_t)r.constData(); if (in->type == 0x05) { qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received 'disconnect' request"; sendControl(current, 0x00, in->seq); //current->wdTimer->stop(); // Keep watchdog running to delete stale connection. deleteConnection(&controlClients, current); } break; } case (PING_SIZE): { ping_packet_t in = (ping_packet_t)r.constData(); if (in->type == 0x07) { // It is a ping request/response if (in->reply == 0x00) { current->rxPingTime = in->time; sendPing(&controlClients, current, in->seq, true); } else if (in->reply == 0x01) { if (in->seq == current->pingSeq || in->seq == current->pingSeq - 1) { // A Reply to our ping! if (in->seq == current->pingSeq) { current->pingSeq++; } else { qDebug(logUdpServer()) << current->ipAddress.toString() << ": got out of sequence ping reply. Got: " << in->seq << " expecting: " << current->pingSeq; } } } } break; } case (TOKEN_SIZE): { // Token request token_packet_t in = (token_packet_t)r.constData(); current->rxSeq = in->seq; current->authInnerSeq = in->innerseq; current->identa = in->identa; current->identb = in->identb; if (in->res == 0x02) { // Request for new token qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received create token request"; sendCapabilities(current); sendConnectionInfo(current); } else if (in->res == 0x01) { // Token disconnect qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received token disconnect request"; sendTokenResponse(current, in->res); } else if (in->res == 0x04) { // Disconnect audio/civ sendTokenResponse(current, in->res); current->isStreaming = false; sendConnectionInfo(current); } else { qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received token request"; sendTokenResponse(current, in->res); } break; } case (LOGIN_SIZE): { login_packet_t in = (login_packet_t)r.constData(); qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received 'login'"; bool userOk = false; foreach(SERVERUSER user, config.users) { QByteArray usercomp; passcode(user.username, usercomp); QByteArray passcomp; passcode(user.password, passcomp); if (!strcmp(in->username, usercomp.constData()) && !strcmp(in->password, passcomp.constData())) { userOk = true; current->user = user; break; } } // Generate login response current->rxSeq = in->seq; current->clientName = in->name; current->authInnerSeq = in->innerseq; current->tokenRx = in->tokrequest; current->tokenTx =(quint8)rand() | (quint8)rand() << 8 | (quint8)rand() << 16 | (quint8)rand() << 24; if (userOk) { qDebug(logUdpServer()) << current->ipAddress.toString() << ": User " << current->user.username << " login OK"; sendLoginResponse(current, true); } else { qDebug(logUdpServer()) << current->ipAddress.toString() << ": Incorrect username/password"; sendLoginResponse(current, false); } break; } case (CONNINFO_SIZE): { conninfo_packet_t in = (conninfo_packet_t)r.constData(); qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received request for radio connection"; // Request to start audio and civ! current->isStreaming = true; current->rxSeq = in->seq; current->rxCodec = in->rxcodec; current->txCodec = in->txcodec; current->rxSampleRate = qFromBigEndian(in->rxsample); current->txSampleRate = qFromBigEndian(in->txsample); current->txBufferLen = qFromBigEndian(in->txbuffer); current->authInnerSeq = in->innerseq; current->identa = in->identa; current->identb = in->identb; sendStatus(current); current->authInnerSeq = 0x00; sendConnectionInfo(current); qDebug(logUdpServer()) << "rxCodec:" << current->rxCodec << " txCodec:" << current->txCodec << " rxSampleRate" << current->rxSampleRate << " txSampleRate" << current->rxSampleRate << " txBufferLen" << current->txBufferLen; if (!config.lan) { // Radio is connected by USB/Serial and we assume that audio is connected as well. // Create audio TX/RX threads if they don't already exist (first client chooses samplerate/codec) if (txaudio == Q_NULLPTR) { bool uLaw = false; quint8 channels = 1; quint8 samples = 8; txSampleRate = current->txSampleRate; txCodec = current->txCodec; if (current->txCodec == 0x01 || current->txCodec == 0x20) { uLaw = true; } if (current->txCodec == 0x08 || current->txCodec == 0x10 || current->txCodec == 0x20) { channels = 2; } if (current->txCodec == 0x04 || current->txCodec == 0x10) { samples = 16; } txaudio = new audioHandler(); txAudioThread = new QThread(this); txaudio->moveToThread(txAudioThread); txAudioThread->start(); connect(this, SIGNAL(setupTxAudio(quint8, quint8, quint16, quint16, bool, bool, QString, quint8)), txaudio, SLOT(init(quint8, quint8, quint16, quint16, bool, bool, QString, quint8))); connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); emit setupTxAudio(samples, channels, current->txSampleRate, in->txbuffer, uLaw, false, config.audioOutput, config.resampleQuality); hasTxAudio=datagram.senderAddress(); connect(this, SIGNAL(haveAudioData(audioPacket)), txaudio, SLOT(incomingAudio(audioPacket))); } if (rxaudio == Q_NULLPTR) { bool uLaw = false; quint8 channels = 1; quint8 samples = 8; rxSampleRate = current->rxSampleRate; rxCodec = current->rxCodec; if (current->rxCodec == 0x01 || current->rxCodec == 0x20) { uLaw = true; } if (current->rxCodec == 0x08 || current->rxCodec == 0x10 || current->rxCodec == 0x20) { channels = 2; } if (current->rxCodec == 0x04 || current->rxCodec == 0x10) { samples = 16; } rxaudio = new audioHandler(); rxAudioThread = new QThread(this); rxaudio->moveToThread(rxAudioThread); rxAudioThread->start(); connect(this, SIGNAL(setupRxAudio(quint8, quint8, quint16, quint16, bool, bool, QString, quint8)), rxaudio, SLOT(init(quint8, quint8, quint16, quint16, bool, bool, QString, quint8))); connect(rxAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); emit setupRxAudio(samples, channels, current->rxSampleRate, 150, uLaw, true, config.audioInput, config.resampleQuality); rxAudioTimer = new QTimer(); connect(rxAudioTimer, &QTimer::timeout, this, std::bind(&udpServer::sendRxAudio, this)); rxAudioTimer->start(10); } } break; } default: { break; } } commonReceived(&controlClients, current, r); } } void udpServer::civReceived() { while (udpCiv->hasPendingDatagrams()) { QNetworkDatagram datagram = udpCiv->receiveDatagram(); QByteArray r = datagram.data(); CLIENT* current = Q_NULLPTR; if (datagram.senderAddress().isNull() || datagram.senderPort() == 65535 || datagram.senderPort() == 0) return; QDateTime now = QDateTime::currentDateTime(); foreach(CLIENT * client, civClients) { if (client != Q_NULLPTR) { if (client->ipAddress == datagram.senderAddress() && client->port == datagram.senderPort()) { current = client; } } } if (current == Q_NULLPTR) { current = new CLIENT(); current->connected = true; current->timeConnected = QDateTime::currentDateTime(); current->ipAddress = datagram.senderAddress(); current->port = datagram.senderPort(); current->myId = civId; current->remoteId = qFromLittleEndian(r.mid(8, 4)); current->socket = udpCiv; current->pingSeq = (quint8)rand() << 8 | (quint8)rand(); current->pingTimer = new QTimer(); connect(current->pingTimer, &QTimer::timeout, this, std::bind(&udpServer::sendPing, this, &civClients, current, (quint16)0x00, false)); current->pingTimer->start(100); current->idleTimer = new QTimer(); connect(current->idleTimer, &QTimer::timeout, this, std::bind(&udpServer::sendControl, this, current, 0x00, (quint16)0x00)); current->idleTimer->start(100); current->wdTimer = new QTimer(); connect(current->wdTimer, &QTimer::timeout, this, std::bind(&udpServer::watchdog, this, current)); current->wdTimer->start(1000); current->retransmitTimer = new QTimer(); connect(current->retransmitTimer, &QTimer::timeout, this, std::bind(&udpServer::sendRetransmitRequest, this, current)); current->retransmitTimer->start(RETRANSMIT_PERIOD); qDebug(logUdpServer()) << "New CIV connection created from :" << current->ipAddress.toString() << ":" << QString::number(current->port); civClients.append(current); } switch (r.length()) { case (CONTROL_SIZE): { } case (PING_SIZE): { ping_packet_t in = (ping_packet_t)r.constData(); if (in->type == 0x07) { // It is a ping request/response if (in->reply == 0x00) { current->rxPingTime = in->time; sendPing(&civClients, current, in->seq, true); } else if (in->reply == 0x01) { if (in->seq == current->pingSeq || in->seq == current->pingSeq - 1) { // A Reply to our ping! if (in->seq == current->pingSeq) { current->pingSeq++; } else { qDebug(logUdpServer()) << current->ipAddress.toString() << ": got out of sequence ping reply. Got: " << in->seq << " expecting: " << current->pingSeq; } } } } break; } default: { if (r.length() > 0x15) { data_packet_t in = (data_packet_t)r.constData(); if (in->type != 0x01) { if (quint16(in->datalen + 0x15) == (quint16)in->len) { emit haveDataFromServer(r.mid(0x15)); } } } break; } } commonReceived(&civClients, current, r); } } void udpServer::audioReceived() { while (udpAudio->hasPendingDatagrams()) { QNetworkDatagram datagram = udpAudio->receiveDatagram(); QByteArray r = datagram.data(); CLIENT* current = Q_NULLPTR; if (datagram.senderAddress().isNull() || datagram.senderPort() == 65535 || datagram.senderPort() == 0) return; QDateTime now = QDateTime::currentDateTime(); foreach(CLIENT * client, audioClients) { if (client != Q_NULLPTR) { if (client->ipAddress == datagram.senderAddress() && client->port == datagram.senderPort()) { current = client; } } } if (current == Q_NULLPTR) { current = new CLIENT(); current->connected = true; current->timeConnected = QDateTime::currentDateTime(); current->ipAddress = datagram.senderAddress(); current->port = datagram.senderPort(); current->myId = audioId; current->remoteId = qFromLittleEndian(r.mid(8, 4)); current->socket = udpAudio; current->pingSeq = (quint8)rand() << 8 | (quint8)rand(); current->pingTimer = new QTimer(); connect(current->pingTimer, &QTimer::timeout, this, std::bind(&udpServer::sendPing, this, &audioClients, current, (quint16)0x00, false)); current->pingTimer->start(100); current->wdTimer = new QTimer(); connect(current->wdTimer, &QTimer::timeout, this, std::bind(&udpServer::watchdog, this, current)); current->wdTimer->start(1000); current->retransmitTimer = new QTimer(); connect(current->retransmitTimer, &QTimer::timeout, this, std::bind(&udpServer::sendRetransmitRequest, this, current)); current->retransmitTimer->start(RETRANSMIT_PERIOD); current->seqPrefix = 0; qDebug(logUdpServer()) << "New Audio connection created from :" << current->ipAddress.toString() << ":" << QString::number(current->port); audioClients.append(current); } switch (r.length()) { case (PING_SIZE): { ping_packet_t in = (ping_packet_t)r.constData(); if (in->type == 0x07) { // It is a ping request/response if (in->reply == 0x00) { current->rxPingTime = in->time; sendPing(&audioClients, current, in->seq, true); } else if (in->reply == 0x01) { if (in->seq == current->pingSeq || in->seq == current->pingSeq - 1) { // A Reply to our ping! if (in->seq == current->pingSeq) { current->pingSeq++; } else { qDebug(logUdpServer()) << current->ipAddress.toString() << ": got out of sequence ping reply. Got: " << in->seq << " expecting: " << current->pingSeq; } } } } break; } default: { /* 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 */ control_packet_t in = (control_packet_t)r.constData(); if (in->type != 0x01 && in->len >= 0xAC) { if (in->seq == 0) { // Seq number has rolled over. current->seqPrefix++; } if (hasTxAudio == datagram.senderAddress()) { // 0xac is the smallest possible audio packet. audioPacket tempAudio; tempAudio.seq = (quint32)current->seqPrefix << 16 | in->seq; tempAudio.time = QTime::currentTime();; tempAudio.sent = 0; tempAudio.datain = r.mid(0x18); //qDebug(logUdpServer()) << "sending tx audio " << in->seq; emit haveAudioData(tempAudio); } } break; } } if (current != Q_NULLPTR) { commonReceived(&audioClients, current, r); } } } void udpServer::commonReceived(QList* l,CLIENT* current, QByteArray r) { Q_UNUSED(l); // We might need it later! current->lastHeard = QDateTime::currentDateTime(); if (r.length() < 0x10) { // Invalid packet qDebug(logUdpServer()) << current->ipAddress.toString() << ": Invalid packet received, len: " << r.length(); return; } switch (r.length()) { case (CONTROL_SIZE): { control_packet_t in = (control_packet_t)r.constData(); if (in->type == 0x03) { qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received 'are you there'"; current->remoteId = in->sentid; sendControl(current, 0x04, in->seq); } // This is This is "Are you ready" in response to "I am here". else if (in->type == 0x06) { qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received 'Are you ready'"; current->remoteId = in->sentid; sendControl(current, 0x06, in->seq); } // This is a retransmit request else if (in->type == 0x01) { // Single packet request qDebug(logUdpServer()) << current->ipAddress.toString() << ": Received 'retransmit' request for " << in->seq; auto match = std::find_if(current->txSeqBuf.begin(), current->txSeqBuf.end(), [&cs = in->seq](SEQBUFENTRY& s) { return s.seqNum == cs; }); if (match != current->txSeqBuf.end() && match->retransmitCount < 5) { // Found matching entry? // Don't constantly retransmit the same packet, give-up eventually QMutexLocker locker(&mutex); qDebug(logUdpServer()) << this->metaObject()->className() << ": Sending retransmit of " << hex << match->seqNum; match->retransmitCount++; current->socket->writeDatagram(match->data, current->ipAddress, current->port); } else { // Just send an idle! sendControl(current, 0x00, in->seq); } } break; } default: { break; } } // The packet is at least 0x10 in length so safe to cast it to control_packet for processing control_packet_t in = (control_packet_t)r.constData(); if (in->type == 0x01 && in->len != 0x10) { for (quint16 i = 0x10; i < r.length(); i = i + 2) { auto match = std::find_if(current->txSeqBuf.begin(), current->txSeqBuf.end(), [&cs = in->seq](SEQBUFENTRY& s) { return s.seqNum == cs; }); if (match == current->txSeqBuf.end()) { qDebug(logUdpServer()) << current->ipAddress.toString() << ":" << current->port << ": Requested packet " << hex << in->seq << " not found"; // Just send idle packet. sendControl(current, 0, in->seq); } else if (match->seqNum != 0x00) { // Found matching entry? // Send "untracked" as it has already been sent once. QMutexLocker locker(&mutex); qDebug(logUdpServer()) << current->ipAddress.toString() << ":" << current->port << ": Sending retransmit of " << hex << match->seqNum; match->retransmitCount++; current->socket->writeDatagram(match->data, current->ipAddress, current->port); match++; } } } else if (in->len != PING_SIZE && in->type == 0x00 && in->seq != 0x00) { QMutexLocker locker(&mutex); if (current->rxSeqBuf.isEmpty()) { current->rxSeqBuf.append(in->seq); } else { std::sort(current->rxSeqBuf.begin(), current->rxSeqBuf.end()); if (in->seq < current->rxSeqBuf.front()) { qDebug(logUdpServer()) << current->ipAddress.toString() << ": ******* seq number may have rolled over ****** previous highest: " << hex << current->rxSeqBuf.back() << " current: " << hex << in->seq; // Looks like it has rolled over so clear buffer and start again. current->rxSeqBuf.clear(); return; } if (!current->rxSeqBuf.contains(in->seq)) { // Add incoming packet to the received buffer and if it is in the missing buffer, remove it. current->rxSeqBuf.append(in->seq); // Check whether this is one of our missing ones! auto s = std::find_if(current->rxMissing.begin(), current->rxMissing.end(), [&cs = in->seq](SEQBUFENTRY& s) { return s.seqNum == cs; }); if (s != current->rxMissing.end()) { qDebug(logUdpServer()) << current->ipAddress.toString() << ": Missing SEQ has been received! " << hex << in->seq; s = current->rxMissing.erase(s); } } } } } void udpServer::sendControl(CLIENT* c, quint8 type, quint16 seq) { //qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending control packet: " << type; QMutexLocker locker(&mutex); control_packet p; memset(p.packet, 0x0, CONTROL_SIZE); // We can't be sure it is initialized with 0x00! p.len = sizeof(p); p.type = type; p.sentid = c->myId; p.rcvdid = c->remoteId; if (seq == 0x00) { p.seq = c->txSeq; c->txSeqBuf.append(SEQBUFENTRY()); c->txSeqBuf.last().seqNum = seq; c->txSeqBuf.last().timeSent = QTime::currentTime(); c->txSeqBuf.last().retransmitCount = 0; c->txSeqBuf.last().data = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); c->txSeq++; if (c->idleTimer != Q_NULLPTR) c->idleTimer->start(100); } else { p.seq = seq; c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); } return; } void udpServer::sendPing(QList *l,CLIENT* c, quint16 seq, bool reply) { QMutexLocker locker(&mutex); // Also use to detect "stale" connections QDateTime now = QDateTime::currentDateTime(); if (c->lastHeard.secsTo(now) > STALE_CONNECTION) { qDebug(logUdpServer()) << "Deleting stale connection " << c->ipAddress.toString(); deleteConnection(l, c); return; } //qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending Ping"; quint32 pingTime = 0; if (reply) { pingTime = c->rxPingTime; } else { pingTime = (quint32)timeStarted.msecsSinceStartOfDay(); seq = c->pingSeq; // Don't increment pingseq until we receive a reply. } // First byte of pings "from" server can be either 0x00 or packet length! ping_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! if (reply) { p.len = sizeof(p); } p.type = 0x07; p.seq = seq; p.sentid = c->myId; p.rcvdid = c->remoteId; p.time = pingTime; p.reply = (char)reply; c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); return; } void udpServer::sendLoginResponse(CLIENT* c, bool allowed) { qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending Login response: " << c->txSeq; login_response_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.len = sizeof(p); p.type = 0x00; p.seq = c->txSeq; p.sentid = c->myId; p.rcvdid = c->remoteId; p.innerseq = c->authInnerSeq; p.tokrequest = c->tokenRx; p.token = c->tokenTx; p.code = 0x0250; if (!allowed) { p.error = 0xFEFFFFFF; if (c->idleTimer != Q_NULLPTR) c->idleTimer->stop(); if (c->pingTimer != Q_NULLPTR) c->pingTimer->stop(); if (c->retransmitTimer != Q_NULLPTR) c->retransmitTimer->stop(); if (c->wdTimer != Q_NULLPTR) c->wdTimer->stop(); } else { strcpy(p.connection,"WFVIEW"); } QMutexLocker locker(&mutex); c->txSeqBuf.append(SEQBUFENTRY()); c->txSeqBuf.last().seqNum = c->txSeq; c->txSeqBuf.last().timeSent = QTime::currentTime(); c->txSeqBuf.last().retransmitCount = 0; c->txSeqBuf.last().data = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); c->socket->writeDatagram(c->txSeqBuf.last().data, c->ipAddress, c->port); c->txSeq++; if (c->idleTimer != Q_NULLPTR) c->idleTimer->start(100); return; } void udpServer::sendCapabilities(CLIENT* c) { qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending Capabilities :" << c->txSeq; capabilities_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.len = sizeof(p); p.type = 0x00; p.seq = c->txSeq; p.sentid = c->myId; p.rcvdid = c->remoteId; p.innerseq = c->authInnerSeq; p.tokrequest = c->tokenRx; p.token = c->tokenTx; p.code = 0x0298; p.res = 0x02; p.capa = 0x01; p.commoncap = c->commonCap; memcpy(p.macaddress, macAddress.toLocal8Bit(), 6); // IRU seems to expect an "Icom" mac address so replace the first 3 octets of our Mac with one in their range! memcpy(p.macaddress, QByteArrayLiteral("\x00\x90\xc7").constData(), 3); memcpy(p.name, rigCaps.modelName.toLocal8Bit(), rigCaps.modelName.length()); memcpy(p.audio, QByteArrayLiteral("ICOM_VAUDIO").constData(), 11); if (rigCaps.hasWiFi && !rigCaps.hasEthernet) { p.conntype = 0x0707; // 0x0707 for wifi rig. } else { p.conntype = 0x073f; // 0x073f for ethernet rig. } p.civ = rigCaps.civ; p.baudrate = (quint32)qToBigEndian(19200); /* 0x80 = 12K only 0x40 = 44.1K only 0x20 = 22.05K only 0x10 = 11.025K only 0x08 = 48K only 0x04 = 32K only 0x02 = 16K only 0x01 = 8K only */ if (rxaudio == Q_NULLPTR) { p.rxsample = 0x8b01; // all rx sample frequencies supported } else { if (rxSampleRate == 48000) { p.rxsample = 0x0800; // fixed rx sample frequency } else if (rxSampleRate == 32000) { p.rxsample = 0x0400; } else if (rxSampleRate == 24000) { p.rxsample = 0x0001; } else if (rxSampleRate == 16000) { p.rxsample = 0x0200; } else if (rxSampleRate == 12000) { p.rxsample = 0x8000; } } if (txaudio == Q_NULLPTR) { p.txsample = 0x8b01; // all tx sample frequencies supported p.enablea = 0x01; // 0x01 enables TX 24K mode? } else { p.enablea = 0x00; // 0x01 enables TX 24K mode? if (txSampleRate == 48000) { p.txsample = 0x0800; // fixed tx sample frequency } else if (txSampleRate == 32000) { p.txsample = 0x0400; } else if (txSampleRate == 24000) { p.txsample = 0x0000; p.enablea = 0x01; } else if (txSampleRate == 16000) { p.txsample = 0x0200; } else if (txSampleRate == 12000) { p.txsample = 0x8000; } } // I still don't know what these are? p.enableb = 0x01; // 0x01 doesn't seem to do anything? p.enablec = 0x01; // 0x01 doesn't seem to do anything? p.capf = 0x5001; p.capg = 0x0190; QMutexLocker locker(&mutex); c->txSeqBuf.append(SEQBUFENTRY()); c->txSeqBuf.last().seqNum = p.seq; c->txSeqBuf.last().timeSent = QTime::currentTime(); c->txSeqBuf.last().retransmitCount = 0; c->txSeqBuf.last().data = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); c->socket->writeDatagram(c->txSeqBuf.last().data, c->ipAddress, c->port); if (c->idleTimer != Q_NULLPTR) c->idleTimer->start(100); c->txSeq++; return; } // When client has requested civ/audio connection, this will contain their details // Also used to display currently connected used information. void udpServer::sendConnectionInfo(CLIENT* c) { qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending ConnectionInfo :" << c->txSeq; conninfo_packet p; memset(p.packet, 0x0, sizeof(p)); p.len = sizeof(p); p.type = 0x00; p.seq = c->txSeq; p.sentid = c->myId; p.rcvdid = c->remoteId; //p.innerseq = c->authInnerSeq; // Innerseq not used in user packet p.tokrequest = c->tokenRx; p.token = c->tokenTx; p.code = 0x0380; p.commoncap = c->commonCap; p.identa = c->identa; p.identb = c->identb; // 0x1a-0x1f is authid (random number? // memcpy(p + 0x40, QByteArrayLiteral("IC-7851").constData(), 7); memcpy(p.packet + 0x40, rigCaps.modelName.toLocal8Bit(), rigCaps.modelName.length()); // This is the current streaming client (should we support multiple clients?) if (c->isStreaming) { p.busy = 0x01; memcpy(p.computer, c->clientName.constData(), c->clientName.length()); p.ipaddress = qToBigEndian(c->ipAddress.toIPv4Address()); p.identa = c->identa; p.identb = c->identb; } QMutexLocker locker(&mutex); c->txSeqBuf.append(SEQBUFENTRY()); c->txSeqBuf.last().seqNum = p.seq; c->txSeqBuf.last().timeSent = QTime::currentTime(); c->txSeqBuf.last().retransmitCount = 0; c->txSeqBuf.last().data = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); if (c->idleTimer != Q_NULLPTR) c->idleTimer->start(100); c->txSeq++; return; } void udpServer::sendTokenResponse(CLIENT* c, quint8 type) { qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending Token response for type: " << type; token_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.len = sizeof(p); p.type = 0x00; p.seq = c->txSeq; p.sentid = c->myId; p.rcvdid = c->remoteId; p.innerseq = c->authInnerSeq; p.tokrequest = c->tokenRx; p.token = c->tokenTx; p.code = 0x0230; p.identa = c->identa; p.identb = c->identb; p.commoncap = c->commonCap; p.res = type; QMutexLocker locker(&mutex); c->txSeqBuf.append(SEQBUFENTRY()); c->txSeqBuf.last().seqNum = p.seq; c->txSeqBuf.last().timeSent = QTime::currentTime(); c->txSeqBuf.last().retransmitCount = 0; c->txSeqBuf.last().data = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); if (c->idleTimer != Q_NULLPTR) c->idleTimer->start(100); c->txSeq++; return; } #define PURGE_SECONDS 60 void udpServer::watchdog(CLIENT* c) { //qDebug(logUdpServer()) << c->ipAddress.toString() << ":" << c->port << ":Buffers tx:"<< c->txSeqBuf.length() << " rx:" << c->rxSeqBuf.length(); QMutexLocker locker(&mutex); // Erase old entries from the tx packet buffer. Keep the first 100 sent packets as we seem to get asked for these? if (!c->txSeqBuf.isEmpty()) { c->txSeqBuf.erase(std::remove_if(c->txSeqBuf.begin(), c->txSeqBuf.end(), [](const SEQBUFENTRY& v) { return v.timeSent.secsTo(QTime::currentTime()) > PURGE_SECONDS; }), c->txSeqBuf.end()); } // Erase old entries from the missing packets buffer if (!c->rxMissing.isEmpty()) { c->rxMissing.erase(std::remove_if(c->rxMissing.begin(), c->rxMissing.end(), [](const SEQBUFENTRY& v) { return v.timeSent.secsTo(QTime::currentTime()) > PURGE_SECONDS; }), c->rxMissing.end()); } if (!c->rxSeqBuf.isEmpty()) { std::sort(c->rxSeqBuf.begin(), c->rxSeqBuf.end()); if (c->rxSeqBuf.length() > 400) { c->rxSeqBuf.remove(0, 200); } } } void udpServer::sendStatus(CLIENT* c) { qDebug(logUdpServer()) << c->ipAddress.toString() << ": Sending Status"; status_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.len = sizeof(p); p.type = 0x00; p.seq = c->txSeq; p.sentid = c->myId; p.rcvdid = c->remoteId; p.innerseq = c->authInnerSeq; p.tokrequest = c->tokenRx; p.token = c->tokenTx; p.code = 0x0240; p.res = 0x03; p.unknown = 0x1000; p.unusede = (char)0x80; p.identa = c->identa; p.identb = c->identb; p.civport=qToBigEndian(c->civPort); p.audioport=qToBigEndian(c->audioPort); // Send this to reject the request to tx/rx audio/civ //memcpy(p + 0x30, QByteArrayLiteral("\xff\xff\xff\xfe").constData(), 4); c->txSeq++; QMutexLocker locker(&mutex); c->txSeqBuf.append(SEQBUFENTRY()); c->txSeqBuf.last().seqNum = p.seq; c->txSeqBuf.last().timeSent = QTime::currentTime(); c->txSeqBuf.last().retransmitCount = 0; c->txSeqBuf.last().data = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); } void udpServer::dataForServer(QByteArray d) { //qDebug(logUdpServer()) << "Server got:" << d; foreach(CLIENT * client, civClients) { if (client != Q_NULLPTR && client->connected) { data_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.len = (quint16)d.length() + sizeof(p); p.seq = client->txSeq; p.sentid = client->myId; p.rcvdid = client->remoteId; p.reply = (char)0xc1; p.datalen = (quint16)d.length(); p.sendseq = client->innerSeq; QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); t.append(d); QMutexLocker locker(&mutex); client->txSeqBuf.append(SEQBUFENTRY()); client->txSeqBuf.last().seqNum = p.seq; client->txSeqBuf.last().timeSent = QTime::currentTime(); client->txSeqBuf.last().retransmitCount = 0; client->txSeqBuf.last().data = t; client->socket->writeDatagram(t, client->ipAddress, client->port); client->txSeq++; client->innerSeq++; } } return; } void udpServer::sendRxAudio() { if (rxaudio && rxaudio->isChunkAvailable()) { QByteArray audio; rxaudio->getNextAudioChunk(audio); int len = 0; while (len < audio.length()) { audioPacket partial; partial.datain = audio.mid(len, 1364); receiveAudioData(partial); len = len + partial.datain.length(); } } } void udpServer::receiveAudioData(const audioPacket &d) { //qDebug(logUdpServer()) << "Server got:" << d.data.length(); foreach(CLIENT * client, audioClients) { if (client != Q_NULLPTR && client->connected) { audio_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.len = sizeof(p) + d.datain.length(); p.sentid = client->myId; p.rcvdid = client->remoteId; p.ident = 0x0080; // audio is always this? p.datalen = (quint16)qToBigEndian((quint16)d.datain.length()); p.sendseq = (quint16)qToBigEndian((quint16)client->sendAudioSeq); // THIS IS BIG ENDIAN! p.seq = client->txSeq; QByteArray t = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); t.append(d.datain); QMutexLocker locker(&mutex); client->txSeqBuf.append(SEQBUFENTRY()); client->txSeqBuf.last().seqNum = p.seq; client->txSeqBuf.last().timeSent = QTime::currentTime(); client->txSeqBuf.last().retransmitCount = 0; client->txSeqBuf.last().data = t; client->socket->writeDatagram(t, client->ipAddress, client->port); client->txSeq++; client->sendAudioSeq++; } } return; } /// /// Find all gaps in received packets and then send requests for them. /// This will run every 100ms so out-of-sequence packets will not trigger a retransmit request. /// /// void udpServer::sendRetransmitRequest(CLIENT *c) { QMutexLocker locker(&mutex); QByteArray missingSeqs; auto i = std::adjacent_find(c->rxSeqBuf.begin(), c->rxSeqBuf.end(), [](quint16 l, quint16 r) {return l + 1 < r; }); while (i != c->rxSeqBuf.end()) { if (i + 1 != c->rxSeqBuf.end()) { if (*(i + 1) - *i < 30) { for (quint16 j = *i + 1; j < *(i + 1); j++) { qDebug(logUdpServer()) << this->metaObject()->className() << ": Found missing seq between " << *i << " : " << *(i + 1) << " (" << j << ")"; auto s = std::find_if(c->rxMissing.begin(), c->rxMissing.end(), [&cs = j](SEQBUFENTRY& s) { return s.seqNum == cs; }); if (s == c->rxMissing.end()) { // We haven't seen this missing packet before qDebug(logUdpServer()) << this->metaObject()->className() << ": Adding to missing buffer (len="<< c->rxMissing.length() << "): " << j; c->rxMissing.append(SEQBUFENTRY()); c->rxMissing.last().seqNum = j; c->rxMissing.last().retransmitCount = 0; c->rxMissing.last().timeSent = QTime::currentTime(); //packetsLost++; } else { if (s->retransmitCount == 4 && j != 0) { // We have tried 4 times to request this packet, time to give up! s = c->rxMissing.erase(s); c->rxSeqBuf.append(j); // Final thing is to add to received buffer! } } } } else { qDebug(logUdpServer()) << c->ipAddress.toString() << ": Too many missing, flushing buffers"; c->rxSeqBuf.clear(); missingSeqs.clear(); break; } } i++; } for (auto it = c->rxMissing.begin(); it != c->rxMissing.end(); ++it) { if (it->retransmitCount < 10) { missingSeqs.append(it->seqNum & 0xff); missingSeqs.append(it->seqNum >> 8 & 0xff); missingSeqs.append(it->seqNum & 0xff); missingSeqs.append(it->seqNum >> 8 & 0xff); it->retransmitCount++; } } if (missingSeqs.length() != 0) { control_packet p; memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! p.type = 0x01; p.seq = 0x0000; p.sentid = c->myId; p.rcvdid = c->remoteId; if (missingSeqs.length() == 4) // This is just a single missing packet so send using a control. { p.seq = (missingSeqs[0] & 0xff) | (quint16)(missingSeqs[1] << 8); qDebug(logUdpServer()) << c->ipAddress.toString() << ": sending request for missing packet : " << hex << p.seq; c->socket->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), c->ipAddress, c->port); } else { qDebug(logUdpServer()) << c->ipAddress.toString() << ": sending request for multiple missing packets : " << missingSeqs.toHex(); missingSeqs.insert(0, p.packet, sizeof(p.packet)); c->socket->writeDatagram(missingSeqs, c->ipAddress, c->port); } } } /// /// This function is passed a pointer to the list of connection objects and a pointer to the object itself /// Needs to stop and delete all timers, remove the connection from the list and delete the connection. /// /// /// void udpServer::deleteConnection(QList *l, CLIENT* c) { qDebug(logUdpServer()) << "Deleting connection to: " << c->ipAddress.toString() << ":" << QString::number(c->port); if (c->idleTimer != Q_NULLPTR) { c->idleTimer->stop(); delete c->idleTimer; } if (c->pingTimer != Q_NULLPTR) { c->pingTimer->stop(); delete c->pingTimer; } if (c->wdTimer != Q_NULLPTR) { c->wdTimer->stop(); delete c->wdTimer; } if (c->retransmitTimer != Q_NULLPTR) { c->retransmitTimer->stop(); delete c->retransmitTimer; } QList::iterator it = l->begin(); while (it != l->end()) { CLIENT* client = *it; if (client != Q_NULLPTR && client == c) { it = l->erase(it); } else { ++it; } } delete c; // Is this needed or will the erase have done it? c = Q_NULLPTR; qDebug(logUdpServer()) << "Current Number of clients connected: " << l->length(); if (l->length() == 0) { if (rxAudioTimer != Q_NULLPTR) { rxAudioTimer->stop(); delete rxAudioTimer; rxAudioTimer = Q_NULLPTR; } if (rxAudioThread != Q_NULLPTR) { rxAudioThread->quit(); rxAudioThread->wait(); rxaudio = Q_NULLPTR; rxAudioThread = Q_NULLPTR; } if (txAudioThread != Q_NULLPTR) { txAudioThread->quit(); txAudioThread->wait(); txaudio = Q_NULLPTR; txAudioThread = Q_NULLPTR; } } }