7#include <QtCore/QMutexLocker>
8#include <QtCore/QThread>
9#include <QtNetwork/QHostInfo>
10#include <QtNetwork/QNetworkDatagram>
11#include <QtNetwork/QNetworkInterface>
12#include <QtNetwork/QNetworkProxy>
13#include <QtNetwork/QUdpSocket>
18 constexpr int BUFFER_TRIGGER_SIZE = 10 * 1024;
19 constexpr int RECEIVE_TIME_LIMIT_MS = 50;
21 bool containsTarget(
const QList<std::shared_ptr<UDPClient>> &list,
const QHostAddress &address, quint16 port)
23 for (
const std::shared_ptr<UDPClient> &target : list) {
24 if ((target->address == address) && (target->port == port)) {
35UDPConfiguration::UDPConfiguration(
const QString &name, QObject *parent)
40UDPConfiguration::UDPConfiguration(
const UDPConfiguration *source, QObject *parent)
43 qCDebug(UDPLinkLog) <<
this;
45 UDPConfiguration::copyFrom(source);
48UDPConfiguration::~UDPConfiguration()
52 qCDebug(UDPLinkLog) <<
this;
55void UDPConfiguration::setAutoConnect(
bool autoc)
57 if (isAutoConnect() != autoc) {
59 const QString targetHostIP = settings->
udpTargetHostIP()->rawValue().toString();
60 const quint16 targetHostPort = settings->
udpTargetHostPort()->rawValue().toUInt();
63 if (!targetHostIP.isEmpty()) {
64 addHost(targetHostIP, targetHostPort);
68 if (!targetHostIP.isEmpty()) {
69 removeHost(targetHostIP, targetHostPort);
72 LinkConfiguration::setAutoConnect(autoc);
78 LinkConfiguration::copyFrom(source);
80 const UDPConfiguration *udpSource = qobject_cast<const UDPConfiguration*>(source);
82 setLocalPort(udpSource->localPort());
85 for (
const std::shared_ptr<UDPClient> &target : udpSource->targetHosts()) {
86 if (!containsTarget(_targetHosts, target->address, target->port)) {
87 _targetHosts.append(std::make_shared<UDPClient>(target.get()));
93void UDPConfiguration::loadSettings(QSettings &settings,
const QString &root)
95 settings.beginGroup(root);
97 setLocalPort(
static_cast<quint16
>(settings.value(
"port", SettingsManager::instance()->autoConnectSettings()->udpListenPort()->rawValue().toUInt()).toUInt()));
100 const qsizetype hostCount = settings.value(
"hostCount", 0).toUInt();
101 for (qsizetype i = 0; i < hostCount; i++) {
102 const QString hkey = QStringLiteral(
"host%1").arg(i);
103 const QString pkey = QStringLiteral(
"port%1").arg(i);
104 if (settings.contains(hkey) && settings.contains(pkey)) {
105 addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
114void UDPConfiguration::saveSettings(QSettings &settings,
const QString &root)
const
116 settings.beginGroup(root);
118 settings.setValue(QStringLiteral(
"hostCount"), _targetHosts.size());
119 settings.setValue(QStringLiteral(
"port"), _localPort);
121 for (qsizetype i = 0; i < _targetHosts.size(); i++) {
122 const std::shared_ptr<UDPClient> target = _targetHosts.at(i);
123 const QString hkey = QStringLiteral(
"host%1").arg(i);
124 settings.setValue(hkey, target->address.toString());
125 const QString pkey = QStringLiteral(
"port%1").arg(i);
126 settings.setValue(pkey, target->port);
132void UDPConfiguration::addHost(
const QString &host)
134 if (host.contains(
":")) {
135 const QStringList hostInfo = host.split(
":");
136 if (hostInfo.size() != 2) {
137 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
141 const QString address = hostInfo.constFirst();
142 const quint16 port = hostInfo.constLast().toUInt();
144 addHost(address, port);
146 addHost(host, _localPort);
150void UDPConfiguration::addHost(
const QString &host, quint16 port)
152 const QString ipAdd = _getIpAddress(host);
153 if (ipAdd.isEmpty()) {
154 qCWarning(UDPLinkLog) <<
"Could not resolve host:" << host <<
"port:" << port;
158 const QHostAddress address(ipAdd);
159 if (!containsTarget(_targetHosts, address, port)) {
160 _targetHosts.append(std::make_shared<UDPClient>(address, port));
165void UDPConfiguration::removeHost(
const QString &host)
167 if (host.contains(
":")) {
168 const QStringList hostInfo = host.split(
":");
169 if (hostInfo.size() != 2) {
170 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
174 const QHostAddress address = QHostAddress(_getIpAddress(hostInfo.constFirst()));
175 const quint16 port = hostInfo.constLast().toUInt();
177 if (!containsTarget(_targetHosts, address, port)) {
178 qCWarning(UDPLinkLog) <<
"Could not remove unknown host:" << host <<
"port:" << port;
182 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
183 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
184 if (target->address == address && target->port == port) {
185 _targetHosts.removeAt(i);
191 removeHost(host, _localPort);
195void UDPConfiguration::removeHost(
const QString &host, quint16 port)
197 const QString ipAdd = _getIpAddress(host);
198 if (ipAdd.isEmpty()) {
199 qCWarning(UDPLinkLog) <<
"Could not resolve host:" << host <<
"port:" << port;
203 const QHostAddress address(ipAdd);
204 if (!containsTarget(_targetHosts, address, port)) {
205 qCWarning(UDPLinkLog) <<
"Could not remove unknown host:" << host <<
"port:" << port;
209 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
210 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
211 if (target->address == address && target->port == port) {
212 _targetHosts.removeAt(i);
219void UDPConfiguration::_updateHostList()
222 for (
const std::shared_ptr<UDPClient> &target : _targetHosts) {
223 const QString host = target->address.toString() +
":" + QString::number(target->port);
224 _hostList.append(host);
230QString UDPConfiguration::_getIpAddress(
const QString &address)
232 const QHostAddress host(address);
233 if (!host.isNull()) {
237 const QHostInfo info = QHostInfo::fromName(address);
238 if (info.error() != QHostInfo::NoError) {
242 const QList<QHostAddress> hostAddresses = info.addresses();
243 for (
const QHostAddress &hostAddress : hostAddresses) {
244 if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
245 return hostAddress.toString();
254const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral(
"224.0.0.1"));
260 qCDebug(UDPLinkLog) <<
this;
267 qCDebug(UDPLinkLog) <<
this;
272 return (_socket && _socket->isValid() && _isConnected);
278 _socket =
new QUdpSocket(
this);
281 const QList<QHostAddress> localAddresses = QNetworkInterface::allAddresses();
282 _localAddresses = QSet(localAddresses.constBegin(), localAddresses.constEnd());
284 _socket->setProxy(QNetworkProxy::NoProxy);
286 (void) connect(_socket, &QUdpSocket::connected,
this, &UDPWorker::_onSocketConnected);
287 (void) connect(_socket, &QUdpSocket::disconnected,
this, &UDPWorker::_onSocketDisconnected);
288 (void) connect(_socket, &QUdpSocket::readyRead,
this, &UDPWorker::_onSocketReadyRead);
289 (void) connect(_socket, &QUdpSocket::errorOccurred,
this, &UDPWorker::_onSocketErrorOccurred);
290 (void) connect(_socket, &QUdpSocket::stateChanged,
this, [
this](QUdpSocket::SocketState state) {
291 qCDebug(UDPLinkLog) <<
"UDP State Changed:" << state;
293 case QAbstractSocket::BoundState:
294 _onSocketConnected();
296 case QAbstractSocket::ClosingState:
297 case QAbstractSocket::UnconnectedState:
298 _onSocketDisconnected();
305 if (UDPLinkLog().isDebugEnabled()) {
308 (void) QObject::connect(_socket, &QUdpSocket::hostFound,
this, []() {
309 qCDebug(UDPLinkLog) <<
"UDP Host Found";
317 qCWarning(UDPLinkLog) <<
"Already connected to" << _udpConfig->localPort();
321 _errorEmitted =
false;
323 qCDebug(UDPLinkLog) <<
"Attempting to bind to port:" << _udpConfig->localPort();
324 const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress);
326 qCWarning(UDPLinkLog) <<
"Failed to bind UDP socket to port" << _udpConfig->localPort();
328 if (!_errorEmitted) {
330 _errorEmitted =
true;
341 qCDebug(UDPLinkLog) <<
"Attempting to join multicast group:" << _multicastGroup.toString();
342 const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup);
344 qCWarning(UDPLinkLog) <<
"Failed to join multicast group" << _multicastGroup.toString();
347#ifdef QGC_ZEROCONF_ENABLED
348 _registerZeroconf(_udpConfig->localPort());
354#ifdef QGC_ZEROCONF_ENABLED
355 _deregisterZeroconf();
359 qCDebug(UDPLinkLog) <<
"Already disconnected";
363 qCDebug(UDPLinkLog) <<
"Disconnecting UDP link";
365 (void) _socket->leaveMulticastGroup(_multicastGroup);
368 _sessionTargets.clear();
374 emit
errorOccurred(tr(
"Could Not Send Data - Link is Disconnected!"));
378 QMutexLocker locker(&_sessionTargetsMutex);
381 for (
const std::shared_ptr<UDPClient> &target : _udpConfig->targetHosts()) {
382 if (!containsTarget(_sessionTargets, target->address, target->port)) {
383 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
384 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
390 for (
const std::shared_ptr<UDPClient> &target: _sessionTargets) {
391 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
392 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
401void UDPWorker::_onSocketConnected()
403 qCDebug(UDPLinkLog) <<
"UDP connected to" << _udpConfig->localPort();
405 _errorEmitted =
false;
409void UDPWorker::_onSocketDisconnected()
411 qCDebug(UDPLinkLog) <<
"UDP disconnected from" << _udpConfig->localPort();
412 _isConnected =
false;
413 _errorEmitted =
false;
417void UDPWorker::_onSocketReadyRead()
420 emit
errorOccurred(tr(
"Could Not Read Data - Link is Disconnected!"));
424 const qint64 byteCount = _socket->pendingDatagramSize();
425 if (byteCount <= 0) {
426 emit
errorOccurred(tr(
"Could Not Read Data - No Data Available!"));
431 buffer.reserve(BUFFER_TRIGGER_SIZE);
434 bool received =
false;
435 while (_socket->hasPendingDatagrams()) {
436 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
437 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
441 (void) buffer.append(datagramIn.data());
443 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
447 (void) timer.restart();
450 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
451 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
453 QMutexLocker locker(&_sessionTargetsMutex);
454 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
455 qCDebug(UDPLinkLog) <<
"UDP Adding target:" << senderAddress << datagramIn.senderPort();
456 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
461 if (!received && buffer.isEmpty()) {
462 qCWarning(UDPLinkLog) <<
"No Data Available to Read!";
469void UDPWorker::_onSocketBytesWritten(qint64 bytes)
471 qCDebug(UDPLinkLog) <<
"Wrote" << bytes <<
"bytes";
474void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError
error)
476 const QString
errorString = _socket->errorString();
477 qCWarning(UDPLinkLog) <<
"UDP Link error:" <<
error << _socket->errorString();
479 if (!_errorEmitted) {
481 _errorEmitted =
true;
485#ifdef QGC_ZEROCONF_ENABLED
486void UDPWorker::_zeroconfRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name,
const char *regtype,
const char *domain,
void *context)
488 Q_UNUSED(sdRef); Q_UNUSED(flags); Q_UNUSED(name); Q_UNUSED(regtype); Q_UNUSED(domain);
491 if (errorCode != kDNSServiceErr_NoError) {
492 emit worker->
errorOccurred(tr(
"Zeroconf Register Error: %1").arg(errorCode));
496void UDPWorker::_registerZeroconf(uint16_t port)
498 static constexpr const char *regType =
"_qgroundcontrol._udp";
500 if (_dnssServiceRef) {
501 qCWarning(UDPLinkLog) <<
"Already registered zeroconf";
505 const DNSServiceErrorType result = DNSServiceRegister(
516 &UDPWorker::_zeroconfRegisterCallback,
520 if (result != kDNSServiceErr_NoError) {
521 _dnssServiceRef = NULL;
522 emit
errorOccurred(tr(
"Error Registering Zeroconf: %1").arg(result));
526 const int sockfd = DNSServiceRefSockFD(_dnssServiceRef);
532 QSocketNotifier *
const socketNotifier =
new QSocketNotifier(sockfd, QSocketNotifier::Read,
this);
533 (void) connect(socketNotifier, &QSocketNotifier::activated,
this, [
this, socketNotifier]() {
534 const DNSServiceErrorType
error = DNSServiceProcessResult(_dnssServiceRef);
535 if (
error != kDNSServiceErr_NoError) {
538 socketNotifier->deleteLater();
542void UDPWorker::_deregisterZeroconf()
544 if (_dnssServiceRef) {
545 DNSServiceRefDeallocate(_dnssServiceRef);
546 _dnssServiceRef = NULL;
557 , _workerThread(new QThread(this))
559 qCDebug(UDPLinkLog) <<
this;
561 _workerThread->setObjectName(QStringLiteral(
"UDP_%1").arg(_udpConfig->name()));
563 _worker->moveToThread(_workerThread);
566 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
568 (void) connect(_worker, &
UDPWorker::connected,
this, &UDPLink::_onConnected, Qt::QueuedConnection);
572 (void) connect(_worker, &
UDPWorker::dataSent,
this, &UDPLink::_onDataSent, Qt::QueuedConnection);
574 _workerThread->start();
580 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::BlockingQueuedConnection);
584 _workerThread->quit();
585 if (!_workerThread->wait()) {
586 qCWarning(UDPLinkLog) <<
"Failed to wait for UDP Thread to close";
589 qCDebug(UDPLinkLog) <<
this;
599 return QMetaObject::invokeMethod(_worker,
"connectLink", Qt::QueuedConnection);
605 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::QueuedConnection);
609void UDPLink::_onConnected()
611 _disconnectedEmitted =
false;
615void UDPLink::_onDisconnected()
617 if (!_disconnectedEmitted.exchange(
true)) {
622void UDPLink::_onErrorOccurred(
const QString &
errorString)
624 qCWarning(UDPLinkLog) <<
"Communication error:" <<
errorString;
628void UDPLink::_onDataReceived(
const QByteArray &data)
633void UDPLink::_onDataSent(
const QByteArray &data)
638void UDPLink::_writeBytes(
const QByteArray& bytes)
640 (void) QMetaObject::invokeMethod(_worker,
"writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Fact *udpTargetHostIP READ udpTargetHostIP CONSTANT Fact * udpTargetHostIP()
Fact *udpListenPort READ udpListenPort CONSTANT Fact * udpListenPort()
Fact *udpTargetHostPort READ udpTargetHostPort CONSTANT Fact * udpTargetHostPort()
Interface holding link specific settings.
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
bool isSecureConnection() const override
Returns true if the connection is secure (e.g. USB, wired ethernet)
bool isConnected() const override
bool _connect() override
connect is private since all links should be created through LinkManager::createConnectedLink calls
UDPLink(SharedLinkConfigurationPtr &config, QObject *parent=nullptr)
void disconnect() override
void dataReceived(const QByteArray &data)
void dataSent(const QByteArray &data)
void errorOccurred(const QString &errorString)
void writeData(const QByteArray &data)
UDPWorker(const UDPConfiguration *config, QObject *parent=nullptr)
bool isNetworkEthernet()
Check if current network connection is Ethernet.