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;
68 const UDPConfiguration *udpSource = qobject_cast<const UDPConfiguration*>(source);
70 qCWarning(UDPLinkLog) <<
"Invalid source configuration type";
80 for (
const std::shared_ptr<UDPClient> &target : udpSource->
targetHosts()) {
81 if (!containsHost(_targetHosts, target->hostname, target->port)) {
82 _targetHosts.append(std::make_shared<UDPClient>(target.get()));
91 settings.beginGroup(root);
96 const qsizetype hostCount = settings.value(
"hostCount", 0).toUInt();
97 for (qsizetype i = 0; i < hostCount; i++) {
98 const QString hkey = QStringLiteral(
"host%1").arg(i);
99 const QString pkey = QStringLiteral(
"port%1").arg(i);
100 if (settings.contains(hkey) && settings.contains(pkey)) {
101 addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
112 settings.beginGroup(root);
114 settings.setValue(QStringLiteral(
"hostCount"), _targetHosts.size());
115 settings.setValue(QStringLiteral(
"port"), _localPort);
117 for (qsizetype i = 0; i < _targetHosts.size(); i++) {
118 const std::shared_ptr<UDPClient> target = _targetHosts.at(i);
119 const QString hkey = QStringLiteral(
"host%1").arg(i);
120 const QString hostValue = target->hostname.isEmpty() ? target->address.toString() : target->hostname;
121 settings.setValue(hkey, hostValue);
122 const QString pkey = QStringLiteral(
"port%1").arg(i);
123 settings.setValue(pkey, target->port);
131 if (host.contains(
":")) {
132 const QStringList hostInfo = host.split(
":");
133 if (hostInfo.size() != 2) {
134 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
138 const QString address = hostInfo.constFirst();
139 const quint16 port = hostInfo.constLast().toUInt();
149 const QString cleanHost = host.trimmed();
150 if (cleanHost.isEmpty()) {
154 if (containsHost(_targetHosts, cleanHost, port)) {
159 const QHostAddress address(cleanHost);
161 _targetHosts.append(std::make_shared<UDPClient>(cleanHost, address, port));
167 if (host.contains(
":")) {
168 const QStringList hostInfo = host.split(
":");
169 if (hostInfo.size() != 2) {
170 qCWarning(UDPLinkLog) <<
"Invalid host format:" << host;
174 removeHost(hostInfo.constFirst(), hostInfo.constLast().toUInt());
182 const QString cleanHost = host.trimmed();
184 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
185 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
186 if ((target->hostname == cleanHost) && (target->port == port)) {
187 _targetHosts.removeAt(i);
193 const QHostAddress resolved(_getIpAddress(cleanHost));
194 if (!resolved.isNull()) {
195 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
196 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
197 if ((target->address == resolved) && (target->port == port)) {
198 _targetHosts.removeAt(i);
205 qCWarning(UDPLinkLog) <<
"Could not remove unknown host:" << host <<
"port:" << port;
208void UDPConfiguration::_updateHostList()
211 for (
const std::shared_ptr<UDPClient> &target : _targetHosts) {
212 const QString
name = target->hostname.isEmpty() ? target->address.toString() : target->hostname;
213 const QString host =
name +
":" + QString::number(target->port);
214 _hostList.append(host);
222 QStringList unresolved;
223 for (
const std::shared_ptr<UDPClient> &target : _targetHosts) {
224 if (target->hostname.isEmpty()) {
228 const QString ipAdd = _getIpAddress(target->hostname);
229 if (!ipAdd.isEmpty()) {
230 target->address = QHostAddress(ipAdd);
232 target->address = QHostAddress();
233 unresolved.append(target->hostname);
240QString UDPConfiguration::_getIpAddress(
const QString &address)
242 const QHostAddress host(address);
243 if (!host.isNull()) {
247 const QHostInfo info = QHostInfo::fromName(address);
248 if (info.error() != QHostInfo::NoError) {
252 const QList<QHostAddress> hostAddresses = info.addresses();
253 for (
const QHostAddress &hostAddress : hostAddresses) {
254 if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
255 return hostAddress.toString();
264const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral(
"224.0.0.1"));
270 qCDebug(UDPLinkLog) <<
this;
277 qCDebug(UDPLinkLog) <<
this;
289 _socket =
new QUdpSocket(
this);
292 const QList<QHostAddress> localAddresses = QNetworkInterface::allAddresses();
293 _localAddresses = QSet(localAddresses.constBegin(), localAddresses.constEnd());
295 _socket->setProxy(QNetworkProxy::NoProxy);
297 (void) connect(_socket, &QUdpSocket::connected,
this, &UDPWorker::_onSocketConnected);
298 (void) connect(_socket, &QUdpSocket::disconnected,
this, &UDPWorker::_onSocketDisconnected);
299 (void) connect(_socket, &QUdpSocket::readyRead,
this, &UDPWorker::_onSocketReadyRead);
300 (void) connect(_socket, &QUdpSocket::errorOccurred,
this, &UDPWorker::_onSocketErrorOccurred);
301 (void) connect(_socket, &QUdpSocket::stateChanged,
this, [
this](QUdpSocket::SocketState state) {
302 qCDebug(UDPLinkLog) <<
"UDP State Changed:" << state;
304 case QAbstractSocket::BoundState:
305 _onSocketConnected();
307 case QAbstractSocket::ClosingState:
308 case QAbstractSocket::UnconnectedState:
309 _onSocketDisconnected();
316 if (UDPLinkLog().isDebugEnabled()) {
319 (void) QObject::connect(_socket, &QUdpSocket::hostFound,
this, []() {
320 qCDebug(UDPLinkLog) <<
"UDP Host Found";
328 qCWarning(UDPLinkLog) <<
"Already connected to" << _udpConfig->
localPort();
332 _errorEmitted =
false;
334 const QStringList unresolvedHosts = _udpConfig->
resolveHosts();
335 if (!unresolvedHosts.isEmpty()) {
336 const QString
error = tr(
"Could not resolve host: %1").arg(unresolvedHosts.join(QStringLiteral(
", ")));
339 _errorEmitted =
true;
340 _onSocketDisconnected();
343 qCWarning(UDPLinkLog) <<
error <<
"- listening without those targets";
346 qCDebug(UDPLinkLog) <<
"Attempting to bind to port:" << _udpConfig->
localPort();
347 const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->
localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress);
349 qCWarning(UDPLinkLog) <<
"Failed to bind UDP socket to port" << _udpConfig->
localPort();
351 if (!_errorEmitted) {
353 _errorEmitted =
true;
356 _onSocketDisconnected();
360 qCDebug(UDPLinkLog) <<
"Attempting to join multicast group:" << _multicastGroup.toString();
361 const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup);
363 qCWarning(UDPLinkLog) <<
"Failed to join multicast group" << _multicastGroup.toString();
371 qCDebug(UDPLinkLog) <<
"Already disconnected";
375 qCDebug(UDPLinkLog) <<
"Disconnecting UDP link";
377 (void) _socket->leaveMulticastGroup(_multicastGroup);
380 _sessionTargets.clear();
386 emit
errorOccurred(tr(
"Could Not Send Data - Link is Disconnected!"));
390 QMutexLocker locker(&_sessionTargetsMutex);
393 for (
const std::shared_ptr<UDPClient> &target : _udpConfig->
targetHosts()) {
394 if (target->address.isNull()) {
397 if (!containsTarget(_sessionTargets, target->address, target->port)) {
398 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
399 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
405 for (
const std::shared_ptr<UDPClient> &target: _sessionTargets) {
406 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
407 qCWarning(UDPLinkLog) <<
"Could Not Send Data - Write Failed!";
416void UDPWorker::_onSocketConnected()
418 qCDebug(UDPLinkLog) <<
"UDP connected to" << _udpConfig->
localPort();
420 _errorEmitted =
false;
424void UDPWorker::_onSocketDisconnected()
426 qCDebug(UDPLinkLog) <<
"UDP disconnected from" << _udpConfig->
localPort();
427 _isConnected =
false;
428 _errorEmitted =
false;
432void UDPWorker::_onSocketReadyRead()
435 emit
errorOccurred(tr(
"Could Not Read Data - Link is Disconnected!"));
439 const qint64 byteCount = _socket->pendingDatagramSize();
440 if (byteCount <= 0) {
441 emit
errorOccurred(tr(
"Could Not Read Data - No Data Available!"));
446 buffer.reserve(BUFFER_TRIGGER_SIZE);
449 bool received =
false;
450 while (_socket->hasPendingDatagrams()) {
451 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
452 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
456 (void) buffer.append(datagramIn.data());
458 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
462 (void) timer.restart();
465 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
466 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
468 QMutexLocker locker(&_sessionTargetsMutex);
469 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
470 qCDebug(UDPLinkLog) <<
"UDP Adding target:" << senderAddress << datagramIn.senderPort();
471 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
476 if (!received && buffer.isEmpty()) {
477 qCWarning(UDPLinkLog) <<
"No Data Available to Read!";
484void UDPWorker::_onSocketBytesWritten(qint64 bytes)
486 qCDebug(UDPLinkLog) <<
"Wrote" << bytes <<
"bytes";
489void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError
error)
491 const QString
errorString = _socket->errorString();
492 qCWarning(UDPLinkLog) <<
"UDP Link error:" <<
error << _socket->errorString();
494 if (!_errorEmitted) {
496 _errorEmitted =
true;
506 , _workerThread(new QThread(this))
508 qCDebug(UDPLinkLog) <<
this;
510 _workerThread->setObjectName(QStringLiteral(
"UDP_%1").arg(_udpConfig->
name()));
512 _worker->moveToThread(_workerThread);
515 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
517 (void) connect(_worker, &
UDPWorker::connected,
this, &UDPLink::_onConnected, Qt::QueuedConnection);
521 (void) connect(_worker, &
UDPWorker::dataSent,
this, &UDPLink::_onDataSent, Qt::QueuedConnection);
523 _workerThread->start();
532 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::QueuedConnection);
539 qCDebug(UDPLinkLog) <<
this;
549 return QMetaObject::invokeMethod(_worker,
"connectLink", Qt::QueuedConnection);
555 (void) QMetaObject::invokeMethod(_worker,
"disconnectLink", Qt::QueuedConnection);
559void UDPLink::_onConnected()
561 _disconnectedEmitted =
false;
565void UDPLink::_onDisconnected()
567 if (!_disconnectedEmitted.exchange(
true)) {
572void UDPLink::_onErrorOccurred(
const QString &
errorString)
574 qCWarning(UDPLinkLog) <<
"Communication error:" <<
errorString;
578void UDPLink::_onDataReceived(
const QByteArray &data)
583void UDPLink::_onDataSent(
const QByteArray &data)
588void UDPLink::_writeBytes(
const QByteArray& bytes)
590 (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)
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 _shutdownWorkerThread(QThread *thread, const QLoggingCategory &category, bool allowTerminate=true)
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
QStringList resolveHosts() const
Returns the hostnames that failed to resolve.
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 setLocalPort(quint16 port)
Q_INVOKABLE void addHost(const QString &host)
virtual ~UDPConfiguration()
void copyFrom(const LinkConfiguration *source) override
void loadSettings(QSettings &settings, const QString &root) override
void setRequireResolvedHosts(bool require)
bool requireResolvedHosts() const
When false, connect proceeds (listening) even if a target host fails to resolve.
bool isSecureConnection() const override
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.