diff --git a/udpbase.h b/udpbase.h index 43f4c77..f042b4d 100644 --- a/udpbase.h +++ b/udpbase.h @@ -46,6 +46,8 @@ struct networkStatus { quint16 txLatency; bool rxUnderrun; bool txUnderrun; + bool rxOverrun; + bool txOverrun; quint16 rxCurrentLatency; quint16 txCurrentLatency; quint32 packetsSent = 0; diff --git a/udphandler.cpp b/udphandler.cpp index 38646e2..93fd3f9 100644 --- a/udphandler.cpp +++ b/udphandler.cpp @@ -645,1112 +645,3 @@ void udpHandler::sendToken(uint8_t magic) return; } - -// Class that manages all Civ Data to/from the rig -udpCivData::udpCivData(QHostAddress local, QHostAddress ip, quint16 civPort, bool splitWf, quint16 localPort=0 ) -{ - qInfo(logUdp()) << "Starting udpCivData"; - localIP = local; - port = civPort; - radioIP = ip; - splitWaterfall = splitWf; - - udpBase::init(localPort); // Perform connection - - QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpCivData::dataReceived); - - sendControl(false, 0x03, 0x00); // First connect packet - - /* - Connect various timers - */ - pingTimer = new QTimer(); - idleTimer = new QTimer(); - areYouThereTimer = new QTimer(); - startCivDataTimer = new QTimer(); - watchdogTimer = new QTimer(); - - connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); - connect(watchdogTimer, &QTimer::timeout, this, &udpCivData::watchdog); - connect(idleTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, true, 0, 0)); - connect(startCivDataTimer, &QTimer::timeout, this, std::bind(&udpCivData::sendOpenClose, this, false)); - connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); - watchdogTimer->start(WATCHDOG_PERIOD); - // Start sending are you there packets - will be stopped once "I am here" received - // send ping packets every 100 ms (maybe change to less frequent?) - pingTimer->start(PING_PERIOD); - // Send idle packets every 100ms, this timer will be reset every time a non-idle packet is sent. - idleTimer->start(IDLE_PERIOD); - areYouThereTimer->start(AREYOUTHERE_PERIOD); -} - -udpCivData::~udpCivData() -{ - sendOpenClose(true); - if (startCivDataTimer != Q_NULLPTR) - { - startCivDataTimer->stop(); - delete startCivDataTimer; - startCivDataTimer = Q_NULLPTR; - } - if (pingTimer != Q_NULLPTR) - { - pingTimer->stop(); - delete pingTimer; - pingTimer = Q_NULLPTR; - } - if (idleTimer != Q_NULLPTR) - { - idleTimer->stop(); - delete idleTimer; - idleTimer = Q_NULLPTR; - } - if (watchdogTimer != Q_NULLPTR) - { - watchdogTimer->stop(); - delete watchdogTimer; - watchdogTimer = Q_NULLPTR; - } -} - -void udpCivData::watchdog() -{ - static bool alerted = false; - if (lastReceived.msecsTo(QTime::currentTime()) > 2000) - { - if (!alerted) { - qInfo(logUdp()) << " CIV Watchdog: no CIV data received for 2s, requesting data start."; - if (startCivDataTimer != Q_NULLPTR) - { - startCivDataTimer->start(100); - } - alerted = true; - } - } - else - { - alerted = false; - } -} - -void udpCivData::send(QByteArray d) -{ - //qInfo(logUdp()) << "Sending: (" << d.length() << ") " << d; - data_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p)+d.length(); - 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)); - t.append(d); - sendTrackedPacket(t); - sendSeqB++; - return; -} - - -void udpCivData::sendOpenClose(bool close) -{ - uint8_t magic = 0x04; - - if (close) - { - magic = 0x00; - } - - 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; - - sendSeqB++; - - sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p))); - return; -} - - - -void udpCivData::dataReceived() -{ - while (udp->hasPendingDatagrams()) - { - QNetworkDatagram datagram = udp->receiveDatagram(); - //qInfo(logUdp()) << "Received: " << datagram.data(); - QByteArray r = datagram.data(); - - - switch (r.length()) - { - case (CONTROL_SIZE): // Control packet - { - control_packet_t in = (control_packet_t)r.constData(); - if (in->type == 0x04) - { - areYouThereTimer->stop(); - } - else if (in->type == 0x06) - { - // Update remoteId - remoteId = in->sentid; - // Manually send a CIV start request and start the timer if it isn't received. - // The timer will be stopped as soon as valid CIV data is received. - sendOpenClose(false); - if (startCivDataTimer != Q_NULLPTR) { - startCivDataTimer->start(100); - } - } - break; - } - default: - { - if (r.length() > 21) { - data_packet_t in = (data_packet_t)r.constData(); - if (in->type != 0x01) { - // Process this packet, any re-transmit requests will happen later. - //uint16_t gotSeq = qFromLittleEndian(r.mid(6, 2)); - // We have received some Civ data so stop sending Start packets! - if (startCivDataTimer != Q_NULLPTR) { - startCivDataTimer->stop(); - } - lastReceived = QTime::currentTime(); - if (quint16(in->datalen + 0x15) == (quint16)in->len) - { - //if (r.mid(0x15).length() != 157) - // Find data length - int pos = r.indexOf(QByteArrayLiteral("\x27\x00\x00"))+2; - int len = r.mid(pos).indexOf(QByteArrayLiteral("\xfd")); - //splitWaterfall = false; - if (splitWaterfall && pos > 1 && len > 100) { - // We need to split waterfall data into its component parts - // There are only 2 types that we are currently aware of - int numDivisions = 0; - if (len == 490) // IC705, IC9700, IC7300(LAN) - { - numDivisions = 11; - } - else if (len == 704) // IC7610, IC7851, ICR8600 - { - numDivisions = 15; - } - else { - qInfo(logUdp()) << "Unknown spectrum size" << len; - break; - } - // (sequence #1) includes center/fixed mode at [05]. No pixels. - // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 " - // "DATA: 27 00 00 01 11 01 00 00 00 14 00 00 00 35 14 00 00 fd " - // (sequences 2-10, 50 pixels) - // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 " - // "DATA: 27 00 00 07 11 27 13 15 01 00 22 21 09 08 06 19 0e 20 23 25 2c 2d 17 27 29 16 14 1b 1b 21 27 1a 18 17 1e 21 1b 24 21 22 23 13 19 23 2f 2d 25 25 0a 0e 1e 20 1f 1a 0c fd " - // ^--^--(seq 7/11) - // ^-- start waveform data 0x00 to 0xA0, index 05 to 54 - // (sequence #11) - // "INDEX: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 " - // "DATA: 27 00 00 11 11 0b 13 21 23 1a 1b 22 1e 1a 1d 13 21 1d 26 28 1f 19 1a 18 09 2c 2c 2c 1a 1b fd " - - int divSize = (len / numDivisions)+6; - QByteArray wfPacket; - for (int i = 0; i < numDivisions; i++) { - - wfPacket = r.mid(pos - 6, 9); // First part of packet - - wfPacket = r.mid(pos - 6, 9); // First part of packet - char tens = ((i + 1) / 10); - char units = ((i + 1) - (10 * tens)); - wfPacket[7] = units | (tens << 4); - - tens = (numDivisions / 10); - units = (numDivisions - (10 * tens)); - wfPacket[8] = units | (tens << 4); - - if (i == 0) { - //Just send initial data, first BCD encode the max number: - wfPacket.append(r.mid(pos + 3, 12)); - } - else - { - wfPacket.append(r.mid((pos + 15) + ((i-1) * divSize),divSize)); - } - if (i < numDivisions-1) { - wfPacket.append('\xfd'); - } - //printHex(wfPacket, false, true); - - emit receive(wfPacket); - wfPacket.clear(); - - } - //qDebug(logUdp()) << "Waterfall packet len" << len << "Num Divisions" << numDivisions << "Division Size" << divSize; - } - else { - // Not waterfall data or split not enabled. - emit receive(r.mid(0x15)); - } - //qDebug(logUdp()) << "Got incoming CIV datagram" << r.mid(0x15).length(); - - } - - } - } - break; - } - } - udpBase::dataReceived(r); // Call parent function to process the rest. - - r.clear(); - datagram.clear(); - - } -} - - -// Audio stream -udpAudio::udpAudio(QHostAddress local, QHostAddress ip, quint16 audioPort, quint16 lport, audioSetup rxSetup, audioSetup txSetup) -{ - qInfo(logUdp()) << "Starting udpAudio"; - this->localIP = local; - this->port = audioPort; - this->radioIP = ip; - - if (txSetup.sampleRate == 0) { - enableTx = false; - } - - init(lport); // Perform connection - - QUdpSocket::connect(udp, &QUdpSocket::readyRead, this, &udpAudio::dataReceived); - - rxaudio = new audioHandler(); - rxAudioThread = new QThread(this); - rxAudioThread->setObjectName("rxAudio()"); - - rxaudio->moveToThread(rxAudioThread); - - rxAudioThread->start(QThread::TimeCriticalPriority); - - connect(this, SIGNAL(setupRxAudio(audioSetup)), rxaudio, SLOT(init(audioSetup))); - - // signal/slot not currently used. - connect(this, SIGNAL(haveAudioData(audioPacket)), rxaudio, SLOT(incomingAudio(audioPacket))); - connect(this, SIGNAL(haveChangeLatency(quint16)), rxaudio, SLOT(changeLatency(quint16))); - connect(this, SIGNAL(haveSetVolume(unsigned char)), rxaudio, SLOT(setVolume(unsigned char))); - connect(rxaudio, SIGNAL(haveLevels(quint16, quint16, quint16,bool,bool)), this, SLOT(getRxLevels(quint16, quint16, quint16,bool,bool))); - connect(rxAudioThread, SIGNAL(finished()), rxaudio, SLOT(deleteLater())); - - - sendControl(false, 0x03, 0x00); // First connect packet - - pingTimer = new QTimer(); - connect(pingTimer, &QTimer::timeout, this, &udpBase::sendPing); - pingTimer->start(PING_PERIOD); // send ping packets every 100ms - - if (enableTx) { - txaudio = new audioHandler(); - txAudioThread = new QThread(this); - rxAudioThread->setObjectName("txAudio()"); - - txaudio->moveToThread(txAudioThread); - - txAudioThread->start(QThread::TimeCriticalPriority); - - connect(this, SIGNAL(setupTxAudio(audioSetup)), txaudio, SLOT(init(audioSetup))); - connect(txaudio, SIGNAL(haveAudioData(audioPacket)), this, SLOT(receiveAudioData(audioPacket))); - connect(txaudio, SIGNAL(haveLevels(quint16, quint16, quint16, bool,bool)), this, SLOT(getTxLevels(quint16, quint16, quint16, bool,bool))); - - connect(txAudioThread, SIGNAL(finished()), txaudio, SLOT(deleteLater())); - emit setupTxAudio(txSetup); - } - - emit setupRxAudio(rxSetup); - - watchdogTimer = new QTimer(); - connect(watchdogTimer, &QTimer::timeout, this, &udpAudio::watchdog); - watchdogTimer->start(WATCHDOG_PERIOD); - - areYouThereTimer = new QTimer(); - connect(areYouThereTimer, &QTimer::timeout, this, std::bind(&udpBase::sendControl, this, false, 0x03, 0)); - areYouThereTimer->start(AREYOUTHERE_PERIOD); -} - -udpAudio::~udpAudio() -{ - if (pingTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping pingTimer"; - pingTimer->stop(); - delete pingTimer; - pingTimer = Q_NULLPTR; - } - - if (idleTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping idleTimer"; - idleTimer->stop(); - delete idleTimer; - idleTimer = Q_NULLPTR; - } - - if (watchdogTimer != Q_NULLPTR) - { - qDebug(logUdp()) << "Stopping watchdogTimer"; - watchdogTimer->stop(); - delete watchdogTimer; - watchdogTimer = Q_NULLPTR; - } - - if (rxAudioThread != Q_NULLPTR) { - qDebug(logUdp()) << "Stopping rxaudio thread"; - rxAudioThread->quit(); - rxAudioThread->wait(); - } - - if (txAudioThread != Q_NULLPTR) { - qDebug(logUdp()) << "Stopping txaudio thread"; - txAudioThread->quit(); - txAudioThread->wait(); - } - qDebug(logUdp()) << "udpHandler successfully closed"; -} - -void udpAudio::watchdog() -{ - static bool alerted = false; - if (lastReceived.msecsTo(QTime::currentTime()) > 2000) - { - if (!alerted) { - /* Just log it at the moment, maybe try signalling the control channel that it needs to - try requesting civ/audio again? */ - - qInfo(logUdp()) << " Audio Watchdog: no audio data received for 2s, restart required?"; - alerted = true; - } - } - else - { - alerted = false; - } -} - - -void udpAudio::sendTxAudio() -{ - if (txaudio == Q_NULLPTR) { - return; - } - -} - -void udpAudio::receiveAudioData(audioPacket audio) { - // I really can't see how this could be possible but a quick sanity check! - if (txaudio == Q_NULLPTR) { - return; - } - if (audio.data.length() > 0) { - int counter = 1; - int len = 0; - - while (len < audio.data.length()) { - QByteArray partial = audio.data.mid(len, 1364); - audio_packet p; - memset(p.packet, 0x0, sizeof(p)); // We can't be sure it is initialized with 0x00! - p.len = sizeof(p) + partial.length(); - p.sentid = myId; - p.rcvdid = remoteId; - if (partial.length() == 0xa0) { - p.ident = 0x9781; - } - else { - p.ident = 0x0080; // TX audio is always this? - } - p.datalen = (quint16)qToBigEndian((quint16)partial.length()); - p.sendseq = (quint16)qToBigEndian((quint16)sendAudioSeq); // THIS IS BIG ENDIAN! - QByteArray tx = QByteArray::fromRawData((const char*)p.packet, sizeof(p)); - tx.append(partial); - len = len + partial.length(); - //qInfo(logUdp()) << "Sending audio packet length: " << tx.length(); - sendTrackedPacket(tx); - sendAudioSeq++; - counter++; - } - } -} - -void udpAudio::changeLatency(quint16 value) -{ - emit haveChangeLatency(value); -} - -void udpAudio::setVolume(unsigned char value) -{ - emit haveSetVolume(value); -} - -void udpAudio::getRxLevels(quint16 amplitude,quint16 latency, quint16 current, bool under, bool over) { - - emit haveRxLevels(amplitude,latency, current, under, over); -} - -void udpAudio::getTxLevels(quint16 amplitude,quint16 latency, quint16 current, bool under, bool over) { - emit haveTxLevels(amplitude,latency, current, under, over); -} - -void udpAudio::dataReceived() -{ - while (udp->hasPendingDatagrams()) { - QNetworkDatagram datagram = udp->receiveDatagram(); - //qInfo(logUdp()) << "Received: " << datagram.data().mid(0,10); - QByteArray r = datagram.data(); - - switch (r.length()) - { - case (16): // Response to control packet handled in udpBase - { - //control_packet_t in = (control_packet_t)r.constData(); - 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 >= 0x20) { - if (in->seq == 0) - { - // Seq number has rolled over. - seqPrefix++; - } - - // 0xac is the smallest possible audio packet. - lastReceived = QTime::currentTime(); - audioPacket tempAudio; - tempAudio.seq = (quint32)seqPrefix << 16 | in->seq; - tempAudio.time = lastReceived; - tempAudio.sent = 0; - tempAudio.data = r.mid(0x18); - // Prefer signal/slot to forward audio as it is thread/safe - // Need to do more testing but latency appears fine. - //rxaudio->incomingAudio(tempAudio); - emit haveAudioData(tempAudio); - } - break; - } - } - - udpBase::dataReceived(r); // Call parent function to process the rest. - r.clear(); - datagram.clear(); - } -} - - - -void udpBase::init(quint16 lport) -{ - //timeStarted.start(); - udp = new QUdpSocket(this); - udp->bind(lport); // Bind to random port. - localPort = udp->localPort(); - qInfo(logUdp()) << "UDP Stream bound to local port:" << localPort << " remote port:" << port; - uint32_t addr = localIP.toIPv4Address(); - myId = (addr >> 8 & 0xff) << 24 | (addr & 0xff) << 16 | (localPort & 0xffff); - - retransmitTimer = new QTimer(); - connect(retransmitTimer, &QTimer::timeout, this, &udpBase::sendRetransmitRequest); - retransmitTimer->start(RETRANSMIT_PERIOD); -} - -udpBase::~udpBase() -{ - qInfo(logUdp()) << "Closing UDP stream :" << radioIP.toString() << ":" << port; - if (udp != Q_NULLPTR) { - sendControl(false, 0x05, 0x00); // Send disconnect - udp->close(); - delete udp; - } - if (areYouThereTimer != Q_NULLPTR) - { - areYouThereTimer->stop(); - delete areYouThereTimer; - } - - if (pingTimer != Q_NULLPTR) - { - pingTimer->stop(); - delete pingTimer; - } - if (idleTimer != Q_NULLPTR) - { - idleTimer->stop(); - delete idleTimer; - } - if (retransmitTimer != Q_NULLPTR) - { - retransmitTimer->stop(); - delete retransmitTimer; - } - - pingTimer = Q_NULLPTR; - idleTimer = Q_NULLPTR; - areYouThereTimer = Q_NULLPTR; - retransmitTimer = Q_NULLPTR; - -} - -// Base class! - -void udpBase::dataReceived(QByteArray r) -{ - if (r.length() < 0x10) - { - return; // Packet too small do to anything with? - } - - switch (r.length()) - { - case (CONTROL_SIZE): // Empty response used for simple comms and retransmit requests. - { - control_packet_t in = (control_packet_t)r.constData(); - if (in->type == 0x01 && in->len == 0x10) - { - // Single packet request - packetsLost++; - congestion = static_cast(packetsSent) / packetsLost * 100; - txBufferMutex.lock(); - auto match = txSeqBuf.find(in->seq); - if (match != txSeqBuf.end()) { - // Found matching entry? - // Send "untracked" as it has already been sent once. - // Don't constantly retransmit the same packet, give-up eventually - qDebug(logUdp()) << this->metaObject()->className() << ": Sending (single packet) retransmit of " << QString("0x%1").arg(match->seqNum, 0, 16); - match->retransmitCount++; - udpMutex.lock(); - udp->writeDatagram(match->data, radioIP, port); - udpMutex.unlock(); - } - else { - qDebug(logUdp()) << this->metaObject()->className() << ": Remote requested packet" - << QString("0x%1").arg(in->seq, 0, 16) << - "not found, have " << QString("0x%1").arg(txSeqBuf.firstKey(), 0, 16) << - "to" << QString("0x%1").arg(txSeqBuf.lastKey(), 0, 16); - } - txBufferMutex.unlock(); - } - if (in->type == 0x04) { - qInfo(logUdp()) << 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; - if (areYouThereTimer != Q_NULLPTR && areYouThereTimer->isActive()) { - // send ping packets every second - areYouThereTimer->stop(); - } - sendControl(false, 0x06, 0x01); // Send Are you ready - untracked. - } - else if (in->type == 0x06) - { - // Just get the seqnum and ignore the rest. - } - break; - } - case (PING_SIZE): // ping packet - { - ping_packet_t in = (ping_packet_t)r.constData(); - if (in->type == 0x07) - { - // It is a ping request/response - 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; - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - } - else if (in->reply == 0x01) { - if (in->seq == pingSendSeq) - { - // This is response to OUR request so increment counter - pingSendSeq++; - } - } - else { - qInfo(logUdp()) << this->metaObject()->className() << "Unhandled response to ping. I have never seen this! 0x10=" << r[16]; - } - - } - break; - } - default: - { - - // All packets "should" be added to the incoming buffer. - // First check that we haven't already received it. - - - } - break; - - } - - - // All packets except ping and retransmit requests should trigger this. - control_packet_t in = (control_packet_t)r.constData(); - - // This is a variable length retransmit request! - if (in->type == 0x01 && in->len != 0x10) - { - - for (quint16 i = 0x10; i < r.length(); i = i + 2) - { - quint16 seq = (quint8)r[i] | (quint8)r[i + 1] << 8; - auto match = txSeqBuf.find(seq); - if (match == txSeqBuf.end()) { - qDebug(logUdp()) << this->metaObject()->className() << ": Remote requested packet" - << QString("0x%1").arg(seq, 0, 16) << - "not found, have " << QString("0x%1").arg(txSeqBuf.firstKey(), 0, 16) << - "to" << QString("0x%1").arg(txSeqBuf.lastKey(), 0, 16); - // Just send idle packet. - sendControl(false, 0, seq); - } - else { - // Found matching entry? - // Send "untracked" as it has already been sent once. - qDebug(logUdp()) << this->metaObject()->className() << ": Sending (multiple packet) retransmit of " << QString("0x%1").arg(match->seqNum,0,16); - match->retransmitCount++; - udpMutex.lock(); - udp->writeDatagram(match->data, radioIP, port); - udpMutex.unlock(); - packetsLost++; - congestion = static_cast(packetsSent) / packetsLost * 100; - } - } - } - else if (in->len != PING_SIZE && in->type == 0x00 && in->seq != 0x00) - { - rxBufferMutex.lock(); - if (rxSeqBuf.isEmpty()) { - rxSeqBuf.insert(in->seq, QTime::currentTime()); - } - else - { - if (in->seq < rxSeqBuf.firstKey() || in->seq - rxSeqBuf.lastKey() > MAX_MISSING) - { - qInfo(logUdp()) << this->metaObject()->className() << "Large seq number gap detected, previous highest: " << - QString("0x%1").arg(rxSeqBuf.lastKey(),0,16) << " current: " << QString("0x%1").arg(in->seq,0,16); - //seqPrefix++; - // Looks like it has rolled over so clear buffer and start again. - rxSeqBuf.clear(); - // Add this packet to the incoming buffer - rxSeqBuf.insert(in->seq, QTime::currentTime()); - rxBufferMutex.unlock(); - missingMutex.lock(); - rxMissing.clear(); - missingMutex.unlock(); - return; - } - - if (!rxSeqBuf.contains(in->seq)) - { - // Add incoming packet to the received buffer and if it is in the missing buffer, remove it. - - if (in->seq > rxSeqBuf.lastKey() + 1) { - qInfo(logUdp()) << this->metaObject()->className() << "1 or more missing packets detected, previous: " << - QString("0x%1").arg(rxSeqBuf.lastKey(),0,16) << " current: " << QString("0x%1").arg(in->seq,0,16); - // We are likely missing packets then! - missingMutex.lock(); - //int missCounter = 0; - // Sanity check! - for (quint16 f = rxSeqBuf.lastKey() + 1; f <= in->seq; f++) - { - if (rxSeqBuf.size() > BUFSIZE) - { - rxSeqBuf.erase(rxSeqBuf.begin()); - } - rxSeqBuf.insert(f, QTime::currentTime()); - if (f != in->seq && !rxMissing.contains(f)) - { - rxMissing.insert(f, 0); - } - } - missingMutex.unlock(); - } - else { - if (rxSeqBuf.size() > BUFSIZE) - { - rxSeqBuf.erase(rxSeqBuf.begin()); - } - rxSeqBuf.insert(in->seq, QTime::currentTime()); - - } - } - else { - // This is probably one of our missing packets! - missingMutex.lock(); - auto s = rxMissing.find(in->seq); - if (s != rxMissing.end()) - { - qInfo(logUdp()) << this->metaObject()->className() << ": Missing SEQ has been received! " << QString("0x%1").arg(in->seq,0,16); - - s = rxMissing.erase(s); - } - missingMutex.unlock(); - - } - - } - rxBufferMutex.unlock(); - - } -} - -bool missing(quint16 i, quint16 j) { - return (i + 1 != j); -} - -void udpBase::sendRetransmitRequest() -{ - // 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. - if (rxMissing.isEmpty()) { - return; - } - else if (rxMissing.size() > MAX_MISSING) { - qInfo(logUdp()) << "Too many missing packets," << rxMissing.size() << "flushing all buffers"; - missingMutex.lock(); - rxMissing.clear(); - missingMutex.unlock(); - - rxBufferMutex.lock(); - rxSeqBuf.clear(); - rxBufferMutex.unlock(); - return; - } - - QByteArray missingSeqs; - - missingMutex.lock(); - auto it = rxMissing.begin(); - while (it != rxMissing.end()) - { - if (it.key() != 0x0) - { - if (it.value() < 4) - { - missingSeqs.append(it.key() & 0xff); - missingSeqs.append(it.key() >> 8 & 0xff); - missingSeqs.append(it.key() & 0xff); - missingSeqs.append(it.key() >> 8 & 0xff); - it.value()++; - it++; - } - else { - qInfo(logUdp()) << this->metaObject()->className() << ": No response for missing packet" << QString("0x%1").arg(it.key(), 0, 16) << "deleting"; - it = rxMissing.erase(it); - } - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": found empty key in missing buffer"; - it++; - } - } - missingMutex.unlock(); - - if (missingSeqs.length() != 0) - { - 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 = 0x01; - p.seq = 0x0000; - p.sentid = myId; - p.rcvdid = 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); - qInfo(logUdp()) << this->metaObject()->className() << ": sending request for missing packet : " << QString("0x%1").arg(p.seq,0,16); - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - } - else - { - qInfo(logUdp()) << this->metaObject()->className() << ": sending request for multiple missing packets : " << missingSeqs.toHex(':'); - missingMutex.lock(); - p.len = sizeof(p)+missingSeqs.size(); - missingSeqs.insert(0, p.packet, sizeof(p)); - missingMutex.unlock(); - - udpMutex.lock(); - udp->writeDatagram(missingSeqs, radioIP, port); - udpMutex.unlock(); - } - } - -} - - - -// Used to send idle and other "control" style messages -void udpBase::sendControl(bool tracked = true, quint8 type = 0, quint16 seq = 0) -{ - 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; - - if (!tracked) { - p.seq = seq; - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - } - else { - sendTrackedPacket(QByteArray::fromRawData((const char*)p.packet, sizeof(p))); - } - return; -} - -// Send periodic ping packets -void udpBase::sendPing() -{ - 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; - QTime now=QTime::currentTime(); - p.time = (quint32)now.msecsSinceStartOfDay(); - lastPingSentTime = QDateTime::currentDateTime(); - udpMutex.lock(); - udp->writeDatagram(QByteArray::fromRawData((const char*)p.packet, sizeof(p)), radioIP, port); - udpMutex.unlock(); - return; -} - - -void udpBase::sendTrackedPacket(QByteArray d) -{ - // As the radio can request retransmission of these packets, store them in a buffer - d[6] = sendSeq & 0xff; - d[7] = (sendSeq >> 8) & 0xff; - SEQBUFENTRY s; - s.seqNum = sendSeq; - s.timeSent = QTime::currentTime(); - s.retransmitCount = 0; - s.data = d; - if (txBufferMutex.tryLock(100)) - { - - if (sendSeq == 0) { - // We are either the first ever sent packet or have rolled-over so clear the buffer. - txSeqBuf.clear(); - congestion = 0; - } - if (txSeqBuf.size() > BUFSIZE) - { - txSeqBuf.erase(txSeqBuf.begin()); - } - txSeqBuf.insert(sendSeq, s); - - txBufferMutex.unlock(); - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": txBuffer mutex is locked"; - } - // Stop using purgeOldEntries() as it is likely slower than just removing the earliest packet. - //qInfo(logUdp()) << this->metaObject()->className() << "RX:" << rxSeqBuf.size() << "TX:" <writeDatagram(d, radioIP, port); - if (congestion>10) { // Poor quality connection? - udp->writeDatagram(d, radioIP, port); - if (congestion>20) // Even worse so send again. - udp->writeDatagram(d, radioIP, port); - } - if (idleTimer != Q_NULLPTR && idleTimer->isActive()) { - idleTimer->start(IDLE_PERIOD); // Reset idle counter if it's running - } - udpMutex.unlock(); - packetsSent++; - return; -} - - -/// -/// Once a packet has reached PURGE_SECONDS old (currently 10) then it is not likely to be any use. -/// -void udpBase::purgeOldEntries() -{ - // Erase old entries from the tx packet buffer - if (txBufferMutex.tryLock(100)) - { - if (!txSeqBuf.isEmpty()) - { - // Loop through the earliest items in the buffer and delete if older than PURGE_SECONDS - for (auto it = txSeqBuf.begin(); it != txSeqBuf.end();) { - if (it.value().timeSent.secsTo(QTime::currentTime()) > PURGE_SECONDS) { - txSeqBuf.erase(it++); - } - else { - break; - } - } - } - txBufferMutex.unlock(); - - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": txBuffer mutex is locked"; - } - - - - if (rxBufferMutex.tryLock(100)) - { - if (!rxSeqBuf.isEmpty()) { - // Loop through the earliest items in the buffer and delete if older than PURGE_SECONDS - for (auto it = rxSeqBuf.begin(); it != rxSeqBuf.end();) { - if (it.value().secsTo(QTime::currentTime()) > PURGE_SECONDS) { - rxSeqBuf.erase(it++); - } - else { - break; - } - } - } - rxBufferMutex.unlock(); - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": rxBuffer mutex is locked"; - } - - if (missingMutex.tryLock(100)) - { - // Erase old entries from the missing packets buffer - if (!rxMissing.isEmpty() && rxMissing.size() > 50) { - for (size_t i = 0; i < 25; ++i) { - rxMissing.erase(rxMissing.begin()); - } - } - missingMutex.unlock(); - } else { - qInfo(logUdp()) << this->metaObject()->className() << ": missingBuffer mutex is locked"; - } -} - -void udpBase::printHex(const QByteArray& pdata) -{ - printHex(pdata, false, true); -} - -void udpBase::printHex(const QByteArray& pdata, bool printVert, bool printHoriz) -{ - qDebug(logUdp()) << "---- Begin hex dump -----:"; - QString sdata("DATA: "); - QString index("INDEX: "); - QStringList strings; - - for (int i = 0; i < pdata.length(); i++) - { - strings << QString("[%1]: %2").arg(i, 8, 10, QChar('0')).arg((unsigned char)pdata[i], 2, 16, QChar('0')); - sdata.append(QString("%1 ").arg((unsigned char)pdata[i], 2, 16, QChar('0'))); - index.append(QString("%1 ").arg(i, 2, 10, QChar('0'))); - } - - if (printVert) - { - for (int i = 0; i < strings.length(); i++) - { - //sdata = QString(strings.at(i)); - qDebug(logUdp()) << strings.at(i); - } - } - - if (printHoriz) - { - qDebug(logUdp()) << index; - qDebug(logUdp()) << sdata; - } - qDebug(logUdp()) << "----- End hex dump -----"; -} - -/// -/// passcode function used to generate secure (ish) code -/// -/// -/// pointer to encoded username or password -void passcode(QString in, QByteArray& out) -{ - const quint8 sequence[] = - { - 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 - - }; - - QByteArray ba = in.toLocal8Bit(); - uchar* ascii = (uchar*)ba.constData(); - for (int i = 0; i < in.length() && i < 16; i++) - { - int p = ascii[i] + i; - if (p > 126) - { - p = 32 + p % 127; - } - out.append(sequence[p]); - } - return; -} - -/// -/// returns a QByteArray of a null terminated string -/// -/// -/// -/// -QByteArray parseNullTerminatedString(QByteArray c, int s) -{ - //QString res = ""; - QByteArray res; - for (int i = s; i < c.length(); i++) - { - if (c[i] != '\0') - { - res.append(c[i]); - } - else - { - break; - } - } - return res; -} -