summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew O'Doherty <andrew.odoherty@qt.io>2018-06-04 17:04:17 +0200
committerKarsten Heimrich <karsten.heimrich@qt.io>2018-08-16 14:06:43 +0000
commit879540023c534d23558ba440bd1a87e41db1968f (patch)
treeffeb9ba854d28b301a32214c7ba3d8a697894151
parent4328ea2df5dd74835b8eb0b55083b35f19da60a0 (diff)
Add QKnxNetIpRoutingInterface class and auto-test
The newly introduced class can be used for transmitting KNX frames between KNXnet/IP routers. Change-Id: Ief8a8731463e251367768f34894b04861f8b5835 Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
-rw-r--r--src/knx/netip/netip.pri11
-rw-r--r--src/knx/netip/qknxnetiproutinginterface.cpp526
-rw-r--r--src/knx/netip/qknxnetiproutinginterface.h137
-rw-r--r--src/knx/netip/qknxnetiproutinginterface_p.cpp411
-rw-r--r--src/knx/netip/qknxnetiproutinginterface_p.h121
-rw-r--r--src/knx/netip/qknxtestingrouter_p.cpp67
-rw-r--r--src/knx/netip/qknxtestingrouter_p.h66
-rw-r--r--tests/auto/auto.pro3
-rw-r--r--tests/auto/qknxnetiproutinginterface/qknxnetiproutinginterface.pro7
-rw-r--r--tests/auto/qknxnetiproutinginterface/tst_qknxnetiproutinginterface.cpp515
10 files changed, 1860 insertions, 4 deletions
diff --git a/src/knx/netip/netip.pri b/src/knx/netip/netip.pri
index aee78d9..6f566e6 100644
--- a/src/knx/netip/netip.pri
+++ b/src/knx/netip/netip.pri
@@ -52,7 +52,8 @@ PUBLIC_HEADERS += $$PWD/qknxnetip.h \
$$PWD/qknxnetipsessionauthenticate.h \
$$PWD/qknxnetipsessionstatus.h \
$$PWD/qknxnetiptimernotify.h \
- $$PWD/qknxnetipsecurewrapper.h
+ $$PWD/qknxnetipsecurewrapper.h \
+ $$PWD/qknxnetiproutinginterface.h
PRIVATE_HEADERS += \
$$PWD/qknxbuilderdata_p.h \
@@ -60,7 +61,8 @@ PRIVATE_HEADERS += \
$$PWD/qknxnetipserverdescriptionagent_p.h \
$$PWD/qknxnetipserverdiscoveryagent_p.h \
$$PWD/qknxnetipserverinfo_p.h \
- $$PWD/qknxbuilderdata_p.h
+ $$PWD/qknxnetiproutinginterface_p.h \
+ $$PWD/qknxtestingrouter_p.h
SOURCES += $$PWD/qknxnetip.cpp \
$$PWD/qknxnetipconfigdib.cpp \
@@ -114,4 +116,7 @@ SOURCES += $$PWD/qknxnetip.cpp \
$$PWD/qknxnetipsessionauthenticate.cpp \
$$PWD/qknxnetipsessionstatus.cpp \
$$PWD/qknxnetiptimernotify.cpp \
- $$PWD/qknxnetipsecurewrapper.cpp
+ $$PWD/qknxnetipsecurewrapper.cpp \
+ $$PWD/qknxnetiproutinginterface_p.cpp \
+ $$PWD/qknxnetiproutinginterface.cpp \
+ $$PWD/qknxtestingrouter_p.cpp
diff --git a/src/knx/netip/qknxnetiproutinginterface.cpp b/src/knx/netip/qknxnetiproutinginterface.cpp
new file mode 100644
index 0000000..e140080
--- /dev/null
+++ b/src/knx/netip/qknxnetiproutinginterface.cpp
@@ -0,0 +1,526 @@
+/******************************************************************************
+**
+** 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 "qknxnetiproutinginterface.h"
+#include "qknxnetiproutinginterface_p.h"
+#include "qknxnetip.h"
+#include "qknxnetipframe.h"
+#include "qknxnetiproutingbusy.h"
+#include "qknxnetiproutingindication.h"
+#include "qknxnetiproutinglostmessage.h"
+
+#include <QtCore/qrandom.h>
+#include <QtCore/qtimer.h>
+#include <QtNetwork/qnetworkdatagram.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QKnxNetIpRoutingInterface
+
+ \inmodule QtKnx
+ \ingroup qtknx-routing
+ \ingroup qtknx-netip
+
+ \brief The QKnxNetIpRoutingInterface class enables sending and receiving
+ routing KNXnet/IP packets to and from other KNXnet/IP routers.
+
+ KNXnet/IP routing is defined as a set of KNXnet/IP routers communicating
+ over a one-to-many communication relationship (multicast), in which KNX
+ data shall be transferred from one device to one or more other devices
+ simultaneously over an IP network. A set of KNXnet/IP routers can replace
+ KNX line and backbone couplers and connected main lines, allowing usage
+ of existing cabling (such as Ethernet) and faster transmission times (and
+ simultaneousness) between KNX subnets. The IP network acts as a fast
+ backbone that connects KNX subnets and is a high-speed replacement for
+ the KNX backbone.
+
+ The QKnxNetIpRoutingInterface is bound to a physical network interface
+ which is used for transmitting and receiving the routing frames. It also
+ requires the multicast address used by the KNX installation.
+
+ The following code sample illustrates how to use the
+ QKnxNetIpRoutingInterface to send and receive KNXnet/IP frames:
+
+ \code
+ QKnxNetIpRoutingInterface router;
+ router.setInterfaceAffinity(QNetworkInterface::interfaceFromName("eth0"));
+ router.setMulticastAddress(QHostAddress("224.0.23.32"));
+ router.start();
+
+ auto busyWaitTime = ...
+ auto busyControlField = ...
+
+ // Sending a routing indication to another router on the same IP network
+ auto routingBusyFrame = QKnxNetIpRoutingBusyProxy::builder()
+ .setDeviceState(QKnxNetIp::DeviceState::IpFault)
+ .setRoutingBusyWaitTime(busyWaitTime)
+ .setRoutingBusyControl(busyControlField)
+ .create();
+ router.sendRoutingBusy(routingBusyFrame);
+
+ // Processing routing indications received
+ QObject::connect(&router,
+ &QKnxNetIpRoutingInterface::routingIndicationReceived,
+ [](QKnxNetIpFrame frame) {
+ QKnxNetIpRoutingIndicationProxy indication(frame);
+ qInfo().noquote() << "Received routing indication:"
+ << indication.isValid();
+ });
+
+ \endcode
+
+ \sa QKnxLinkLayerFrame
+*/
+
+/*!
+ \typedef QKnxNetIpRoutingInterface::FilterTable
+
+ A synonym for QKnxNetIpRoutingInterface::QSet<QKnxAddress> which is the
+ type used to store the filter table of the routing interface.
+*/
+
+/*!
+ \enum QKnxNetIpRoutingInterface::State
+
+ This enum holds the state of the QKnxNetIpRoutingInterface.
+
+ \value NotInit
+ Router not started yet.
+ \value Routing
+ Router is listening for incoming messages and accepting messages
+ for sending.
+ \value NeighborBusy
+ A KNX router in the same network sent a busy message or the
+ QKnxNetIpRoutingInterface itself detected a busy situation.
+ \value Stop
+ Router has been explicitly stopped by the user.
+ \value Failure
+ Any error that occurs in the router shall set this state.
+*/
+
+/*!
+ \enum QKnxNetIpRoutingInterface::Error
+
+ This enum holds the state of the QKnxNetIpRoutingInterface.
+
+ \value None
+ No errors.
+ \value KnxRouting
+ Error found at the KNX routing protocol level.
+ \value Network
+ UDP/IP network error.
+*/
+
+/*!
+ \enum QKnxNetIpRoutingInterface::FilterAction
+
+ This enum holds the possible courses of action of a router in response to a
+ received layer service data unit (LSDU). An LSDU is a type of frame used by
+ the L_Data service belonging to the data link layer.
+
+ \value RouteUnmodified
+ The LSDU shall be routed from one subnetwork (on the primary side or the
+ secondary side) to the second subnetwork side (on the secondary side or
+ the primary side) without modification of the hop count value. The LSDU
+ shall only be routed to the second subnetwork after it has been
+ positively acknowledged on the first subnetwork.
+ \value RouteDecremented
+ The LSDU shall be routed from one subnetwork (on the primary
+ side or the secondary side) to the second subnetwork side (on the
+ secondary side or the primary side) after the hop count value is
+ decremented. The LSDU shall only be routed to the second subnetwork
+ after it has been positively acknowledged on the first subnetwork.
+ \value RouteLast
+ The LSDU shall be routed from the first subnetwork to the second
+ subnetwork and the hop count value shall be set to zero. The
+ telegram shall be acknowledged according to the Data Link Layer
+ specifications of the medium of the first subnetwork and then
+ the LSDU shall be routed to the second subnetwork.
+ \value ForwardLocally
+ The LSDU shall be processed to an NSDU and given to the local
+ network layer user after it has been acknowledged positively on
+ the subnetwork from which it has arrived.
+ \value IgnoreTotally
+ The LSDU shall be ignored; no acknowledgment shall be sent back
+ to the originator of the LSDU.
+ \value IgnoreAcked
+ The LSDU shall be ignored, nonetheless it shall be acknowledged
+ on the subnetwork from which it has arrived.
+*/
+
+/*!
+ \enum QKnxNetIpRoutingInterface::RoutingMode
+
+ This enum holds the possible parameterization actions that a router shall
+ apply on telegrams of a point-to-multipoint connectionless communication
+ mode with standard group addresses.
+
+ \value BlockRouting
+ No telegrams are allowed to be forwarded on the interface.
+ \value RouteAll
+ All telegrams are routed and the filter table is ignored.
+ \value FilterTableRouting
+ All telegrams with destination addresses not in the filter table are
+ blocked, the rest of telegrams are forwarded.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::routingIndicationReceived(QKnxNetIpFrame frame, QKnxNetIpRoutingInterface::FilterAction routingAction)
+
+ This signal is emitted when the KNXnet/IP router receives a routing indication
+ \a frame and specifies the action \a routingAction to be applied by the router.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::routingBusyReceived(QKnxNetIpFrame frame)
+
+ This signal is emitted when the KNXnet/IP router receives a routing busy
+ \a frame.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::routingLostCountReceived(QKnxNetIpFrame frame)
+
+ This signal is emitted when the KNXnet/IP router receives a routing lost
+ count message held in \a frame.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::routingIndicationSent(QKnxNetIpFrame frame)
+
+ This signal is emitted when the KNXnet/IP router has finished sending the
+ routing indication \a frame.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::routingBusySent(QKnxNetIpFrame frame)
+
+ This signal is emitted when the KNXnet/IP router has finished sending the
+ routing busy \a frame.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::routingLostCountSent(QKnxNetIpFrame frame)
+
+ This signal is emitted when the KNXnet/IP router has finished sending the
+ routing lost count \a frame.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::stateChanged(QKnxNetIpRoutingInterface::State state)
+
+ This signal is emitted when the KNXnet/IP router transitions to a different
+ \a state.
+*/
+
+/*!
+ \fn void QKnxNetIpRoutingInterface::errorOccurred(QKnxNetIpRoutingInterface::Error error, QString errorString)
+
+ This signal is emitted when the KNXnet/IP router encounters the error \a
+ error that is described by \a errorString.
+*/
+
+/*!
+ Returns the current state of the KNX routing interface.
+*/
+QKnxNetIpRoutingInterface::State QKnxNetIpRoutingInterface::state() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_state;
+}
+
+/*!
+ Returns a QString describing the latest known error that occurred in the
+ KNX routing interface.
+*/
+QString QKnxNetIpRoutingInterface::errorString() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_errorMessage;
+}
+
+/*!
+ Returns the filter table used by the routing algorithm.
+*/
+QKnxNetIpRoutingInterface::FilterTable QKnxNetIpRoutingInterface::filterTable() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_filterTable;
+}
+
+/*!
+ Sets the filter table used by the routing algorithm to \a table.
+ */
+void QKnxNetIpRoutingInterface::setFilterTable(const QKnxNetIpRoutingInterface::FilterTable &table)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+ d->m_filterTable = table;
+ d->m_routingMode = RoutingMode::FilterTableRouting;
+}
+
+/*!
+ Returns the routing mode.
+*/
+QKnxNetIpRoutingInterface::RoutingMode QKnxNetIpRoutingInterface::routingMode() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_routingMode;
+}
+
+/*!
+ Sets the interface routing mode to \a mode.
+ */
+void QKnxNetIpRoutingInterface::setRoutingMode(QKnxNetIpRoutingInterface::RoutingMode mode)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+ d->m_routingMode = mode;
+}
+
+/*!
+ Returns an error code that describes the last error that occurred in the
+ KNX routing interface.
+*/
+QKnxNetIpRoutingInterface::Error QKnxNetIpRoutingInterface::error() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_error;
+}
+
+/*!
+ Constructs a KNXnet/IP routing interface with the parent \a parent.
+*/
+QKnxNetIpRoutingInterface::QKnxNetIpRoutingInterface(QObject *parent)
+ : QObject(*new QKnxNetIpRoutingInterfacePrivate, parent)
+{}
+
+/*!
+ Returns the current network interface used by this KNX routing instance.
+ */
+QNetworkInterface QKnxNetIpRoutingInterface::interfaceAffinity() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_iface;
+}
+
+/*!
+ Searches for the network interface that is bound to \a address that the
+ QKnxNetIpRoutingInterface instance shall use.
+ */
+void QKnxNetIpRoutingInterface::setInterfaceAffinity(const QHostAddress &address)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &iface : interfaces) {
+ if (iface.addressEntries().isEmpty())
+ continue;
+
+ auto ifaceAddress = iface.addressEntries().first().ip();
+ if (address != ifaceAddress)
+ continue;
+
+ if (!iface.flags().testFlag(QNetworkInterface::IsRunning)
+ || !iface.flags().testFlag(QNetworkInterface::CanMulticast)) {
+ break;
+ }
+
+ d->m_iface = iface;
+ return;
+ }
+
+ d->errorOccurred(Error::Network, tr("Could not set an affinity to any interface"));
+}
+
+/*!
+ Sets the network interface \a iface that the QNetworkInterface instance
+ shall use.
+ */
+void QKnxNetIpRoutingInterface::setInterfaceAffinity(const QNetworkInterface &iface)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+ if (iface.flags().testFlag(QNetworkInterface::IsRunning))
+ d->m_iface = iface;
+ else
+ d->errorOccurred(Error::Network, tr("Could not set an affinity to any interface"));
+}
+
+/*!
+ Returns the multicast address used by the QKnxNetIpRoutingInterface.
+ */
+QHostAddress QKnxNetIpRoutingInterface::multicastAddress() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_multicastAddress;
+}
+
+/*!
+ Sets the multicast address to \a address.
+ */
+void QKnxNetIpRoutingInterface::setMulticastAddress(const QHostAddress &address)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+
+ auto isIPv4 = false;
+ address.toIPv4Address(&isIPv4);
+ if (!isIPv4 || !address.isMulticast())
+ return;
+
+ d->m_multicastAddress = address;
+}
+
+/*!
+ Returns the routing interface's individual address.
+*/
+QKnxAddress QKnxNetIpRoutingInterface::individualAddress() const
+{
+ Q_D(const QKnxNetIpRoutingInterface);
+ return d->m_individualAddress;
+}
+
+/*!
+ Sets the routing interface's individual address to \a address.
+*/
+void QKnxNetIpRoutingInterface::setIndividualAddress(const QKnxAddress &address)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+ if (!address.isCouplerOrRouter()) {
+ d->errorOccurred(QKnxNetIpRoutingInterface::Error::KnxRouting, tr("Could not set "
+ "individual address."));
+ } else {
+ d->m_individualAddress = address;
+ }
+}
+
+/*!
+ Multicasts the routing indication \a frame through the network interface
+ associated with the QKnxNetIpRoutingInterface.
+ */
+void QKnxNetIpRoutingInterface::sendRoutingIndication(const QKnxNetIpFrame &frame)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+
+ if (d->m_state != QKnxNetIpRoutingInterface::State::Routing)
+ return;
+
+ QKnxNetIpRoutingIndicationProxy indication(frame);
+ if (!indication.isValid())
+ return;
+
+ if (!d->sendFrame(frame)) {
+ d->errorOccurred(QKnxNetIpRoutingInterface::Error::KnxRouting, tr("Could not send routing "
+ "indication."));
+ } else {
+ emit routingIndicationSent(frame);
+ }
+}
+
+/*!
+ Multicasts the routing indication \a linkFrame through the network interface
+ associated with the QKnxNetIpRoutingInterface.
+ */
+void QKnxNetIpRoutingInterface::sendRoutingIndication(const QKnxLinkLayerFrame &linkFrame)
+{
+ auto netIpFrame = QKnxNetIpRoutingIndicationProxy::builder()
+ .setCemi(linkFrame)
+ .create();
+ sendRoutingIndication(netIpFrame);
+}
+
+/*!
+ Multicasts the routing busy message containing \a frame through the
+ network interface associated with the QKnxNetIpRoutingInterface.
+*/
+void QKnxNetIpRoutingInterface::sendRoutingBusy(const QKnxNetIpFrame &frame)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+
+ if (d->m_state != QKnxNetIpRoutingInterface::State::Routing)
+ return;
+
+ QKnxNetIpRoutingBusyProxy routingBusy(frame);
+ if (!routingBusy.isValid())
+ return;
+
+ if (!d->sendFrame(frame)) {
+ d->errorOccurred(QKnxNetIpRoutingInterface::Error::KnxRouting, tr("Could not send routing "
+ "busy."));
+ } else {
+ emit routingBusySent(frame);
+ }
+}
+
+/*!
+ Multicasts the routing lost message \a frame through the network interface
+ associated with the QKnxNetIpRoutingInterface.
+*/
+void QKnxNetIpRoutingInterface::sendRoutingLostMessage(const QKnxNetIpFrame &frame)
+{
+ Q_D(QKnxNetIpRoutingInterface);
+
+ if (d->m_state != QKnxNetIpRoutingInterface::State::Routing)
+ return;
+
+ QKnxNetIpRoutingLostMessageProxy lostMessageFrame(frame);
+ if (!lostMessageFrame.isValid())
+ return;
+
+ if (!d->sendFrame(frame)) {
+ d->errorOccurred(QKnxNetIpRoutingInterface::Error::KnxRouting, tr("Could not send routing "
+ "lost count."));
+ } else {
+ emit routingLostCountSent(frame);
+ }
+}
+
+/*!
+ Signals the QKnxNetIpRoutingInterface to start listening for messages
+ received and accept sending messages.
+*/
+void QKnxNetIpRoutingInterface::start()
+{
+ Q_D(QKnxNetIpRoutingInterface);
+ if (d->m_state == State::NotInit)
+ d->start();
+ else if (d->m_state == State::Failure || d->m_state == State::Stop)
+ d->restart();
+}
+
+/*!
+ Stops the QKnxNetIpRoutingInterface. No messages can be received and/or
+ sent to the network.
+*/
+void QKnxNetIpRoutingInterface::stop()
+{
+ Q_D(QKnxNetIpRoutingInterface);
+ d->stop();
+ d->changeState(QKnxNetIpRoutingInterface::State::Stop);
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetiproutinginterface.h b/src/knx/netip/qknxnetiproutinginterface.h
new file mode 100644
index 0000000..39560f6
--- /dev/null
+++ b/src/knx/netip/qknxnetiproutinginterface.h
@@ -0,0 +1,137 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef QKNXNETIPROUTINGINTERFACE_H
+#define QKNXNETIPROUTINGINTERFACE_H
+
+#include <QtKnx/qknxaddress.h>
+#include <QtKnx/qknxnetipframe.h>
+#include <QtKnx/qknxlinklayerframe.h>
+#include <QtKnx/qtknxglobal.h>
+
+#include <QtNetwork/qhostaddress.h>
+#include <QtNetwork/qnetworkinterface.h>
+#include <QtNetwork/qudpsocket.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxNetIpRoutingInterfacePrivate;
+class Q_KNX_EXPORT QKnxNetIpRoutingInterface : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(QKnxNetIpRoutingInterface)
+ Q_DECLARE_PRIVATE(QKnxNetIpRoutingInterface)
+
+public:
+ using FilterTable = QSet<QKnxAddress>;
+
+ enum class State : quint8
+ {
+ NotInit,
+ Routing,
+ NeighborBusy,
+ Stop,
+ Failure
+ };
+ Q_ENUM(State)
+ QKnxNetIpRoutingInterface::State state() const;
+
+ enum class Error : quint8
+ {
+ None,
+ KnxRouting,
+ Network
+ };
+ Q_ENUM(Error)
+
+ QString errorString() const;
+ QKnxNetIpRoutingInterface::Error error() const;
+
+ enum class FilterAction : quint8
+ {
+ RouteUnmodified,
+ RouteDecremented,
+ RouteLast,
+ ForwardLocally,
+ IgnoreTotally,
+ IgnoreAcked
+ };
+ Q_ENUM(FilterAction)
+
+ enum class RoutingMode : quint8
+ {
+ BlockRouting,
+ RouteAll,
+ FilterTableRouting
+ };
+
+ QKnxNetIpRoutingInterface(QObject *parent = nullptr);
+ ~QKnxNetIpRoutingInterface() = default;
+
+ RoutingMode routingMode() const;
+ void setRoutingMode(RoutingMode mode);
+
+ FilterTable filterTable() const;
+ void setFilterTable(const FilterTable &table);
+
+ QNetworkInterface interfaceAffinity() const;
+ void setInterfaceAffinity(const QHostAddress &address);
+ void setInterfaceAffinity(const QNetworkInterface &iface);
+
+ QHostAddress multicastAddress() const;
+ void setMulticastAddress(const QHostAddress &address);
+
+ QKnxAddress individualAddress() const;
+ void setIndividualAddress(const QKnxAddress &address);
+
+public Q_SLOTS:
+ void sendRoutingIndication(const QKnxLinkLayerFrame &linkFrame);
+ void sendRoutingIndication(const QKnxNetIpFrame &frame);
+ void sendRoutingBusy(const QKnxNetIpFrame &frame);
+ void sendRoutingLostMessage(const QKnxNetIpFrame &frame);
+
+ void start();
+ void stop();
+
+Q_SIGNALS:
+ void routingIndicationSent(QKnxNetIpFrame frame);
+ void routingBusySent(QKnxNetIpFrame frame);
+ void routingLostCountSent(QKnxNetIpFrame frame);
+
+ void routingIndicationReceived(QKnxNetIpFrame frame, QKnxNetIpRoutingInterface::FilterAction action);
+ void routingBusyReceived(QKnxNetIpFrame frame);
+ void routingLostCountReceived(QKnxNetIpFrame frame);
+
+ void stateChanged(QKnxNetIpRoutingInterface::State state);
+ void errorOccurred(QKnxNetIpRoutingInterface::Error error, QString errorString);
+};
+
+QT_END_NAMESPACE
+
+#endif
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
diff --git a/src/knx/netip/qknxnetiproutinginterface_p.h b/src/knx/netip/qknxnetiproutinginterface_p.h
new file mode 100644
index 0000000..2b1648a
--- /dev/null
+++ b/src/knx/netip/qknxnetiproutinginterface_p.h
@@ -0,0 +1,121 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef QKNXNETIPROUTINGINTERFACE_P_H
+#define QKNXNETIPROUTINGINTERFACE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt KNX API. It exists for the convenience
+// of the Qt KNX implementation. This header file may change from version
+// to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qobject_p.h>
+#include <QtCore/qtimer.h>
+
+#include <QtKnx/qknxnetip.h>
+#include <QtKnx/qknxnetipframe.h>
+#include <QtKnx/qknxnetiproutinginterface.h>
+
+#include <QtNetwork/qnetworkdatagram.h>
+#include <QtNetwork/qnetworkinterface.h>
+#include <QtNetwork/qudpsocket.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxNetIpRoutingInterfacePrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QKnxNetIpRoutingInterface)
+public:
+ QKnxNetIpRoutingInterfacePrivate() = default;
+ ~QKnxNetIpRoutingInterfacePrivate() = default;
+
+ void start();
+ void restart();
+ void stop();
+
+ void cleanup();
+
+ void processRoutingIndication(const QKnxNetIpFrame &frame);
+ void processRoutingBusy(const QKnxNetIpFrame &frame);
+ void processRoutingLostMessage(const QKnxNetIpFrame &frame);
+
+ bool sendFrame(const QKnxNetIpFrame &frame);
+
+ void flowControlHandling(quint16 newBusyWaitTime);
+
+ void changeState(QKnxNetIpRoutingInterface::State state);
+ void errorOccurred(QKnxNetIpRoutingInterface::Error error, const QString &errorString);
+
+ QKnxNetIpRoutingInterface::FilterAction filterAction(const QKnxLinkLayerFrame &frame);
+
+ QUdpSocket *m_socket { nullptr };
+
+ QKnxAddress m_individualAddress;
+
+ quint16 m_framesReadCount;
+ QKnxAddress m_lastIndicationAddress;
+ quint16 m_sameKnxDstAddressIndicationCount;
+
+ QNetworkInterface m_iface;
+ QHostAddress m_multicastAddress { QLatin1String(QKnxNetIp::Constants::MulticastAddress) };
+ quint16 m_multicastPort { QKnxNetIp::Constants::DefaultPort };
+ QHostAddress m_ownAddress;
+
+ QKnxNetIpRoutingInterface::State m_state { QKnxNetIpRoutingInterface::State::NotInit };
+
+ quint16 m_busyWaitTime { QKnxNetIp::RoutingBusyWaitTime };
+ QTimer *m_busyTimer { nullptr };
+
+ enum class BusyTimerStage : quint8
+ {
+ NotInit,
+ Wait,
+ RandomWait,
+ SlowDuration,
+ DecrementBusyCounter
+ };
+ BusyTimerStage m_busyStage { BusyTimerStage::NotInit };
+ quint32 m_busyCounter { 0 };
+
+ QKnxNetIpRoutingInterface::Error m_error { QKnxNetIpRoutingInterface::Error::None };
+ QString m_errorMessage;
+
+ QKnxNetIpRoutingInterface::FilterTable m_filterTable;
+ QKnxNetIpRoutingInterface::RoutingMode m_routingMode { QKnxNetIpRoutingInterface::RoutingMode::BlockRouting };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/netip/qknxtestingrouter_p.cpp b/src/knx/netip/qknxtestingrouter_p.cpp
new file mode 100644
index 0000000..22302c3
--- /dev/null
+++ b/src/knx/netip/qknxtestingrouter_p.cpp
@@ -0,0 +1,67 @@
+/******************************************************************************
+**
+** 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 "qknxtestingrouter_p.h"
+#include "qknxnetiproutinginterface_p.h"
+
+#include <QtNetwork/qudpsocket.h>
+#include <QtNetwork/qhostaddress.h>
+
+QT_BEGIN_NAMESPACE
+
+TestingRouter *TestingRouter::instance()
+{
+ static TestingRouter routerInterface;
+ return &routerInterface;
+}
+
+void TestingRouter::emitReadyRead(bool readAllPackets)
+{
+ auto socket = m_router->m_socket;
+ Q_ASSERT(socket);
+
+ // By default the QKnxNetIpRoutingInterface gets the IP of the interface. A dummy
+ // address is assigned to trick the router into not discarding packets that
+ // have the same address as the interface used by the QKnxNetIpRoutingInterface
+ if (readAllPackets)
+ m_router->m_ownAddress = QHostAddress(16843010); // setting ip 1.1.1.2
+ socket->waitForReadyRead(1);
+}
+
+void TestingRouter::setRouterInstance(QKnxNetIpRoutingInterfacePrivate *router)
+{
+ m_router = router;
+}
+
+QKnxNetIpRoutingInterfacePrivate *TestingRouter::routerInstance()
+{
+ return m_router;
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxtestingrouter_p.h b/src/knx/netip/qknxtestingrouter_p.h
new file mode 100644
index 0000000..b6d0b76
--- /dev/null
+++ b/src/knx/netip/qknxtestingrouter_p.h
@@ -0,0 +1,66 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef QKNXTESTINGROUTER_P_H
+#define QKNXTESTINGROUTER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt KNX API. It exists for the convenience
+// of the Qt KNX implementation. This header file may change from version
+// to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxNetIpRoutingInterfacePrivate;
+class Q_AUTOTEST_EXPORT TestingRouter
+{
+public:
+ static TestingRouter *instance();
+
+ void emitReadyRead(bool readAllPackets = true);
+
+ QKnxNetIpRoutingInterfacePrivate *routerInstance();
+ void setRouterInstance(QKnxNetIpRoutingInterfacePrivate *router);
+
+private:
+ TestingRouter() = default;
+ QKnxNetIpRoutingInterfacePrivate *m_router { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index a598ec0..a2b49ba 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -45,4 +45,5 @@ SUBDIRS += cmake \
qknxnetiproutingsystembroadcast \
qknxnetipsecuredservicefamiliesdib \
qknxnetipsessionrequest \
- qknxnetipsessionresponse
+ qknxnetipsessionresponse \
+ qknxnetiproutinginterface
diff --git a/tests/auto/qknxnetiproutinginterface/qknxnetiproutinginterface.pro b/tests/auto/qknxnetiproutinginterface/qknxnetiproutinginterface.pro
new file mode 100644
index 0000000..11f421d
--- /dev/null
+++ b/tests/auto/qknxnetiproutinginterface/qknxnetiproutinginterface.pro
@@ -0,0 +1,7 @@
+TARGET = tst_qknxnetiproutinginterface
+
+QT = core testlib knx network knx-private
+CONFIG += testcase c++11
+
+CONFIG -= app_bundle
+SOURCES += tst_qknxnetiproutinginterface.cpp
diff --git a/tests/auto/qknxnetiproutinginterface/tst_qknxnetiproutinginterface.cpp b/tests/auto/qknxnetiproutinginterface/tst_qknxnetiproutinginterface.cpp
new file mode 100644
index 0000000..aa20035
--- /dev/null
+++ b/tests/auto/qknxnetiproutinginterface/tst_qknxnetiproutinginterface.cpp
@@ -0,0 +1,515 @@
+/******************************************************************************
+**
+** 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 <QtKnx/qknxnetipframe.h>
+#include <QtKnx/qknxlinklayerframebuilder.h>
+#include <QtKnx/qknxnetiproutinginterface.h>
+#include <QtKnx/qknxnetiproutingbusy.h>
+#include <QtKnx/qknxnetiproutingindication.h>
+#include <QtKnx/qknxnetiproutinglostmessage.h>
+
+#include <QtKnx/private/qknxnetiproutinginterface_p.h>
+#include <QtKnx/private/qknxtestingrouter_p.h>
+#include <QtKnx/private/qknxtpdufactory_p.h>
+
+#include <QtCore/qdebug.h>
+#include <QtNetwork/qnetworkdatagram.h>
+#include <QtNetwork/qnetworkinterface.h>
+#include <QtNetwork/qudpsocket.h>
+#include <QtTest>
+
+Q_DECLARE_METATYPE(QKnxAddress)
+
+#ifdef QT_BUILD_INTERNAL
+
+class tst_QKnxNetIpRoutingInterface : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QKnxNetIpRoutingInterface();
+
+private slots:
+ void initTestCase();
+ void cleanup();
+ void test_udp_sockets();
+ void test_network_interface();
+ void test_multicast_address();
+ void test_routing();
+ void test_routing_sends_indications();
+ void test_routing_receives_indications();
+ void test_routing_receives_busy();
+ void test_routing_busy_sent_packets_same_individual_address();
+ void test_routing_filter();
+ void test_routing_filter_data();
+
+private:
+ void simulateFramesReceived(const QKnxNetIpFrame &netIpFrame, int numFrames = 1);
+
+ bool runTests { true };
+ int kMulticastPort { 3671 };
+
+ QNetworkInterface kIface;
+ QHostAddress kMulticastAddress { QString("224.0.23.12") };
+
+ QKnxNetIpRoutingInterface m_routingInterface;
+};
+
+tst_QKnxNetIpRoutingInterface::tst_QKnxNetIpRoutingInterface()
+{
+ // look for one interface running and able to multicast
+
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ QNetworkInterface foundIface;
+ for (const auto &iface : interfaces) {
+ if (iface.addressEntries().isEmpty())
+ continue;
+
+ if (!iface.flags().testFlag(QNetworkInterface::IsRunning)
+ || !iface.flags().testFlag(QNetworkInterface::CanMulticast)) {
+ continue;
+ }
+
+ kIface = iface;
+ }
+}
+
+void tst_QKnxNetIpRoutingInterface::initTestCase()
+{
+ // don't run test case if this check fails
+ runTests = kIface.flags().testFlag(QNetworkInterface::IsRunning)
+ && kIface.flags().testFlag(QNetworkInterface::CanMulticast);
+
+ if (!runTests)
+ return;
+
+ auto interfaceIndividualAddress = QKnxAddress::createIndividual(1, 1, 0);
+ m_routingInterface.setIndividualAddress(interfaceIndividualAddress);
+ m_routingInterface.setInterfaceAffinity(kIface);
+}
+
+void tst_QKnxNetIpRoutingInterface::cleanup()
+{
+ if (!runTests)
+ return;
+
+ m_routingInterface.disconnect();
+ m_routingInterface.stop();
+}
+
+void tst_QKnxNetIpRoutingInterface::test_network_interface()
+{
+ if (!runTests)
+ return;
+
+ bool errorEmitted = false;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::errorOccurred,
+ [&](QKnxNetIpRoutingInterface::Error error, QString errorString){
+ QCOMPARE(error, QKnxNetIpRoutingInterface::Error::Network);
+ QCOMPARE(error, m_routingInterface.error());
+ QCOMPARE(errorString, m_routingInterface.errorString());
+ QCOMPARE(m_routingInterface.state(), QKnxNetIpRoutingInterface::State::Failure);
+ errorEmitted = true;
+ });
+ {
+ // setting invalid interface should cause error
+ auto m_iface = QNetworkInterface();
+ m_routingInterface.setInterfaceAffinity(m_iface);
+ QVERIFY(errorEmitted);
+ }
+ {
+ // setting invalid interface using IP address of the interface
+ errorEmitted = false;
+ m_routingInterface.setInterfaceAffinity(QHostAddress("2.2.2.2"));
+ QVERIFY(errorEmitted);
+ }
+ {
+ // local host address does not allow multicast
+ errorEmitted = false;
+ m_routingInterface.setInterfaceAffinity(QHostAddress(QHostAddress::LocalHost));
+ QVERIFY(errorEmitted);
+ }
+ {
+ // setting a valid interface no signal emitted
+ errorEmitted = false;
+ m_routingInterface.setInterfaceAffinity(kIface);
+ QVERIFY(!errorEmitted);
+ QCOMPARE(kIface.index(), m_routingInterface.interfaceAffinity().index());
+ }
+}
+
+void tst_QKnxNetIpRoutingInterface::test_multicast_address()
+{
+ if (!runTests)
+ return;
+
+ {
+ // default multicast address
+ QKnxNetIpRoutingInterface m_router;
+ QCOMPARE(m_router.multicastAddress(), kMulticastAddress);
+ }
+ {
+ // setting an invalid multicast address shouldn't work
+ QKnxNetIpRoutingInterface m_router;
+ auto kMulticastAddress = QHostAddress(QHostAddress::LocalHost);
+ m_router.setMulticastAddress(kMulticastAddress);
+ QVERIFY(m_router.multicastAddress() != kMulticastAddress);
+ }
+ {
+ // setting a valid multicast address
+ QKnxNetIpRoutingInterface m_router;
+ auto address = QHostAddress("224.0.55.55");
+ m_router.setMulticastAddress(address);
+ QCOMPARE(m_router.multicastAddress(), address);
+ }
+}
+
+void tst_QKnxNetIpRoutingInterface::test_udp_sockets()
+{
+ if (!runTests)
+ return;
+
+ QUdpSocket receiver;
+ QSignalSpy recvSpy(&receiver, SIGNAL(readyRead()));
+ receiver.bind(QHostAddress(QHostAddress::AnyIPv4),
+ kMulticastPort,
+ (QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint));
+ receiver.joinMulticastGroup(kMulticastAddress);
+ QList<QByteArray> datagrams = QList<QByteArray>()
+ << QByteArray("0123")
+ << QByteArray("4567")
+ << QByteArray("89ab")
+ << QByteArray("cdef");
+
+ QUdpSocket sender;
+ QSignalSpy bytesspy(&sender, SIGNAL(bytesWritten(qint64)));
+ for (const QByteArray &datagram : datagrams) {
+ QNetworkDatagram dgram(datagram, kMulticastAddress , kMulticastPort);
+ sender.writeDatagram(dgram);
+ }
+
+ QCOMPARE(bytesspy.count(), 4);
+
+ receiver.waitForReadyRead(1);
+ for (int i = 0; i < 4; ++i) {
+ QCOMPARE(receiver.pendingDatagramSize(), 4);
+ QCOMPARE(receiver.readDatagram(0, 0), qint64(0));
+ QCOMPARE(recvSpy.count(), 1);
+ }
+}
+
+QKnxNetIpFrame dummyRoutingIndication(QKnxAddress dst = QKnxAddress::createIndividual(1,1,1),
+ quint8 hopCount = 6)
+{
+ //auto dst = QKnxAddress::createIndividual(1,1,1);
+ auto tpdu = QKnxTpduFactory::Multicast::createGroupValueReadTpdu();
+ auto ctrl = QKnxControlField::builder()
+ .setFrameFormat(QKnxControlField::FrameFormat::Standard)
+ .setBroadcast(QKnxControlField::Broadcast::Domain)
+ .setPriority(QKnxControlField::Priority::Normal)
+ .create();
+
+ auto extCtrl = QKnxExtendedControlField::builder()
+ .setDestinationAddressType(dst.type())
+ .setHopCount(hopCount)
+ .create();
+
+ auto frame = QKnxLinkLayerFrame::builder()
+ .setControlField(ctrl)
+ .setExtendedControlField(extCtrl)
+ .setTpdu(tpdu)
+ .setDestinationAddress(dst)
+ .setSourceAddress({ QKnxAddress::Type::Individual, 0 })
+ .setMessageCode(QKnxLinkLayerFrame::MessageCode::DataIndication)
+ .setMedium(QKnx::MediumType::NetIP)
+ .createFrame();
+ return QKnxNetIpRoutingIndicationProxy::builder()
+ .setLinkLayerFrame(frame)
+ .create();
+}
+
+void tst_QKnxNetIpRoutingInterface::simulateFramesReceived(const QKnxNetIpFrame &netIpFrame, int numFrames)
+{
+ if (!runTests)
+ return;
+
+ QUdpSocket * s = new QUdpSocket();
+
+ s->bind(QHostAddress(QHostAddress::AnyIPv4), 0);
+ s->setMulticastInterface(kIface);
+
+ for (; numFrames > 0 ; --numFrames) {
+ QNetworkDatagram datagram(netIpFrame.bytes().toByteArray(),
+ kMulticastAddress, kMulticastPort);
+ s->writeDatagram(datagram);
+ }
+
+ // notify router socket to wait for any received packet
+ TestingRouter::instance()->emitReadyRead();
+
+ if (s) {
+ s->disconnect();
+ s->deleteLater();
+ }
+}
+
+void tst_QKnxNetIpRoutingInterface::test_routing()
+{
+ if (!runTests)
+ return;
+
+ bool stateChangedEmitted = false;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::stateChanged
+ , [&](QKnxNetIpRoutingInterface::State state) {
+ stateChangedEmitted = true;
+ QCOMPARE(state, QKnxNetIpRoutingInterface::State::Routing);
+ });
+
+ m_routingInterface.start();
+ QVERIFY(stateChangedEmitted);
+}
+
+
+void tst_QKnxNetIpRoutingInterface::test_routing_sends_indications()
+{
+ if (!runTests)
+ return;
+
+ m_routingInterface.start();
+
+ auto bytes = QKnxByteArray::fromHex("2900b4e000000002010000");
+ auto frameSent = QKnxLinkLayerFrame::builder()
+ .setData(bytes)
+ .setMedium(QKnx::MediumType::NetIP)
+ .createFrame();
+
+ bool indicationSentEmitted = false;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::routingIndicationSent
+ , [&](QKnxNetIpFrame frame) {
+ indicationSentEmitted = true;
+ QKnxNetIpRoutingIndicationProxy indicationSent(frame);
+ QVERIFY(indicationSent.isValid());
+ QCOMPARE(indicationSent.linkLayerFrame().bytes(), frameSent.bytes());
+ });
+ m_routingInterface.sendRoutingIndication(frameSent);
+ QVERIFY(indicationSentEmitted);
+
+ bool stateChangedEmitted = false;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::stateChanged
+ , [&](QKnxNetIpRoutingInterface::State state) {
+ stateChangedEmitted = true;
+ QCOMPARE(state, QKnxNetIpRoutingInterface::State::Stop);
+ });
+ m_routingInterface.stop();
+ QVERIFY(stateChangedEmitted);
+}
+
+// Test receiving routing indications!
+void tst_QKnxNetIpRoutingInterface::test_routing_receives_indications()
+{
+ if (!runTests)
+ return;
+
+ m_routingInterface.start();
+
+ bool indicationRcvEmitted = false;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::routingIndicationReceived
+ , [&](QKnxNetIpFrame frame
+ , QKnxNetIpRoutingInterface::FilterAction routingAction) {
+ indicationRcvEmitted = true;
+ QKnxNetIpRoutingIndicationProxy indicationRcv(frame);
+ QVERIFY(indicationRcv.isValid());
+ QCOMPARE(routingAction, QKnxNetIpRoutingInterface::FilterAction::RouteDecremented);
+ });
+ simulateFramesReceived(dummyRoutingIndication());
+ QVERIFY(indicationRcvEmitted);
+}
+
+void tst_QKnxNetIpRoutingInterface::test_routing_receives_busy()
+{
+ if (!runTests)
+ return;
+
+ // test receiving routing busy!
+ m_routingInterface.start();
+
+ bool indicationRcvEmitted = false;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::routingBusyReceived
+ , [&](QKnxNetIpFrame frame) {
+ indicationRcvEmitted = true;
+ QKnxNetIpRoutingBusyProxy busy(frame);
+ QVERIFY(busy.isValid());
+ });
+
+ auto routingBusyFrame = QKnxNetIpRoutingBusyProxy::builder()
+ .setDeviceState(QKnxNetIp::DeviceState::IpFault)
+ .setRoutingBusyWaitTime(50)
+ .setRoutingBusyControl(0)
+ .create();
+ simulateFramesReceived(routingBusyFrame);
+ QVERIFY(indicationRcvEmitted);
+}
+
+void tst_QKnxNetIpRoutingInterface::test_routing_busy_sent_packets_same_individual_address()
+{
+ if (!runTests)
+ return;
+
+ // test receiving routing indications!
+ m_routingInterface.start();
+
+ int indRecvCount = 0;
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::routingIndicationReceived
+ , [&](QKnxNetIpFrame frame
+ , QKnxNetIpRoutingInterface::FilterAction routingAction) {
+ QKnxNetIpRoutingIndicationProxy indicationRcv(frame);
+ QVERIFY(indicationRcv.isValid());
+ QCOMPARE(routingAction, QKnxNetIpRoutingInterface::FilterAction::RouteDecremented);
+ indRecvCount++;
+ });
+ simulateFramesReceived(dummyRoutingIndication(), 7);
+
+ // The 7th packet is ignored because there were 5 consecutive packets with
+ // the same individual address. Router shall send automatically a busy message
+ QCOMPARE(indRecvCount, 6);
+
+ QCOMPARE(TestingRouter::instance()->routerInstance()->m_framesReadCount, 6);
+ QCOMPARE(TestingRouter::instance()->routerInstance()->m_sameKnxDstAddressIndicationCount, 5);
+
+ QCOMPARE(m_routingInterface.state(), QKnxNetIpRoutingInterface::State::NeighborBusy);
+}
+
+void tst_QKnxNetIpRoutingInterface::test_routing_filter()
+{
+ if (!runTests)
+ return;
+
+ QFETCH(QKnxAddress, interfaceIndividualAddress);
+ QFETCH(QKnxNetIpRoutingInterface::FilterAction, expectedRoutingAction);
+ QFETCH(QKnxAddress, dst);
+ QFETCH(int, hopCount);
+ QFETCH(QKnxNetIpRoutingInterface::FilterTable, filterTable);
+
+ QKnxNetIpRoutingInterface m_routingInterface;
+ m_routingInterface.setIndividualAddress(interfaceIndividualAddress);
+ m_routingInterface.setInterfaceAffinity(kIface);
+ m_routingInterface.setFilterTable(filterTable);
+ m_routingInterface.start();
+
+ bool receivedIndication = false;
+
+ QObject::connect(&m_routingInterface, &QKnxNetIpRoutingInterface::routingIndicationReceived
+ , [&](QKnxNetIpFrame frame
+ , QKnxNetIpRoutingInterface::FilterAction routingAction) {
+ QKnxNetIpRoutingIndicationProxy indicationRcv(frame);
+ QVERIFY(indicationRcv.isValid());
+ QCOMPARE(routingAction, expectedRoutingAction);
+ receivedIndication = true;
+ });
+
+
+ auto frame = dummyRoutingIndication(dst, hopCount);
+ simulateFramesReceived(frame);
+ QVERIFY(receivedIndication);
+}
+
+void tst_QKnxNetIpRoutingInterface::test_routing_filter_data()
+{
+ QTest::addColumn<QKnxAddress>("interfaceIndividualAddress");
+ QTest::addColumn<QKnxAddress>("dst");
+ QTest::addColumn<int>("hopCount");
+ QTest::addColumn<QKnxNetIpRoutingInterface::FilterAction>("expectedRoutingAction");
+ QTest::addColumn<QKnxNetIpRoutingInterface::FilterTable>("filterTable");
+
+ QKnxNetIpRoutingInterface::FilterTable filterTable;
+ filterTable << QKnxAddress::createGroup(1,1,0);
+ QTest::newRow("RouterIndividual(1.1.0)_groupDestination(1.1.1)_hop6")
+ << QKnxAddress::createIndividual(1,1,0)
+ << QKnxAddress::createGroup(1,1,1)
+ << 6
+ << QKnxNetIpRoutingInterface::FilterAction::RouteDecremented
+ << filterTable;
+
+ QTest::newRow("destinationAddressFiltered")
+ << QKnxAddress::createIndividual(1,1,0)
+ << QKnxAddress::createGroup(1,2,1)
+ << 6
+ << QKnxNetIpRoutingInterface::FilterAction::IgnoreTotally
+ << filterTable;
+
+ QTest::newRow("RouterIndividual(1.1.0)_groupDestination(1.1.1)_hop0")
+ << QKnxAddress::createIndividual(1,1,0)
+ << QKnxAddress::createGroup(1,1,1)
+ << 0
+ << QKnxNetIpRoutingInterface::FilterAction::IgnoreAcked
+ << filterTable;
+
+ QTest::newRow("RouterIndividual(1.1.0)_individualDestination(1.1.1)_hop0")
+ << QKnxAddress::createIndividual(1,1,0)
+ << QKnxAddress::createIndividual(1,1,1)
+ << 0
+ << QKnxNetIpRoutingInterface::FilterAction::IgnoreAcked
+ << filterTable;
+
+ QTest::newRow("RouterIndividual(1.1.0)_individualDestination(1.1.1)_hop5")
+ << QKnxAddress::createIndividual(1,1,0)
+ << QKnxAddress::createIndividual(1,1,1)
+ << 5
+ << QKnxNetIpRoutingInterface::FilterAction::RouteDecremented
+ << filterTable;
+
+ QTest::newRow("RouterIndividual(1.1.0)_individualDestination(1.1.1)_hop5")
+ << QKnxAddress::createIndividual(1,1,0)
+ << QKnxAddress::createIndividual(1,1,0)
+ << 5
+ << QKnxNetIpRoutingInterface::FilterAction::ForwardLocally
+ << filterTable;
+}
+
+//TODO: test threshold for sending busy after incoming queue is
+// filled with 10 packets (queue should be able to hold until 30 messages)
+
+#else
+
+class tst_QKnxNetIpRoutingInterface : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase()
+ {
+ QSKIP("QKnxNetIpRoutingInterface methods aren't available to test");
+ }
+};
+
+#endif
+
+QTEST_MAIN(tst_QKnxNetIpRoutingInterface)
+
+#include "tst_qknxnetiproutinginterface.moc"