summaryrefslogtreecommitdiffstats
path: root/src/knx/netip/qknxnetiproutinginterface_p.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/knx/netip/qknxnetiproutinginterface_p.cpp')
-rw-r--r--src/knx/netip/qknxnetiproutinginterface_p.cpp411
1 files changed, 411 insertions, 0 deletions
diff --git a/src/knx/netip/qknxnetiproutinginterface_p.cpp b/src/knx/netip/qknxnetiproutinginterface_p.cpp
new file mode 100644
index 0000000..19067cd
--- /dev/null
+++ b/src/knx/netip/qknxnetiproutinginterface_p.cpp
@@ -0,0 +1,411 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 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 "qknxnetiproutingbusy.h"
+#include "qknxnetiproutingindication.h"
+#include "qknxnetiproutinginterface_p.h"
+#include "qknxnetiproutinginterface.h"
+#include "qknxnetiproutinglostmessage.h"
+
+#ifdef QT_BUILD_INTERNAL
+#include "qknxtestingrouter_p.h"
+#endif
+
+#include <QtCore/qrandom.h>
+
+QT_BEGIN_NAMESPACE
+
+void QKnxNetIpRoutingInterfacePrivate::errorOccurred(QKnxNetIpRoutingInterface::Error error,
+ const QString &errorString)
+{
+ m_error = error;
+ m_errorMessage = errorString;
+ changeState(QKnxNetIpRoutingInterface::State::Failure);
+
+ Q_Q(QKnxNetIpRoutingInterface);
+ emit q->errorOccurred(m_error, m_errorMessage);
+}
+
+void QKnxNetIpRoutingInterfacePrivate::changeState(QKnxNetIpRoutingInterface::State state)
+{
+ if (m_state == state)
+ return;
+ m_state = state;
+
+ Q_Q(QKnxNetIpRoutingInterface);
+ emit q->stateChanged(m_state);
+}
+
+void QKnxNetIpRoutingInterfacePrivate::start()
+{
+ if (m_state != QKnxNetIpRoutingInterface::State::NotInit
+ && m_state != QKnxNetIpRoutingInterface::State::Stop)
+ return;
+
+#ifdef QT_BUILD_INTERNAL
+ TestingRouter::instance()->setRouterInstance(this);
+#endif
+
+ if (!m_iface.isValid()) {
+ // Choose first interface available and capable of multicasting
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &iface : interfaces) {
+ if (iface.flags().testFlag(QNetworkInterface::IsRunning)
+ && iface.flags().testFlag(QNetworkInterface::CanMulticast)
+ && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
+ m_iface = iface;
+ break;
+ }
+ }
+ // still no interface valid found
+ if (!m_iface.isValid()) {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::Network,
+ QKnxNetIpRoutingInterface::tr("Could not start routing because there isn't a "
+ "valid interface."));
+ return;
+ }
+ }
+ m_ownAddress = m_iface.addressEntries().first().ip();
+
+ m_busyTimer = new QTimer;
+ m_busyTimer->setSingleShot(true);
+
+ // while neighbor router busy don't overflow him with messages, timeout until some msec
+ QObject::connect(m_busyTimer, &QTimer::timeout, [&]() {
+ switch (m_busyStage) {
+ case BusyTimerStage::NotInit:
+ break;
+ case BusyTimerStage::Wait:
+ m_busyTimer->setInterval(QRandomGenerator().bounded(m_busyCounter * 50));
+ m_busyTimer->start();
+ m_busyStage = BusyTimerStage::RandomWait;
+ break;
+ case BusyTimerStage::RandomWait:
+ m_busyTimer->setInterval(m_busyCounter * 100);
+ m_busyTimer->start();
+ m_busyStage = BusyTimerStage::SlowDuration;
+ this->changeState(QKnxNetIpRoutingInterface::State::Routing);
+ break;
+ case BusyTimerStage::SlowDuration:
+ m_busyTimer->setInterval(5);
+ m_busyTimer->setSingleShot(false);
+ m_busyTimer->start();
+ m_busyStage = BusyTimerStage::DecrementBusyCounter;
+ break;
+ case BusyTimerStage::DecrementBusyCounter:
+ if (--m_busyCounter == 0) {
+ m_busyTimer->stop();
+ m_busyStage = BusyTimerStage::NotInit;
+ m_busyTimer->setSingleShot(true);
+ }
+ break;
+ }
+ });
+
+ m_socket = new QUdpSocket;
+ m_socket->setSocketOption(QUdpSocket::SocketOption::MulticastTtlOption, 60);
+
+ // handle QUdpSocket state changes here
+ QObject::connect(m_socket, &QUdpSocket::stateChanged, [&](QUdpSocket::SocketState s) {
+ switch (s) {
+ case QUdpSocket::BoundState:
+ m_socket->setMulticastInterface(m_iface);
+ if (m_socket->joinMulticastGroup(m_multicastAddress, m_iface)) {
+ changeState(QKnxNetIpRoutingInterface::State::Routing);
+ } else {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::Network,
+ QKnxNetIpRoutingInterface::tr("Could not join multicast group."));
+ }
+ break;
+ case QUdpSocket::ClosingState:
+ if (!m_socket->leaveMulticastGroup(m_multicastAddress, m_iface)) {
+ changeState(QKnxNetIpRoutingInterface::State::NotInit);
+ } else {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::Network,
+ QKnxNetIpRoutingInterface::tr("Could not leave multicast group."));
+ }
+ break;
+ default:
+ break;
+ }
+ });
+
+ // handle frames received by the UDP socket
+ QObject::connect(m_socket, &QUdpSocket::readyRead, [&]() {
+ // TODO: Review this part, the following members might get cleared unexpectedly
+ // when messages come in one after the other and are not contained all in a single
+ // datagram.
+ m_framesReadCount = 0;
+ m_sameKnxDstAddressIndicationCount = 0;
+ m_lastIndicationAddress = QKnxAddress();
+ while (m_socket && m_socket->state() == QUdpSocket::BoundState
+ && m_socket->hasPendingDatagrams()) {
+
+ QNetworkDatagram datagram = m_socket->receiveDatagram();
+ if (datagram.senderAddress() == m_ownAddress
+ || m_framesReadCount == 10 // incoming queue too big, signal busy
+ || m_sameKnxDstAddressIndicationCount == 5) {
+ continue; // discard packet
+ }
+
+ auto data = QKnxByteArray::fromByteArray(datagram.data());
+ const auto header = QKnxNetIpFrameHeader::fromBytes(data, 0);
+ if (!header.isValid() || header.totalSize() != data.size())
+ continue; // discard packet
+
+ m_framesReadCount++;
+ switch (header.serviceType()) {
+ case QKnxNetIp::ServiceType::RoutingIndication:
+ processRoutingIndication(QKnxNetIpFrame::fromBytes(data, 0));
+ break;
+ case QKnxNetIp::ServiceType::RoutingBusy:
+ processRoutingBusy(QKnxNetIpFrame::fromBytes(data, 0));
+ break;
+ case QKnxNetIp::ServiceType::RoutingLostMessage:
+ processRoutingLostMessage(QKnxNetIpFrame::fromBytes(data, 0));
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (m_framesReadCount == 10 || m_sameKnxDstAddressIndicationCount == 5) {
+ // incoming queue over 10 packets or over 5 packets with
+ // individual address destination.
+ auto routingBusyNetIpFrame = QKnxNetIpRoutingBusyProxy::builder()
+ .setDeviceState(QKnxNetIp::DeviceState::KnxFault)
+ .setRoutingBusyWaitTime(m_busyWaitTime)
+ .setRoutingBusyControl(0)
+ .create();
+ sendFrame(routingBusyNetIpFrame);
+ flowControlHandling(m_busyWaitTime);
+ }
+ });
+
+ // handle UDP socket errors
+ using overload = void (QUdpSocket::*)(QUdpSocket::SocketError);
+ QObject::connect(m_socket,
+ static_cast<overload>(&QUdpSocket::error), [&](QUdpSocket::SocketError) {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::Network,
+ m_socket->errorString());
+ });
+
+
+ // initialize UDP socket and bind
+ if (!m_socket->bind(QHostAddress(QHostAddress::AnyIPv4), m_multicastPort,
+ (QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint))) {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::Network,
+ QKnxNetIpRoutingInterface::tr("Could not bind endpoint: %1")
+ .arg(m_socket->errorString()));
+ }
+}
+
+void QKnxNetIpRoutingInterfacePrivate::restart()
+{
+ stop();
+ start();
+}
+
+void QKnxNetIpRoutingInterfacePrivate::stop()
+{
+ if (m_state == QKnxNetIpRoutingInterface::State::NotInit)
+ return;
+ cleanup();
+}
+
+void QKnxNetIpRoutingInterfacePrivate::cleanup()
+{
+ if (m_socket) {
+ m_socket->disconnect();
+ m_socket->deleteLater();
+ m_socket = nullptr;
+ }
+
+ if (m_busyTimer) {
+ (m_busyTimer)->stop();
+ (m_busyTimer)->disconnect();
+ (m_busyTimer)->deleteLater();
+ (m_busyTimer) = nullptr;
+ }
+ m_busyCounter = 0;
+ m_busyStage = BusyTimerStage::NotInit;
+
+ m_errorMessage = QString();
+ m_error = QKnxNetIpRoutingInterface::Error::None;
+}
+
+void QKnxNetIpRoutingInterfacePrivate::processRoutingIndication(const QKnxNetIpFrame &frame)
+{
+ QKnxNetIpRoutingIndicationProxy indication(frame);
+ if (!indication.isValid()) {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::KnxRouting,
+ QKnxNetIpRoutingInterface::tr("QKnxNetIp Routing Indication Message is not "
+ "correctly formed."));
+ return;
+ }
+
+ auto cemi = indication.cemi();
+ auto currentDstAddress = cemi.destinationAddress();
+ if (currentDstAddress.type() == QKnxAddress::Type::Individual
+ && m_lastIndicationAddress == currentDstAddress) {
+ m_sameKnxDstAddressIndicationCount++;
+ } else {
+ m_lastIndicationAddress = currentDstAddress;
+ m_sameKnxDstAddressIndicationCount = 0;
+ }
+
+ Q_Q(QKnxNetIpRoutingInterface);
+ emit q->routingIndicationReceived(frame, filterAction(cemi));
+}
+
+void QKnxNetIpRoutingInterfacePrivate::processRoutingBusy(const QKnxNetIpFrame &frame)
+{
+ QKnxNetIpRoutingBusyProxy busyMessage(frame);
+ if (!busyMessage.isValid())
+ return;
+
+ flowControlHandling(busyMessage.routingBusyWaitTime());
+
+ Q_Q(QKnxNetIpRoutingInterface);
+ emit q->routingBusyReceived(frame);
+}
+
+void QKnxNetIpRoutingInterfacePrivate::processRoutingLostMessage(const QKnxNetIpFrame &frame)
+{
+ QKnxNetIpRoutingLostMessageProxy lostMessage(frame);
+ if (!lostMessage.isValid()) {
+ errorOccurred(QKnxNetIpRoutingInterface::Error::KnxRouting,
+ QKnxNetIpRoutingInterface::tr("QKnxNetIp Routing Lost Message is not "
+ "correctly formed."));
+ return;
+ }
+
+ Q_Q(QKnxNetIpRoutingInterface);
+ emit q->routingLostCountReceived(frame);
+}
+
+bool QKnxNetIpRoutingInterfacePrivate::sendFrame(const QKnxNetIpFrame &frame)
+{
+ if (m_state != QKnxNetIpRoutingInterface::State::Routing)
+ return true; // no errors, only ignore the frame
+
+ return m_socket->writeDatagram(frame.bytes().toByteArray(),
+ m_multicastAddress,
+ m_multicastPort) != -1;
+}
+
+void QKnxNetIpRoutingInterfacePrivate::flowControlHandling(quint16 newBusyWaitTime)
+{
+ if (m_busyStage == BusyTimerStage::Wait) {
+ auto elapsedTime = (m_busyTimer->interval() - m_busyTimer->remainingTime());
+ if (elapsedTime >= 10)
+ m_busyCounter++;
+
+ if (m_busyTimer->remainingTime() < newBusyWaitTime) {
+ m_busyTimer->stop();
+ m_busyTimer->setInterval(newBusyWaitTime);
+ }
+ } else {
+ m_busyTimer->stop();
+ m_busyTimer->setInterval(newBusyWaitTime);
+ m_busyTimer->setSingleShot(true);
+ m_busyStage = BusyTimerStage::Wait;
+ }
+ m_busyTimer->start();
+ changeState(QKnxNetIpRoutingInterface::State::NeighborBusy);
+}
+
+QKnxNetIpRoutingInterface::FilterAction
+ QKnxNetIpRoutingInterfacePrivate::filterAction(const QKnxLinkLayerFrame &frame)
+{
+ auto dst = frame.destinationAddress();
+ auto extCtrlField = frame.extendedControlField();
+ auto hopCount = extCtrlField.hopCount();
+
+ if (dst.type() == QKnxAddress::Type::Group) {
+ auto gAdd = QKnxAddress::createGroup(dst.main(), dst.middle(), 0);
+ bool routingCondition = true;
+ if (m_routingMode == QKnxNetIpRoutingInterface::RoutingMode::FilterTableRouting)
+ routingCondition = m_filterTable.contains(gAdd);
+ if (m_routingMode == QKnxNetIpRoutingInterface::RoutingMode::BlockRouting)
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreTotally;
+ if (routingCondition && hopCount > 0 && hopCount < 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteDecremented;
+ if (routingCondition && hopCount == 0)
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreAcked;
+ if (hopCount == 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteUnmodified;
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreTotally;
+ }
+
+ // only gets here if destination address is individual
+ if (!m_individualAddress.isValid())
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreTotally;
+
+ auto ownAddress = m_individualAddress;
+ bool isLineCoupler = ownAddress.middle() != 0;
+ if (isLineCoupler) {
+ // sub-line to main line routing
+ bool notInOwnSubnetwork = (dst.main() != ownAddress.main())
+ || (dst.middle() == ownAddress.middle());
+ if (notInOwnSubnetwork) {
+ if (dst.sub() == 0)
+ // address to this line coupler
+ return QKnxNetIpRoutingInterface::FilterAction::ForwardLocally;
+ if (hopCount == 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteUnmodified;
+ if (hopCount > 0 && hopCount < 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteDecremented;
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreAcked;
+ }
+ // no main line to sub-line routing done by a KNXnet/IP RoutingInteface
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreTotally;
+ }
+
+ // backbone to main line
+ if (dst.main() == ownAddress.main()) {
+ if (dst.middle() == 0 && dst.sub() == 0)
+ // address to area coupler or backbone router.
+ return QKnxNetIpRoutingInterface::FilterAction::ForwardLocally;
+ if (hopCount == 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteUnmodified;
+ if (hopCount > 0 && hopCount < 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteDecremented;
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreAcked;
+ }
+
+ // main line to backbone routing
+ if (hopCount == 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteUnmodified;
+ if (hopCount > 0 && hopCount < 7)
+ return QKnxNetIpRoutingInterface::FilterAction::RouteDecremented;
+ return QKnxNetIpRoutingInterface::FilterAction::IgnoreAcked;
+}
+
+QT_END_NAMESPACE