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)) {
32 bool containsHost(
const QList<std::shared_ptr<UDPClient>> &list,
const QString &hostname, quint16 port)
34 for (
const std::shared_ptr<UDPClient> &target : list) {
35 if ((target->hostname == hostname) && (target->port == port)) {
54 qCDebug(UDPLinkLog) <<
this;
63 qCDebug(UDPLinkLog) <<
this;
70 const QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString();
71 const quint16 targetHostPort = settings->udpTargetHostPort()->rawValue().toUInt();
73 setLocalPort(settings->udpListenPort()->rawValue().toInt());
74 if (!targetHostIP.isEmpty()) {
75 addHost(targetHostIP, targetHostPort);
79 if (!targetHostIP.isEmpty()) {
91 const UDPConfiguration *udpSource = qobject_cast<const UDPConfiguration*>(source);
96 for (
const std::shared_ptr<UDPClient> &target : udpSource->
targetHosts()) {
97 if (!containsHost(_targetHosts, target->hostname, target->port)) {
98 _targetHosts.append(std::make_shared<UDPClient>(target.get()));
107 settings.beginGroup(root);
111 _targetHosts.clear();
112 const qsizetype hostCount = settings.value(
"hostCount", 0).toUInt();
113 for (qsizetype i = 0; i < hostCount; i++) {
114 const QString hkey = QStringLiteral(
"host%1").arg(i);
115 const QString pkey = QStringLiteral(
"port%1").arg(i);
116 if (settings.contains(hkey) && settings.contains(pkey)) {
117 addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
128 settings.beginGroup(root);
130 settings.setValue(QStringLiteral(
"hostCount"), _targetHosts.size());
131 settings.setValue(QStringLiteral(
"port"), _localPort);
133 for (qsizetype i = 0; i < _targetHosts.size(); i++) {
134 const std::shared_ptr<UDPClient> target = _targetHosts.at(i);
135 const QString hkey = QStringLiteral(
"host%1").arg(i);
136 const QString hostValue = target->hostname.isEmpty() ? target->address.toString() : target->hostname;
137 settings.setValue(hkey, hostValue);
138 const QString pkey = QStringLiteral(
"port%1").arg(i);
139 settings.setValue(pkey, target->port);
147 if (host.contains(
":")) {
148 const QStringList hostInfo = host.split(
":");
149 if (hostInfo.size() != 2) {
150 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
154 const QString address = hostInfo.constFirst();
155 const quint16 port = hostInfo.constLast().toUInt();
165 const QString cleanHost = host.trimmed();
166 if (cleanHost.isEmpty()) {
170 if (containsHost(_targetHosts, cleanHost, port)) {
174 const QHostAddress address(_getIpAddress(cleanHost));
175 if (address.isNull()) {
176 qCWarning(UDPLinkLog) <<
"Could not resolve host:" << cleanHost <<
"port:" << port <<
"- will retry on connect";
179 _targetHosts.append(std::make_shared<UDPClient>(cleanHost, address, port));
185 if (host.contains(
":")) {
186 const QStringList hostInfo = host.split(
":");
187 if (hostInfo.size() != 2) {
188 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
192 removeHost(hostInfo.constFirst(), hostInfo.constLast().toUInt());
200 const QString cleanHost = host.trimmed();
202 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
203 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
204 if ((target->hostname == cleanHost) && (target->port == port)) {
205 _targetHosts.removeAt(i);
211 const QHostAddress resolved(_getIpAddress(cleanHost));
212 if (!resolved.isNull()) {
213 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
214 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
215 if ((target->address == resolved) && (target->port == port)) {
216 _targetHosts.removeAt(i);
223 qCWarning(UDPLinkLog) <<
"Could not remove unknown host:" << host <<
"port:" << port;
226void UDPConfiguration::_updateHostList()
229 for (
const std::shared_ptr<UDPClient> &target : _targetHosts) {
230 const QString
name = target->hostname.isEmpty() ? target->address.toString() : target->hostname;
231 const QString host =
name +
":" + QString::number(target->port);
232 _hostList.append(host);
240 for (
const std::shared_ptr<UDPClient> &target : _targetHosts) {
241 if (target->hostname.isEmpty()) {
245 const QString ipAdd = _getIpAddress(target->hostname);
246 if (!ipAdd.isEmpty()) {
247 target->address = QHostAddress(ipAdd);
249 qCWarning(UDPLinkLog) <<
"Could not resolve host:" << target->hostname <<
"port:" << target->port;
254QString UDPConfiguration::_getIpAddress(
const QString &address)
256 const QHostAddress host(address);
257 if (!host.isNull()) {
261 const QHostInfo info = QHostInfo::fromName(address);
262 if (info.error() != QHostInfo::NoError) {
266 const QList<QHostAddress> hostAddresses = info.addresses();
267 for (
const QHostAddress &hostAddress : hostAddresses) {
268 if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
269 return hostAddress.toString();
278const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral(
"224.0.0.1"));
284 qCDebug(UDPLinkLog) <<
this;
291 qCDebug(UDPLinkLog) <<
this;
296 return (_socket && _socket->isValid() && _isConnected);
302 _socket =
new QUdpSocket(
this);
305 const QList<QHostAddress> localAddresses = QNetworkInterface::allAddresses();
306 _localAddresses = QSet(localAddresses.constBegin(), localAddresses.constEnd());
308 _socket->setProxy(QNetworkProxy::NoProxy);
310 (void) connect(_socket, &QUdpSocket::connected,
this, &UDPWorker::_onSocketConnected);
311 (void) connect(_socket, &QUdpSocket::disconnected,
this, &UDPWorker::_onSocketDisconnected);
312 (void) connect(_socket, &QUdpSocket::readyRead,
this, &UDPWorker::_onSocketReadyRead);
313 (void) connect(_socket, &QUdpSocket::errorOccurred,
this, &UDPWorker::_onSocketErrorOccurred);
314 (void) connect(_socket, &QUdpSocket::stateChanged,
this, [
this](QUdpSocket::SocketState state) {
315 qCDebug(UDPLinkLog) <<
"UDP State Changed:" << state;
317 case QAbstractSocket::BoundState:
318 _onSocketConnected();
320 case QAbstractSocket::ClosingState:
321 case QAbstractSocket::UnconnectedState:
322 _onSocketDisconnected();
329 if (UDPLinkLog().isDebugEnabled()) {
332 (void) QObject::connect(_socket, &QUdpSocket::hostFound,
this, []() {
333 qCDebug(UDPLinkLog) <<
"UDP Host Found";
341 qCWarning(UDPLinkLog) <<
"Already connected to" << _udpConfig->
localPort();
345 _errorEmitted =
false;
349 qCDebug(UDPLinkLog) <<
"Attempting to bind to port:" << _udpConfig->
localPort();
350 const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->
localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress);
352 qCWarning(UDPLinkLog) <<
"Failed to bind UDP socket to port" << _udpConfig->
localPort();
354 if (!_errorEmitted) {
356 _errorEmitted =
true;
367 qCDebug(UDPLinkLog) <<
"Attempting to join multicast group:" << _multicastGroup.toString();
368 const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup);
370 qCWarning(UDPLinkLog) <<
"Failed to join multicast group" << _multicastGroup.toString();
378 qCDebug(UDPLinkLog) <<
"Already disconnected";
382 qCDebug(UDPLinkLog) <<
"Disconnecting UDP link";
384 (void) _socket->leaveMulticastGroup(_multicastGroup);
387 _sessionTargets.clear();
393 emit
errorOccurred(tr(
"Could Not Send Data - Link is Disconnected!"));
397 QMutexLocker locker(&_sessionTargetsMutex);
400 for (
const std::shared_ptr<UDPClient> &target : _udpConfig->
targetHosts()) {
401 if (target->address.isNull()) {
404 if (!containsTarget(_sessionTargets, target->address, target->port)) {
405 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
406 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
412 for (
const std::shared_ptr<UDPClient> &target: _sessionTargets) {
413 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
414 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
423void UDPWorker::_onSocketConnected()
425 qCDebug(UDPLinkLog) <<
"UDP connected to" << _udpConfig->
localPort();
427 _errorEmitted =
false;
431void UDPWorker::_onSocketDisconnected()
433 qCDebug(UDPLinkLog) <<
"UDP disconnected from" << _udpConfig->
localPort();
434 _isConnected =
false;
435 _errorEmitted =
false;
439void UDPWorker::_onSocketReadyRead()
442 emit
errorOccurred(tr(
"Could Not Read Data - Link is Disconnected!"));
446 const qint64 byteCount = _socket->pendingDatagramSize();
447 if (byteCount <= 0) {
448 emit
errorOccurred(tr(
"Could Not Read Data - No Data Available!"));
453 buffer.reserve(BUFFER_TRIGGER_SIZE);
456 bool received =
false;
457 while (_socket->hasPendingDatagrams()) {
458 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
459 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
463 (void) buffer.append(datagramIn.data());
465 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
469 (void) timer.restart();
472 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
473 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
475 QMutexLocker locker(&_sessionTargetsMutex);
476 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
477 qCDebug(UDPLinkLog) <<
"UDP Adding target:" << senderAddress << datagramIn.senderPort();
478 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
483 if (!received && buffer.isEmpty()) {
484 qCWarning(UDPLinkLog) <<
"No Data Available to Read!";
491void UDPWorker::_onSocketBytesWritten(qint64 bytes)
493 qCDebug(UDPLinkLog) <<
"Wrote" << bytes <<
"bytes";
496void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError
error)
498 const QString
errorString = _socket->errorString();
499 qCWarning(UDPLinkLog) <<
"UDP Link error:" <<
error << _socket->errorString();
501 if (!_errorEmitted) {
503 _errorEmitted =
true;
513 , _workerThread(new QThread(this))
515 qCDebug(UDPLinkLog) <<
this;
517 _workerThread->setObjectName(QStringLiteral(
"UDP_%1").arg(_udpConfig->
name()));
519 _worker->moveToThread(_workerThread);
522 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
524 (void) connect(_worker, &
UDPWorker::connected,
this, &UDPLink::_onConnected, Qt::QueuedConnection);
528 (void) connect(_worker, &
UDPWorker::dataSent,
this, &UDPLink::_onDataSent, Qt::QueuedConnection);
530 _workerThread->start();
536 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::BlockingQueuedConnection);
540 _workerThread->quit();
541 if (!_workerThread->wait()) {
542 qCWarning(UDPLinkLog) <<
"Failed to wait for UDP Thread to close";
545 qCDebug(UDPLinkLog) <<
this;
555 return QMetaObject::invokeMethod(_worker,
"connectLink", Qt::QueuedConnection);
561 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::QueuedConnection);
565void UDPLink::_onConnected()
567 _disconnectedEmitted =
false;
571void UDPLink::_onDisconnected()
573 if (!_disconnectedEmitted.exchange(
true)) {
578void UDPLink::_onErrorOccurred(
const QString &
errorString)
580 qCWarning(UDPLinkLog) <<
"Communication error:" <<
errorString;
584void UDPLink::_onDataReceived(
const QByteArray &data)
589void UDPLink::_onDataSent(
const QByteArray &data)
594void UDPLink::_writeBytes(
const QByteArray& bytes)
596 (void) QMetaObject::invokeMethod(_worker,
"writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Interface holding link specific settings.
virtual void copyFrom(const LinkConfiguration *source)
bool isAutoConnect() const
virtual void setAutoConnect(bool autoc=true)
Set if this is this an Auto Connect configuration.
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)
AutoConnectSettings * autoConnectSettings() const
static SettingsManager * instance()
void saveSettings(QSettings &settings, const QString &root) const override
UDPConfiguration(const QString &name, QObject *parent=nullptr)
QList< std::shared_ptr< UDPClient > > targetHosts() const
quint16 localPort() const
Q_INVOKABLE void removeHost(const QString &host)
void setAutoConnect(bool autoc=true) override
Set if this is this an Auto Connect configuration.
void setLocalPort(quint16 port)
Q_INVOKABLE void addHost(const QString &host)
void resolveHosts() const
virtual ~UDPConfiguration()
void copyFrom(const LinkConfiguration *source) override
void loadSettings(QSettings &settings, const QString &root) override
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.