diff --git a/src/Comms/UDPLink.cc b/src/Comms/UDPLink.cc index d70a9c71896..ec1700d33bd 100644 --- a/src/Comms/UDPLink.cc +++ b/src/Comms/UDPLink.cc @@ -8,455 +8,453 @@ ****************************************************************************/ #include "UDPLink.h" -#include "QGCApplication.h" -#include "SettingsManager.h" #include "AutoConnectSettings.h" #include "DeviceInfo.h" +#include "QGCApplication.h" +#include "QGCLoggingCategory.h" +#include "SettingsManager.h" -#include #include -#include -#include #include +#include +#include +#include #include -static bool is_ip(const QString& address) -{ - int a,b,c,d; - if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4 && strcmp("::1", address.toStdString().c_str())) { - return false; - } else { - return true; - } -} +QGC_LOGGING_CATEGORY(UDPLinkLog, "READY.comms.udplink") -static QString get_ip_address(const QString& address) -{ - if (is_ip(address)) { - return address; - } - // Need to look it up - QHostInfo info = QHostInfo::fromName(address); - if (info.error() == QHostInfo::NoError) { - QList hostAddresses = info.addresses(); - for (int i=0; i list, const QHostAddress& address, quint16 port) +static bool containsTarget(const QList> &list, const QHostAddress &address, quint16 port) { - for (int i=0; iaddress == address && target->port == port) { + for (const std::shared_ptr &target : list) { + if ((target->address == address) && (target->port == port)) { return true; } } + return false; } -UDPLink::UDPLink(SharedLinkConfigurationPtr& config) - : LinkInterface (config) - , _running (false) - , _socket (nullptr) - , _udpConfig (qobject_cast(config.get())) - , _connectState (false) -#if defined(QGC_ZEROCONF_ENABLED) - , _dnssServiceRef (nullptr) -#endif +UDPLink::UDPLink(SharedLinkConfigurationPtr &config, QObject *parent) + : LinkInterface(config, parent) + , _localAddresses(QNetworkInterface::allAddresses()) + , _udpConfig(qobject_cast(config.get())) + , _socket(new QUdpSocket(this)) { - if (!_udpConfig) { - qWarning() << "Internal error"; - } - auto allAddresses = QNetworkInterface::allAddresses(); - for (int i=0; ierrorString(); + emit communicationError(QStringLiteral("UDP Link Error"), QStringLiteral("Link: %1, %2.").arg(_udpConfig->name(), _socket->errorString())); + }, Qt::AutoConnection); + +#ifdef QT_DEBUG + (void) connect(_socket, &QUdpSocket::stateChanged, this, [](QUdpSocket::SocketState state) { + qCDebug(UDPLinkLog) << "UDP State Changed:" << state; + }, Qt::AutoConnection); +#endif } UDPLink::~UDPLink() { - disconnect(); - // Tell the thread to exit - _running = false; - // Clear client list - qDeleteAll(_sessionTargets); +#ifdef QGC_ZEROCONF_ENABLED + _deregisterZeroconf(); +#endif + + UDPLink::disconnect(); + _sessionTargets.clear(); - quit(); - // Wait for it to exit - wait(); - this->deleteLater(); + + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; } -void UDPLink::run() +bool UDPLink::isSecureConnection() { - if (_hardwareConnect()) { - exec(); - } - if (_socket) { - _deregisterZeroconf(); + return QGCDeviceInfo::isNetworkWired(); +} + +bool UDPLink::isConnected() const +{ + return (_socket->isValid() && ((_socket->state() == QAbstractSocket::SocketState::ConnectedState) || (_socket->state() == QAbstractSocket::SocketState::ConnectingState) || (_socket->state() == QAbstractSocket::SocketState::BoundState))); +} + +void UDPLink::disconnect() +{ + if (isConnected()) { + (void) _socket->leaveMulticastGroup(_multicastGroup); + (void) QObject::disconnect(_socket, &QUdpSocket::readyRead, this, &UDPLink::_readBytes); _socket->close(); } } -bool UDPLink::_isIpLocal(const QHostAddress& add) +bool UDPLink::_connect() { - // In simulation and testing setups the vehicle and the GCS can be - // running on the same host. This leads to packets arriving through - // the local network or the loopback adapter, which makes it look - // like the vehicle is connected through two different links, - // complicating routing. - // - // We detect this case and force all traffic to a simulated instance - // onto the local loopback interface. - // Run through all IPv4 interfaces and check if their canonical - // IP address in string representation matches the source IP address - // - // On Windows, this is a very expensive call only Redmond would know - // why. As such, we make it once and keep the list locally. If a new - // interface shows up after we start, it won't be on this list. - for (int i=0; i<_localAddresses.count(); i++) { - QHostAddress &address = _localAddresses[i]; - if (address == add) { - // This is a local address of the same host - return true; - } + if (isConnected()) { + return true; } + + if (_socket->bind(QHostAddress(QHostAddress::AnyIPv4), _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress)) { + (void) QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::_readBytes, Qt::AutoConnection); + (void) _socket->joinMulticastGroup(_multicastGroup); + +#ifdef QGC_ZEROCONF_ENABLED + _registerZeroconf(_udpConfig->localPort()); +#endif + + return true; + } + + emit communicationError(QStringLiteral("UDP Link Error"), QStringLiteral("Link: %1, %2.").arg(_udpConfig->name(), "Failed to Connect.")); return false; } +bool UDPLink::_isIpLocal(const QHostAddress &address) const +{ + return (address.isLoopback() || _localAddresses.contains(address)); +} + void UDPLink::_writeBytes(const QByteArray &data) { - if (!_socket) { + if (!_socket->isValid()) { + return; + } + + static const QString title = QStringLiteral("UDP Link Write Error"); + static const QString error = QStringLiteral("Link %1: %2."); + + if (!isConnected()) { + emit communicationError(title, error.arg(_udpConfig->name(), "Could Not Send Data - Link is Disconnected!")); return; } - emit bytesSent(this, data); QMutexLocker locker(&_sessionTargetsMutex); // Send to all manually targeted systems - for (int i=0; i<_udpConfig->targetHosts().count(); i++) { - UDPCLient* target = _udpConfig->targetHosts()[i]; - // Skip it if it's part of the session clients below - if(!contains_target(_sessionTargets, target->address, target->port)) { - _writeDataGram(data, target); + for (const std::shared_ptr &target : _udpConfig->targetHosts()) { + if (!_sessionTargets.contains(target)) { + if (_socket->writeDatagram(data, target->address, target->port) < 0) { + emit communicationError(title, error.arg(_udpConfig->name(), "Could Not Send Data - Write Failed!")); + } } } + // Send to all connected systems - for(UDPCLient* target: _sessionTargets) { - _writeDataGram(data, target); + for (const std::shared_ptr &target: _sessionTargets) { + if (_socket->writeDatagram(data, target->address, target->port) < 0) { + emit communicationError(title, error.arg(_udpConfig->name(), "Could Not Send Data - Write Failed!")); + } } + + locker.unlock(); + + emit bytesSent(this, data); } -void UDPLink::_writeDataGram(const QByteArray data, const UDPCLient* target) +void UDPLink::_readBytes() { - //qDebug() << "UDP Out" << target->address << target->port; - if(_socket->writeDatagram(data, target->address, target->port) < 0) { - qWarning() << "Error writing to" << target->address << target->port; + if (!_socket->isValid()) { + return; } -} -void UDPLink::readBytes() -{ - if (!_socket) { + static const QString title = QStringLiteral("UDP Link Read Error"); + static const QString error = QStringLiteral("Link %1: %2."); + + if (!isConnected()) { + emit communicationError(title, error.arg(_udpConfig->name(), QStringLiteral("Could Not Read Data - link is Disconnected!"))); return; } - QByteArray databuffer; - while (_socket->hasPendingDatagrams()) - { - QByteArray datagram; - datagram.resize(_socket->pendingDatagramSize()); - QHostAddress sender; - quint16 senderPort; - // If the other end is reset then it will still report data available, - // but will fail on the readDatagram call - qint64 slen = _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); - if (slen == -1) { - break; - } - databuffer.append(datagram); - //-- Wait a bit before sending it over - if (databuffer.size() > 10 * 1024) { - emit bytesReceived(this, databuffer); - databuffer.clear(); + + const qint64 byteCount = _socket->pendingDatagramSize(); + if (byteCount <= 0) { + emit communicationError(title, error.arg(_udpConfig->name(), QStringLiteral("Could Not Read Data - No Data Available!"))); + return; + } + + QByteArray buffer; + QElapsedTimer timer; + timer.start(); + while (_socket->hasPendingDatagrams()) { + const QNetworkDatagram datagramIn = _socket->receiveDatagram(); + if (datagramIn.isNull() || datagramIn.data().isEmpty()) { + continue; } - // TODO: This doesn't validade the sender. Anything sending UDP packets to this port gets - // added to the list and will start receiving datagrams from here. Even a port scanner - // would trigger this. - // Add host to broadcast list if not yet present, or update its port - QHostAddress asender = sender; - if(_isIpLocal(sender)) { - asender = QHostAddress(QString("127.0.0.1")); + + (void) buffer.append(datagramIn.data()); + + if (buffer.size() > (10 * 1024) || (timer.elapsed() > 50)) { + emit bytesReceived(this, buffer); + buffer.clear(); + (void) timer.restart(); } + + const QHostAddress senderAddress = _isIpLocal(datagramIn.senderAddress()) ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress(); + QMutexLocker locker(&_sessionTargetsMutex); - if (!contains_target(_sessionTargets, asender, senderPort)) { - qDebug() << "Adding target" << asender << senderPort; - UDPCLient* target = new UDPCLient(asender, senderPort); - _sessionTargets.append(target); + if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) { + qCDebug(UDPLinkLog) << Q_FUNC_INFO << "Adding target:" << senderAddress << datagramIn.senderPort(); + (void) _sessionTargets.append(std::make_shared(senderAddress, datagramIn.senderPort())); } locker.unlock(); } - //-- Send whatever is left - if (databuffer.size()) { - emit bytesReceived(this, databuffer); - } -} -void UDPLink::disconnect(void) -{ - _running = false; - quit(); - wait(); - if (_socket) { - // This prevents stale signal from calling the link after it has been deleted - QObject::disconnect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes); - // Make sure delete happen on correct thread - _socket->deleteLater(); - _socket = nullptr; - emit disconnected(); + if (buffer.isEmpty()) { + emit communicationError(title, error.arg(_udpConfig->name(), "Could Not Read Data - Read Failed!")); + return; } - _connectState = false; + + emit bytesReceived(this, buffer); } -bool UDPLink::_connect(void) + +#ifdef QGC_ZEROCONF_ENABLED +void UDPLink::_zeroconfRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) { - if (this->isRunning() || _running) { - _running = false; - quit(); - wait(); + Q_UNUSED(sdRef) + Q_UNUSED(flags) + Q_UNUSED(name) + Q_UNUSED(regtype) + Q_UNUSED(domain) + qCDebug(UDPLinkLog) << Q_FUNC_INFO; + + static const QString title = QStringLiteral("UDP Link Zeroconf Error"); + static const QString error = QStringLiteral("Link %1: %2."); + + UDPLink* const udplink = static_cast(context); + if (errorCode != kDNSServiceErr_NoError) { + emit udplink->communicationError(title, error.arg(udplink->linkConfiguration->name(), QStringLiteral("Zeroconf Register Error!"))); } - _running = true; - start(NormalPriority); - return true; } -bool UDPLink::_hardwareConnect() +void UDPLink::_registerZeroconf(uint16_t port) { - if (_socket) { - delete _socket; - _socket = nullptr; - } - QHostAddress host = QHostAddress::AnyIPv4; - _socket = new QUdpSocket(this); - _socket->setProxy(QNetworkProxy::NoProxy); - _connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress); - if (_connectState) { - _socket->joinMulticastGroup(QHostAddress("224.0.0.1")); - //-- Make sure we have a large enough IO buffers -#ifdef __mobile__ - _socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 64 * 1024); - _socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 128 * 1024); -#else - _socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 256 * 1024); - _socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 512 * 1024); -#endif - _registerZeroconf(_udpConfig->localPort(), kZeroconfRegistration); - QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes); - emit connected(); - } else { - emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString())); + static constexpr const char *regType = "_qgroundcontrol._udp"; + + if (_dnssServiceRef) { + qCWarning(UDPLinkLog) << "Already registered zeroconf"; + return; } - return _connectState; -} -bool UDPLink::isConnected() const -{ - return _connectState; -} +#if (Q_BYTE_ORDER == Q_LITTLE_ENDIAN) + port = htons(port); // qToBigEndian +#endif -void UDPLink::_registerZeroconf(uint16_t port, const std::string ®Type) -{ -#if defined(QGC_ZEROCONF_ENABLED) - DNSServiceErrorType result = DNSServiceRegister(&_dnssServiceRef, 0, 0, 0, - regType.c_str(), - NULL, - NULL, - htons(port), - 0, - NULL, - NULL, - NULL); - if (result != kDNSServiceErr_NoError) - { - emit communicationError(tr("UDP Link Error"), tr("Error registering Zeroconf")); + const DNSServiceErrorType result = DNSServiceRegister( + &_dnssServiceRef, + 0, + 0, + 0, + regType, + NULL, + NULL, + port, + 0, + NULL, + &UDPLink::_zeroconfRegisterCallback, + this + ); + + static const QString title = QStringLiteral("UDP Link Zeroconf Error"); + static const QString error = QStringLiteral("Link %1: %2."); + + if (result != kDNSServiceErr_NoError) { _dnssServiceRef = NULL; + emit communicationError(title, error.arg(_udpConfig->name(), QStringLiteral("Error Registering Zeroconf"))); + return; } -#else - Q_UNUSED(port); - Q_UNUSED(regType); -#endif + + const int sockfd = DNSServiceRefSockFD(_dnssServiceRef); + if (sockfd == -1) { + emit communicationError(title, error.arg(_udpConfig->name(), QStringLiteral("Invalid sockfd"))); + return; + } + + _socketNotifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, this); + (void) connect(_socketNotifier, &QSocketNotifier::activated, this, [this]() { + const DNSServiceErrorType error = DNSServiceProcessResult(_dnssServiceRef); + if (err != kDNSServiceErr_NoError) { + emit communicationError(title, error.arg(_udpConfig->name(), error)); + } + _socketNotifier->deleteLater(); + }); } void UDPLink::_deregisterZeroconf() { -#if defined(QGC_ZEROCONF_ENABLED) - if (_dnssServiceRef) - { + if (_dnssServiceRef) { DNSServiceRefDeallocate(_dnssServiceRef); _dnssServiceRef = NULL; } -#endif } +#endif // QGC_ZEROCONF_ENABLED -bool UDPLink::isSecureConnection() -{ - return QGCDeviceInfo::isNetworkWired(); -} - -//-------------------------------------------------------------------------- -//-- UDPConfiguration +//------------------------------------------------------------------------------ -UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name) +UDPConfiguration::UDPConfiguration(const QString &name, QObject *parent) + : LinkConfiguration(name, parent) { - AutoConnectSettings* settings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings(); + AutoConnectSettings* const settings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings(); _localPort = settings->udpListenPort()->rawValue().toInt(); - QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString(); + + const QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString(); if (!targetHostIP.isEmpty()) { - addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toUInt()); + const quint16 targetHostPort = settings->udpTargetHostPort()->rawValue().toUInt(); + addHost(targetHostIP, targetHostPort); } } -UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source) +UDPConfiguration::UDPConfiguration(UDPConfiguration *copy, QObject *parent) + : LinkConfiguration(copy, parent) { - _copyFrom(source); + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; + + Q_CHECK_PTR(copy); + + UDPConfiguration::copyFrom(copy); } UDPConfiguration::~UDPConfiguration() { - _clearTargetHosts(); + _targetHosts.clear(); + + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; } void UDPConfiguration::copyFrom(LinkConfiguration *source) { + Q_CHECK_PTR(source); LinkConfiguration::copyFrom(source); - _copyFrom(source); -} -void UDPConfiguration::_copyFrom(LinkConfiguration *source) -{ - auto* usource = qobject_cast(source); - if (usource) { - _localPort = usource->localPort(); - _clearTargetHosts(); - for (int i=0; itargetHosts().count(); i++) { - UDPCLient* target = usource->targetHosts()[i]; - if(!contains_target(_targetHosts, target->address, target->port)) { - UDPCLient* newTarget = new UDPCLient(target); - _targetHosts.append(newTarget); - _updateHostList(); - } - } - } else { - qWarning() << "Internal error"; - } -} + const UDPConfiguration* const udpSource = qobject_cast(source); + Q_CHECK_PTR(udpSource); -void UDPConfiguration::_clearTargetHosts() -{ - qDeleteAll(_targetHosts); + setLocalPort(udpSource->localPort()); _targetHosts.clear(); + + for (const std::shared_ptr &target : udpSource->targetHosts()) { + if (!_targetHosts.contains(target)) { + (void) _targetHosts.append(std::make_shared(target.get())); + _updateHostList(); + } + } } -/** - * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551 - */ -void UDPConfiguration::addHost(const QString host) +void UDPConfiguration::addHost(const QString &host) { - // Handle x.x.x.x:p if (host.contains(":")) { - addHost(host.split(":").first(), host.split(":").last().toUInt()); + const QStringList hostInfo = host.split(":"); + addHost(hostInfo.constFirst(), hostInfo.constLast().toUInt()); } else { - // If no port, use default addHost(host, _localPort); } } -void UDPConfiguration::addHost(const QString& host, quint16 port) +void UDPConfiguration::addHost(const QString &host, quint16 port) { - QString ipAdd = get_ip_address(host); + const QString ipAdd = _getIpAddress(host); if (ipAdd.isEmpty()) { - qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port; - } else { - QHostAddress address(ipAdd); - if(!contains_target(_targetHosts, address, port)) { - UDPCLient* newTarget = new UDPCLient(address, port); - _targetHosts.append(newTarget); - _updateHostList(); - } + qCWarning(UDPLinkLog) << Q_FUNC_INFO << "Could not resolve host:" << host << "port:" << port; + return; + } + + const QHostAddress address(ipAdd); + if (!containsTarget(_targetHosts, address, port)) { + (void) _targetHosts.append(std::make_shared(address, port)); + _updateHostList(); } } -void UDPConfiguration::removeHost(const QString host) +void UDPConfiguration::removeHost(const QString &host) { if (host.contains(":")) { - QHostAddress address = QHostAddress(get_ip_address(host.split(":").first())); - quint16 port = host.split(":").last().toUInt(); - for (int i=0; i<_targetHosts.size(); i++) { - UDPCLient* target = _targetHosts.at(i); - if(target->address == address && target->port == port) { - _targetHosts.removeAt(i); - delete target; + const QHostAddress address = QHostAddress(_getIpAddress(host.split(":").constFirst())); + const quint16 port = host.split(":").constLast().toUInt(); + + QMutableListIterator> it(_targetHosts); + while (it.hasNext()) { + std::shared_ptr target = it.next(); + if ((target->address == address) && (target->port == port)) { + target.reset(); + it.remove(); _updateHostList(); return; } } } - qWarning() << "UDP:" << "Could not remove unknown host:" << host; + _updateHostList(); -} -void UDPConfiguration::setLocalPort(quint16 port) -{ - _localPort = port; + qCWarning(UDPLinkLog) << Q_FUNC_INFO << "Could not remove unknown host:" << host; } -void UDPConfiguration::saveSettings(QSettings& settings, const QString& root) +void UDPConfiguration::loadSettings(QSettings &settings, const QString &root) { settings.beginGroup(root); - settings.setValue("port", (int)_localPort); - settings.setValue("hostCount", _targetHosts.size()); - for (int i=0; i<_targetHosts.size(); i++) { - UDPCLient* target = _targetHosts.at(i); - QString hkey = QString("host%1").arg(i); - settings.setValue(hkey, target->address.toString()); - QString pkey = QString("port%1").arg(i); - settings.setValue(pkey, target->port); + + AutoConnectSettings* const autoConnectSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings(); + setLocalPort(static_cast(settings.value("port", autoConnectSettings->udpListenPort()->rawValue().toUInt()).toUInt())); + + _targetHosts.clear(); + const qsizetype hostCount = settings.value("hostCount", 0).toUInt(); + for (qsizetype i = 0; i < hostCount; i++) { + const QString hkey = QStringLiteral("host%1").arg(i); + const QString pkey = QStringLiteral("port%1").arg(i); + if(settings.contains(hkey) && settings.contains(pkey)) { + addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt()); + } } + _updateHostList(); + settings.endGroup(); } -void UDPConfiguration::loadSettings(QSettings& settings, const QString& root) +void UDPConfiguration::saveSettings(QSettings &settings, const QString &root) { - AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings(); - _clearTargetHosts(); settings.beginGroup(root); - _localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt(); - int hostCount = settings.value("hostCount", 0).toInt(); - for (int i=0; i target = _targetHosts.at(i); + const QString hkey = QString("host%1").arg(i); + settings.setValue(hkey, target->address.toString()); + const QString pkey = QString("port%1").arg(i); + settings.setValue(pkey, target->port); } + settings.endGroup(); - _updateHostList(); } void UDPConfiguration::_updateHostList() { _hostList.clear(); - for (int i=0; i<_targetHosts.size(); i++) { - UDPCLient* target = _targetHosts.at(i); - QString host = QString("%1").arg(target->address.toString()) + ":" + QString("%1").arg(target->port); - _hostList << host; + for (const std::shared_ptr &target : _targetHosts) { + const QString host = target->address.toString() + ":" + QString::number(target->port); + (void) _hostList.append(host); } + emit hostListChanged(); } + +QString UDPConfiguration::_getIpAddress(const QString &address) +{ + const QHostAddress host(address); + if (!host.isNull()) { + return address; + } + + const QHostInfo info = QHostInfo::fromName(address); + if (info.error() == QHostInfo::NoError) { + const QList hostAddresses = info.addresses(); + for (const QHostAddress &hostAddress : hostAddresses) { + if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + return hostAddress.toString(); + } + } + } + + return QString(); +} diff --git a/src/Comms/UDPLink.h b/src/Comms/UDPLink.h index 71dffdea561..382eca62b11 100644 --- a/src/Comms/UDPLink.h +++ b/src/Comms/UDPLink.h @@ -9,130 +9,139 @@ #pragma once -#include "LinkConfiguration.h" -#include "LinkInterface.h" - -#include +#include #include +#include #include -#include +#include #include -#if defined(QGC_ZEROCONF_ENABLED) +#ifdef QGC_ZEROCONF_ENABLED +#ifdef Q_OS_WIN +#define WIN32_LEAN_AND_MEAN +#endif #include #endif -class LinkManager; +#include "LinkConfiguration.h" +#include "LinkInterface.h" + +Q_DECLARE_LOGGING_CATEGORY(UDPLinkLog) + class QUdpSocket; +class QSocketNotifier; -class UDPCLient { -public: - UDPCLient(const QHostAddress& address_, quint16 port_) - : address(address_) - , port(port_) +struct UDPClient +{ + UDPClient(const QHostAddress &address, quint16 port) + : address(address) + , port(port) {} - UDPCLient(const UDPCLient* other) + + explicit UDPClient(const UDPClient *other) : address(other->address) , port(other->port) {} - QHostAddress address; - quint16 port; + + bool operator==(const UDPClient &other) const + { + return ((address == other.address) && (port == other.port)); + } + + UDPClient &operator=(const UDPClient &other) + { + address = other.address; + port = other.port; + + return *this; + } + + QHostAddress address; + quint16 port = 0; }; +/*===========================================================================*/ + class UDPConfiguration : public LinkConfiguration { Q_OBJECT -public: - - Q_PROPERTY(quint16 localPort READ localPort WRITE setLocalPort NOTIFY localPortChanged) - Q_PROPERTY(QStringList hostList READ hostList NOTIFY hostListChanged) - UDPConfiguration(const QString& name); - UDPConfiguration(UDPConfiguration* source); - ~UDPConfiguration(); + Q_PROPERTY(QStringList hostList READ hostList NOTIFY hostListChanged) + Q_PROPERTY(quint16 localPort READ localPort WRITE setLocalPort NOTIFY localPortChanged) - quint16 localPort () const{ return _localPort; } - - /// @param[in] host Host name in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551 - Q_INVOKABLE void addHost (const QString host); +public: + explicit UDPConfiguration(const QString &name, QObject *parent = nullptr); + explicit UDPConfiguration(UDPConfiguration *copy, QObject *parent = nullptr); + virtual ~UDPConfiguration(); - /// @param[in] host Host name, e.g. localhost or 192.168.1.1 - /// @param[in] port Port number - void addHost (const QString& host, quint16 port); + Q_INVOKABLE void addHost(const QString &host); + Q_INVOKABLE void addHost(const QString &host, quint16 port); + Q_INVOKABLE void removeHost(const QString &host); - /// @param[in] host Host name, e.g. localhost or 192.168.1.1 - Q_INVOKABLE void removeHost (const QString host); + LinkType type() override { return LinkConfiguration::TypeUdp; } + void copyFrom(LinkConfiguration *source) override; - void setLocalPort(quint16 port); - QStringList hostList (void) { return _hostList; } - const QList targetHosts (void) { return _targetHosts; } + quint16 localPort() const { return _localPort; } + void setLocalPort(quint16 port) { if (port != _localPort) { _localPort = port; emit localPortChanged(); } } + QStringList hostList() const { return _hostList; } + QList> targetHosts() const { return _targetHosts; } - /// LinkConfiguration overrides - LinkType type (void) override { return LinkConfiguration::TypeUdp; } - void copyFrom (LinkConfiguration* source) override; - void loadSettings (QSettings& settings, const QString& root) override; - void saveSettings (QSettings& settings, const QString& root) override; - QString settingsURL (void) override { return "UdpSettings.qml"; } - QString settingsTitle (void) override { return tr("UDP Link Settings"); } + void loadSettings(QSettings &settings, const QString &root) override; + void saveSettings(QSettings &settings, const QString &root) override; + QString settingsURL() override { return QStringLiteral("UdpSettings.qml"); } + QString settingsTitle() override { return QStringLiteral("UDP Link Settings"); } signals: - void localPortChanged (void); - void hostListChanged (void); + void hostListChanged(); + void localPortChanged(); private: - void _updateHostList (void); - void _clearTargetHosts (void); - void _copyFrom (LinkConfiguration *source); + void _updateHostList(); - QList _targetHosts; - QStringList _hostList; - quint16 _localPort; + static QString _getIpAddress(const QString &address); + + QStringList _hostList; + quint16 _localPort = 0; + QList> _targetHosts; }; +/*===========================================================================*/ + class UDPLink : public LinkInterface { Q_OBJECT public: - UDPLink(SharedLinkConfigurationPtr& config); + explicit UDPLink(SharedLinkConfigurationPtr &config, QObject *parent = nullptr); virtual ~UDPLink(); - // LinkInterface overrides - bool isConnected (void) const override; - void disconnect (void) override; - bool isSecureConnection (void) override; - - // QThread overrides - void run(void) override; - -public slots: - void readBytes(void); + void run() override {}; + bool isConnected() const override; + void disconnect() override; + bool isSecureConnection() override; private slots: - // LinkInterface overrides void _writeBytes(const QByteArray &data) override; + void _readBytes(); private: + bool _connect() override; + bool _isIpLocal(const QHostAddress &address) const; - // LinkInterface overrides - bool _connect(void) override; - - bool _isIpLocal (const QHostAddress& add); - bool _hardwareConnect (void); - void _registerZeroconf (uint16_t port, const std::string& regType); - void _deregisterZeroconf(void); - void _writeDataGram (const QByteArray data, const UDPCLient* target); - - bool _running; - QUdpSocket* _socket; - UDPConfiguration* _udpConfig; - bool _connectState; - QList _sessionTargets; - QMutex _sessionTargetsMutex; - QList _localAddresses; -#if defined(QGC_ZEROCONF_ENABLED) - DNSServiceRef _dnssServiceRef; -#endif + QMutex _sessionTargetsMutex; + QList> _sessionTargets; + const QList _localAddresses; + const UDPConfiguration *_udpConfig = nullptr; + QUdpSocket *_socket = nullptr; + + static const QHostAddress _multicastGroup; - static constexpr const char* kZeroconfRegistration = "_qgroundcontrol._udp"; +#ifdef QGC_ZEROCONF_ENABLED + void _registerZeroconf(uint16_t port); + void _deregisterZeroconf(); + static void _zeroconfRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context); + + DNSServiceRef _dnssServiceRef = nullptr; + QSocketNotifier *_socketNotifier = nullptr; +#endif };