summaryrefslogtreecommitdiffstats
path: root/src/knx/netip/qknxnetipserverdiscoveryagent_p.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/knx/netip/qknxnetipserverdiscoveryagent_p.cpp')
-rw-r--r--src/knx/netip/qknxnetipserverdiscoveryagent_p.cpp381
1 files changed, 381 insertions, 0 deletions
diff --git a/src/knx/netip/qknxnetipserverdiscoveryagent_p.cpp b/src/knx/netip/qknxnetipserverdiscoveryagent_p.cpp
new file mode 100644
index 0000000..e1365a7
--- /dev/null
+++ b/src/knx/netip/qknxnetipserverdiscoveryagent_p.cpp
@@ -0,0 +1,381 @@
+/******************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "qknxnetipserverdiscoveryagent.h"
+#include "qknxnetipserverdiscoveryagent_p.h"
+
+#include <QtNetwork/qnetworkinterface.h>
+
+QT_BEGIN_NAMESPACE
+
+QKnxNetIpServerDiscoveryAgentPrivate::QKnxNetIpServerDiscoveryAgentPrivate(const QHostAddress &addr,
+ quint16 prt)
+ : port(prt)
+ , address(addr)
+{}
+
+namespace QKnxPrivate
+{
+ static void clearSocket(QUdpSocket **socket)
+ {
+ if (*socket) {
+ (*socket)->disconnect();
+ (*socket)->deleteLater();
+ (*socket) = nullptr;
+ }
+ }
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::setupSocket()
+{
+ usedPort = port;
+ usedAddress = address;
+ QKnxPrivate::clearSocket(&socket);
+
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ socket = new QUdpSocket(q);
+
+ QObject::connect(socket, &QUdpSocket::stateChanged, q, [&](QUdpSocket::SocketState s) {
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ switch (s) {
+ case QUdpSocket::BoundState:
+ setAndEmitStateChanged(QKnxNetIpServerDiscoveryAgent::State::Running);
+ socket->setSocketOption(QUdpSocket::SocketOption::MulticastTtlOption, ttl);
+
+ if (type == QKnxNetIpServerDiscoveryAgent::ResponseType::Multicast) {
+ QNetworkInterface mni;
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &iface : interfaces) {
+ if (!iface.flags().testFlag(QNetworkInterface::CanMulticast))
+ continue;
+
+ const auto entries = iface.addressEntries();
+ for (const auto &entry : entries) {
+ auto ip = entry.ip();
+ if (ip.protocol() != QAbstractSocket::NetworkLayerProtocol::IPv4Protocol)
+ continue;
+ if (ip != address)
+ continue;
+ mni = iface;
+ break;
+ }
+ }
+
+ if (mni.isValid())
+ socket->setMulticastInterface(mni);
+
+ if (socket->joinMulticastGroup(multicastAddress, mni)) {
+ usedPort = multicastPort;
+ usedAddress = multicastAddress;
+ } else {
+ setAndEmitErrorOccurred(QKnxNetIpServerDiscoveryAgent::Error::Network,
+ QKnxNetIpServerDiscoveryAgent::tr("Could not join multicast group."));
+ q->stop();
+ }
+ } else {
+ usedPort = socket->localPort();
+ usedAddress = socket->localAddress();
+ }
+
+ if (q->state() == QKnxNetIpServerDiscoveryAgent::State::Running) {
+ servers.clear();
+
+ const QFlags<QKnxNetIpServerDiscoveryAgent::DiscoveryMode> flags(mode);
+ if (flags.testFlag(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1)) {
+ auto frame = QKnxNetIpSearchRequestProxy::builder()
+ .setDiscoveryEndpoint(QKnxNetIpHpaiProxy::builder()
+ .setHostAddress(nat ? QHostAddress::AnyIPv4 : usedAddress)
+ .setPort(nat ? quint16(0u) : usedPort).create()
+ ).create();
+ socket->writeDatagram(frame.bytes().toByteArray(), multicastAddress,
+ multicastPort);
+ }
+
+ if (flags.testFlag(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2)) {
+ auto frame = QKnxNetIpSearchRequestProxy::extendedBuilder()
+ .setDiscoveryEndpoint(QKnxNetIpHpaiProxy::builder()
+ .setHostAddress(nat ? QHostAddress::AnyIPv4 : usedAddress)
+ .setPort(nat ? quint16(0u) : usedPort).create()
+ )
+ .setExtendedParameters(srps).create();
+ socket->writeDatagram(frame.bytes().toByteArray(), multicastAddress,
+ multicastPort);
+ }
+
+ setupAndStartReceiveTimer();
+ setupAndStartFrequencyTimer();
+ }
+ break;
+ default:
+ break;
+ }
+ });
+
+ using overload = void (QUdpSocket::*)(QUdpSocket::SocketError);
+ QObject::connect(socket,
+ static_cast<overload>(&QUdpSocket::error), q, [&](QUdpSocket::SocketError) {
+ setAndEmitErrorOccurred(QKnxNetIpServerDiscoveryAgent::Error::Network,
+ socket->errorString());
+
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ q->stop();
+ });
+
+ QObject::connect(socket, &QUdpSocket::readyRead, q, [&]() {
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ while (socket->hasPendingDatagrams()) {
+ if (q->state() != QKnxNetIpServerDiscoveryAgent::State::Running)
+ break;
+
+ auto datagram = socket->receiveDatagram();
+ auto data = QKnxByteArray::fromByteArray(datagram.data());
+ const auto header = QKnxNetIpFrameHeader::fromBytes(data, 0);
+ if (!header.isValid())
+ continue;
+
+ if (header.serviceType() != QKnxNetIp::ServiceType::SearchResponse &&
+ header.serviceType() != QKnxNetIp::ServiceType::ExtendedSearchResponse) {
+ continue;
+ }
+
+ auto frame = QKnxNetIpFrame::fromBytes(data);
+ auto response = QKnxNetIpSearchResponseProxy(frame);
+ if (!response.isValid())
+ continue;
+
+ const QFlags<QKnxNetIpServerDiscoveryAgent::DiscoveryMode> flags(mode);
+ if (flags.testFlag(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1)
+ && !response.isExtended()) {
+ setAndEmitDeviceDiscovered({
+ (nat ? QKnxNetIpHpaiProxy::builder()
+ .setHostAddress(datagram.senderAddress())
+ .setPort(datagram.senderPort()).create()
+ : response.controlEndpoint()
+ ), response.deviceHardware(), response.supportedFamilies(),
+ QNetworkInterface::interfaceFromIndex(datagram.interfaceIndex())
+ });
+ }
+
+ if (flags.testFlag(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2)
+ && response.isExtended()) {
+ const auto optionalDibs = response.optionalDibs();
+ setAndEmitDeviceDiscovered({
+ (nat ? QKnxNetIpHpaiProxy::builder()
+ .setHostAddress(datagram.senderAddress())
+ .setPort(datagram.senderPort()).create()
+ : response.controlEndpoint()
+ ), response.deviceHardware(), response.supportedFamilies(),
+ QNetworkInterface::interfaceFromIndex(datagram.interfaceIndex()),
+ [&optionalDibs]() -> QKnxNetIpDib {
+ for (const auto &dib : qAsConst(optionalDibs)) {
+ if (dib.code() == QKnxNetIp::DescriptionType::TunnelingInfo)
+ return dib;
+ }
+ return {};
+ }(),
+ [&optionalDibs]() -> QKnxNetIpDib {
+ for (const auto &dib : qAsConst(optionalDibs)) {
+ if (dib.code() == QKnxNetIp::DescriptionType::ExtendedDeviceInfo)
+ return dib;
+ }
+ return {};
+ }()
+ });
+ }
+ }
+ });
+}
+
+namespace QKnxPrivate
+{
+ static void clearTimer(QTimer **timer)
+ {
+ if (*timer) {
+ (*timer)->stop();
+ (*timer)->disconnect();
+ (*timer)->deleteLater();
+ (*timer) = nullptr;
+ }
+ }
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::setupAndStartReceiveTimer()
+{
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+
+ QKnxPrivate::clearTimer(&receiveTimer);
+ if (timeout >= 0) {
+ receiveTimer = new QTimer(q);
+ receiveTimer->setSingleShot(true);
+ receiveTimer->start(timeout);
+ QObject::connect(receiveTimer, &QTimer::timeout, q, &QKnxNetIpServerDiscoveryAgent::stop);
+ }
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::setupAndStartFrequencyTimer()
+{
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+
+ QKnxPrivate::clearTimer(&frequencyTimer);
+ if (frequency > 0) {
+ frequencyTimer = new QTimer(q);
+ frequencyTimer->setSingleShot(false);
+ frequencyTimer->start(60000 / frequency);
+
+ QObject::connect(frequencyTimer, &QTimer::timeout, q, [&]() {
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ if (q->state() == QKnxNetIpServerDiscoveryAgent::State::Running) {
+ servers.clear();
+
+ const QFlags<QKnxNetIpServerDiscoveryAgent::DiscoveryMode> flags(mode);
+ if (flags.testFlag(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1)) {
+ auto frame = QKnxNetIpSearchRequestProxy::builder()
+ .setDiscoveryEndpoint(QKnxNetIpHpaiProxy::builder()
+ .setHostAddress(nat ? QHostAddress::AnyIPv4 : usedAddress)
+ .setPort(nat ? quint16(0u) : usedPort).create()
+ ).create();
+ socket->writeDatagram(frame.bytes().toByteArray(), multicastAddress,
+ multicastPort);
+ }
+
+ if (flags.testFlag(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2)) {
+ auto frame = QKnxNetIpSearchRequestProxy::extendedBuilder()
+ .setDiscoveryEndpoint(QKnxNetIpHpaiProxy::builder()
+ .setHostAddress(nat ? QHostAddress::AnyIPv4 : usedAddress)
+ .setPort(nat ? quint16(0u) : usedPort).create()
+ )
+ .setExtendedParameters(srps).create();
+ socket->writeDatagram(frame.bytes().toByteArray(), multicastAddress,
+ multicastPort);
+ }
+ }
+ });
+ }
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::setAndEmitStateChanged(
+ QKnxNetIpServerDiscoveryAgent::State newState)
+{
+ state = newState;
+
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ emit q->stateChanged(newState);
+
+ if (state == QKnxNetIpServerDiscoveryAgent::State::Running)
+ emit q->started();
+ else if (state == QKnxNetIpServerDiscoveryAgent::State::NotRunning)
+ emit q->finished();
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::setAndEmitDeviceDiscovered(
+ const QKnxNetIpServerInfo &discoveryInfo)
+{
+ servers.append(discoveryInfo);
+
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ emit q->deviceDiscovered(discoveryInfo);
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::setAndEmitErrorOccurred(
+ QKnxNetIpServerDiscoveryAgent::Error newError, const QString &message)
+{
+ error = newError;
+ errorString = message;
+
+ Q_Q(QKnxNetIpServerDiscoveryAgent);
+ emit q->errorOccurred(error, errorString);
+}
+
+namespace QKnxPrivate
+{
+ QString interfaceFromAddress(const QHostAddress &address)
+ {
+ auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &interface : qAsConst(interfaces)) {
+ auto entries = interface.addressEntries();
+ for (const auto &entry : entries) {
+ if (entry.ip() == address)
+ return interface.humanReadableName();
+ }
+ }
+ return QLatin1String("Unknown");
+ }
+}
+void QKnxNetIpServerDiscoveryAgentPrivate::start()
+{
+ if (state != QKnxNetIpServerDiscoveryAgent::State::NotRunning)
+ return;
+
+ if (address.isNull())
+ address = QHostAddress(QHostAddress::AnyIPv4);
+
+ auto isIPv4 = true;
+ address.toIPv4Address(&isIPv4);
+ if (isIPv4) {
+ setAndEmitStateChanged(QKnxNetIpServerDiscoveryAgent::State::Starting);
+ setupSocket();
+ if (type == QKnxNetIpServerDiscoveryAgent::ResponseType::Multicast) {
+ socket->bind(QHostAddress::AnyIPv4, multicastPort, QUdpSocket::ShareAddress
+ | QAbstractSocket::ReuseAddressHint);
+ qDebug() << "Multicast response using iface:" << QKnxPrivate::interfaceFromAddress(address);
+ } else {
+ socket->bind(address, port);
+ qDebug() << "Unicast response using interface:" << QKnxPrivate::interfaceFromAddress(address);
+ }
+ } else {
+ setAndEmitErrorOccurred(QKnxNetIpServerDiscoveryAgent::Error::NotIPv4,
+ QKnxNetIpServerDiscoveryAgent::tr("Only IPv4 local address supported."));
+ }
+}
+
+void QKnxNetIpServerDiscoveryAgentPrivate::stop()
+{
+ if (state == QKnxNetIpServerDiscoveryAgent::State::Stopping
+ || state == QKnxNetIpServerDiscoveryAgent::State::NotRunning) {
+ return;
+ }
+
+ setAndEmitStateChanged(QKnxNetIpServerDiscoveryAgent::State::Stopping);
+
+ if (socket) {
+ if (type == QKnxNetIpServerDiscoveryAgent::ResponseType::Multicast
+ && socket->state() == QUdpSocket::BoundState) {
+ socket->leaveMulticastGroup(multicastAddress);
+ }
+ socket->close();
+ }
+
+ QKnxPrivate::clearSocket(&(socket));
+ QKnxPrivate::clearTimer(&(receiveTimer));
+ QKnxPrivate::clearTimer(&(frequencyTimer));
+
+ setAndEmitStateChanged(QKnxNetIpServerDiscoveryAgent::State::NotRunning);
+}
+
+QT_END_NAMESPACE