summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--examples/knx/device/device.pro18
-rw-r--r--examples/knx/device/deviceitem.cpp66
-rw-r--r--examples/knx/device/deviceitem.h70
-rw-r--r--examples/knx/device/main.cpp62
-rw-r--r--examples/knx/device/mainwindow.cpp387
-rw-r--r--examples/knx/device/mainwindow.h106
-rw-r--r--examples/knx/device/mainwindow.ui469
-rw-r--r--examples/knx/discoverer/main.cpp4
-rw-r--r--examples/knx/doc/device.qdoc165
-rw-r--r--examples/knx/doc/feature.qdoc144
-rw-r--r--examples/knx/doc/group.qdoc147
-rw-r--r--examples/knx/doc/images/device.pngbin0 -> 23533 bytes
-rw-r--r--examples/knx/doc/images/features.pngbin0 -> 24140 bytes
-rw-r--r--examples/knx/doc/images/group.pngbin0 -> 24802 bytes
-rw-r--r--examples/knx/feature/deviceitem.cpp66
-rw-r--r--examples/knx/feature/deviceitem.h70
-rw-r--r--examples/knx/feature/feature.pro18
-rw-r--r--examples/knx/feature/main.cpp62
-rw-r--r--examples/knx/feature/mainwindow.cpp401
-rw-r--r--examples/knx/feature/mainwindow.h113
-rw-r--r--examples/knx/feature/mainwindow.ui332
-rw-r--r--examples/knx/group/deviceitem.cpp66
-rw-r--r--examples/knx/group/deviceitem.h70
-rw-r--r--examples/knx/group/group.pro20
-rw-r--r--examples/knx/group/groupaddressvalidater.cpp67
-rw-r--r--examples/knx/group/groupaddressvalidater.h68
-rw-r--r--examples/knx/group/main.cpp62
-rw-r--r--examples/knx/group/mainwindow.cpp374
-rw-r--r--examples/knx/group/mainwindow.h106
-rw-r--r--examples/knx/group/mainwindow.ui402
-rw-r--r--examples/knx/knx.pro2
-rw-r--r--examples/knx/knxeditor/localdevicemanagement.cpp51
-rw-r--r--examples/knx/knxeditor/localdevicemanagement.h5
-rw-r--r--examples/knx/knxeditor/localdevicemanagement.ui61
-rw-r--r--examples/knx/knxeditor/mainwindow.cpp37
-rw-r--r--examples/knx/knxeditor/mainwindow.h4
-rw-r--r--examples/knx/knxeditor/mainwindow.ui13
-rw-r--r--examples/knx/knxeditor/tunneling.cpp55
-rw-r--r--examples/knx/knxeditor/tunneling.h8
-rw-r--r--examples/knx/knxeditor/tunneling.ui177
-rw-r--r--examples/knx/knxeditor/tunnelingfeatures.cpp64
-rw-r--r--examples/knx/knxeditor/tunnelingfeatures.h7
-rw-r--r--examples/knx/knxeditor/tunnelingfeatures.ui125
-rw-r--r--src/knx/doc/qtknx.qdocconf3
-rw-r--r--src/knx/dpt/qknx1bit.cpp2
-rw-r--r--src/knx/dpt/qknxdatapointtype.cpp6
-rw-r--r--src/knx/dpt/qknxdatapointtypefactory.cpp5
-rw-r--r--src/knx/ets/ets.pri4
-rw-r--r--src/knx/ets/manufacturers.cpp490
-rw-r--r--src/knx/ets/manufacturers.h47
-rw-r--r--src/knx/knx.pro1
-rw-r--r--src/knx/netip/netip.pri9
-rw-r--r--src/knx/netip/qknxbuilderdata_p.h35
-rw-r--r--src/knx/netip/qknxnetip.cpp63
-rw-r--r--src/knx/netip/qknxnetip.h17
-rw-r--r--src/knx/netip/qknxnetipdevicemanagement.cpp12
-rw-r--r--src/knx/netip/qknxnetipendpointconnection.cpp707
-rw-r--r--src/knx/netip/qknxnetipendpointconnection.h18
-rw-r--r--src/knx/netip/qknxnetipendpointconnection_p.h71
-rw-r--r--src/knx/netip/qknxnetiproutingindication.cpp2
-rw-r--r--src/knx/netip/qknxnetiproutingsystembroadcast.cpp2
-rw-r--r--src/knx/netip/qknxnetipsecureconfiguration.cpp472
-rw-r--r--src/knx/netip/qknxnetipsecureconfiguration.h106
-rw-r--r--src/knx/netip/qknxnetipsecureconfiguration_p.h68
-rw-r--r--src/knx/netip/qknxnetipsecurewrapper.cpp209
-rw-r--r--src/knx/netip/qknxnetipsecurewrapper.h22
-rw-r--r--src/knx/netip/qknxnetipserverdescriptionagent.cpp46
-rw-r--r--src/knx/netip/qknxnetipserverdescriptionagent.h1
-rw-r--r--src/knx/netip/qknxnetipserverdescriptionagent_p.h2
-rw-r--r--src/knx/netip/qknxnetipserverdiscoveryagent.cpp2
-rw-r--r--src/knx/netip/qknxnetipservicefamiliesdib.cpp23
-rw-r--r--src/knx/netip/qknxnetipservicefamiliesdib.h3
-rw-r--r--src/knx/netip/qknxnetipsessionauthenticate.cpp168
-rw-r--r--src/knx/netip/qknxnetipsessionauthenticate.h24
-rw-r--r--src/knx/netip/qknxnetipsessionrequest.cpp8
-rw-r--r--src/knx/netip/qknxnetipsessionresponse.cpp171
-rw-r--r--src/knx/netip/qknxnetipsessionresponse.h22
-rw-r--r--src/knx/netip/qknxnetiptimernotify.cpp171
-rw-r--r--src/knx/netip/qknxnetiptimernotify.h20
-rw-r--r--src/knx/netip/qknxnetiptunnel.cpp112
-rw-r--r--src/knx/netip/qknxnetiptunnel.h4
-rw-r--r--src/knx/qknxnamespace.h3
-rw-r--r--src/knx/ssl/qknxcryptographicengine.cpp494
-rw-r--r--src/knx/ssl/qknxcryptographicengine.h (renamed from src/knx/ssl/qknxcurve25519.h)93
-rw-r--r--src/knx/ssl/qknxcurve25519.cpp819
-rw-r--r--src/knx/ssl/qknxkeyring.cpp515
-rw-r--r--src/knx/ssl/qknxkeyring_p.h160
-rw-r--r--src/knx/ssl/qknxsecurekey.cpp389
-rw-r--r--src/knx/ssl/qknxsecurekey.h86
-rw-r--r--src/knx/ssl/qknxssl_openssl.cpp178
-rw-r--r--src/knx/ssl/qknxssl_p.h (renamed from src/knx/ssl/qknxcryptographicdata_p.h)43
-rw-r--r--src/knx/ssl/qssl.cpp191
-rw-r--r--src/knx/ssl/qssl.h114
-rw-r--r--src/knx/ssl/qssl_p.h65
-rw-r--r--src/knx/ssl/qsslsocket_openssl11_symbols_p.h2
-rw-r--r--src/knx/ssl/qsslsocket_openssl_symbols.cpp6
-rw-r--r--src/knx/ssl/qsslsocket_openssl_symbols_p.h3
-rw-r--r--src/knx/ssl/ssl.pri21
-rw-r--r--tests/auto/auto.pro6
-rw-r--r--tests/auto/qknxcryptographicengine/qknxcryptographicengine.pro2
-rw-r--r--tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp286
-rw-r--r--tests/auto/qknxdatapointtype/tst_qknxdatapointtype.cpp12
-rw-r--r--tests/auto/qknxnetipdescriptionresponse/tst_qknxnetipdescriptionresponse.cpp12
-rw-r--r--tests/auto/qknxnetiprouter/tst_qknxnetiprouter.cpp2
-rw-r--r--tests/auto/qknxnetipsearchrequest/tst_qknxnetipsearchrequest.cpp10
106 files changed, 9313 insertions, 1893 deletions
diff --git a/.qmake.conf b/.qmake.conf
index bbb67e5..b5e35ea 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -3,4 +3,4 @@ load(qt_build_config)
CONFIG += warning_clean
DEFINES += QT_NO_FOREACH
-MODULE_VERSION = 5.12.5
+MODULE_VERSION = 5.13.1
diff --git a/examples/knx/device/device.pro b/examples/knx/device/device.pro
new file mode 100644
index 0000000..c8510f1
--- /dev/null
+++ b/examples/knx/device/device.pro
@@ -0,0 +1,18 @@
+TEMPLATE = app
+TARGET = device
+INCLUDEPATH += .
+
+CONFIG += c++11
+QT += knx widgets network core
+
+FORMS += mainwindow.ui
+
+HEADERS += deviceitem.h \
+ mainwindow.h
+
+SOURCES += deviceitem.cpp \
+ main.cpp \
+ mainwindow.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/knx/device
+INSTALLS += target
diff --git a/examples/knx/device/deviceitem.cpp b/examples/knx/device/deviceitem.cpp
new file mode 100644
index 0000000..c353d1b
--- /dev/null
+++ b/examples/knx/device/deviceitem.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "deviceitem.h"
+
+DeviceItem::DeviceItem(const QKnxNetIpServerInfo &info)
+ : QStandardItem(info.deviceName())
+ , m_serverInfo(info)
+{}
+
+DeviceItem *DeviceItem::clone() const
+{
+ return new DeviceItem(m_serverInfo);
+}
+
+const QKnxNetIpServerInfo &DeviceItem::info() const
+{
+ return m_serverInfo;
+}
diff --git a/examples/knx/device/deviceitem.h b/examples/knx/device/deviceitem.h
new file mode 100644
index 0000000..5f59407
--- /dev/null
+++ b/examples/knx/device/deviceitem.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef DEVICEITEM_H
+#define DEVICEITEM_H
+
+#include <QtGui>
+#include <QtKnx>
+
+class DeviceItem : public QStandardItem
+{
+public:
+ DeviceItem(const QKnxNetIpServerInfo &info);
+ ~DeviceItem() = default;
+
+ DeviceItem *clone() const override;
+ const QKnxNetIpServerInfo &info() const;
+
+private:
+ QKnxNetIpServerInfo m_serverInfo;
+};
+
+#endif // DEVICEITEM_H
diff --git a/examples/knx/device/main.cpp b/examples/knx/device/main.cpp
new file mode 100644
index 0000000..e3e9a74
--- /dev/null
+++ b/examples/knx/device/main.cpp
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}
diff --git a/examples/knx/device/mainwindow.cpp b/examples/knx/device/mainwindow.cpp
new file mode 100644
index 0000000..54d3b6a
--- /dev/null
+++ b/examples/knx/device/mainwindow.cpp
@@ -0,0 +1,387 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include "deviceitem.h"
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ for (int i = 0; i < ui->communication->columnCount(); ++i)
+ ui->communication->resizeColumnToContents(i);
+
+ setupInterfaces();
+
+ auto treeWidget = new QTreeWidget;
+ treeWidget->setHeaderHidden(true);
+ treeWidget->setUniformRowHeights(true);
+
+ ui->propertyId->setModel(treeWidget->model());
+ ui->propertyId->setView(treeWidget);
+ on_interfaceObjectTypes_currentTextChanged(QStringLiteral("General"));
+
+ populateInterfaceObjectTypesComboBox();
+
+ connect(ui->actionExit, &QAction::triggered, this, &QApplication::quit);
+ connect(ui->actionClear, &QAction::triggered, ui->communication, &QTreeWidget::clear);
+
+ connect(&m_management, &QKnxNetIpDeviceManagement::connected, this, &MainWindow::onConnected);
+ connect(&m_management, &QKnxNetIpDeviceManagement::disconnected, this, &MainWindow::onDisconnected);
+ connect(&m_management, &QKnxNetIpDeviceManagement::frameReceived, this, &MainWindow::populateFrame);
+ connect(&m_management, &QKnxNetIpDeviceManagement::errorOccurred, this, &MainWindow::onErrorOccurred);
+
+ connect(ui->communication, &QTreeWidget::currentItemChanged, this,
+ [&](QTreeWidgetItem *current, QTreeWidgetItem * /* previuos */) {
+ m_current = current;
+ });
+
+ m_discoveryAgent.setTimeout(-1);
+ m_discoveryAgent.setSearchFrequency(6);
+ m_discoveryAgent.setDiscoveryMode(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1
+ | QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2);
+ connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::deviceDiscovered,
+ this, &MainWindow::onDeviceDiscovered);
+
+ m_discoveryAgent.start();
+}
+
+MainWindow::~MainWindow()
+{
+ m_discoveryAgent.stop();
+
+ delete ui;
+ ui = nullptr;
+ delete m_device;
+}
+
+void MainWindow::onConnected()
+{
+ toggleUi(true);
+ ui->connection->setText(tr("Disconnect"));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Successfully connected to: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::onDisconnected()
+{
+ toggleUi(false);
+ ui->connection->setText(tr("Connect"));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Successfully disconnected from: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::onDeviceDiscovered(QKnxNetIpServerInfo info)
+{
+ if (ui->devices->findText(info.deviceName()) == -1)
+ qobject_cast<QStandardItemModel*>(ui->devices->model())->appendRow(new DeviceItem(info));
+
+ if (m_management.state() == QKnxNetIpTunnel::State::Disconnected)
+ ui->devices->setEnabled(bool(ui->devices->count()));
+}
+
+void MainWindow::onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ const auto metaEnum = QMetaEnum::fromType<QKnxNetIpEndpointConnection::Error>();
+ m_last->setText(0, tr("Error occurred: %1 (%2).").arg(errorString)
+ .arg(metaEnum.valueToKey(int(error))));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::on_actionImport_triggered()
+{
+ const auto fileName = QFileDialog::getOpenFileName(this, tr("Import keyring file"),
+ QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).value(0),
+ tr("KNX keyring file (*.knxkeys)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ bool ok;
+ const auto password = QInputDialog::getText(this, tr("Import keyring file"),
+ tr("Keyring file password:"), QLineEdit::Normal, {}, &ok);
+ if (!ok || password.isEmpty())
+ return;
+
+ m_secureConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::DeviceManagement, fileName, password.toUtf8(), true);
+ on_devices_currentIndexChanged(ui->devices->currentIndex());
+}
+
+void MainWindow::on_devices_currentIndexChanged(int /* index */)
+{
+ delete m_device;
+ ui->secureConfigs->clear();
+
+ const auto model = qobject_cast<QStandardItemModel*>(ui->devices->model());
+ m_device = static_cast<DeviceItem *> (model->item(ui->devices->currentIndex()))->clone();
+
+ if (m_device) {
+ const auto deviceInfo = m_device->info();
+ for (int i = 0; i < m_secureConfigs.size(); ++i) {
+ const auto &config = m_secureConfigs[i];
+ if (deviceInfo.individualAddress() != config.host())
+ continue;
+
+ const auto ia = config.individualAddress();
+ ui->secureConfigs->addItem(tr("User ID: %1 (Individual Address: %2)")
+ .arg(config.userId())
+ .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i);
+ }
+ }
+
+ ui->secureConfigs->setEnabled(bool(ui->secureConfigs->count())
+ && ui->secureSession->isChecked());
+ ui->secureSession->setEnabled(bool(ui->secureConfigs->count()));
+}
+
+void MainWindow::on_interfaceObjectTypes_currentTextChanged(const QString &type)
+{
+ auto treeWidget = qobject_cast<QTreeWidget *>(ui->propertyId->view());
+ if (!treeWidget)
+ return;
+
+ // keep the 'General' property top level item
+ auto topLevelItem = treeWidget->takeTopLevelItem(0);
+ ui->propertyId->clear();
+ treeWidget->addTopLevelItem(topLevelItem);
+
+ int index = QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator(type.toLatin1());
+ if (index >= 0) {
+ const auto typeEnum = QKnxInterfaceObjectProperty::staticMetaObject.enumerator(index);
+ topLevelItem = new QTreeWidgetItem(treeWidget, { typeEnum.name() });
+ for (auto a = 0; a < typeEnum.keyCount(); ++a) {
+ auto item = new QTreeWidgetItem({ typeEnum.key(a) });
+ item->setData(0, Qt::UserRole, typeEnum.value(a));
+ topLevelItem->addChild(item);
+ }
+ topLevelItem->setFlags(topLevelItem->flags() &~Qt::ItemIsSelectable);
+ }
+
+ // select first sub item
+ treeWidget->expandItem(topLevelItem);
+ treeWidget->setCurrentItem(topLevelItem, 0);
+ ui->propertyId->setRootModelIndex(treeWidget->currentIndex());
+ ui->propertyId->setCurrentIndex(0);
+ treeWidget->setCurrentItem(treeWidget->invisibleRootItem(), 0);
+ ui->propertyId->setRootModelIndex(treeWidget->currentIndex());
+}
+
+void MainWindow::on_sendRead_clicked()
+{
+ const auto frame = QKnxDeviceManagementFrame::propertyReadBuilder()
+ .setObjectType(ui->interfaceObjectTypes->currentData().toInt())
+ .setObjectInstance(quint8(ui->objectInstance->value()))
+ .setProperty(quint16(ui->propertyId->currentData().toUInt()))
+ .setNumberOfElements(quint8(ui->numberOfElements->value()))
+ .setStartIndex(quint16(ui->startIndex->value()))
+ .createRequest();
+
+ populateFrame(frame);
+ m_management.sendFrame(frame);
+}
+
+void MainWindow::on_sendWrite_clicked()
+{
+ const auto frame = QKnxDeviceManagementFrame::propertyWriteBuilder()
+ .setObjectType(ui->interfaceObjectTypes->currentData().toInt())
+ .setObjectInstance(quint8(ui->objectInstance->value()))
+ .setProperty(quint16(ui->propertyId->currentData().toUInt()))
+ .setNumberOfElements(quint8(ui->numberOfElements->value()))
+ .setStartIndex(quint16(ui->startIndex->value()))
+ .createRequest(QKnxByteArray::fromHex(ui->data->text().toLatin1()));
+
+ populateFrame(frame);
+ m_management.sendFrame(frame);
+}
+
+void MainWindow::on_connection_clicked()
+{
+ if (ui->devices->count() <= 0)
+ return;
+
+ if (m_management.state() == QKnxNetIpTunnel::State::Connected)
+ return m_management.disconnectFromHost();
+
+ const auto list = ui->interfaces->currentData().toStringList();
+ m_management.setLocalAddress(QHostAddress(list.first()));
+ m_management.setSerialNumber(QKnxByteArray::fromHex(list.last().toLatin1()));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Establish connection to: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+
+ if (ui->secureSession->isChecked()) {
+ auto secureConfiguration = m_secureConfigs.value(ui->secureConfigs->currentData().toInt());
+ secureConfiguration.setKeepSecureSessionAlive(true);
+ m_management.setSecureConfiguration(secureConfiguration);
+ m_management.connectToHostEncrypted(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort());
+ } else {
+ m_management.connectToHost(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort(), QKnxNetIp::HostProtocol::UDP_IPv4);
+ }
+}
+
+void MainWindow::setupInterfaces()
+{
+ auto firstItem = new QStandardItem(tr("Interface: --Select One--"));
+ qobject_cast<QStandardItemModel*>(ui->interfaces->model())->appendRow(firstItem);
+ firstItem->setSelectable(false);
+
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &iface : interfaces) {
+ const auto addressEntries = iface.addressEntries();
+ for (int j = 0; j < addressEntries.size(); j++) {
+ const auto ip = addressEntries[j].ip();
+ if (ip.isLoopback() || ip.toIPv4Address() == 0)
+ continue;
+ ui->interfaces->addItem(iface.name() + ": " + ip.toString(),
+ QStringList { ip.toString(), iface.hardwareAddress().remove(QLatin1String(":")) });
+ }
+ }
+ ui->interfaces->setCurrentIndex(bool(ui->interfaces->count()));
+
+ connect(ui->interfaces, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int i) {
+ if (i < 0)
+ return;
+ m_discoveryAgent.stop();
+ m_discoveryAgent.setLocalAddress(QHostAddress(ui->interfaces->currentData()
+ .toStringList().first()));
+ m_discoveryAgent.start();
+ });
+}
+
+void MainWindow::toggleUi(bool value)
+{
+ ui->interfaceObjectTypes->setEnabled(value);
+ ui->objectInstance->setEnabled(value);
+ ui->propertyId->setEnabled(value);
+ ui->numberOfElements->setEnabled(value);
+ ui->startIndex->setEnabled(value);
+ ui->sendRead->setEnabled(value);
+ ui->data->setEnabled(value);
+ ui->sendWrite->setEnabled(value);
+ ui->interfaces->setDisabled(value);
+ ui->devices->setDisabled(value && bool(ui->devices->count()));
+}
+
+void MainWindow::populateInterfaceObjectTypesComboBox()
+{
+ const auto typeEnum = QMetaEnum::fromType<QKnxInterfaceObjectType::System>();
+ for (int i = 0; i < typeEnum.keyCount(); ++i)
+ ui->interfaceObjectTypes->addItem(typeEnum.key(i), typeEnum.value(i));
+}
+
+void MainWindow::populateFrame(const QKnxDeviceManagementFrame &frame)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+
+ if (frame.isNegativeConfirmation()) {
+ const auto metaEnum = QMetaEnum::fromType<QKnxNetIpCemiServer::Error>();
+ m_last->setText(0, tr("Received negative confirmation: Error (%1)")
+ .arg(metaEnum.valueToKey(int(frame.error()))));
+ return m_last->setFirstColumnSpanned(true);
+ }
+
+ auto metaEnum = QMetaEnum::fromType<QKnxDeviceManagementFrame::MessageCode>();
+ m_last->setText(1, metaEnum.valueToKey(int(frame.messageCode())));
+
+ metaEnum = QMetaEnum::fromType<QKnxInterfaceObjectType::System>();
+ const auto type = metaEnum.valueToKey(int(frame.objectType()));
+ m_last->setText(2, QString::fromLatin1(type));
+
+ m_last->setText(3, QString::number(frame.objectInstance()));
+
+ int index = QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator(type);
+ if (index >= 0) {
+ metaEnum = QKnxInterfaceObjectProperty::staticMetaObject.enumerator(index);
+ m_last->setText(4, metaEnum.valueToKey(int(frame.property())));
+ } else {
+ m_last->setText(4, QString::number(int(frame.property())));
+ }
+
+ m_last->setText(5, QString::number(frame.numberOfElements()));
+ m_last->setText(6, QString::number(frame.startIndex()));
+
+ auto bytes = frame.data().toHex();
+ m_last->setText(7, QString::fromLatin1(bytes.toByteArray(), bytes.size()));
+ bytes = frame.bytes().toHex();
+ m_last->setText(8, QString::fromLatin1(bytes.toByteArray(), bytes.size()));
+
+ if (m_current) {
+ const auto region = ui->communication->viewport()->visibleRegion();
+ const auto currentRect = ui->communication->visualItemRect(m_current);
+ if (region.contains({ currentRect.left(), currentRect.top() - currentRect.height() })
+ && region.contains(ui->communication->visualItemRect(m_last))) {
+ ui->communication->scrollToItem(m_last);
+ }
+ } else {
+ ui->communication->scrollToItem(m_last);
+ }
+}
diff --git a/examples/knx/device/mainwindow.h b/examples/knx/device/mainwindow.h
new file mode 100644
index 0000000..3676cc9
--- /dev/null
+++ b/examples/knx/device/mainwindow.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QtKnx>
+#include <QtWidgets>
+
+QT_BEGIN_NAMESPACE
+
+namespace Ui {
+ class MainWindow;
+}
+
+QT_END_NAMESPACE
+
+class DeviceItem;
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void onConnected();
+ void onDisconnected();
+ void onDeviceDiscovered(QKnxNetIpServerInfo info);
+ void onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString);
+
+ void on_actionImport_triggered();
+ void on_devices_currentIndexChanged(int index);
+ void on_interfaceObjectTypes_currentTextChanged(const QString &type);
+
+ void on_sendRead_clicked();
+ void on_sendWrite_clicked();
+ void on_connection_clicked();
+
+private:
+ void setupInterfaces();
+ void toggleUi(bool enable);
+ void populateInterfaceObjectTypesComboBox();
+ void populateFrame(const QKnxDeviceManagementFrame &frame);
+
+private:
+ Ui::MainWindow *ui { nullptr };
+
+ DeviceItem *m_device { nullptr };
+ QTreeWidgetItem *m_last { nullptr };
+ QTreeWidgetItem *m_current { nullptr };
+
+ QKnxNetIpDeviceManagement m_management;
+ QKnxNetIpServerDiscoveryAgent m_discoveryAgent;
+ QVector<QKnxNetIpSecureConfiguration> m_secureConfigs;
+};
+
+#endif // MAINWINDOW_H
diff --git a/examples/knx/device/mainwindow.ui b/examples/knx/device/mainwindow.ui
new file mode 100644
index 0000000..6e5791b
--- /dev/null
+++ b/examples/knx/device/mainwindow.ui
@@ -0,0 +1,469 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>867</width>
+ <height>416</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>KNX local device management example</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,0,0,1,0,0">
+ <item row="0" column="0">
+ <widget class="QLabel" name="interfaceObjectTypeLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Interface object type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="interfaceObjectTypes">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="objectInstanceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Object instance:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QSpinBox" name="objectInstance">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="propertyIdLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Property ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QComboBox" name="propertyId">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="7">
+ <widget class="QPushButton" name="sendRead">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Read</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="numberOfElementsLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Number of elements:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QSpinBox" name="numberOfElements">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>15</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="startIndexLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Start index:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="startIndex">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>4095</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="dataLabel">
+ <property name="text">
+ <string>Data:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="3">
+ <widget class="QLineEdit" name="data">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QPushButton" name="sendWrite">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Write</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="communication">
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerShowSortIndicator" stdset="0">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>#</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Message code</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Interface object type</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Object instance</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Property ID</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Number of elements</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Start index</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Data</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Frame</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0,1,0,1,0,0">
+ <item>
+ <widget class="QLabel" name="interfaceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Interface:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="interfaces">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="deviceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="devices">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="secureSession">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use secure session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="secureConfigs">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacerRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="connection">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>867</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <widget class="QMenu" name="menuImport">
+ <property name="title">
+ <string>Import</string>
+ </property>
+ <addaction name="actionImport"/>
+ </widget>
+ <addaction name="menuImport"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuEdit">
+ <property name="title">
+ <string>Edit</string>
+ </property>
+ <addaction name="actionClear"/>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuEdit"/>
+ </widget>
+ <action name="actionExit">
+ <property name="text">
+ <string>Exit</string>
+ </property>
+ </action>
+ <action name="actionImport">
+ <property name="text">
+ <string>KNX keyring file...</string>
+ </property>
+ </action>
+ <action name="actionClear">
+ <property name="text">
+ <string>Clear telegram list</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>secureSession</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>secureConfigs</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>516</x>
+ <y>391</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>634</x>
+ <y>393</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/knx/discoverer/main.cpp b/examples/knx/discoverer/main.cpp
index cce5a52..3cf564b 100644
--- a/examples/knx/discoverer/main.cpp
+++ b/examples/knx/discoverer/main.cpp
@@ -58,7 +58,7 @@
#include <QtNetwork/QNetworkInterface>
-static QString familieToString(QKnxNetIp::ServiceFamily id)
+static QString familyToString(QKnxNetIp::ServiceFamily id)
{
switch (id) {
case QKnxNetIp::ServiceFamily::Core:
@@ -214,7 +214,7 @@ int main(int argc, char *argv[])
qInfo().noquote() << QString::fromLatin1(" Supported services:");
for (const auto service : services) {
qInfo().noquote() << QString::fromLatin1(" KNXnet/IP %1, Version: %2")
- .arg(familieToString(service.ServiceFamily)).arg(service.ServiceFamilyVersion);
+ .arg(familyToString(service.ServiceFamily)).arg(service.ServiceFamilyVersion);
}
const auto dib = server.extendedHardware();
diff --git a/examples/knx/doc/device.qdoc b/examples/knx/doc/device.qdoc
new file mode 100644
index 0000000..33cd8e6
--- /dev/null
+++ b/examples/knx/doc/device.qdoc
@@ -0,0 +1,165 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \example device
+ \title KNX Local Device Management Example
+ \ingroup qtknx-examples
+
+ \brief A KNX client for handling KNXnet/IP local device management.
+
+ \image device.png "KNX local device management example"
+
+ The KNX local device management example user interface contains
+ various Qt Widgets, most prominently a \l QTreeWidget to display
+ detailed information about sent and received KNX property read,
+ write, and confirmation messages.
+
+ To get started, users select one of the network interfaces on their
+ machine in the \uicontrol {Interface} field. Once that is done, the
+ application automatically performs a continuous search for available
+ KNXnet/IP devices and displays the results in the \uicontrol {Device}
+ field.
+
+ To connect to a KNXnet/IP device, either the one preselected in the
+ \uicontrol {Device} can be used or a different one must be chosen from
+ the list of discovered devices.
+
+ The application also supports KNXnet/IP secure devices, but to be able to
+ connect to such a device, a KNX ETS keyring file needs to be imported via
+ the \uicontrol {File} menu.
+
+ Once a connection is successfully established, the user has the
+ possibility to issue a property read or write command to an existing
+ \c {Interface Object} in the management server. The example currently
+ limits the possible read or write operations that can be used to
+ \c {M_PropRead.req} and \c {m_PropWrite.req} messages.
+
+ The application consists of two classes:
+
+ \list
+ \li \c MainWindow is a \l QMainWindow that renders the general layout
+ of the application.
+ \li \c DeviceItem is a \l QStandardItem that is used to display
+ and store information about discovered KNXnet/IP devices.
+ \endlist
+
+ \section1 Main Window Class Definition and Implementation
+
+ \quotefromfile device/mainwindow.h
+ \skipto class MainWindow :
+ \printuntil /^\}/
+
+ The \c MainWindow class uses a \l QKnxNetIpServerDiscoveryAgent instance
+ that allows discovering KNXnet/IP servers by sending continuous search
+ requests to the network that the client is connected to.
+ It also saves an instance of the \l QKnxNetIpDeviceManagement used to
+ establish the connection to the KNXnet/Ip device and a list of imported
+ KNX \l QKnxNetIpSecureConfiguration secure configurations.
+
+ There are signal handlers installed for every signal emitted by the
+ \l QKnxNetIpDeviceManagement. Here is an example of the setup capturing the
+ signals emitted when an event occurs targeting the KNXnet/IP connection.
+ In this specific example, we will also see how to set up the KNXnet/IP
+ device management connection and connect to the KNXnet/IP device:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto QKnxNetIpDeviceManagement::connected
+ \printuntil MainWindow::onErrorOccurred);
+ \dots
+ \skipto /^\}/
+ \printuntil /^\}/
+ \skipto void MainWindow::on_connection_clicked
+ \printuntil /^\}/
+
+ The \c QKnxNetIpServerDiscoveryAgent is initialized and started after
+ the user interface has been set up and the necessary device management
+ connections have been made. Here is the code snippet doing it:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto m_discoveryAgent.setTimeout
+ \printuntil /^\}/
+
+ There is a signal handler installed for the device discovered signal
+ emitted by the discovery agent.
+ When the signal \l QKnxNetIpServerDiscoveryAgent::deviceDiscovered is
+ triggered, the function \c MainWindow::onDeviceDiscovered() is called.
+ It adds a new device item to the \uicontrol {Device} if it is not already
+ there.
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto void MainWindow::onDeviceDiscovered
+ \printuntil /^\}/
+
+ At this point, users can select one of the available devices to establish
+ a connection, create and send the different types of management frames. The
+ \c MainWindow::on_devices_currentIndexChanged method saves the selected
+ KNXnet/IP device in the the \c MainWindow instance.
+
+ To populate the \uicontrol {Interface object type} drop-down box with
+ available KNX interface object types, the function
+ \c MainWindow::populateInterfaceObjectTypesComboBox is invoked. When the
+ application user switches the currently selected object type to a different
+ one, the \uicontrol {Property ID} gets updated with matching property IDs
+ for the active interface object:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::on_interfaceObjectTypes_currentTextChanged
+ \printuntil {
+ \dots
+ \skipto QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator
+ \printuntil topLevelItem->setFlags
+ \skipto /^ {4}\}/
+ \printuntil /^ {4}\}/
+ \dots
+ \skipto /^\}/
+ \printuntil /^\}/
+
+ In this last example, when the user triggers the \uicontrol {Read} button,
+ the function \c MainWindow::on_sendRead_clicked() is called and a frame is
+ sent to the KNX network to trigger a property read from the given interface
+ object type and for selected property ID:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::on_sendRead_clicked
+ \printuntil /^\}/
+
+ \section1 The Main Function
+
+ The KNX local device management example \c main() function does not have
+ any special handling. It looks like the main function for any Qt app:
+
+ \quotefromfile group/main.cpp
+ \skipto #include
+ \printuntil /^\}/
+*/
diff --git a/examples/knx/doc/feature.qdoc b/examples/knx/doc/feature.qdoc
new file mode 100644
index 0000000..8daa8e5
--- /dev/null
+++ b/examples/knx/doc/feature.qdoc
@@ -0,0 +1,144 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \example feature
+ \title KNX Tunneling Features Example
+ \ingroup qtknx-examples
+
+ \brief A KNX client for handling KNXnet/IP tunneling features.
+
+ \image features.png "KNX tunneling features example"
+
+ The \e{KNX Tunneling Features} user interface contains various
+ Qt Widgets, most prominently a \l QTreeWidget to display detailed
+ information about sent and received KNX tunneling feature messages.
+
+ To get started, users select one of the network interfaces on their
+ machine in the \uicontrol {Interface} field. Once that is done, the
+ application automatically performs a continuous search for available
+ KNXnet/IP devices and displays the results in the \uicontrol {Device}
+ field.
+
+ To connect to a KKXnet/IP device, either the one preselected in the
+ \uicontrol {Device} can be used or a different one must be chosen from
+ the list of discovered devices.
+
+ The application also supports KNXnet/IP secure devices, but to be able to
+ connect to such a device, a KNX ETS keyring file needs to be imported via
+ the \uicontrol {File} menu.
+
+ Once a connection is successfully established, the user has the possibility
+ to send tunneling feature get or set commands and to monitor incoming tunnel
+ feature info messages.
+
+ The application consists of two classes:
+
+ \list
+ \li \c MainWindow is a \l QMainWindow that renders the general layout
+ of the application.
+ \li \c DeviceItem is a \l QStandardItem that is used to display
+ and store information about discovered KNXnet/IP devices.
+ \endlist
+
+ \section1 Main Window Class Definition and Implementation
+
+ \quotefromfile feature/mainwindow.h
+ \skipto class MainWindow :
+ \printuntil /^\}/
+
+ The \c MainWindow class uses a \l QKnxNetIpServerDiscoveryAgent instance
+ that allows discovering KNXnet/IP servers by sending continuous search
+ requests to the network that the client is connected to. It also saves an
+ instance of the \l QKnxNetIpTunnel used to establish the connection to the
+ KNX network and a list of imported KNX \l QKnxNetIpSecureConfiguration
+ secure configurations.
+
+ There are signal handlers installed for every signal emitted by the
+ \l QKnxNetIpTunnel. Here is an example of the setup capturing the
+ signals emitted when an event occurs targeting the KNXnet/IP connection.
+ In this specific example, we will also see how to set up the KNXnet/IP
+ tunnel and connect to the KNXnet/IP device:
+
+ \quotefromfile feature/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto QKnxNetIpTunnel::connected
+ \printuntil MainWindow::onErrorOccurred);
+ \dots
+ \skipto /^\}/
+ \printuntil /^\}/
+ \skipto void MainWindow::on_connection_clicked
+ \printuntil /^\}/
+
+ The \c QKnxNetIpServerDiscoveryAgent is initialized and started after the
+ user interface has been set up and the necessary tunnel connections have
+ been made. Here is the code snippet doing it:
+
+ \quotefromfile feature/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto m_discoveryAgent.setTimeout
+ \printuntil /^\}/
+
+ There is a signal handler installed for the device discovered signal
+ emitted by the discovery agent.
+ When the signal \l QKnxNetIpServerDiscoveryAgent::deviceDiscovered is
+ triggered, the function \c MainWindow::onDeviceDiscovered() is called.
+ It adds a new device item to the \uicontrol {Device} if it is not already
+ there.
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto void MainWindow::onDeviceDiscovered
+ \printuntil /^\}/
+
+ At this point, users can select one of the available devices to establish
+ a connection, create and send the different types of frames or monitor the
+ KNX tunneling feature info messages.
+ The \c MainWindow::on_devices_currentIndexChanged method saves the selected
+ KNXnet/IP device in the the \c MainWindow instance.
+
+ In this last example, after the user has triggered the \uicontrol {Read}
+ button and a valid tunneling feature response was received, the function
+ \c MainWindow::setText() is called and the frame content gets extracted
+ and visually processed:
+
+ \quotefromfile feature/mainwindow.cpp
+ \skipto MainWindow::setText
+ \printuntil /^\}/
+
+ \section1 The Main Function
+
+ The KNX tunneling feature example \c main() function does not have any
+ special handling. It looks like the main function for any Qt application:
+
+ \quotefromfile feature/main.cpp
+ \skipto #include
+ \printuntil /^\}/
+*/
diff --git a/examples/knx/doc/group.qdoc b/examples/knx/doc/group.qdoc
new file mode 100644
index 0000000..9d9dd15
--- /dev/null
+++ b/examples/knx/doc/group.qdoc
@@ -0,0 +1,147 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \example group
+ \title KNX Group Communication Example
+ \ingroup qtknx-examples
+
+ \brief A KNX client for handling KNXnet/IP group communication.
+
+ \image group.png "KNX group communication example"
+
+ The KNX group communication example user interface contains various
+ Qt Widgets, most prominently a \l QTreeWidget to display detailed
+ information about sent and received KNX group messages.
+
+ To get started, users select one of the network interfaces on their
+ machine in the \uicontrol {Interface} field. Once that is done, the
+ application automatically performs a continuous search for available
+ KNXnet/IP devices and displays the results in the \uicontrol {Device}
+ field.
+
+ To connect to a KKXnet/IP device, either the one preselected in the
+ \uicontrol {Device} can be used or a different one must be chosen from
+ the list of discovered devices.
+
+ The application also supports KNXnet/IP secure devices, but to be able to
+ connect to such a device a KNX ETS keyring file needs to be imported via
+ the \uicontrol {File} menu.
+
+ Once a connection is successfully established, the user has the
+ possibility to just monitor KNX group messages or issue a read or write
+ command to an existing KNX group address. The example currently limits
+ the possible datapoint types that can be used to DPT1.
+
+ The application consists of three classes:
+
+ \list
+ \li \c MainWindow is a \l QMainWindow that renders the general layout
+ of the application.
+ \li \c GroupAddressValidator is a \l QValidator that provides
+ validation of group address input text.
+ \li \c DeviceItem is a \l QStandardItem that is used to display
+ and store information about discovered KNXnet/IP devices.
+ \endlist
+
+ \section1 Main Window Class Definition and Implementation
+
+ \quotefromfile group/mainwindow.h
+ \skipto class MainWindow :
+ \printuntil /^\}/
+
+ The \c MainWindow class uses a \l QKnxNetIpServerDiscoveryAgent instance
+ that allows discovering KNXnet/IP servers by sending continuous search
+ requests to the network that the client is connected to. It also saves an
+ instance of the \l QKnxNetIpTunnel used to establish the connection to the
+ KNX network and a list of imported KNX \l QKnxNetIpSecureConfiguration
+ secure configurations.
+
+ There are signal handlers installed for every signal emitted by the
+ \l QKnxNetIpTunnel. Here is an example of the setup capturing the
+ signals emitted when an event occurs targeting the KNXnet/IP connection.
+ In this specific example, we will also see how to set up the KNXnet/IP
+ tunnel and connect to the KNXnet/IP device:
+
+ \quotefromfile group/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto QKnxNetIpTunnel::connected
+ \printuntil MainWindow::onErrorOccurred);
+ \dots
+ \skipto /^\}/
+ \printuntil /^\}/
+ \skipto void MainWindow::on_connection_clicked
+ \printuntil /^\}/
+
+ The \c QKnxNetIpServerDiscoveryAgent is initialized and started after the
+ user interface has been set up and the necessary tunnel connections have
+ been made. Here is the code snippet doing it:
+
+ \quotefromfile group/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto m_discoveryAgent.setTimeout
+ \printuntil /^\}/
+
+ There is a signal handler installed for the device discovered signal
+ emitted by the discovery agent.
+ When the signal \l QKnxNetIpServerDiscoveryAgent::deviceDiscovered is
+ triggered, the function \c MainWindow::onDeviceDiscovered() is called.
+ It adds a new device item to the \uicontrol {Device} if it is not already
+ there.
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto void MainWindow::onDeviceDiscovered
+ \printuntil /^\}/
+
+ At this point, users can select one of the available devices to establish
+ a connection, create and send the different types of frames or simply
+ monitor the KNX bus traffic.
+ The \c MainWindow::on_devices_currentIndexChanged method saves the selected
+ KNXnet/IP device in the the \c MainWindow instance.
+
+ In this last example, when the user triggers the \uicontrol {Read} button,
+ the function \c MainWindow::on_sendRead_clicked() is called and a frame is
+ sent to the KNX network to trigger a group value read for the given group
+ address:
+
+ \quotefromfile group/mainwindow.cpp
+ \skipto MainWindow::on_sendRead_clicked
+ \printuntil /^\}/
+
+ \section1 The Main Function
+
+ The KNX group communication example \c main() function does not have any
+ special handling. It looks like the main function for any Qt app:
+
+ \quotefromfile group/main.cpp
+ \skipto #include
+ \printuntil /^\}/
+*/
diff --git a/examples/knx/doc/images/device.png b/examples/knx/doc/images/device.png
new file mode 100644
index 0000000..7df2d17
--- /dev/null
+++ b/examples/knx/doc/images/device.png
Binary files differ
diff --git a/examples/knx/doc/images/features.png b/examples/knx/doc/images/features.png
new file mode 100644
index 0000000..b4e0f25
--- /dev/null
+++ b/examples/knx/doc/images/features.png
Binary files differ
diff --git a/examples/knx/doc/images/group.png b/examples/knx/doc/images/group.png
new file mode 100644
index 0000000..849e835
--- /dev/null
+++ b/examples/knx/doc/images/group.png
Binary files differ
diff --git a/examples/knx/feature/deviceitem.cpp b/examples/knx/feature/deviceitem.cpp
new file mode 100644
index 0000000..c353d1b
--- /dev/null
+++ b/examples/knx/feature/deviceitem.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "deviceitem.h"
+
+DeviceItem::DeviceItem(const QKnxNetIpServerInfo &info)
+ : QStandardItem(info.deviceName())
+ , m_serverInfo(info)
+{}
+
+DeviceItem *DeviceItem::clone() const
+{
+ return new DeviceItem(m_serverInfo);
+}
+
+const QKnxNetIpServerInfo &DeviceItem::info() const
+{
+ return m_serverInfo;
+}
diff --git a/examples/knx/feature/deviceitem.h b/examples/knx/feature/deviceitem.h
new file mode 100644
index 0000000..5f59407
--- /dev/null
+++ b/examples/knx/feature/deviceitem.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef DEVICEITEM_H
+#define DEVICEITEM_H
+
+#include <QtGui>
+#include <QtKnx>
+
+class DeviceItem : public QStandardItem
+{
+public:
+ DeviceItem(const QKnxNetIpServerInfo &info);
+ ~DeviceItem() = default;
+
+ DeviceItem *clone() const override;
+ const QKnxNetIpServerInfo &info() const;
+
+private:
+ QKnxNetIpServerInfo m_serverInfo;
+};
+
+#endif // DEVICEITEM_H
diff --git a/examples/knx/feature/feature.pro b/examples/knx/feature/feature.pro
new file mode 100644
index 0000000..f75ad7a
--- /dev/null
+++ b/examples/knx/feature/feature.pro
@@ -0,0 +1,18 @@
+TEMPLATE = app
+TARGET = feature
+INCLUDEPATH += .
+
+CONFIG += c++11
+QT += knx widgets network core
+
+FORMS += mainwindow.ui
+
+HEADERS += deviceitem.h \
+ mainwindow.h
+
+SOURCES += deviceitem.cpp \
+ main.cpp \
+ mainwindow.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/knx/feature
+INSTALLS += target
diff --git a/examples/knx/feature/main.cpp b/examples/knx/feature/main.cpp
new file mode 100644
index 0000000..e3e9a74
--- /dev/null
+++ b/examples/knx/feature/main.cpp
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}
diff --git a/examples/knx/feature/mainwindow.cpp b/examples/knx/feature/mainwindow.cpp
new file mode 100644
index 0000000..7e44327
--- /dev/null
+++ b/examples/knx/feature/mainwindow.cpp
@@ -0,0 +1,401 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include "deviceitem.h"
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ for (int i = 0; i < ui->communication->columnCount(); ++i)
+ ui->communication->resizeColumnToContents(i);
+
+ setupInterfaces();
+ populateServiceTypesComboBox();
+ populateInterfaceFeaturesComboBox();
+
+ connect(ui->actionExit, &QAction::triggered, this, &QApplication::quit);
+ connect(ui->actionClear, &QAction::triggered, ui->communication, &QTreeWidget::clear);
+
+ connect(&m_tunnel, &QKnxNetIpTunnel::connected, this, &MainWindow::onConnected);
+ connect(&m_tunnel, &QKnxNetIpTunnel::disconnected, this, &MainWindow::onDisconnected);
+ connect(&m_tunnel, &QKnxNetIpTunnel::tunnelingFeatureInfoReceived, this,
+ &MainWindow::onFeatureInfoReceived);
+ connect(&m_tunnel, &QKnxNetIpTunnel::tunnelingFeatureResponseReceived, this,
+ &MainWindow::onFeatureResponseReceived);
+ connect(&m_tunnel, &QKnxNetIpTunnel::errorOccurred, this, &MainWindow::onErrorOccurred);
+
+ connect(ui->communication, &QTreeWidget::currentItemChanged, this,
+ [&](QTreeWidgetItem *current, QTreeWidgetItem * /* previuos */) {
+ m_current = current;
+ });
+
+ m_discoveryAgent.setTimeout(-1);
+ m_discoveryAgent.setSearchFrequency(6);
+ connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::deviceDiscovered, this,
+ &MainWindow::onDeviceDiscovered);
+ m_discoveryAgent.setDiscoveryMode(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2);
+
+ m_discoveryAgent.start();
+}
+
+MainWindow::~MainWindow()
+{
+ m_discoveryAgent.stop();
+
+ delete ui;
+ ui = nullptr;
+ delete m_device;
+}
+
+void MainWindow::onConnected()
+{
+ toggleUi(true);
+ ui->connection->setText(tr("Disconnect"));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Successfully connected to: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::onDisconnected()
+{
+ toggleUi(false);
+ ui->connection->setText(tr("Connect"));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Successfully disconnected from: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::onDeviceDiscovered(QKnxNetIpServerInfo info)
+{
+ if (ui->devices->findText(info.deviceName()) == -1)
+ qobject_cast<QStandardItemModel*>(ui->devices->model())->appendRow(new DeviceItem(info));
+
+ if (m_tunnel.state() == QKnxNetIpTunnel::State::Disconnected)
+ ui->devices->setEnabled(bool(ui->devices->count()));
+}
+
+void MainWindow::onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ const auto metaEnum = QMetaEnum::fromType<QKnxNetIpEndpointConnection::Error>();
+ m_last->setText(0, tr("Error occurred: %1 (%2).").arg(errorString)
+ .arg(metaEnum.valueToKey(int(error))));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::on_actionImport_triggered()
+{
+ const auto fileName = QFileDialog::getOpenFileName(this, tr("Import keyring file"),
+ QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).value(0),
+ tr("KNX keyring file (*.knxkeys)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ bool ok;
+ const auto password = QInputDialog::getText(this, tr("Import keyring file"),
+ tr("Keyring file password:"), QLineEdit::Normal, {}, &ok);
+ if (!ok || password.isEmpty())
+ return;
+
+ m_secureConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::DeviceManagement, fileName, password.toUtf8(), true);
+ for (auto &config : m_secureConfigs)
+ config.setIndividualAddress({});
+
+ m_secureConfigs += QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::Tunneling, fileName, password.toUtf8(), true);
+
+ on_devices_currentIndexChanged(ui->devices->currentIndex());
+}
+
+void MainWindow::on_devices_currentIndexChanged(int /* index */)
+{
+ delete m_device;
+ ui->secureConfigs->clear();
+
+ const auto model = qobject_cast<QStandardItemModel*>(ui->devices->model());
+ m_device = static_cast<DeviceItem *> (model->item(ui->devices->currentIndex()))->clone();
+
+ if (m_device) {
+ const auto deviceInfo = m_device->info();
+ for (int i = 0; i < m_secureConfigs.size(); ++i) {
+ const auto &config = m_secureConfigs[i];
+ if (deviceInfo.individualAddress() != config.host())
+ continue;
+
+ const auto ia = config.individualAddress();
+ ui->secureConfigs->addItem(tr("User ID: %1 (Individual Address: %2)")
+ .arg(config.userId())
+ .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i);
+ }
+ }
+
+ ui->secureConfigs->setEnabled(bool(ui->secureConfigs->count())
+ && ui->secureSession->isChecked());
+ ui->secureSession->setEnabled(bool(ui->secureConfigs->count()));
+}
+
+void MainWindow::on_serviceTypes_currentIndexChanged(int index)
+{
+ bool enable = m_tunnel.state() == QKnxNetIpTunnel::State::Connected;
+ const auto type = ui->serviceTypes->itemData(index).value<QKnxNetIp::ServiceType>();
+
+ ui->sendRead->setEnabled(enable && type == QKnxNetIp::ServiceType::TunnelingFeatureGet);
+ ui->sendWrite->setEnabled(enable && type == QKnxNetIp::ServiceType::TunnelingFeatureSet);
+ ui->data->setEnabled(enable && type == QKnxNetIp::ServiceType::TunnelingFeatureSet);
+}
+
+void MainWindow::on_sendRead_clicked()
+{
+ const auto feature = ui->interfaceFeatures->itemData(ui->interfaceFeatures->currentIndex())
+ .value<QKnx::InterfaceFeature>();
+
+ m_tunnel.sendTunnelingFeatureGet(feature);
+ populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureGet, feature, {});
+}
+
+void MainWindow::on_sendWrite_clicked()
+{
+ const auto feature = ui->interfaceFeatures->itemData(ui->interfaceFeatures->currentIndex())
+ .value<QKnx::InterfaceFeature>();
+
+ const auto data = QKnxByteArray::fromHex(ui->data->text().toLatin1());
+ m_tunnel.sendTunnelingFeatureSet(feature, data);
+ populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureSet, feature, data);
+}
+
+void MainWindow::on_connection_clicked()
+{
+ if (ui->devices->count() <= 0)
+ return;
+
+ if (m_tunnel.state() == QKnxNetIpTunnel::State::Connected)
+ return m_tunnel.disconnectFromHost();
+
+ const auto list = ui->interfaces->currentData().toStringList();
+ m_tunnel.setLocalAddress(QHostAddress(list.first()));
+ m_tunnel.setSerialNumber(QKnxByteArray::fromHex(list.last().toLatin1()));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Establish connection to: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+
+ if (ui->secureSession->isChecked()) {
+ auto secureConfiguration = m_secureConfigs.value(ui->secureConfigs->currentData().toInt());
+ secureConfiguration.setKeepSecureSessionAlive(true);
+ m_tunnel.setSecureConfiguration(secureConfiguration);
+ m_tunnel.connectToHostEncrypted(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort());
+ } else {
+ m_tunnel.connectToHost(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort(), QKnxNetIp::HostProtocol::UDP_IPv4);
+ }
+}
+
+void MainWindow::setupInterfaces()
+{
+ auto firstItem = new QStandardItem(tr("Interface: --Select One--"));
+ qobject_cast<QStandardItemModel*>(ui->interfaces->model())->appendRow(firstItem);
+ firstItem->setSelectable(false);
+
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &iface : interfaces) {
+ const auto addressEntries = iface.addressEntries();
+ for (int j = 0; j < addressEntries.size(); j++) {
+ const auto ip = addressEntries[j].ip();
+ if (ip.isLoopback() || ip.toIPv4Address() == 0)
+ continue;
+ ui->interfaces->addItem(iface.name() + ": " + ip.toString(),
+ QStringList { ip.toString(), iface.hardwareAddress().remove(QLatin1String(":")) });
+ }
+ }
+ ui->interfaces->setCurrentIndex(bool(ui->interfaces->count()));
+
+ connect(ui->interfaces, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int i) {
+ if (i < 0)
+ return;
+ m_discoveryAgent.stop();
+ m_discoveryAgent.setLocalAddress(QHostAddress(ui->interfaces->currentData()
+ .toStringList().first()));
+ m_discoveryAgent.start();
+ });
+}
+
+void MainWindow::toggleUi(bool value)
+{
+ ui->serviceTypes->setEnabled(value);
+ ui->interfaceFeatures->setEnabled(value);
+ const auto type = ui->serviceTypes->itemData(ui->serviceTypes->currentIndex())
+ .value<QKnxNetIp::ServiceType>();
+ ui->sendRead->setEnabled(value && type == QKnxNetIp::ServiceType::TunnelingFeatureGet);
+ ui->sendWrite->setEnabled(value && type == QKnxNetIp::ServiceType::TunnelingFeatureSet);
+ ui->data->setEnabled(value && type == QKnxNetIp::ServiceType::TunnelingFeatureSet);
+ ui->interfaces->setDisabled(value);
+ ui->devices->setDisabled(value && bool(ui->devices->count()));
+}
+
+void MainWindow::populateServiceTypesComboBox()
+{
+ const auto typeEnum = QMetaEnum::fromType<QKnxNetIp::ServiceType>();
+ ui->serviceTypes->addItem(tr("Tunneling Feature Get"), typeEnum.keyToValue("TunnelingFeatureGet"));
+ ui->serviceTypes->addItem(tr("Tunneling Feature Set"), typeEnum.keyToValue("TunnelingFeatureSet"));
+}
+
+void MainWindow::populateInterfaceFeaturesComboBox()
+{
+ const auto typeEnum = QMetaEnum::fromType<QKnx::InterfaceFeature>();
+ for (int i = 0; i < typeEnum.keyCount(); ++i) {
+ if (!QKnx::isInterfaceFeature(QKnx::InterfaceFeature(typeEnum.value(i))))
+ continue;
+ ui->interfaceFeatures->addItem(typeEnum.key(i), typeEnum.value(i));
+ }
+}
+
+void MainWindow::setText(QKnx::InterfaceFeature feature, const QKnxByteArray &data)
+{
+ const auto hex = data.toByteArray().toHex();
+ m_last->setText(5, QStringLiteral("0x") + QLatin1String(hex, hex.size()));
+
+ QString value;
+ switch (feature) {
+ case QKnx::InterfaceFeature::ActiveEmiType:
+ case QKnx::InterfaceFeature::SupportedEmiType: {
+ QStringList types;
+ const auto emi = QKnx::EmiTypes(hex.toUShort(nullptr, 16));
+ const auto metaEnum = QMetaEnum::fromType<QKnx::EmiType>();
+ if (emi.testFlag(QKnx::EmiType::EMI1))
+ types += QLatin1String(metaEnum.valueToKey(int(QKnx::EmiType::EMI1)));
+ if (emi.testFlag(QKnx::EmiType::EMI2))
+ types += QLatin1String(metaEnum.valueToKey(int(QKnx::EmiType::EMI2)));
+ if (emi.testFlag(QKnx::EmiType::cEMI))
+ types += QLatin1String(metaEnum.valueToKey(int(QKnx::EmiType::cEMI)));
+ value = types.join(QLatin1Char('|'));
+ } break;
+
+ case QKnx::InterfaceFeature::HostDeviceDescriptorType0:
+ value = QString::number(hex.toUInt(nullptr, 16));
+ break;
+
+ case QKnx::InterfaceFeature::BusConnectionStatus: {
+ const auto metaEnum = QMetaEnum::fromType<QKnxState::State>();
+ value = metaEnum.valueToKey(int(QKnxState::State(data.value(0))));
+ } break;
+
+ case QKnx::InterfaceFeature::MaximumApduLength:
+ value = QString::number(hex.toUShort(nullptr, 16));
+ break;
+
+ case QKnx::InterfaceFeature::KnxManufacturerCode: {
+ const auto id = hex.toUShort(nullptr, 16);
+ value = QKnx::Ets::Manufacturers::fromId(id, QString::number(id));
+ } break;
+
+ case QKnx::InterfaceFeature::IndividualAddress:
+ value = QKnxAddress(QKnxAddress::Type::Individual, data).toString();
+ break;
+
+ case QKnx::InterfaceFeature::InterfaceFeatureInfoServiceEnable: {
+ const auto metaEnum = QMetaEnum::fromType<QKnxEnable::State>();
+ value = metaEnum.valueToKey(int(QKnxEnable::State(data.value(0))));
+ } break;
+
+ default:
+ break;
+ }
+ m_last->setText(3, value);
+}
+
+void MainWindow::populateFrame(QKnxNetIp::ServiceType type, QKnx::InterfaceFeature feature,
+ const QKnxByteArray &value, int returnCode)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+
+ auto metaEnum = QMetaEnum::fromType<QKnxNetIp::ServiceType>();
+ m_last->setText(1, metaEnum.valueToKey(int(type)));
+
+ metaEnum = QMetaEnum::fromType<QKnx::InterfaceFeature>();
+ m_last->setText(2, metaEnum.valueToKey(int(feature)));
+
+ if (type != QKnxNetIp::ServiceType::TunnelingFeatureGet)
+ setText(feature, value);
+
+ metaEnum = QMetaEnum::fromType<QKnx::ReturnCode>();
+ if (const auto code = metaEnum.valueToKey(returnCode))
+ m_last->setText(4, code);
+}
+
+void MainWindow::onFeatureResponseReceived(QKnx::InterfaceFeature feature, QKnx::ReturnCode code,
+ const QKnxByteArray &value)
+{
+ populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureResponse, feature, value, int(code));
+}
+
+void MainWindow::onFeatureInfoReceived(QKnx::InterfaceFeature feature, const QKnxByteArray &value)
+{
+ populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureInfo, feature, value);
+}
diff --git a/examples/knx/feature/mainwindow.h b/examples/knx/feature/mainwindow.h
new file mode 100644
index 0000000..42d827b
--- /dev/null
+++ b/examples/knx/feature/mainwindow.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QtKnx>
+#include <QtWidgets>
+
+QT_BEGIN_NAMESPACE
+
+namespace Ui {
+ class MainWindow;
+}
+
+QT_END_NAMESPACE
+
+class DeviceItem;
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void onConnected();
+ void onDisconnected();
+ void onDeviceDiscovered(QKnxNetIpServerInfo info);
+ void onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString);
+
+ void on_actionImport_triggered();
+ void on_devices_currentIndexChanged(int index);
+ void on_serviceTypes_currentIndexChanged(int index);
+
+ void on_sendRead_clicked();
+ void on_sendWrite_clicked();
+ void on_connection_clicked();
+
+private:
+ void setupInterfaces();
+ void toggleUi(bool enable);
+ void populateServiceTypesComboBox();
+ void populateInterfaceFeaturesComboBox();
+
+ void setText(QKnx::InterfaceFeature feature, const QKnxByteArray &value);
+ void populateFrame(QKnxNetIp::ServiceType type, QKnx::InterfaceFeature feature,
+ const QKnxByteArray &value, int returnCode = -1);
+ void onFeatureResponseReceived(QKnx::InterfaceFeature feature, QKnx::ReturnCode code,
+ const QKnxByteArray &value);
+ void onFeatureInfoReceived(QKnx::InterfaceFeature feature, const QKnxByteArray &value);
+
+private:
+ Ui::MainWindow *ui { nullptr };
+
+ DeviceItem *m_device { nullptr };
+ QTreeWidgetItem *m_last { nullptr };
+ QTreeWidgetItem *m_current { nullptr };
+
+ QKnxNetIpTunnel m_tunnel;
+ QKnxNetIpServerDiscoveryAgent m_discoveryAgent;
+ QVector<QKnxNetIpSecureConfiguration> m_secureConfigs;
+};
+
+#endif // MAINWINDOW_H
diff --git a/examples/knx/feature/mainwindow.ui b/examples/knx/feature/mainwindow.ui
new file mode 100644
index 0000000..ec92b4f
--- /dev/null
+++ b/examples/knx/feature/mainwindow.ui
@@ -0,0 +1,332 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>867</width>
+ <height>416</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>KNX tunneling features example</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,1,0,0">
+ <item row="0" column="0">
+ <widget class="QLabel" name="serviceTypesLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Service type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="serviceTypes">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="interfaceFeaturesLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Interface feature:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QComboBox" name="interfaceFeatures">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="5">
+ <widget class="QPushButton" name="sendRead">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Read</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="dataLabel">
+ <property name="text">
+ <string>Data:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QPushButton" name="sendWrite">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Write</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="3">
+ <widget class="QLineEdit" name="data">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="communication">
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerShowSortIndicator" stdset="0">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>#</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Service type</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Interface feature</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Value</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Status code</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Data</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0,1,0,1,0,0">
+ <item>
+ <widget class="QLabel" name="interfaceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Interface:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="interfaces">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="deviceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="devices">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="secureSession">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use secure session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="secureConfigs">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacerRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="connection">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>867</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <widget class="QMenu" name="menuImport">
+ <property name="title">
+ <string>Import</string>
+ </property>
+ <addaction name="actionImport"/>
+ </widget>
+ <addaction name="menuImport"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuEdit">
+ <property name="title">
+ <string>Edit</string>
+ </property>
+ <addaction name="actionClear"/>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuEdit"/>
+ </widget>
+ <action name="actionExit">
+ <property name="text">
+ <string>Exit</string>
+ </property>
+ </action>
+ <action name="actionImport">
+ <property name="text">
+ <string>KNX keyring file...</string>
+ </property>
+ </action>
+ <action name="actionClear">
+ <property name="text">
+ <string>Clear telegram list</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>secureSession</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>secureConfigs</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>516</x>
+ <y>391</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>634</x>
+ <y>393</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/knx/group/deviceitem.cpp b/examples/knx/group/deviceitem.cpp
new file mode 100644
index 0000000..c353d1b
--- /dev/null
+++ b/examples/knx/group/deviceitem.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "deviceitem.h"
+
+DeviceItem::DeviceItem(const QKnxNetIpServerInfo &info)
+ : QStandardItem(info.deviceName())
+ , m_serverInfo(info)
+{}
+
+DeviceItem *DeviceItem::clone() const
+{
+ return new DeviceItem(m_serverInfo);
+}
+
+const QKnxNetIpServerInfo &DeviceItem::info() const
+{
+ return m_serverInfo;
+}
diff --git a/examples/knx/group/deviceitem.h b/examples/knx/group/deviceitem.h
new file mode 100644
index 0000000..5f59407
--- /dev/null
+++ b/examples/knx/group/deviceitem.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef DEVICEITEM_H
+#define DEVICEITEM_H
+
+#include <QtGui>
+#include <QtKnx>
+
+class DeviceItem : public QStandardItem
+{
+public:
+ DeviceItem(const QKnxNetIpServerInfo &info);
+ ~DeviceItem() = default;
+
+ DeviceItem *clone() const override;
+ const QKnxNetIpServerInfo &info() const;
+
+private:
+ QKnxNetIpServerInfo m_serverInfo;
+};
+
+#endif // DEVICEITEM_H
diff --git a/examples/knx/group/group.pro b/examples/knx/group/group.pro
new file mode 100644
index 0000000..95d739e
--- /dev/null
+++ b/examples/knx/group/group.pro
@@ -0,0 +1,20 @@
+TEMPLATE = app
+TARGET = group
+INCLUDEPATH += .
+
+CONFIG += c++11
+QT += knx widgets network core
+
+FORMS += mainwindow.ui
+
+HEADERS += deviceitem.h \
+ groupaddressvalidater.h \
+ mainwindow.h
+
+SOURCES += deviceitem.cpp \
+ groupaddressvalidater.cpp \
+ main.cpp \
+ mainwindow.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/knx/group
+INSTALLS += target
diff --git a/examples/knx/group/groupaddressvalidater.cpp b/examples/knx/group/groupaddressvalidater.cpp
new file mode 100644
index 0000000..af03d1b
--- /dev/null
+++ b/examples/knx/group/groupaddressvalidater.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "groupaddressvalidater.h"
+
+GroupAddressValidator::GroupAddressValidator(QObject *parent)
+ : QValidator(parent)
+{}
+
+QValidator::State GroupAddressValidator::validate(QString &input, int & /* pos */) const
+{
+ const auto result = m_groupRegExp.match(input);
+ if (result.hasMatch())
+ return State::Invalid;
+
+ auto n = input.split(QLatin1Char('/'));
+ if (n.value(0).toInt() > 15 || n.value(1).toInt() > 15 || n.value(2).toInt() > 255)
+ return State::Invalid;
+ return State::Intermediate;
+}
diff --git a/examples/knx/group/groupaddressvalidater.h b/examples/knx/group/groupaddressvalidater.h
new file mode 100644
index 0000000..ed629cb
--- /dev/null
+++ b/examples/knx/group/groupaddressvalidater.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef GROUPADDRESSVALIDATER_H
+#define GROUPADDRESSVALIDATER_H
+
+#include <QtGui>
+
+class GroupAddressValidator : public QValidator
+{
+ Q_OBJECT
+
+public:
+ GroupAddressValidator(QObject *parent = nullptr);
+ QValidator::State validate(QString &input, int &pos) const override;
+
+private:
+ QRegularExpression m_groupRegExp { QStringLiteral("[^0-9\\/]") };
+};
+
+#endif // GROUPADDRESSVALIDATER_H
diff --git a/examples/knx/group/main.cpp b/examples/knx/group/main.cpp
new file mode 100644
index 0000000..e3e9a74
--- /dev/null
+++ b/examples/knx/group/main.cpp
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}
diff --git a/examples/knx/group/mainwindow.cpp b/examples/knx/group/mainwindow.cpp
new file mode 100644
index 0000000..4100e03
--- /dev/null
+++ b/examples/knx/group/mainwindow.cpp
@@ -0,0 +1,374 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include "deviceitem.h"
+#include "groupaddressvalidater.h"
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+ ui->groupAddress->setValidator(new GroupAddressValidator);
+
+ for (int i = 0; i < ui->communication->columnCount(); ++i)
+ ui->communication->resizeColumnToContents(i);
+
+ setupInterfaces();
+ populateDatapointComboBox();
+
+ connect(ui->actionExit, &QAction::triggered, this, &QApplication::quit);
+ connect(ui->actionClear, &QAction::triggered, ui->communication, &QTreeWidget::clear);
+
+ connect(&m_tunnel, &QKnxNetIpTunnel::connected, this, &MainWindow::onConnected);
+ connect(&m_tunnel, &QKnxNetIpTunnel::disconnected, this, &MainWindow::onDisconnected);
+ connect(&m_tunnel, &QKnxNetIpTunnel::frameReceived, this, &MainWindow::populateFrame);
+ connect(&m_tunnel, &QKnxNetIpTunnel::errorOccurred, this, &MainWindow::onErrorOccurred);
+
+ connect(ui->communication, &QTreeWidget::currentItemChanged, this,
+ [&](QTreeWidgetItem *current, QTreeWidgetItem * /* previuos */) {
+ m_current = current;
+ });
+
+ m_discoveryAgent.setTimeout(-1);
+ m_discoveryAgent.setSearchFrequency(6);
+ m_discoveryAgent.setDiscoveryMode(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1
+ | QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2);
+ connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::deviceDiscovered,
+ this, &MainWindow::onDeviceDiscovered);
+
+ m_discoveryAgent.start();
+}
+
+MainWindow::~MainWindow()
+{
+ m_discoveryAgent.stop();
+
+ delete ui;
+ ui = nullptr;
+ delete m_device;
+}
+
+void MainWindow::onConnected()
+{
+ toggleUi(true);
+ ui->connection->setText(tr("Disconnect"));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Successfully connected to: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::onDisconnected()
+{
+ toggleUi(false);
+ ui->connection->setText(tr("Connect"));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Successfully disconnected from: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::onDeviceDiscovered(QKnxNetIpServerInfo info)
+{
+ if (ui->devices->findText(info.deviceName()) == -1)
+ qobject_cast<QStandardItemModel*>(ui->devices->model())->appendRow(new DeviceItem(info));
+
+ if (m_tunnel.state() == QKnxNetIpTunnel::State::Disconnected)
+ ui->devices->setEnabled(bool(ui->devices->count()));
+}
+
+void MainWindow::onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ const auto metaEnum = QMetaEnum::fromType<QKnxNetIpEndpointConnection::Error>();
+ m_last->setText(0, tr("Error occurred: %1 (%2).").arg(errorString)
+ .arg(metaEnum.valueToKey(int(error))));
+ m_last->setFirstColumnSpanned(true);
+}
+
+void MainWindow::on_actionImport_triggered()
+{
+ const auto fileName = QFileDialog::getOpenFileName(this, tr("Import keyring file"),
+ QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).value(0),
+ tr("KNX keyring file (*.knxkeys)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ bool ok;
+ const auto password = QInputDialog::getText(this, tr("Import keyring file"),
+ tr("Keyring file password:"), QLineEdit::Normal, {}, &ok);
+ if (!ok || password.isEmpty())
+ return;
+
+ m_secureConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::DeviceManagement, fileName, password.toUtf8(), true);
+ for (auto &config : m_secureConfigs)
+ config.setIndividualAddress({});
+
+ m_secureConfigs += QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::Tunneling, fileName, password.toUtf8(), true);
+
+ on_devices_currentIndexChanged(ui->devices->currentIndex());
+}
+
+void MainWindow::on_devices_currentIndexChanged(int /* index */)
+{
+ delete m_device;
+ ui->secureConfigs->clear();
+
+ const auto model = qobject_cast<QStandardItemModel*>(ui->devices->model());
+ m_device = static_cast<DeviceItem *> (model->item(ui->devices->currentIndex()))->clone();
+
+ if (m_device) {
+ const auto deviceInfo = m_device->info();
+ for (int i = 0; i < m_secureConfigs.size(); ++i) {
+ const auto &config = m_secureConfigs[i];
+ if (deviceInfo.individualAddress() != config.host())
+ continue;
+
+ const auto ia = config.individualAddress();
+ ui->secureConfigs->addItem(tr("User ID: %1 (Individual Address: %2)")
+ .arg(config.userId())
+ .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i);
+ }
+ }
+
+ ui->secureConfigs->setEnabled(bool(ui->secureConfigs->count())
+ && ui->secureSession->isChecked());
+ ui->secureSession->setEnabled(bool(ui->secureConfigs->count()));
+}
+
+void MainWindow::on_datapointTypes_currentIndexChanged(int index)
+{
+ ui->values->clear();
+
+ const auto type = ui->datapointTypes->itemData(index).value<QKnxDatapointType::Type>();
+ QScopedPointer<QKnxDatapointType> p(QKnxDatapointTypeFactory::instance().createType(type));
+ if (p) {
+ ui->values->addItem(p->minimumText(), p->minimum());
+ ui->values->addItem(p->maximumText(), p->maximum());
+ }
+}
+
+void MainWindow::on_sendRead_clicked()
+{
+ const auto frame = QKnxLinkLayerFrame::builder()
+ .setSourceAddress(m_tunnel.individualAddress())
+ .setDestinationAddress({ QKnxAddress::Type::Group, ui->groupAddress->text() })
+ .createFrame();
+
+ populateFrame(frame);
+ m_tunnel.sendFrame(frame);
+}
+
+void MainWindow::on_sendWrite_clicked()
+{
+ const auto frame = QKnxLinkLayerFrame::builder()
+ .setSourceAddress(m_tunnel.individualAddress())
+ .setDestinationAddress({ QKnxAddress::Type::Group, ui->groupAddress->text() })
+ .setTpdu({
+ QKnxTpdu::TransportControlField::DataGroup,
+ QKnxTpdu::ApplicationControlField::GroupValueWrite,
+ QKnx1Bit(ui->values->currentData().toBool()).bytes()
+ }).createFrame();
+
+ populateFrame(frame);
+ m_tunnel.sendFrame(frame);
+}
+
+void MainWindow::on_connection_clicked()
+{
+ if (ui->devices->count() <= 0)
+ return;
+
+ if (m_tunnel.state() == QKnxNetIpTunnel::State::Connected)
+ return m_tunnel.disconnectFromHost();
+
+ const auto list = ui->interfaces->currentData().toStringList();
+ m_tunnel.setLocalAddress(QHostAddress(list.first()));
+ m_tunnel.setSerialNumber(QKnxByteArray::fromHex(list.last().toLatin1()));
+
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+ m_last->setText(0, tr("Establish connection to: %1 (%2 : %3)")
+ .arg(m_device->info().deviceName())
+ .arg(m_device->info().controlEndpointAddress().toString())
+ .arg(m_device->info().controlEndpointPort()));
+ m_last->setFirstColumnSpanned(true);
+
+ if (ui->secureSession->isChecked()) {
+ auto secureConfiguration = m_secureConfigs.value(ui->secureConfigs->currentData().toInt());
+ secureConfiguration.setKeepSecureSessionAlive(true);
+ m_tunnel.setSecureConfiguration(secureConfiguration);
+ m_tunnel.connectToHostEncrypted(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort());
+ } else {
+ m_tunnel.connectToHost(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort(), QKnxNetIp::HostProtocol::UDP_IPv4);
+ }
+}
+
+void MainWindow::setupInterfaces()
+{
+ auto firstItem = new QStandardItem(tr("Interface: --Select One--"));
+ qobject_cast<QStandardItemModel*>(ui->interfaces->model())->appendRow(firstItem);
+ firstItem->setSelectable(false);
+
+ const auto interfaces = QNetworkInterface::allInterfaces();
+ for (const auto &iface : interfaces) {
+ const auto addressEntries = iface.addressEntries();
+ for (int j = 0; j < addressEntries.size(); j++) {
+ const auto ip = addressEntries[j].ip();
+ if (ip.isLoopback() || ip.toIPv4Address() == 0)
+ continue;
+ ui->interfaces->addItem(iface.name() + ": " + ip.toString(),
+ QStringList { ip.toString(), iface.hardwareAddress().remove(QLatin1String(":")) });
+ }
+ }
+ ui->interfaces->setCurrentIndex(bool(ui->interfaces->count()));
+
+ connect(ui->interfaces, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int i) {
+ if (i < 0)
+ return;
+ m_discoveryAgent.stop();
+ m_discoveryAgent.setLocalAddress(QHostAddress(ui->interfaces->currentData()
+ .toStringList().first()));
+ m_discoveryAgent.start();
+ });
+}
+
+void MainWindow::toggleUi(bool value)
+{
+ ui->groupAddress->setEnabled(value);
+ ui->datapointTypes->setEnabled(value);
+ ui->values->setEnabled(value);
+ ui->sendRead->setEnabled(value);
+ ui->sendWrite->setEnabled(value);
+ ui->interfaces->setDisabled(value);
+ ui->devices->setDisabled(value && bool(ui->devices->count()));
+}
+
+void MainWindow::populateDatapointComboBox()
+{
+ const auto typeEnum = QMetaEnum::fromType<QKnxDatapointType::Type>();
+ for (int i = 0; i < typeEnum.keyCount(); ++i) {
+ const auto type = QKnxDatapointType::Type(typeEnum.value(i));
+ if (type >= QKnxDatapointType::Type::Dpt2_1BitControlled)
+ break;
+ QScopedPointer<QKnxDatapointType> p(QKnxDatapointTypeFactory::instance().createType(type));
+ if (p)
+ ui->datapointTypes->addItem(p->description(), QVariant::fromValue(type));
+ }
+}
+
+void MainWindow::populateFrame(const QKnxLinkLayerFrame &frame)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+
+ auto metaEnum = QMetaEnum::fromType<QKnxLinkLayerFrame::MessageCode>();
+ m_last->setText(1, metaEnum.valueToKey(int(frame.messageCode())));
+ m_last->setText(2, frame.sourceAddress().toString());
+ m_last->setText(3, frame.destinationAddress().toString());
+
+ const auto ctrl = frame.controlField();
+ metaEnum = QMetaEnum::fromType<QKnxControlField::FrameFormat>();
+ m_last->setText(4, metaEnum.valueToKey(int(ctrl.frameFormat())));
+ metaEnum = QMetaEnum::fromType<QKnxControlField::Repeat>();
+ m_last->setText(5, metaEnum.valueToKey(int(ctrl.repeat())));
+ metaEnum = QMetaEnum::fromType<QKnxControlField::Broadcast>();
+ m_last->setText(6, metaEnum.valueToKey(int(ctrl.broadcast())));
+ metaEnum = QMetaEnum::fromType<QKnxControlField::Priority>();
+ m_last->setText(7, metaEnum.valueToKey(int(ctrl.priority())));
+ metaEnum = QMetaEnum::fromType<QKnxControlField::Acknowledge>();
+ m_last->setText(8, metaEnum.valueToKey(int(ctrl.acknowledge())));
+ metaEnum = QMetaEnum::fromType<QKnxControlField::Confirm>();
+ m_last->setText(9, metaEnum.valueToKey(int(ctrl.confirm())));
+
+ const auto extCtrl = frame.extendedControlField();
+ m_last->setText(10, (extCtrl.destinationAddressType() == QKnxAddress::Type::Group
+ ? tr("Group")
+ : tr("Individual")));
+ m_last->setText(11, QString::number(extCtrl.hopCount()));
+ metaEnum = QMetaEnum::fromType<QKnxExtendedControlField::ExtendedFrameFormat>();
+ m_last->setText(12, metaEnum.valueToKey(int(extCtrl.format())));
+
+ auto tpdu = frame.tpdu();
+ metaEnum = QMetaEnum::fromType<QKnxTpdu::TransportControlField>();
+ m_last->setText(13, metaEnum.valueToKey(int(tpdu.transportControlField())));
+ metaEnum = QMetaEnum::fromType<QKnxTpdu::ApplicationControlField>();
+ m_last->setText(14, metaEnum.valueToKey(int(tpdu.applicationControlField())));
+
+ auto bytes = tpdu.data().toHex();
+ m_last->setText(15, QString::fromLatin1(bytes.toByteArray(), bytes.size()));
+
+ bytes = frame.bytes().toHex();
+ m_last->setText(16, QString::fromLatin1(bytes.toByteArray(), bytes.size()));
+
+ if (m_current) {
+ const auto region = ui->communication->viewport()->visibleRegion();
+ const auto currentRect = ui->communication->visualItemRect(m_current);
+ if (region.contains({ currentRect.left(), currentRect.top() - currentRect.height() })
+ && region.contains(ui->communication->visualItemRect(m_last))) {
+ ui->communication->scrollToItem(m_last);
+ }
+ } else {
+ ui->communication->scrollToItem(m_last);
+ }
+}
diff --git a/examples/knx/group/mainwindow.h b/examples/knx/group/mainwindow.h
new file mode 100644
index 0000000..ceee2df
--- /dev/null
+++ b/examples/knx/group/mainwindow.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtKnx module.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QtKnx>
+#include <QtWidgets>
+
+QT_BEGIN_NAMESPACE
+
+namespace Ui {
+ class MainWindow;
+}
+
+QT_END_NAMESPACE
+
+class DeviceItem;
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void onConnected();
+ void onDisconnected();
+ void onDeviceDiscovered(QKnxNetIpServerInfo info);
+ void onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString);
+
+ void on_actionImport_triggered();
+ void on_devices_currentIndexChanged(int index);
+ void on_datapointTypes_currentIndexChanged(int index);
+
+ void on_sendRead_clicked();
+ void on_sendWrite_clicked();
+ void on_connection_clicked();
+
+private:
+ void setupInterfaces();
+ void toggleUi(bool enable);
+ void populateDatapointComboBox();
+ void populateFrame(const QKnxLinkLayerFrame &frame);
+
+private:
+ Ui::MainWindow *ui { nullptr };
+
+ DeviceItem *m_device { nullptr };
+ QTreeWidgetItem *m_last { nullptr };
+ QTreeWidgetItem *m_current { nullptr };
+
+ QKnxNetIpTunnel m_tunnel;
+ QKnxNetIpServerDiscoveryAgent m_discoveryAgent;
+ QVector<QKnxNetIpSecureConfiguration> m_secureConfigs;
+};
+
+#endif // MAINWINDOW_H
diff --git a/examples/knx/group/mainwindow.ui b/examples/knx/group/mainwindow.ui
new file mode 100644
index 0000000..5714453
--- /dev/null
+++ b/examples/knx/group/mainwindow.ui
@@ -0,0 +1,402 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>867</width>
+ <height>416</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>KNX group communication example</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,0,1,0,1,0,0,0">
+ <item>
+ <widget class="QLabel" name="groupAddressLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Group address:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="groupAddress">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>0/0/0</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="datappointTypeLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Datapoint type:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="datapointTypes">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="valueLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Value:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="values">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacerTop">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="sendRead">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Read</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="sendWrite">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Write</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="communication">
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerShowSortIndicator" stdset="0">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>#</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Message code</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Source address</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Destination address</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Frame format</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Repeat</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Broadcast</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Priority</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Acknowledge</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Confirm</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Destination address type</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Hop count</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Extended frame format</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>TCPI</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>APCI</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Data</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Frame</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0,1,0,1,0,0">
+ <item>
+ <widget class="QLabel" name="interfaceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Interface:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="interfaces">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="deviceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="devices">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="secureSession">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use secure session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="secureConfigs">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacerRight">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="connection">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>867</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <widget class="QMenu" name="menuImport">
+ <property name="title">
+ <string>Import</string>
+ </property>
+ <addaction name="actionImport"/>
+ </widget>
+ <addaction name="menuImport"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuEdit">
+ <property name="title">
+ <string>Edit</string>
+ </property>
+ <addaction name="actionClear"/>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuEdit"/>
+ </widget>
+ <action name="actionExit">
+ <property name="text">
+ <string>Exit</string>
+ </property>
+ </action>
+ <action name="actionImport">
+ <property name="text">
+ <string>KNX keyring file...</string>
+ </property>
+ </action>
+ <action name="actionClear">
+ <property name="text">
+ <string>Clear telegram list</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>secureSession</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>secureConfigs</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>516</x>
+ <y>391</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>634</x>
+ <y>393</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/knx/knx.pro b/examples/knx/knx.pro
index 2628db9..2a74801 100644
--- a/examples/knx/knx.pro
+++ b/examples/knx/knx.pro
@@ -5,5 +5,5 @@ TEMPLATE = subdirs
SUBDIRS += discoverer knxproj tunnelclient router
qtHaveModule(widgets) {
- SUBDIRS += knxeditor
+ SUBDIRS += knxeditor group device feature
}
diff --git a/examples/knx/knxeditor/localdevicemanagement.cpp b/examples/knx/knxeditor/localdevicemanagement.cpp
index 803badf..dd52493 100644
--- a/examples/knx/knxeditor/localdevicemanagement.cpp
+++ b/examples/knx/knxeditor/localdevicemanagement.cpp
@@ -52,8 +52,9 @@
#include "ui_localdevicemanagement.h"
#include <QKnxByteArray>
-#include <QtKnx/QKnxDeviceManagementFrameBuilder>
+#include <QKnxDeviceManagementFrameBuilder>
#include <QKnxInterfaceObjectPropertyDataType>
+#include <QKnxNetIpSecureConfiguration>
#include <QMetaEnum>
#include <QMetaType>
#include <QTreeWidget>
@@ -82,9 +83,16 @@ LocalDeviceManagement::LocalDeviceManagement(QWidget* parent)
connect(ui->connectRequestDeviceManagement, &QPushButton::clicked, this, [&]() {
m_management.setLocalPort(0);
- m_management.connectToHost(m_server.controlEndpointAddress(),
- m_server.controlEndpointPort(),
- m_proto);
+ if (ui->secureSessionCheckBox->isChecked()) {
+ auto config = m_configs.value(ui->secureSessionCb->currentIndex());
+ config.setKeepSecureSessionAlive(true);
+ m_management.setSecureConfiguration(config);
+ m_management.connectToHostEncrypted(m_server.controlEndpointAddress(),
+ m_server.controlEndpointPort());
+ } else {
+ m_management.connectToHost(m_server.controlEndpointAddress(),
+ m_server.controlEndpointPort(), m_proto);
+ }
});
connect(&m_management, &QKnxNetIpDeviceManagement::connected, this, [&] {
@@ -107,6 +115,8 @@ LocalDeviceManagement::LocalDeviceManagement(QWidget* parent)
});
connect(&m_management, &QKnxNetIpDeviceManagement::disconnected, this, [&] {
+ if (!ui)
+ return;
m_awaitIoListResponse = true;
ui->deviceManagementSendRequest->setEnabled(false);
ui->connectRequestDeviceManagement->setEnabled(true);
@@ -135,6 +145,11 @@ LocalDeviceManagement::LocalDeviceManagement(QWidget* parent)
handleIoListResponse(frame);
});
+ connect(&m_management, &QKnxNetIpDeviceManagement::errorOccurred, this,
+ [&] (QKnxNetIpEndpointConnection::Error, QString errorString) {
+ ui->textOuputDeviceManagement->append(errorString);
+ });
+
ui->cemiData->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]+")));
ui->cemiFrame->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]+")));
}
@@ -142,6 +157,7 @@ LocalDeviceManagement::LocalDeviceManagement(QWidget* parent)
LocalDeviceManagement::~LocalDeviceManagement()
{
delete ui;
+ ui = nullptr;
}
void LocalDeviceManagement::setNatAware(bool isNatAware)
@@ -164,6 +180,8 @@ void LocalDeviceManagement::setKnxNetIpServer(const QKnxNetIpServerInfo &server)
ui->connectRequestDeviceManagement->setEnabled(true);
ui->disconnectRequestDeviceManagement->setEnabled(false);
}
+
+ updateSecureConfigCombo();
ui->deviceManagementSendRequest->setEnabled(false);
}
@@ -273,6 +291,12 @@ void LocalDeviceManagement::on_manualInput_clicked(bool checked)
}
}
+void LocalDeviceManagement::onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs)
+{
+ m_configs = configs;
+ updateSecureConfigCombo();
+}
+
void LocalDeviceManagement::setupMessageCodeComboBox()
{
ui->mc->addItem("M_PropRead.req",
@@ -362,7 +386,7 @@ int LocalDeviceManagement::keyToValue(const QMetaObject &object, const QString &
auto enumCount = object.enumeratorCount();
for (auto i = 0; i < enumCount; ++i) {
int value = object.enumerator(i).keyToValue(key.toLatin1(), ok);
- if (value != -1 && ok)
+ if (value != -1 && *ok)
return value;
}
return -1;
@@ -403,3 +427,20 @@ void LocalDeviceManagement::selectFirstSubitem(QTreeWidget *treeWidget, QTreeWid
treeWidget->setCurrentItem(treeWidget->invisibleRootItem(), 0);
comboBox->setRootModelIndex(treeWidget->currentIndex());
}
+
+void LocalDeviceManagement::updateSecureConfigCombo()
+{
+ ui->secureSessionCb->clear();
+
+ ui->secureSessionCheckBox->setEnabled(!m_configs.isEmpty());
+ ui->secureSessionCheckBox->setChecked(m_proto == QKnxNetIp::HostProtocol::TCP_IPv4);
+
+ for (int i = 0; i < m_configs.size(); ++i) {
+ const auto &config = m_configs[i];
+ if (m_server.individualAddress() != config.host())
+ continue;
+
+ ui->secureSessionCb->addItem(tr("User ID: %1 (Individual Address: %2)").arg(config.userId())
+ .arg(config.individualAddress().toString()), i);
+ }
+}
diff --git a/examples/knx/knxeditor/localdevicemanagement.h b/examples/knx/knxeditor/localdevicemanagement.h
index 218ad95..195023f 100644
--- a/examples/knx/knxeditor/localdevicemanagement.h
+++ b/examples/knx/knxeditor/localdevicemanagement.h
@@ -51,9 +51,9 @@
#ifndef LOCALDEVICEMANAGEMENT_H
#define LOCALDEVICEMANAGEMENT_H
-#include <QWidget>
#include <QKnxNetIpDeviceManagement>
#include <QKnxNetIpServerInfo>
+#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
@@ -78,6 +78,7 @@ public:
void setLocalAddress(const QHostAddress &address);
void setKnxNetIpServer(const QKnxNetIpServerInfo &server);
void setTcpEnable(bool value);
+ void onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs);
public slots:
void clearLogging();
@@ -100,6 +101,7 @@ private:
int keyToValue(const QMetaObject &object, const QString &key, bool *ok);
void setupComboBox(QComboBox *comboBox, const QMetaObject &object, const QSet<int> &values = {});
void selectFirstSubitem(QTreeWidget *treeView, QTreeWidgetItem *rootItem, QComboBox *comboBox);
+ void updateSecureConfigCombo();
private:
Ui::LocalDeviceManagement *ui { nullptr };
@@ -110,6 +112,7 @@ private:
QKnxNetIpServerInfo m_server;
QKnxNetIpDeviceManagement m_management;
QKnxNetIp::HostProtocol m_proto { QKnxNetIp::HostProtocol::UDP_IPv4 };
+ QVector<QKnxNetIpSecureConfiguration> m_configs;
};
#endif
diff --git a/examples/knx/knxeditor/localdevicemanagement.ui b/examples/knx/knxeditor/localdevicemanagement.ui
index 05c3145..99d809f 100644
--- a/examples/knx/knxeditor/localdevicemanagement.ui
+++ b/examples/knx/knxeditor/localdevicemanagement.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>830</width>
+ <width>856</width>
<height>385</height>
</rect>
</property>
@@ -265,6 +265,29 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
+ <widget class="QCheckBox" name="secureSessionCheckBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use secure session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="secureSessionCb">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -387,8 +410,8 @@
<y>45</y>
</hint>
<hint type="destinationlabel">
- <x>332</x>
- <y>16</y>
+ <x>384</x>
+ <y>26</y>
</hint>
</hints>
</connection>
@@ -403,8 +426,8 @@
<y>42</y>
</hint>
<hint type="destinationlabel">
- <x>439</x>
- <y>22</y>
+ <x>485</x>
+ <y>29</y>
</hint>
</hints>
</connection>
@@ -419,8 +442,8 @@
<y>49</y>
</hint>
<hint type="destinationlabel">
- <x>753</x>
- <y>19</y>
+ <x>845</x>
+ <y>29</y>
</hint>
</hints>
</connection>
@@ -451,8 +474,8 @@
<y>49</y>
</hint>
<hint type="destinationlabel">
- <x>666</x>
- <y>19</y>
+ <x>752</x>
+ <y>29</y>
</hint>
</hints>
</connection>
@@ -467,8 +490,24 @@
<y>49</y>
</hint>
<hint type="destinationlabel">
- <x>378</x>
- <y>19</y>
+ <x>433</x>
+ <y>29</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>secureSessionCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>secureSessionCb</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>16</x>
+ <y>368</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>147</x>
+ <y>369</y>
</hint>
</hints>
</connection>
diff --git a/examples/knx/knxeditor/mainwindow.cpp b/examples/knx/knxeditor/mainwindow.cpp
index 17200ef..c967ca2 100644
--- a/examples/knx/knxeditor/mainwindow.cpp
+++ b/examples/knx/knxeditor/mainwindow.cpp
@@ -52,12 +52,15 @@
#include "ui_mainwindow.h"
#include <QElapsedTimer>
+#include <QFileDialog>
+#include <QInputDialog>
#include <QNetworkInterface>
#include <QStandardItem>
+#include <QStandardPaths>
Ui::MainWindow *MainWindow::s_ui { nullptr };
-static QString familieToString(QKnxNetIp::ServiceFamily id)
+static QString familyToString(QKnxNetIp::ServiceFamily id)
{
switch (id) {
case QKnxNetIp::ServiceFamily::Core:
@@ -198,7 +201,7 @@ void MainWindow::newServerSelected(int serverBoxIndex)
const auto services = info.supportedServices();
for (const auto &service : services) {
value.append(tr("<tr><td class=\"padding\">%1</td></th>")
- .arg(tr("KNXnet/IP %1, Version: %2").arg(familieToString(service.ServiceFamily))
+ .arg(tr("KNXnet/IP %1, Version: %2").arg(familyToString(service.ServiceFamily))
.arg(service.ServiceFamilyVersion)));
if (service.ServiceFamilyVersion >= 2)
version2Supported = true;
@@ -260,7 +263,7 @@ void MainWindow::showServerAndServices(const QKnxNetIpServerInfo &info)
const auto services = info.supportedServices();
for (const auto service : services) {
ui->outputEdit->append(tr(" KNXnet/IP %1, Version: %2")
- .arg(familieToString(service.ServiceFamily)).arg(service.ServiceFamilyVersion));
+ .arg(familyToString(service.ServiceFamily)).arg(service.ServiceFamilyVersion));
}
ui->serverBox->addItem(tr("%1 (%2:%3)").arg(info.deviceName(), info.controlEndpointAddress()
@@ -274,6 +277,34 @@ void MainWindow::on_radioButtonTCP_toggled(bool checked)
ui->tunnelingFeatures->setTcpEnable(checked);
}
+void MainWindow::on_actionEtsKeyringImport_triggered()
+{
+ auto fileName = QFileDialog::getOpenFileName(this, tr("Import keyring file"),
+ QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).value(0),
+ tr("KNX keyring file (*.knxkeys)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ bool ok;
+ auto password = QInputDialog::getText(this, tr("Import keyring file"),
+ tr("Keyring file password:"), QLineEdit::Normal, {}, &ok);
+ if (!ok || password.isEmpty())
+ return;
+
+ auto mgmtConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::DeviceManagement, fileName, password.toUtf8(), true);
+ ui->deviceManagement->onKeyringChanged(mgmtConfigs);
+
+ for (auto &config : mgmtConfigs)
+ config.setIndividualAddress({});
+
+ auto tunnelConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
+ ::Type::Tunneling, fileName, password.toUtf8(), true);
+ ui->tunneling->onKeyringChanged(mgmtConfigs + tunnelConfigs);
+ ui->tunnelingFeatures->onKeyringChanged(mgmtConfigs + tunnelConfigs);
+}
+
void MainWindow::fillLocalIpBox()
{
auto firstItem = new QStandardItem(tr("Interface: IP address --Select One--"));
diff --git a/examples/knx/knxeditor/mainwindow.h b/examples/knx/knxeditor/mainwindow.h
index 8aa256f..6c1a3d7 100644
--- a/examples/knx/knxeditor/mainwindow.h
+++ b/examples/knx/knxeditor/mainwindow.h
@@ -69,11 +69,15 @@ public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
+signals:
+ void keyringChanged(const QString &fileName, const QString &password);
+
private slots:
void newServerSelected(int serverBoxIndex);
void newIPAddressSelected(int localIpBoxIndex);
void showServerAndServices(const QKnxNetIpServerInfo &info);
void on_radioButtonTCP_toggled(bool checked);
+ void on_actionEtsKeyringImport_triggered();
private:
void fillLocalIpBox();
diff --git a/examples/knx/knxeditor/mainwindow.ui b/examples/knx/knxeditor/mainwindow.ui
index 0682a17..65ff6a3 100644
--- a/examples/knx/knxeditor/mainwindow.ui
+++ b/examples/knx/knxeditor/mainwindow.ui
@@ -247,7 +247,13 @@
<property name="title">
<string>File</string>
</property>
- <addaction name="separator"/>
+ <widget class="QMenu" name="menuImport">
+ <property name="title">
+ <string>Import</string>
+ </property>
+ <addaction name="actionEtsKeyringImport"/>
+ </widget>
+ <addaction name="menuImport"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
@@ -310,6 +316,11 @@
<string>Clear All</string>
</property>
</action>
+ <action name="actionEtsKeyringImport">
+ <property name="text">
+ <string>KNX keyring file...</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/examples/knx/knxeditor/tunneling.cpp b/examples/knx/knxeditor/tunneling.cpp
index 7b21ee0..6a5e967 100644
--- a/examples/knx/knxeditor/tunneling.cpp
+++ b/examples/knx/knxeditor/tunneling.cpp
@@ -51,11 +51,13 @@
#include "tunneling.h"
#include "ui_tunneling.h"
-#include <QtCore/QMetaEnum>
-#include <QtCore/QMetaType>
-#include <QtGui/QStandardItemModel>
-#include <QtKnx/QKnxLinkLayerFrameBuilder>
-#include <QtWidgets/QTreeView>
+#include <QKnxLinkLayerFrameBuilder>
+#include <QKnxNetIpSecureConfiguration>
+#include <QMetaEnum>
+#include <QMetaType>
+#include <QStandardItemModel>
+#include <QTreeView>
+#include <QTreeWidget>
// -- KnxAddressValidator
@@ -94,9 +96,16 @@ Tunneling::Tunneling(QWidget* parent)
connect(ui->connectTunneling, &QPushButton::clicked, this, [&]() {
m_tunnel.setLocalPort(0);
- m_tunnel.connectToHost(m_server.controlEndpointAddress(),
- m_server.controlEndpointPort(),
- m_proto);
+ if (ui->secureSessionCheckBox->isChecked()) {
+ auto config = m_configs.value(ui->secureSessionCb->currentIndex());
+ config.setKeepSecureSessionAlive(true);
+ m_tunnel.setSecureConfiguration(config);
+ m_tunnel.connectToHostEncrypted(m_server.controlEndpointAddress(),
+ m_server.controlEndpointPort());
+ } else {
+ m_tunnel.connectToHost(m_server.controlEndpointAddress(),
+ m_server.controlEndpointPort(), m_proto);
+ }
});
connect(&m_tunnel, &QKnxNetIpTunnel::connected, this, [&] {
@@ -113,6 +122,8 @@ Tunneling::Tunneling(QWidget* parent)
});
connect(&m_tunnel, &QKnxNetIpTunnel::disconnected, this, [&] {
+ if (!ui)
+ return;
ui->connectTunneling->setEnabled(true);
ui->disconnectTunneling->setEnabled(false);
ui->tunnelingSendRequest->setEnabled(false);
@@ -241,6 +252,7 @@ Tunneling::Tunneling(QWidget* parent)
Tunneling::~Tunneling()
{
delete ui;
+ ui = nullptr;
}
void Tunneling::setNatAware(bool isNatAware)
@@ -263,6 +275,8 @@ void Tunneling::setKnxNetIpServer(const QKnxNetIpServerInfo &server)
ui->connectTunneling->setEnabled(true);
ui->disconnectTunneling->setEnabled(false);
}
+
+ updateSecureConfigCombo();
ui->tunnelingSendRequest->setEnabled(false);
}
@@ -331,6 +345,12 @@ void Tunneling::on_manualInput_clicked(bool checked)
ui->cemiFrame->setFocus();
}
+void Tunneling::onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs)
+{
+ m_configs = configs;
+ updateSecureConfigCombo();
+}
+
void Tunneling::setupApciTpciComboBox()
{
int index = QKnxTpdu::staticMetaObject.indexOfEnumerator("ApplicationControlField");
@@ -384,3 +404,22 @@ void Tunneling::updateAdditionalInfoTypesComboBox()
model->item(0)->setEnabled(false);
model->item(model->rowCount() - 1)->setEnabled(false);
}
+
+void Tunneling::updateSecureConfigCombo()
+{
+ ui->secureSessionCb->clear();
+
+ ui->secureSessionCheckBox->setEnabled(!m_configs.isEmpty());
+ ui->secureSessionCheckBox->setChecked(m_proto == QKnxNetIp::HostProtocol::TCP_IPv4);
+
+ for (int i = 0; i < m_configs.size(); ++i) {
+ const auto &config = m_configs[i];
+ if (m_server.individualAddress() != config.host())
+ continue;
+
+ const auto ia = config.individualAddress();
+ ui->secureSessionCb->addItem(tr("User ID: %1 (Individual Address: %2)")
+ .arg(config.userId())
+ .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i);
+ }
+}
diff --git a/examples/knx/knxeditor/tunneling.h b/examples/knx/knxeditor/tunneling.h
index 4431223..c95e41b 100644
--- a/examples/knx/knxeditor/tunneling.h
+++ b/examples/knx/knxeditor/tunneling.h
@@ -53,11 +53,11 @@
#include <QKnxControlField>
#include <QKnxExtendedControlField>
+#include <QKnxLinkLayerFrame>
#include <QKnxNetIpTunnel>
#include <QKnxNetIpServerInfo>
-#include <QKnxLinkLayerFrame>
-#include <QValidator>
#include <QRegularExpression>
+#include <QValidator>
#include <QWidget>
QT_BEGIN_NAMESPACE
@@ -94,6 +94,7 @@ public:
void setLocalAddress(const QHostAddress &address);
void setKnxNetIpServer(const QKnxNetIpServerInfo &server);
void setTcpEnable(bool value);
+ void onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs);
public slots:
void clearLogging();
@@ -108,6 +109,7 @@ private:
void setupApciTpciComboBox();
void setupMessageCodeComboBox();
void updateAdditionalInfoTypesComboBox();
+ void updateSecureConfigCombo();
private:
Ui::Tunneling *ui { nullptr };
@@ -119,6 +121,8 @@ private:
QKnxNetIpTunnel m_tunnel;
QKnxNetIpServerInfo m_server;
QKnxNetIp::HostProtocol m_proto { QKnxNetIp::HostProtocol::UDP_IPv4 };
+ QVector<QKnxNetIpSecureConfiguration> m_configs;
+
};
#endif
diff --git a/examples/knx/knxeditor/tunneling.ui b/examples/knx/knxeditor/tunneling.ui
index 072211b..ddf01a3 100644
--- a/examples/knx/knxeditor/tunneling.ui
+++ b/examples/knx/knxeditor/tunneling.ui
@@ -7,13 +7,13 @@
<x>0</x>
<y>0</y>
<width>856</width>
- <height>385</height>
+ <height>404</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,0,1,0">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0,0,0,0,0">
<item>
@@ -540,7 +540,7 @@
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
@@ -608,26 +608,7 @@
</layout>
</item>
<item>
- <widget class="QTextEdit" name="textOuputTunneling">
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <property name="contextMenuPolicy">
- <enum>Qt::DefaultContextMenu</enum>
- </property>
- <property name="acceptDrops">
- <bool>false</bool>
- </property>
- <property name="undoRedoEnabled">
- <bool>false</bool>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QCheckBox" name="manualInput">
<property name="text">
@@ -636,7 +617,7 @@
</widget>
</item>
<item>
- <spacer name="horizontalSpacer_3">
+ <spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -679,18 +660,70 @@
</item>
<item>
<widget class="QPushButton" name="tunnelingSendRequest">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="text">
<string>Send Request</string>
</property>
</widget>
</item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTextEdit" name="textOuputTunneling">
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
+ <property name="acceptDrops">
+ <bool>false</bool>
+ </property>
+ <property name="undoRedoEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="secureSessionCheckBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use secure session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="secureSessionCb">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
+ <enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -740,8 +773,6 @@
<tabstop>data</tabstop>
<tabstop>manualInput</tabstop>
<tabstop>tunnelingSendRequest</tabstop>
- <tabstop>connectTunneling</tabstop>
- <tabstop>disconnectTunneling</tabstop>
</tabstops>
<resources/>
<connections>
@@ -752,8 +783,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>16</x>
- <y>364</y>
+ <x>26</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>23</x>
@@ -768,8 +799,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>37</x>
- <y>363</y>
+ <x>47</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>94</x>
@@ -784,8 +815,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>58</x>
- <y>361</y>
+ <x>68</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>42</x>
@@ -800,8 +831,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>69</x>
- <y>362</y>
+ <x>79</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>462</x>
@@ -816,8 +847,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>14</x>
- <y>359</y>
+ <x>24</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>576</x>
@@ -832,8 +863,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>56</x>
- <y>360</y>
+ <x>66</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>402</x>
@@ -848,8 +879,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>57</x>
- <y>364</y>
+ <x>67</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>706</x>
@@ -864,8 +895,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>50</x>
- <y>357</y>
+ <x>60</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>845</x>
@@ -880,12 +911,12 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>29</x>
- <y>355</y>
+ <x>39</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
- <x>181</x>
- <y>256</y>
+ <x>539</x>
+ <y>254</y>
</hint>
</hints>
</connection>
@@ -896,8 +927,8 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>79</x>
- <y>355</y>
+ <x>89</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
<x>680</x>
@@ -912,12 +943,12 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>12</x>
- <y>365</y>
+ <x>22</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
- <x>19</x>
- <y>251</y>
+ <x>280</x>
+ <y>254</y>
</hint>
</hints>
</connection>
@@ -928,12 +959,12 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>15</x>
- <y>369</y>
+ <x>25</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
- <x>116</x>
- <y>245</y>
+ <x>411</x>
+ <y>254</y>
</hint>
</hints>
</connection>
@@ -944,12 +975,12 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>51</x>
- <y>363</y>
+ <x>61</x>
+ <y>282</y>
</hint>
<hint type="destinationlabel">
- <x>416</x>
- <y>256</y>
+ <x>36</x>
+ <y>254</y>
</hint>
</hints>
</connection>
@@ -960,12 +991,28 @@
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>51</x>
- <y>363</y>
+ <x>61</x>
+ <y>282</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>254</x>
+ <y>254</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>secureSessionCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>secureSessionCb</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>75</x>
+ <y>384</y>
</hint>
<hint type="destinationlabel">
- <x>472</x>
- <y>256</y>
+ <x>150</x>
+ <y>383</y>
</hint>
</hints>
</connection>
diff --git a/examples/knx/knxeditor/tunnelingfeatures.cpp b/examples/knx/knxeditor/tunnelingfeatures.cpp
index 851cc5f..dedec6d 100644
--- a/examples/knx/knxeditor/tunnelingfeatures.cpp
+++ b/examples/knx/knxeditor/tunnelingfeatures.cpp
@@ -74,12 +74,21 @@ TunnelingFeatures::TunnelingFeatures(QWidget *parent)
ui->featureValue->setEnabled(false);
connect(ui->connectTunneling, &QPushButton::clicked, this, [&]() {
- ui->textOuputTunneling->append(tr("Connecting to: %1 on port: %2 protocol: %3").arg(m_server
- .controlEndpointAddress().toString()).arg(m_server.controlEndpointPort()).arg(int(m_protocol)));
+ ui->textOuputTunneling->append(tr("Connecting to: %1 on port: %2 protocol: %3")
+ .arg(m_server.controlEndpointAddress().toString())
+ .arg(m_server.controlEndpointPort()).arg(int(m_protocol)));
+
m_tunnel.setLocalPort(0);
- m_tunnel.connectToHost(m_server.controlEndpointAddress(),
- m_server.controlEndpointPort(),
- m_protocol);
+ if (ui->secureSessionCheckBox->isChecked()) {
+ auto config = m_configs.value(ui->secureSessionCb->currentIndex());
+ config.setKeepSecureSessionAlive(true);
+ m_tunnel.setSecureConfiguration(config);
+ m_tunnel.connectToHostEncrypted(m_server.controlEndpointAddress(),
+ m_server.controlEndpointPort());
+ } else {
+ m_tunnel.connectToHost(m_server.controlEndpointAddress(),
+ m_server.controlEndpointPort(), m_protocol);
+ }
});
connect(ui->tunnelingSend, &QPushButton::clicked, this, [&]() {
@@ -100,7 +109,7 @@ TunnelingFeatures::TunnelingFeatures(QWidget *parent)
else if (type == ServType::TunnelingFeatureSet)
m_tunnel.sendTunnelingFeatureSet(featureType, bytes);
- ui->statusBar->setText(tr("Status: (%1) Messages sent.").arg(m_tunnel
+ ui->textOuputTunneling->append(tr("Status: (%1) Messages sent.").arg(m_tunnel
.sequenceCount(QKnxNetIpEndpointConnection::SequenceType::Send) + 1));
});
@@ -190,7 +199,7 @@ TunnelingFeatures::TunnelingFeatures(QWidget *parent)
ui->textOuputTunneling->append(tr("Successfully connected to: %1 on port: %2").arg(m_server
.controlEndpointAddress().toString()).arg(m_server.controlEndpointPort()));
- ui->statusBar->setText("Status: Connected.");
+ ui->textOuputTunneling->append("Status: Connected.");
});
connect(ui->disconnectTunneling, &QPushButton::clicked, this, [&]() {
@@ -206,7 +215,7 @@ TunnelingFeatures::TunnelingFeatures(QWidget *parent)
ui->featureValue->setEnabled(false);
ui->textOuputTunneling->append(tr("Successfully disconnected from: %1 on port: %2\n")
.arg(m_server.controlEndpointAddress().toString()).arg(m_server.controlEndpointPort()));
- ui->statusBar->setText("Status: Disconnected.");
+ ui->textOuputTunneling->append("Status: Disconnected.");
});
connect(&m_tunnel, &QKnxNetIpTunnel::errorOccurred, this,
@@ -217,10 +226,10 @@ TunnelingFeatures::TunnelingFeatures(QWidget *parent)
connect(ui->tunnelServiceType, &QComboBox::currentTextChanged, this, [&](const QString &text) {
if (text == QString("TunnelingFeatureSet")) {
ui->featureValue->setEnabled(true);
- ui->statusBar->setText("Status: Fill in the feature type and value fields.");
+ ui->textOuputTunneling->append("Status: Fill in the feature type and value fields.");
} else {
ui->featureValue->setEnabled(false);
- ui->statusBar->setText("");
+ ui->textOuputTunneling->append("");
}
checkFeatureValue();
});
@@ -259,8 +268,10 @@ void TunnelingFeatures::setKnxNetIpServer(const QKnxNetIpServerInfo &server)
ui->connectTunneling->setEnabled(true);
ui->disconnectTunneling->setEnabled(false);
}
+
+ updateSecureConfigCombo();
ui->tunnelingSend->setEnabled(false);
- ui->statusBar->setText("Status: Start by clicking connect.");
+ ui->textOuputTunneling->append("Status: Start by clicking connect.");
}
void TunnelingFeatures::setTcpEnable(bool value)
@@ -268,10 +279,16 @@ void TunnelingFeatures::setTcpEnable(bool value)
m_protocol = (value ? QKnxNetIp::HostProtocol::TCP_IPv4 : QKnxNetIp::HostProtocol::UDP_IPv4);
}
+void TunnelingFeatures::onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs)
+{
+ m_configs = configs;
+ updateSecureConfigCombo();
+}
+
void TunnelingFeatures::checkFeatureValue()
{
if (!ui->featureValue->isEnabled()) {
- ui->statusBar->setText("");
+ ui->textOuputTunneling->append("");
ui->tunnelingSend->setEnabled(m_tunnel.state() == QKnxNetIpEndpointConnection::State::Connected);
return;
}
@@ -289,10 +306,29 @@ void TunnelingFeatures::checkFeatureValue()
auto text = ui->featureValue->text();
if (text.isEmpty() || !validFeature(type, featureType, bytes)
|| ((text.size() % 2) != 0)) {
- ui->statusBar->setText("Status: Invalid value entered");
+ ui->textOuputTunneling->append("Status: Invalid value entered");
ui->tunnelingSend->setEnabled(false);
return;
}
- ui->statusBar->setText("Status: Valid value entered, click send.");
+ ui->textOuputTunneling->append("Status: Valid value entered, click send.");
ui->tunnelingSend->setEnabled(m_tunnel.state() == QKnxNetIpEndpointConnection::State::Connected);
}
+
+void TunnelingFeatures::updateSecureConfigCombo()
+{
+ ui->secureSessionCb->clear();
+
+ ui->secureSessionCheckBox->setEnabled(!m_configs.isEmpty());
+ ui->secureSessionCheckBox->setChecked(m_protocol == QKnxNetIp::HostProtocol::TCP_IPv4);
+
+ for (int i = 0; i < m_configs.size(); ++i) {
+ const auto &config = m_configs[i];
+ if (m_server.individualAddress() != config.host())
+ continue;
+
+ const auto ia = config.individualAddress();
+ ui->secureSessionCb->addItem(tr("User ID: %1 (Individual Address: %2)")
+ .arg(config.userId())
+ .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i);
+ }
+}
diff --git a/examples/knx/knxeditor/tunnelingfeatures.h b/examples/knx/knxeditor/tunnelingfeatures.h
index 8c93667..162497e 100644
--- a/examples/knx/knxeditor/tunnelingfeatures.h
+++ b/examples/knx/knxeditor/tunnelingfeatures.h
@@ -53,15 +53,20 @@ public:
void setLocalAddress(const QHostAddress &address);
void setKnxNetIpServer(const QKnxNetIpServerInfo &server);
void setTcpEnable(bool value);
+ void onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs);
private:
void checkFeatureValue();
- Ui::TunnelingFeatures *ui;
+ void updateSecureConfigCombo();
+
+private:
+ Ui::TunnelingFeatures *ui { nullptr };
QKnxNetIpServerInfo m_server;
QKnxNetIpTunnel m_tunnel;
QKnxNetIp::HostProtocol m_protocol = { QKnxNetIp::HostProtocol::UDP_IPv4 };
+ QVector<QKnxNetIpSecureConfiguration> m_configs;
};
#endif // TUNNELINGFEATURES_H
diff --git a/examples/knx/knxeditor/tunnelingfeatures.ui b/examples/knx/knxeditor/tunnelingfeatures.ui
index 50abfc8..112eb27 100644
--- a/examples/knx/knxeditor/tunnelingfeatures.ui
+++ b/examples/knx/knxeditor/tunnelingfeatures.ui
@@ -13,8 +13,8 @@
<property name="windowTitle">
<string>Form</string>
</property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0" colspan="4">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,0">
<item row="0" column="0">
<widget class="QLabel" name="label_13">
@@ -184,7 +184,7 @@
</item>
</layout>
</item>
- <item row="1" column="0" colspan="4">
+ <item>
<widget class="QTextEdit" name="textOuputTunneling">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
@@ -203,48 +203,93 @@
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="statusBar">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Expanding</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>662</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="2">
- <widget class="QPushButton" name="connectTunneling">
- <property name="text">
- <string>Connect</string>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="disconnectTunneling">
- <property name="text">
- <string>Disconnect</string>
- </property>
- </widget>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QCheckBox" name="secureSessionCheckBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use secure session</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="secureSessionCb">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>388</width>
+ <height>17</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="connectTunneling">
+ <property name="text">
+ <string>Connect</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="disconnectTunneling">
+ <property name="text">
+ <string>Disconnect</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
</layout>
</widget>
<tabstops>
+ <tabstop>tunnelServiceType</tabstop>
+ <tabstop>featureIdentifier</tabstop>
+ <tabstop>featureValue</tabstop>
+ <tabstop>tunnelingSend</tabstop>
+ <tabstop>textOuputTunneling</tabstop>
+ <tabstop>secureSessionCheckBox</tabstop>
+ <tabstop>secureSessionCb</tabstop>
+ <tabstop>connectTunneling</tabstop>
<tabstop>disconnectTunneling</tabstop>
</tabstops>
<resources/>
- <connections/>
+ <connections>
+ <connection>
+ <sender>secureSessionCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>secureSessionCb</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>42</x>
+ <y>363</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>153</x>
+ <y>363</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/src/knx/doc/qtknx.qdocconf b/src/knx/doc/qtknx.qdocconf
index 64deeb8..a20ee87 100644
--- a/src/knx/doc/qtknx.qdocconf
+++ b/src/knx/doc/qtknx.qdocconf
@@ -39,7 +39,7 @@ moduleheader = QtKnx
Cpp.ignoretokens += Q_KNX_EXPORT
-depends += qtcore qtdoc qtnetwork qmake
+depends += qtcore qtdoc qtgui qtnetwork qmake qtwidgets
# Custom module header for Clang doc builds
moduleheader = QtKnx_pch.h
@@ -50,3 +50,4 @@ manifestmeta.thumbnail.names += "QtKnx/Discoverer*" \
navigation.landingpage = "Qt KNX"
navigation.cppclassespage = "Qt KNX C++ Classes"
+navigation.homepage = Qt for Automation
diff --git a/src/knx/dpt/qknx1bit.cpp b/src/knx/dpt/qknx1bit.cpp
index 7e0f64c..2bbf3af 100644
--- a/src/knx/dpt/qknx1bit.cpp
+++ b/src/knx/dpt/qknx1bit.cpp
@@ -111,7 +111,7 @@ QKnx1Bit::QKnx1Bit(int subType, bool bit)
: QKnxFixedSizeDatapointType(MainType, subType, TypeSize)
{
setDescription(tr("1-bit"));
- setRangeText(tr("true"), tr("false"));
+ setRangeText(tr("false"), tr("true"));
setRange(QVariant(0x00), QVariant(0x01));
setBit(bit);
diff --git a/src/knx/dpt/qknxdatapointtype.cpp b/src/knx/dpt/qknxdatapointtype.cpp
index b3fa6f0..7ee6368 100644
--- a/src/knx/dpt/qknxdatapointtype.cpp
+++ b/src/knx/dpt/qknxdatapointtype.cpp
@@ -698,8 +698,8 @@ QKnxDatapointType::QKnxDatapointType(Type type, int size)
// Datapoint Type shall be identified by a 16 bit main number separated
// by a dot from a 16 bit sub number. The assumption being made is that
// QKnxDatapointType::Type is encoded in that way while omitting the dot.
- int mainType = number.left(number.size() - 5).toInt(&okMain);
- int subType = number.right(5).toInt(&okSub);
+ quint16 mainType = number.leftRef(number.size() - 5).toUShort(&okMain);
+ quint16 subType = number.rightRef(5).toUShort(&okSub);
if (okMain && okSub)
d_ptr->setup(mainType, subType, quint32(type), size);
@@ -1052,7 +1052,7 @@ QKnxDatapointType::Type QKnxDatapointType::toType(const QString &dpt)
}
quint32 type;
- if (dtp.toType(mainType, subType, &type))
+ if (QKnxDatapointTypePrivate::toType(mainType, subType, &type))
return static_cast<Type> (type);
return QKnxDatapointType::Type::Unknown;
}
diff --git a/src/knx/dpt/qknxdatapointtypefactory.cpp b/src/knx/dpt/qknxdatapointtypefactory.cpp
index 4ab11a0..184e843 100644
--- a/src/knx/dpt/qknxdatapointtypefactory.cpp
+++ b/src/knx/dpt/qknxdatapointtypefactory.cpp
@@ -138,8 +138,8 @@ QKnxDatapointType *QKnxDatapointTypeFactory::createType(QKnxDatapointType::Type
// Datapoint Type shall be identified by a 16 bit main number separated by a dot from a 16 bit
// sub number. The assumption being made is that QKnxDatapointType::Type is encoded in that way
// while omitting the dot.
- int mainType = number.left(number.size() - 5).toInt(&okMain);
- int subType = number.right(5).toInt(&okSub);
+ int mainType = number.leftRef(number.size() - 5).toInt(&okMain);
+ int subType = number.rightRef(5).toInt(&okSub);
if (okMain && okSub)
return createType(mainType, subType);
@@ -223,6 +223,7 @@ QKnxDatapointTypeFactory::QKnxDatapointTypeFactory()
registerType<QKnxLogicalFunction>();
registerType<QKnxSceneAB>();
registerType<QKnxShutterBlindsMode>();
+ registerType<QKnxHeatCool>();
// DPT-2
registerType<QKnx1BitControlled>();
diff --git a/src/knx/ets/ets.pri b/src/knx/ets/ets.pri
new file mode 100644
index 0000000..f6b40fd
--- /dev/null
+++ b/src/knx/ets/ets.pri
@@ -0,0 +1,4 @@
+INCLUDEPATH += $$PWD
+
+SOURCES += $$PWD/manufacturers.cpp
+PUBLIC_HEADERS += $$PWD/manufacturers.h
diff --git a/src/knx/ets/manufacturers.cpp b/src/knx/ets/manufacturers.cpp
new file mode 100644
index 0000000..064675d
--- /dev/null
+++ b/src/knx/ets/manufacturers.cpp
@@ -0,0 +1,490 @@
+/******************************************************************************
+**
+** 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 "manufacturers.h"
+
+#include <QtCore/qvector.h>
+
+QT_BEGIN_NAMESPACE
+
+struct Manufacturer
+{
+ Manufacturer() = default;
+ Manufacturer(int i, const char *mId, const char *n)
+ : id(i)
+ , manufacturerId(mId)
+ , name(QString::fromUtf8(QByteArray::fromHex(n)))
+ {}
+
+ int id;
+ QLatin1String manufacturerId;
+ QString name;
+
+ static const QVector<Manufacturer> &manufacturers()
+ {
+ static QVector<Manufacturer> sManufacturers {
+ { 1, "M-0001", "5369656d656e73" },
+ { 2, "M-0002", "414242" },
+ { 4, "M-0004", "416c627265636874204a756e67" },
+ { 5, "M-0005", "42746963696e6f" },
+ { 6, "M-0006", "4265726b6572" },
+ { 7, "M-0007", "42757363682d4a616567657220456c656b74726f" },
+ { 11, "M-000B", "4c454752414e4420417070617265696c6c61676520c3a96c6563747269717565" },
+ { 12, "M-000C", "4d657274656e" },
+ { 14, "M-000E", "4142425370412d53414345204469766973696f6e" },
+ { 22, "M-0016", "536965646c6520262053c3b6686e65" },
+ { 24, "M-0018", "456265726c65" },
+ { 25, "M-0019", "474557495353" },
+ { 27, "M-001B", "416c626572742041636b65726d616e6e" },
+ { 28, "M-001C", "53636875706120476d6248" },
+ { 29, "M-001D", "414242205343485745495a" },
+ { 30, "M-001E", "46656c6c6572" },
+ { 32, "M-0020", "4445484e20262053c396484e45" },
+ { 33, "M-0021", "4352414254524545" },
+ { 36, "M-0024", "5061756c20486f63686bc3b670706572" },
+ { 37, "M-0025", "416c74656e62757267657220456c656374726f6e6963" },
+ { 41, "M-0029", "4772c3a473736c696e" },
+ { 42, "M-002A", "53696d6f6e" },
+ { 44, "M-002C", "56494d4152" },
+ { 45, "M-002D", "4d6f656c6c657220476562c3a47564656175746f6d6174696f6e204b47" },
+ { 46, "M-002E", "456c74616b6f" },
+ { 49, "M-0031", "426f7363682d5369656d656e73204861757368616c7473676572c3a47465" },
+ { 52, "M-0034", "524954544f20476d624826436f2e4b47" },
+ { 53, "M-0035", "506f77657220436f6e74726f6c73" },
+ { 55, "M-0037", "5a554d544f42454c" },
+ { 57, "M-0039", "50686f656e697820436f6e74616374" },
+ { 61, "M-003D", "5741474f204b6f6e74616b74746563686e696b" },
+ { 66, "M-0042", "5769656c616e6420456c656374726963" },
+ { 67, "M-0043", "4865726d616e6e204b6c65696e68756973" },
+ { 69, "M-0045", "5374696562656c20456c74726f6e" },
+ { 71, "M-0047", "546568616c6974" },
+ { 72, "M-0048", "54686562656e204147" },
+ { 73, "M-0049", "57696c68656c6d20527574656e6265636b" },
+ { 75, "M-004B", "57696e6b68617573" },
+ { 76, "M-004C", "526f6265727420426f736368" },
+ { 78, "M-004E", "536f6d6679" },
+ { 80, "M-0050", "576f6572747a" },
+ { 81, "M-0051", "56696573736d616e6e205765726b65" },
+ { 82, "M-0052", "494d4920487964726f6e696320456e67696e656572696e67" },
+ { 83, "M-0053", "4a6f682e205661696c6c616e74" },
+ { 85, "M-0055", "414d5020446575747363686c616e64" },
+ { 89, "M-0059", "426f73636820546865726d6f746563686e696b20476d6248" },
+ { 90, "M-005A", "534546202d2045434f544543" },
+ { 92, "M-005C", "444f524d4120476d6248202b20436f2e204b47" },
+ { 93, "M-005D", "57696e646f774d617374657220412f53" },
+ { 94, "M-005E", "57616c74686572205765726b65" },
+ { 95, "M-005F", "4f524153" },
+ { 97, "M-0061", "44c3a47477796c6572" },
+ { 98, "M-0062", "456c65637472616b" },
+ { 99, "M-0063", "54656368656d" },
+ { 100, "M-0064", "5363686e656964657220456c65637472696320496e647573747269657320534153" },
+ { 101, "M-0065", "5748442057696c68656c6d204875626572202b2053c3b6686e65" },
+ { 102, "M-0066", "42697363686f666620456c656b74726f6e696b" },
+ { 104, "M-0068", "4a4550415a" },
+ { 105, "M-0069", "525453204175746f6d6174696f6e" },
+ { 106, "M-006A", "4549424d41524b5420476d6248" },
+ { 107, "M-006B", "574152454d412052656e6b686f6666205345" },
+ { 108, "M-006C", "45656c656374726f6e" },
+ { 109, "M-006D", "42656c64656e20576972652026204361626c6520422e562e" },
+ { 110, "M-006E", "4265636b65722d416e74726965626520476d6248" },
+ { 111, "M-006F", "4a2e537465686c652b53c3b6686e6520476d6248" },
+ { 112, "M-0070", "414746454f" },
+ { 113, "M-0071", "5a656e6e696f" },
+ { 114, "M-0072", "5441504b4f20546563686e6f6c6f67696573" },
+ { 115, "M-0073", "48444c" },
+ { 116, "M-0074", "55706f6e6f72" },
+ { 117, "M-0075", "7365204c696768746d616e6167656d656e74204147" },
+ { 118, "M-0076", "41726375732d656473" },
+ { 119, "M-0077", "496e7465736973" },
+ { 120, "M-0078", "486572686f6c647420436f6e74726f6c732073726c" },
+ { 121, "M-0079", "4e696b6f2d5a75626c696e" },
+ { 122, "M-007A", "44757261626c6520546563686e6f6c6f67696573" },
+ { 123, "M-007B", "496e6e6f7465616d" },
+ { 124, "M-007C", "69736520476d6248" },
+ { 125, "M-007D", "5445414d20464f522054524f4e494353" },
+ { 126, "M-007E", "43494154" },
+ { 127, "M-007F", "52656d656861204256" },
+ { 128, "M-0080", "4553594c5558" },
+ { 129, "M-0081", "424153414c5445" },
+ { 130, "M-0082", "56657374616d61746963" },
+ { 131, "M-0083", "4d445420746563686e6f6c6f67696573" },
+ { 132, "M-0084", "576172656e646f72666572204bc3bc6368656e20476d6248" },
+ { 133, "M-0085", "566964656f2d53746172" },
+ { 134, "M-0086", "536974656b" },
+ { 135, "M-0087", "434f4e54524f4c74726f6e6963" },
+ { 136, "M-0088", "66756e6374696f6e20546563686e6f6c6f6779" },
+ { 137, "M-0089", "414d58" },
+ { 138, "M-008A", "454c444154" },
+ { 139, "M-008B", "50616e61736f6e6963" },
+ { 140, "M-008C", "50756c736520546563686e6f6c6f67696573" },
+ { 141, "M-008D", "4372657374726f6e" },
+ { 142, "M-008E", "535445494e454c2070726f66657373696f6e616c" },
+ { 143, "M-008F", "42494c544f4e204c4544204c69676874696e67" },
+ { 144, "M-0090", "64656e726f204147" },
+ { 145, "M-0091", "476550726f" },
+ { 146, "M-0092", "707265757373656e206175746f6d6174696f6e" },
+ { 147, "M-0093", "5a6f7070617320496e6475737472696573" },
+ { 148, "M-0094", "4d414354454348" },
+ { 149, "M-0095", "544543484e4f2d5452454e44" },
+ { 150, "M-0096", "4653204361626c6573" },
+ { 151, "M-0097", "44656c746120446f7265" },
+ { 152, "M-0098", "456973736f756e64" },
+ { 153, "M-0099", "436973636f" },
+ { 154, "M-009A", "44696e7579" },
+ { 155, "M-009B", "694b4e6958" },
+ { 156, "M-009C", "526164656d616368657220476572c3a474652d456c656b74726f6e696b20476d6248" },
+ { 157, "M-009D", "45476920456c656374726f61637573746963612047656e6572616c2049626572696361" },
+ { 158, "M-009E", "42657320e2809320496e67656e69756d" },
+ { 159, "M-009F", "456c61624e4554" },
+ { 160, "M-00A0", "426c756d6f746978" },
+ { 161, "M-00A1", "48756e74657220446f75676c6173" },
+ { 162, "M-00A2", "4150524943554d" },
+ { 163, "M-00A3", "5449414e5355204175746f6d6174696f6e" },
+ { 164, "M-00A4", "427562656e646f726666" },
+ { 165, "M-00A5", "4d425320476d6248" },
+ { 166, "M-00A6", "456e65727465782042617965726e20476d6248" },
+ { 167, "M-00A7", "424d53" },
+ { 168, "M-00A8", "53696e61707369" },
+ { 169, "M-00A9", "456d6265646465642053797374656d7320534941" },
+ { 170, "M-00AA", "4b4e5831" },
+ { 171, "M-00AB", "546f6b6b61" },
+ { 172, "M-00AC", "4e616e6f53656e7365" },
+ { 173, "M-00AD", "50454152204175746f6d6174696f6e20476d6248" },
+ { 174, "M-00AE", "444741" },
+ { 175, "M-00AF", "4c7574726f6e" },
+ { 176, "M-00B0", "4149525a4f4e4520e2809320414c545241" },
+ { 177, "M-00B1", "4c6974686f73732044657369676e205377697463686573" },
+ { 178, "M-00B2", "334154454c" },
+ { 179, "M-00B3", "5068696c69707320436f6e74726f6c73" },
+ { 180, "M-00B4", "56454c555820412f53" },
+ { 181, "M-00B5", "4c4f59544543" },
+ { 182, "M-00B6", "53425320532e702e412e" },
+ { 183, "M-00B7", "5349524c414e20546563686e6f6c6f67696573" },
+ { 184, "M-00B8", "426c657520436f6d6d2720417a7572" },
+ { 185, "M-00B9", "495420476d6248" },
+ { 186, "M-00BA", "52454e534f4e" },
+ { 187, "M-00BB", "4845502047726f7570" },
+ { 188, "M-00BC", "42616c6d617274" },
+ { 189, "M-00BD", "47465320476d6248" },
+ { 190, "M-00BE", "536368656e6b65722053746f72656e204147" },
+ { 191, "M-00BF", "416c676f64756520456c657474726f6e69636120532e722e4c2e" },
+ { 192, "M-00C0", "4e6577726f6e2053797374656d" },
+ { 193, "M-00C1", "6d61696e74726f6e6963" },
+ { 194, "M-00C2", "56616e74616765" },
+ { 195, "M-00C3", "466f7265736973" },
+ { 196, "M-00C4", "526573656172636820262050726f64756374696f6e204173736f63696174696f6e2053454d" },
+ { 197, "M-00C5", "5765696e7a6965726c20456e67696e656572696e6720476d6248" },
+ { 198, "M-00C6", "4dc3b6686c656e686f66662057c3a4726d65746563686e696b20476d6248" },
+ { 199, "M-00C7", "504b432d47524f5550204f796a" },
+ { 200, "M-00C8", "422e452e472e" },
+ { 201, "M-00C9", "456c736e657220456c656b74726f6e696b20476d6248" },
+ { 202, "M-00CA", "5369656d656e73204275696c64696e6720546563686e6f6c6f676965732028484b2f4368696e6129204c74642e" },
+ { 204, "M-00CC", "457574726163" },
+ { 205, "M-00CD", "4775737461762048656e73656c20476d6248202620436f2e204b47" },
+ { 206, "M-00CE", "4741524f204142" },
+ { 207, "M-00CF", "57616c646d616e6e204c69636874746563686e696b" },
+ { 208, "M-00D0", "534348c39c434f" },
+ { 209, "M-00D1", "454d55" },
+ { 210, "M-00D2", "4a4e65742053797374656d73204147" },
+ { 214, "M-00D6", "4f2e592e4c2e20456c656374726f6e696373" },
+ { 215, "M-00D7", "47616c61782053797374656d" },
+ { 216, "M-00D8", "4469736368" },
+ { 217, "M-00D9", "4175636f7465616d" },
+ { 218, "M-00DA", "4c75786d61746520436f6e74726f6c73" },
+ { 219, "M-00DB", "44616e666f7373" },
+ { 220, "M-00DC", "41535420476d6248" },
+ { 222, "M-00DE", "57494c41204c6575636874656e" },
+ { 223, "M-00DF", "622b62204175746f6d6174696f6e732d20756e6420537465756572756e6773746563686e696b" },
+ { 225, "M-00E1", "4c696e67672026204a616e6b65" },
+ { 227, "M-00E3", "536175746572" },
+ { 228, "M-00E4", "53494d55" },
+ { 232, "M-00E8", "54686562656e20485453204147" },
+ { 233, "M-00E9", "416d616e6e20476d6248" },
+ { 234, "M-00EA", "4245524720456e65726769656b6f6e74726f6c6c73797374656d6520476d6248" },
+ { 235, "M-00EB", "48c3bc70706520466f726d20536f6e6e656e73636875747a73797374656d6520476d6248" },
+ { 237, "M-00ED", "4f76656e74726f70204b47" },
+ { 238, "M-00EE", "4772696573736572204147" },
+ { 239, "M-00EF", "4950415320476d6248" },
+ { 240, "M-00F0", "656c65726f20476d6248" },
+ { 241, "M-00F1", "417264616e2050726f64756374696f6e20616e6420496e647573747269616c20436f6e74726f6c73204c74642e" },
+ { 242, "M-00F2", "4d65746563204d65c39f746563686e696b20476d6248" },
+ { 244, "M-00F4", "454c4b412d456c656b74726f6e696b20476d6248" },
+ { 245, "M-00F5", "454c454b54524f414e4c4147454e20442e204e4147454c" },
+ { 246, "M-00F6", "547269646f6e696320426175656c656d656e746520476d6248" },
+ { 248, "M-00F8", "5374656e676c657220476573656c6c736368616674" },
+ { 249, "M-00F9", "5363686e656964657220456c65637472696320284d4729" },
+ { 250, "M-00FA", "4b4e58204173736f63696174696f6e" },
+ { 251, "M-00FB", "5649564f" },
+ { 252, "M-00FC", "4875676f204dc3bc6c6c657220476d6248202620436f204b47" },
+ { 253, "M-00FD", "5369656d656e732048564143" },
+ { 254, "M-00FE", "415054" },
+ { 256, "M-0100", "48696768446f6d" },
+ { 257, "M-0101", "546f70205365727669636573" },
+ { 258, "M-0102", "616d6269486f6d65" },
+ { 259, "M-0103", "444154454320656c656374726f6e6963204147" },
+ { 260, "M-0104", "414255532053656375726974792d43656e746572" },
+ { 261, "M-0105", "4c6974652d5075746572" },
+ { 262, "M-0106", "54616e74726f6e20456c656374726f6e6963" },
+ { 263, "M-0107", "59c3b66e6e6574" },
+ { 264, "M-0108", "444b582054656368" },
+ { 265, "M-0109", "56696174726f6e" },
+ { 266, "M-010A", "4e61757469627573" },
+ { 267, "M-010B", "4f4e2053656d69636f6e647563746f72" },
+ { 268, "M-010C", "4c6f6e67636875616e67" },
+ { 269, "M-010D", "4169722d4f6e204147" },
+ { 270, "M-010E", "69622d636f6d70616e7920476d6248" },
+ { 271, "M-010F", "536174696f6e20466163746f7279" },
+ { 272, "M-0110", "4167656e74696c6f20476d6248" },
+ { 273, "M-0111", "4d616b656c20456c656b7472696b" },
+ { 274, "M-0112", "48656c696f732056656e74696c61746f72656e" },
+ { 275, "M-0113", "4f74746f20536f6c7574696f6e7320507465204c7464" },
+ { 276, "M-0114", "4169726d6173746572" },
+ { 277, "M-0115", "56616c6c6f7820476d6248" },
+ { 278, "M-0116", "44616c6974656b" },
+ { 279, "M-0117", "4153494e" },
+ { 280, "M-0118", "4272696467657320496e74656c6c6967656e636520546563686e6f6c6f677920496e632e" },
+ { 281, "M-0119", "4152424f4e4941" },
+ { 282, "M-011A", "4b45524d49" },
+ { 283, "M-011B", "50524f4c5558" },
+ { 284, "M-011C", "436c6963486f6d65" },
+ { 285, "M-011D", "434f4d4d4158" },
+ { 286, "M-011E", "454145" },
+ { 287, "M-011F", "54656e7365" },
+ { 288, "M-0120", "5365796f756e6720456c656374726f6e696373" },
+ { 289, "M-0121", "4c696665646f6d7573" },
+ { 290, "M-0122", "4555524f74726f6e696320546563686e6f6c6f677920476d6248" },
+ { 291, "M-0123", "746369" },
+ { 292, "M-0124", "52697368756e20456c656374726f6e6963" },
+ { 293, "M-0125", "5a697061746f" },
+ { 294, "M-0126", "636d2d736563757269747920476d6248202620436f204b47" },
+ { 295, "M-0127", "51696e67204361626c6573" },
+ { 296, "M-0128", "4c4142494f" },
+ { 297, "M-0129", "436f73746572205465636e6f6c6f67696520456c657474726f6e6963686520532e702e412e" },
+ { 298, "M-012A", "452e472e45" },
+ { 299, "M-012B", "4e4554784175746f6d6174696f6e" },
+ { 300, "M-012C", "746563616c6f72" },
+ { 301, "M-012D", "55726d657420456c656374726f6e69637320284875697a686f7529204c74642e" },
+ { 302, "M-012E", "50656979696e67204275696c64696e6720436f6e74726f6c" },
+ { 303, "M-012F", "42505420532e702e412e206120536f63696f20556e69636f" },
+ { 304, "M-0130", "4b616e6f6e746563202d204b616e6f6e425553" },
+ { 305, "M-0131", "495345522054656368" },
+ { 306, "M-0132", "46696e656c696e65" },
+ { 307, "M-0133", "435020456c656374726f6e696373204c7464" },
+ { 308, "M-0134", "4e696b6f2d536572766f64616e20412f53" },
+ { 309, "M-0135", "53696d6f6e" },
+ { 310, "M-0136", "474d206d6f64756c6172207076742e204c74642e" },
+ { 311, "M-0137", "4655204348454e4720496e74656c6c6967656e6365" },
+ { 312, "M-0138", "4e65784b6f6e" },
+ { 313, "M-0139", "4645454c20732e722e6c" },
+ { 314, "M-013A", "4e6f742041737369676e6564" },
+ { 315, "M-013B", "5368656e7a68656e2046616e6861692053616e6a69616e6720456c656374726f6e69637320436f2e2c204c74642e" },
+ { 316, "M-013C", "4a69757a686f752047726565626c65" },
+ { 317, "M-013D", "41756dc3bc6c6c65722041756d6174696320476d6248" },
+ { 318, "M-013E", "45746d616e20456c656374726963" },
+ { 319, "M-013F", "454d5420436f6e74726f6c73" },
+ { 320, "M-0140", "5a69646154656368204147" },
+ { 321, "M-0141", "494447532062766261" },
+ { 322, "M-0142", "64616b616e696d6f" },
+ { 323, "M-0143", "547265626f72204175746f6d6174696f6e204142" },
+ { 324, "M-0144", "536174656c2073702e207a206f2e6f2e" },
+ { 325, "M-0145", "527573736f756e642c20496e632e" },
+ { 326, "M-0146", "4d696465612048656174696e6720262056656e74696c6174696e672045717569706d656e7420434f204c5444" },
+ { 327, "M-0147", "436f6e736f727a696f2054657272616e756f7661" },
+ { 328, "M-0148", "576f6c66204865697a746563686e696b20476d6248" },
+ { 329, "M-0149", "534f4e544543" },
+ { 330, "M-014A", "42656c636f6d204361626c6573204c74642e" },
+ { 331, "M-014B", "4775616e677a686f752053656157696e20456c656374726963616c20546563686e6f6c6f6769657320436f2e2c204c74642e" },
+ { 332, "M-014C", "416372656c" },
+ { 333, "M-014D", "4672616e6b652041717561726f7474657220476d6248" },
+ { 334, "M-014E", "4f72696f6e2053797374656d73" },
+ { 335, "M-014F", "5363687261636b20546563686e696b20476d6248" },
+ { 336, "M-0150", "494e5350524944" },
+ { 337, "M-0151", "53756e726963686572" },
+ { 338, "M-0152", "4d656e726564206175746f6d6174696f6e2073797374656d287368616e676861692920436f2e2c4c74642e" },
+ { 339, "M-0153", "4175726578" },
+ { 340, "M-0154", "4a6f736566204261727468656c6d6520476d6248202620436f2e204b47" },
+ { 341, "M-0155", "417263686974656374757265204e756d657269717565" },
+ { 342, "M-0156", "55502047524f5550" },
+ { 343, "M-0157", "54656b6e6f732d4176696e6e6f" },
+ { 344, "M-0158", "4e696e67626f20446f6f7961204d656368616e6963202620456c656374726f6e696320546563686e6f6c6f6779" },
+ { 345, "M-0159", "546865726d6f6b6f6e2053656e736f72746563686e696b20476d6248" },
+ { 346, "M-015A", "42454c494d4f204175746f6d6174696f6e204147" },
+ { 347, "M-015B", "5a65686e6465722047726f757020496e7465726e6174696f6e616c204147" },
+ { 348, "M-015C", "736b73204b696e6b656c20456c656b74726f6e696b" },
+ { 349, "M-015D", "454345205775726d69747a657220476d6248" },
+ { 350, "M-015E", "4c415253" },
+ { 351, "M-015F", "555243" },
+ { 352, "M-0160", "4c69676874436f6e74726f6c" },
+ { 353, "M-0161", "5368656e5a68656e20594d" },
+ { 354, "M-0162", "4d45414e2057454c4c20456e74657270726973657320436f2e204c74642e" },
+ { 355, "M-0163", "4f536978" },
+ { 356, "M-0164", "415950524f20546563686e6f6c6f6779" },
+ { 357, "M-0165", "48656665692045636f6c69746520536f667477617265" },
+ { 358, "M-0166", "456e6e6f" },
+ { 359, "M-0167", "4f484f53555245" },
+ { 360, "M-0168", "47617265666f776c" },
+ { 361, "M-0169", "47455a45" },
+ { 362, "M-016A", "4c4720456c656374726f6e69637320496e632e" },
+ { 363, "M-016B", "534d4320696e746572696f7273" },
+ { 364, "M-016C", "4e6f742041737369676e6564" },
+ { 365, "M-016D", "534353204361626c65" },
+ { 366, "M-016E", "486f76616c" },
+ { 367, "M-016F", "43414e5354" },
+ { 368, "M-0170", "48616e675a686f75204265726c696e" },
+ { 369, "M-0171", "45564e2d4c69636874746563686e696b" },
+ { 370, "M-0172", "7275746563" },
+ { 371, "M-0173", "46696e646572" },
+ { 372, "M-0174", "46756a697473752047656e6572616c204c696d69746564" },
+ { 373, "M-0175", "5a462046726965647269636873686166656e204147" },
+ { 374, "M-0176", "437265616c6564" },
+ { 375, "M-0177", "4d696c6573204d61676963204175746f6d6174696f6e2050726976617465204c696d69746564" },
+ { 376, "M-0178", "452b" },
+ { 377, "M-0179", "4974616c636f6e64" },
+ { 378, "M-017A", "534154494f4e" },
+ { 379, "M-017B", "4e657742657374" },
+ { 380, "M-017C", "474453204449474954414c2053595354454d53" },
+ { 381, "M-017D", "49646465726f" },
+ { 382, "M-017E", "4d424e4c4544" },
+ { 383, "M-017F", "56495452554d" },
+ { 384, "M-0180", "656b65792062696f6d65747269632073797374656d7320476d6248" },
+ { 385, "M-0181", "414d43" },
+ { 386, "M-0182", "5452494c555820476d6248202620436f2e204b47" },
+ { 387, "M-0183", "5745786365646f" },
+ { 388, "M-0184", "56454d455220535041" },
+ { 389, "M-0185", "416c6578616e6465722042c3bc726b6c6520476d6248202620436f204b47" },
+ { 390, "M-0186", "53656574726f6c6c" },
+ { 391, "M-0187", "5368656e7a68656e2048654775616e67" },
+ { 392, "M-0188", "4e6f742041737369676e6564" },
+ { 393, "M-0189", "5452414e4520422e562e422e41" },
+ { 394, "M-018A", "434152454c" },
+ { 395, "M-018B", "50726f6c69746520436f6e74726f6c73" },
+ { 396, "M-018C", "424f534d4552" },
+ { 397, "M-018D", "45554348495053" },
+ { 398, "M-018E", "636f6e6e65637420285468696e6b6120636f6e6e65637429" },
+ { 399, "M-018F", "5045414b6e78206120444f47415749535420636f6d70616e7920" },
+ { 400, "M-0190", "4143454d41544943" },
+ { 401, "M-0191", "454c4155535953" },
+ { 402, "M-0192", "49544b20456e67696e656572696e67204147" },
+ { 403, "M-0193", "417175616d6574726f204147" },
+ { 404, "M-0194", "464d5320486f73706974616c69747920507465204c7464" },
+ { 405, "M-0195", "4e75766f" },
+ { 406, "M-0196", "753a3a4c757820476d6248" },
+ { 407, "M-0197", "4272756d62657267204c6575636874656e" },
+ { 408, "M-0198", "4c696d65" },
+ { 409, "M-0199", "477265617420456d7069726520496e7465726e6174696f6e616c2047726f757020436f2e2c204c74642e" },
+ { 410, "M-019A", "4b61766f736870697368726f2041736961" },
+ { 411, "M-019B", "563220537041" },
+ { 412, "M-019C", "4a6f686e736f6e20436f6e74726f6c73" },
+ { 413, "M-019D", "41726b7564" },
+ { 414, "M-019E", "4972696469756d204c74642e" },
+ { 415, "M-019F", "62736d617274" },
+ { 416, "M-01A0", "42414220544543484e4f4c4f47494520476d6248" },
+ { 417, "M-01A1", "4e49434520537061" },
+ { 418, "M-01A2", "526564666973682047726f757020507479204c7464" },
+ { 419, "M-01A3", "53414249414e4120737061" },
+ { 420, "M-01A4", "5562656520496e746572616374697665204575726f7065" },
+ { 421, "M-01A5", "526578656c" },
+ { 422, "M-01A6", "4765732054656b6e696b20412e532e" },
+ { 423, "M-01A7", "41766520532e702e412e20" },
+ { 424, "M-01A8", "5a6875686169204c7465636820546563686e6f6c6f677920436f2e2c204c74642e20" },
+ { 425, "M-01A9", "4152434f4d" },
+ { 426, "M-01AA", "56494120546563686e6f6c6f676965732c20496e632e" },
+ { 427, "M-01AB", "4645454c534d4152542e" },
+ { 428, "M-01AC", "535550434f4e" },
+ { 429, "M-01AD", "4d414e4943" },
+ { 430, "M-01AE", "5472c3a4756d20646575747363686520456c656b74726f6e696b20476d6248" },
+ { 431, "M-01AF", "4e616e6a696e672053687566616e20496e666f726d6174696f6e20746563686e6f6c6f677920436f2e2c4c74642e" },
+ { 432, "M-01B0", "455754656368" },
+ { 433, "M-01B1", "4b6c75676572204175746f6d6174696f6e20476d6248" },
+ { 434, "M-01B2", "4a6f6f6e67416e6720436f6e74726f6c" },
+ { 435, "M-01B3", "477265656e436f6e74726f6c7320546563686e6f6c6f67792053646e2e204268642e" },
+ { 436, "M-01B4", "494d4520532e702e612e" },
+ { 437, "M-01B5", "5369436875616e2048616f44696e67" }
+ };
+ return sManufacturers;
+ }
+};
+
+/*!
+ \since 5.13
+ \inmodule QtKnx
+ \namespace QKnx::Ets
+
+ \brief Contains miscellaneous and ETS specific identifiers used throughout the QtKnx library.
+*/
+
+/*!
+ \since 5.13
+ \inmodule QtKnx
+ \namespace QKnx::Ets::Manufacturers
+
+ \brief Contains KNX manufacturer-specific identifiers used throughout the QtKnx library.
+*/
+
+/*!
+ \since 5.13
+
+ Returns the name of a KNX manufacturer for the given manufacturer ID
+ \a id or the given default value \a defaultValue if the \c id was not
+ found in the list of known manufacturers.
+*/
+QString QKnx::Ets::Manufacturers::fromId(int id, const QString &defaultValue)
+{
+ const auto m = Manufacturer::manufacturers();
+ const auto it = std::lower_bound(m.constBegin(), m.constEnd(), id,
+ [](const Manufacturer &mf, int id) {
+ return mf.id < id;
+ });
+ if (it != m.constEnd())
+ return it->name;
+ return defaultValue;
+}
+
+/*!
+ \since 5.13
+
+ Returns the name of a KNX manufacturer for the given manufacturer ID
+ \a id or the given default value \a defaultValue if the \c id was not
+ found in the list of known manufacturers.
+
+ \note: The \c id must be in the format of \b {M-\\d{4}}.
+*/
+QString QKnx::Ets::Manufacturers::fromId(const QString &id, const QString &defaultValue)
+{
+ const auto m = Manufacturer::manufacturers();
+ const auto it = std::lower_bound(m.constBegin(), m.constEnd(), id,
+ [](const Manufacturer &mf, const QString &id) {
+ return mf.id < id;
+ });
+ if (it != m.constEnd())
+ return it->name;
+ return defaultValue;
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/ets/manufacturers.h b/src/knx/ets/manufacturers.h
new file mode 100644
index 0000000..d7c181c
--- /dev/null
+++ b/src/knx/ets/manufacturers.h
@@ -0,0 +1,47 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef MANUFACTURERS_H
+#define MANUFACTURERS_H
+
+#include <QtCore/qstring.h>
+#include <QtKnx/qtknxglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QKnx { namespace Ets { namespace Manufacturers {
+
+ Q_KNX_EXPORT QString fromId(int id, const QString &defaultValue = {});
+ Q_KNX_EXPORT QString fromId(const QString &id, const QString &defaultValue = {});
+
+}}}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/knx.pro b/src/knx/knx.pro
index 083bef8..918f0fb 100644
--- a/src/knx/knx.pro
+++ b/src/knx/knx.pro
@@ -8,6 +8,7 @@ QMAKE_DOCS = $$PWD/doc/qtknx.qdocconf
include(core/core.pri)
include(dpt/dpt.pri)
+include(ets/ets.pri)
include(knxproj/knxproj.pri)
include(netip/netip.pri)
include(ssl/ssl.pri)
diff --git a/src/knx/netip/netip.pri b/src/knx/netip/netip.pri
index 5652563..88e2d58 100644
--- a/src/knx/netip/netip.pri
+++ b/src/knx/netip/netip.pri
@@ -53,7 +53,8 @@ PUBLIC_HEADERS += $$PWD/qknxnetip.h \
$$PWD/qknxnetipsessionstatus.h \
$$PWD/qknxnetiptimernotify.h \
$$PWD/qknxnetipsecurewrapper.h \
- $$PWD/qknxnetiprouter.h
+ $$PWD/qknxnetiprouter.h \
+ $$PWD/qknxnetipsecureconfiguration.h
PRIVATE_HEADERS += \
$$PWD/qknxbuilderdata_p.h \
@@ -61,7 +62,8 @@ PRIVATE_HEADERS += \
$$PWD/qknxnetipserverdescriptionagent_p.h \
$$PWD/qknxnetipserverdiscoveryagent_p.h \
$$PWD/qknxnetipserverinfo_p.h \
- $$PWD/qknxnetiptestrouter_p.h
+ $$PWD/qknxnetiptestrouter_p.h \
+ $$PWD/qknxnetipsecureconfiguration_p.h
SOURCES += $$PWD/qknxnetip.cpp \
$$PWD/qknxnetipconfigdib.cpp \
@@ -117,4 +119,5 @@ SOURCES += $$PWD/qknxnetip.cpp \
$$PWD/qknxnetiptimernotify.cpp \
$$PWD/qknxnetipsecurewrapper.cpp \
$$PWD/qknxnetiprouter.cpp \
- $$PWD/qknxnetiprouter_p.cpp
+ $$PWD/qknxnetiprouter_p.cpp \
+ $$PWD/qknxnetipsecureconfiguration.cpp
diff --git a/src/knx/netip/qknxbuilderdata_p.h b/src/knx/netip/qknxbuilderdata_p.h
index 8f8b527..a449535 100644
--- a/src/knx/netip/qknxbuilderdata_p.h
+++ b/src/knx/netip/qknxbuilderdata_p.h
@@ -42,6 +42,8 @@
//
#include <QtCore/qshareddata.h>
+
+#include <QtKnx/qknxcryptographicengine.h>
#include <QtKnx/qknxnamespace.h>
#include <QtKnx/qknxnetipframe.h>
#include <QtKnx/qknxnetipservicefamiliesdib.h>
@@ -126,6 +128,39 @@ public:
QKnxByteArray m_authCode;
};
+class QKnxNetIpTimerNotifyBuilderPrivate : public QSharedData
+{
+public:
+ QKnxNetIpTimerNotifyBuilderPrivate() = default;
+ ~QKnxNetIpTimerNotifyBuilderPrivate() = default;
+
+ quint64 m_timer { Q_UINT48_MAX + 1 };
+ QKnxByteArray m_serial;
+ qint32 m_tag { -1 };
+ QKnxByteArray m_authCode;
+};
+
+class QKnxNetIpSessionResponseBuilderPrivate : public QSharedData
+{
+public:
+ QKnxNetIpSessionResponseBuilderPrivate() = default;
+ ~QKnxNetIpSessionResponseBuilderPrivate() = default;
+
+ qint32 m_id { -1 };
+ QKnxByteArray m_serverPublicKey;
+ QKnxByteArray m_authCode;
+};
+
+class QKnxNetIpSessionAuthenticateBuilderPrivate : public QSharedData
+{
+public:
+ QKnxNetIpSessionAuthenticateBuilderPrivate() = default;
+ ~QKnxNetIpSessionAuthenticateBuilderPrivate() = default;
+
+ QKnxNetIp::SecureUserId m_id { QKnxNetIp::SecureUserId::Invalid };
+ QKnxByteArray m_authCode;
+};
+
QT_END_NAMESPACE
#endif
diff --git a/src/knx/netip/qknxnetip.cpp b/src/knx/netip/qknxnetip.cpp
index 335c4a8..e68bf47 100644
--- a/src/knx/netip/qknxnetip.cpp
+++ b/src/knx/netip/qknxnetip.cpp
@@ -579,6 +579,15 @@ bool QKnxNetIp::isCapability(Capability capability)
The timeout used to empty the incoming queue of a KNXnet/IP router or KNX
IP device if the number of received datagrams exceeds the number of frames
that the device can actually process.
+ \value SecureSessionTimeout
+ The maximum time an authenticated secure session can remain unused
+ without any communication before the secure session gets dropped.
+ \value SecureSessionRequestTimeout
+ The maximum time the KNXnet/IP client will wait for the session
+ response from the KNXnet/IP server after sending a session request.
+ \value SecureSessionAuthenticateTimeout
+ The maximum time the authentication process for a newly created secure
+ session may last until the unauthenticated session will be dropped.
*/
/*!
@@ -727,4 +736,58 @@ bool QKnx::NetIp::isStructType(QKnx::NetIp::SearchParameterType type)
\omitvalue Unknown
*/
+/*!
+ \enum QKnx::NetIp::SecureUserId
+ \since 5.13
+
+ The ID of the management client (MaC) or user that is used for the
+ authentication of the secure session.
+
+ \value Reserved
+ Reserved, please do not use.
+ \value Management
+ The management user ID.
+ \value UserRole
+ The first value in the possible range of user IDs.
+ \value Invalid
+ Invalid, please do not use any ID equal to or more than this value.
+
+ The management server (MaS) will use the user ID as an index into the
+ password hashes table to authenticate the MaC.
+
+ The access level (management or user level access - with possibly any device
+ dependent role) will determine the set of services accepted by the MaS after
+ authentication.
+
+ \table
+ \header
+ \li User ID
+ \li Description
+ \row
+ \li \c Management
+ \li This user ID requests access to all resources exposed by the
+ MaS including device management.
+ \row
+ \li \c User
+ \li This user ID requests access to all resources exposed by the
+ MaS except device management.
+ \endtable
+
+ In addition to access level, the user ID serves as an index into the
+ tunneling user table to determine if there are individual addresses
+ associated with the user. The management user ID has implicit access
+ to all available tunneling addresses.
+*/
+
+/*!
+ \since 5.13
+
+ Returns \c true if the specified \a userId is a in the range of the
+ \l SecureUserId enumeration; otherwise returns \c false.
+*/
+bool QKnx::NetIp::isSecureUserId(QKnx::NetIp::SecureUserId userId)
+{
+ return (userId > SecureUserId::Reserved && userId < SecureUserId::Invalid);
+}
+
QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetip.h b/src/knx/netip/qknxnetip.h
index 26a891c..96e3a63 100644
--- a/src/knx/netip/qknxnetip.h
+++ b/src/knx/netip/qknxnetip.h
@@ -238,7 +238,12 @@ namespace QKnx
TunnelingRequestTimeout = 1000,
// KNXnet/IP routing service
- RoutingBusyWaitTime = 100
+ RoutingBusyWaitTime = 100,
+
+ // KNXnet/IP secure session
+ SecureSessionTimeout = 60000,
+ SecureSessionRequestTimeout = 10000,
+ SecureSessionAuthenticateTimeout = 10000
};
Q_ENUM_NS(Timeout)
@@ -297,6 +302,16 @@ namespace QKnx
Unknown = 0xff
};
Q_ENUM_NS(SecureSessionStatus)
+
+ enum SecureUserId : quint8
+ {
+ Reserved = 0x00,
+ Management = 0x01,
+ UserRole = 0x02,
+ Invalid = 0x80
+ };
+ Q_ENUM_NS(SecureUserId)
+ Q_KNX_EXPORT bool isSecureUserId(SecureUserId userId);
}
}
namespace QKnxNetIp = QKnx::NetIp;
diff --git a/src/knx/netip/qknxnetipdevicemanagement.cpp b/src/knx/netip/qknxnetipdevicemanagement.cpp
index eb3d5b7..1da9b15 100644
--- a/src/knx/netip/qknxnetipdevicemanagement.cpp
+++ b/src/knx/netip/qknxnetipdevicemanagement.cpp
@@ -87,13 +87,15 @@ public:
QKnxNetIp::DeviceConfigurationRequestTimeout)
{}
- void process(const QKnxDeviceManagementFrame &frame) override
- {
- Q_Q(QKnxNetIpDeviceManagement);
- emit q->frameReceived(frame);
- }
+ void process(const QKnxDeviceManagementFrame &frame) override;
};
+void QKnxNetIpDeviceManagementPrivate::process(const QKnxDeviceManagementFrame &frame)
+{
+ Q_Q(QKnxNetIpDeviceManagement);
+ emit q->frameReceived(frame);
+}
+
/*!
Creates a device management connection with the parent \a parent.
*/
diff --git a/src/knx/netip/qknxnetipendpointconnection.cpp b/src/knx/netip/qknxnetipendpointconnection.cpp
index 18939a6..dfc6501 100644
--- a/src/knx/netip/qknxnetipendpointconnection.cpp
+++ b/src/knx/netip/qknxnetipendpointconnection.cpp
@@ -27,6 +27,7 @@
**
******************************************************************************/
+#include "qknxcryptographicengine.h"
#include "qknxnetipconnectrequest.h"
#include "qknxnetipconnectresponse.h"
#include "qknxnetipconnectionstaterequest.h"
@@ -37,6 +38,11 @@
#include "qknxnetipdisconnectresponse.h"
#include "qknxnetipendpointconnection.h"
#include "qknxnetipendpointconnection_p.h"
+#include "qknxnetipsessionauthenticate.h"
+#include "qknxnetipsecurewrapper.h"
+#include "qknxnetipsessionrequest.h"
+#include "qknxnetipsessionresponse.h"
+#include "qknxnetipsessionstatus.h"
#include "qknxnetiptunnelingacknowledge.h"
#include "qknxnetiptunnelingfeatureget.h"
#include "qknxnetiptunnelingfeatureset.h"
@@ -47,6 +53,8 @@
#include "qtcpsocket.h"
#include "qudpsocket.h"
+#include "private/qknxnetipsecureconfiguration_p.h"
+
QT_BEGIN_NAMESPACE
/*!
\class QKnxNetIpEndpointConnection
@@ -106,7 +114,18 @@ QT_BEGIN_NAMESPACE
State request timeout.
\value Cemi
No cEMI frame acknowledge in time.
+ \value SecureConfig
+ An invalid secure configuration was used to establish the connection.
+ \value SerialNumber
+ An invalid serial number was set for the device.
+ \value AuthFailed
+ The secure client was not successfully authenticated.
+ \value Timeout
+ A timeout occurred during secure session handshake.
+ \value Close
+ The server requested to close the secure session.
\value Unknown
+ An unknown error occurred.
*/
/*!
\enum QKnxNetIpEndpointConnection::SequenceType
@@ -177,17 +196,18 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
QKnxPrivate::clearTimer(&m_connectionStateTimer);
QKnxPrivate::clearTimer(&m_disconnectRequestTimer);
QKnxPrivate::clearTimer(&m_acknowledgeTimer);
+ QKnxPrivate::clearTimer(&m_secureTimer);
Q_Q(QKnxNetIpEndpointConnection);
m_heartbeatTimer = new QTimer(q);
m_heartbeatTimer->setSingleShot(true);
- QObject::connect(m_heartbeatTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_heartbeatTimer, &QTimer::timeout, q, [&]() {
sendStateRequest();
});
m_connectRequestTimer = new QTimer(q);
m_connectRequestTimer->setSingleShot(true);
- QObject::connect(m_connectRequestTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_connectRequestTimer, &QTimer::timeout, q, [&]() {
setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Bound);
setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Acknowledge,
QKnxNetIpEndpointConnection::tr("Connect request timeout."));
@@ -198,7 +218,7 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
m_connectionStateTimer = new QTimer(q);
m_connectionStateTimer->setSingleShot(true);
- QObject::connect(m_connectionStateTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_connectionStateTimer, &QTimer::timeout, q, [&]() {
m_heartbeatTimer->stop();
m_connectionStateTimer->stop();
if (m_stateRequests > m_maxStateRequests) {
@@ -214,7 +234,7 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
m_disconnectRequestTimer = new QTimer(q);
m_disconnectRequestTimer->setSingleShot(true);
- QObject::connect(m_disconnectRequestTimer, &QTimer::timeout, [&] () {
+ QObject::connect(m_disconnectRequestTimer, &QTimer::timeout, q, [&] () {
setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Acknowledge,
QKnxNetIpEndpointConnection::tr("Disconnect request timeout."));
processDisconnectResponse(QKnxNetIpDisconnectResponseProxy::builder()
@@ -223,7 +243,7 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
m_acknowledgeTimer = new QTimer(q);
m_acknowledgeTimer->setSingleShot(true);
- QObject::connect(m_acknowledgeTimer, &QTimer::timeout, [&]() {
+ QObject::connect(m_acknowledgeTimer, &QTimer::timeout, q, [&]() {
if (m_cemiRequests > m_maxCemiRequest) {
setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Cemi,
QKnxNetIpEndpointConnection::tr("Did not receive acknowledge in time."));
@@ -235,43 +255,26 @@ void QKnxNetIpEndpointConnectionPrivate::setupTimer()
sendCemiRequest();
}
});
-}
-namespace QKnxPrivate
-{
- static bool isNullOrLocal(const QHostAddress &address)
- {
- return address.isNull() || address == QHostAddress::Any || address.isLoopback();
- }
+ m_secureTimer = new QTimer(q);
+ m_secureTimer->setSingleShot(true);
}
-void QKnxNetIpEndpointConnectionPrivate::processReceivedFrame(const QHostAddress &address, int port)
+QKnxNetIp::ServiceType
+ QKnxNetIpEndpointConnectionPrivate::processReceivedFrame(const QKnxNetIpFrame &frame)
{
- const auto frame = QKnxNetIpFrame::fromBytes(m_rxBuffer);
- if (!frame.isValid())
- return;
-
- // remove already processed KNX frame from buffer
- m_rxBuffer.remove(0, frame.size());
-
// TODO: fix the version and validity checks
- // if (!m_supportedVersions.contains(header.protocolVersion())) {
+ // if (!m_supportedVersions.contains(frame.header().protocolVersion())) {
// send E_VERSION_NOT_SUPPORTED confirmation frame
// send disconnect request
- // } else if (header.protocolVersion() != m_controlEndpointVersion) {
+ // } else if (frame.header().protocolVersion() != m_controlEndpointVersion) {
// send disconnect request
// } else {
// TODO: set the m_dataEndpointVersion once we receive or send the first frame
- switch (frame.serviceType()) {
+ auto serviceType = frame.serviceType();
+ switch (serviceType) {
case QKnxNetIp::ServiceType::ConnectResponse:
- if (m_nat) { // TODO: Review this for TCP connections
- if (QKnxPrivate::isNullOrLocal(m_remoteDataEndpoint.address)
- || m_remoteDataEndpoint.port == 0) {
- m_remoteDataEndpoint.port = port;
- m_remoteDataEndpoint.address = address;
- }
- }
processConnectResponse(frame);
break;
case QKnxNetIp::ServiceType::ConnectionStateResponse:
@@ -302,14 +305,241 @@ void QKnxNetIpEndpointConnectionPrivate::processReceivedFrame(const QHostAddress
case QKnxNetIp::ServiceType::TunnelingFeatureResponse:
processFeatureFrame(frame);
break;
+
+ case QKnxNetIp::ServiceType::SecureWrapper: {
+ qDebug() << "Received secure wrapper frame:" << frame;
+
+ const QKnxNetIpSecureWrapperProxy proxy(frame);
+ if (!proxy.isValid())
+ break;
+
+ const auto seqNumber = proxy.sequenceNumber();
+ const auto serialNumber = proxy.serialNumber();
+ const auto messageTag = proxy.messageTag();
+
+ const auto decData = QKnxCryptographicEngine::decryptSecureWrapperPayload(m_sessionKey,
+ proxy.encapsulatedFrame(), seqNumber, serialNumber, messageTag);
+
+ const auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(m_sessionKey,
+ frame.header(), proxy.secureSessionId(), decData, seqNumber, serialNumber, messageTag);
+ const auto decMac = QKnxCryptographicEngine::decryptMessageAuthenticationCode(m_sessionKey,
+ proxy.messageAuthenticationCode(), seqNumber, serialNumber, messageTag);
+
+ if (decMac != mac)
+ break; // MAC could not be verified, bail out
+
+ return processReceivedFrame(QKnxNetIpFrame::fromBytes(decData));
+ } break;
+
+ case QKnxNetIp::ServiceType::SessionRequest:
+ qDebug() << "Unexpectedly received session request frame:" << frame;
+ break;
+
+ case QKnxNetIp::ServiceType::SessionResponse: {
+ qDebug() << "Received session response frame:" << frame;
+
+ QKnxNetIpSessionResponseProxy proxy(frame);
+ if (!proxy.isValid())
+ break;
+
+ const auto authHash = QKnxCryptographicEngine::deviceAuthenticationCodeHash(m_secureConfig.
+ d->deviceAuthenticationCode);
+ const auto xorX_Y = QKnxCryptographicEngine::XOR(m_secureConfig.d->publicKey.bytes(), proxy
+ .publicKey());
+
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(authHash,
+ frame.header(), proxy.secureSessionId(), xorX_Y);
+ auto decMac = QKnxCryptographicEngine::decryptMessageAuthenticationCode(authHash,
+ proxy.messageAuthenticationCode());
+
+ if (decMac != mac)
+ break; // MAC could not be verified, bail out
+
+ m_secureTimer->stop();
+ m_secureTimer->disconnect();
+ m_sessionId = proxy.secureSessionId();
+ m_sessionKey = QKnxCryptographicEngine::sessionKey(m_secureConfig.d->privateKey,
+ QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, proxy.publicKey()));
+
+ auto secureWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionAuthenticateProxy::secureBuilder()
+ .setUserId(m_secureConfig.d->userId)
+ .create(m_secureConfig.d->userPassword, m_secureConfig.d->publicKey.bytes(), proxy
+ .publicKey()))
+ .create(m_sessionKey);
+
+ ++m_sequenceNumber;
+ m_waitForAuthentication = true;
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureWrapper.bytes().toByteArray());
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ QObject::connect(m_secureTimer, &QTimer::timeout, q, [&]() {
+ m_secureTimer->stop();
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Did not receive session status frame."));
+
+ auto secureStatusWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionStatusProxy::builder()
+ .setStatus(QKnxNetIp::SecureSessionStatus::Close)
+ .create())
+ .create(m_sessionKey);
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureStatusWrapper.bytes().toByteArray());
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ q->disconnectFromHost();
+ });
+ m_secureTimer->start(QKnxNetIp::SecureSessionAuthenticateTimeout);
+ } break;
+
+ case QKnxNetIp::ServiceType::SessionAuthenticate:
+ qDebug() << "Unexpectedly received session authenticate frame:" << frame;
+ break;
+
+ case QKnxNetIp::ServiceType::SessionStatus: {
+ qDebug() << "Received session status frame:" << frame;
+
+ QKnxNetIpSessionStatusProxy ssp(frame);
+ if (!ssp.isValid())
+ break;
+
+ bool close { false };
+ switch (ssp.status()) {
+ case QKnxNetIp::SecureSessionStatus::AuthenticationSuccess: {
+ if (m_waitForAuthentication) {
+ m_secureTimer->stop();
+ m_secureTimer->disconnect();
+ m_waitForAuthentication = false;
+ auto ep = (m_tcpSocket ? m_routeBack : (m_nat ? m_routeBack : m_localEndpoint));
+ auto secureWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpConnectRequestProxy::builder()
+ .setControlEndpoint(ep)
+ .setDataEndpoint(ep)
+ .setRequestInformation(m_cri)
+ .create())
+ .create(m_sessionKey);
+
+ ++m_sequenceNumber;
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureWrapper.bytes().toByteArray());
+
+ if (!m_secureConfig.d->keepAlive)
+ break;
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ QObject::connect(m_secureTimer, &QTimer::timeout, q, [&]() {
+ auto secureStatusWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionStatusProxy::builder()
+ .setStatus(QKnxNetIp::SecureSessionStatus::KeepAlive)
+ .create())
+ .create(m_sessionKey);
+ qDebug() << "Sending keep alive status frame:" << secureStatusWrapper;
+
+ ++m_sequenceNumber;
+ if (m_tcpSocket)
+ m_tcpSocket->write(secureStatusWrapper.bytes().toByteArray());
+ });
+ m_secureTimer->setSingleShot(false);
+ m_secureTimer->start(QKnxNetIp::Timeout::SecureSessionTimeout - 5000);
+ }
+ } break;
+
+ case QKnxNetIp::SecureSessionStatus::KeepAlive:
+ // TODO: implement in case we're the server
+ qDebug() << "Unexpectedly received keep alive status frame:" << frame;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::AuthenticationFailed:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Secure session authentication failed."));
+ close = true;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::Unauthenticated:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Secure session not authenticated."));
+ close = true;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::Timeout:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Timeout,
+ QKnxNetIpEndpointConnection::tr("A timeout occurred during secure session handshake."));
+ close = true;
+ break;
+
+ case QKnxNetIp::SecureSessionStatus::Close:
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Timeout,
+ QKnxNetIpEndpointConnection::tr("The server requested to close the secure session."));
+ close = true;
+ break;
+
+ default:
+ qDebug() << "Received unknown status frame:" << frame;
+ break;
+ }
+
+ if (close) {
+ Q_Q(QKnxNetIpEndpointConnection);
+ q->disconnectFromHost();
+ }
+ } break;
+
+ case QKnxNetIp::ServiceType::TimerNotify:
+ qDebug() << "Unexpectedly received timer notify frame:" << frame;
+ break;
+
default:
break;
}
+ return serviceType;
}
-void QKnxNetIpEndpointConnectionPrivate::setup()
+bool QKnxNetIpEndpointConnectionPrivate::initConnection(const QHostAddress &a, quint16 p,
+ QKnxNetIp::HostProtocol hp)
{
- setupTimer();
+ if (!QKnxNetIp::isStructType(hp))
+ return false;
+
+ if (m_state != QKnxNetIpEndpointConnection::State::Disconnected)
+ return false;
+
+ auto isIPv4 = false;
+ a.toIPv4Address(&isIPv4);
+ if (!isIPv4) {
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::NotIPv4,
+ QKnxNetIpEndpointConnection::tr("Only IPv4 addresses as remote control endpoint "
+ "supported."));
+ return false;
+ }
+ m_remoteControlEndpoint = { a, p, hp };
+
+ isIPv4 = false;
+ m_user.address.toIPv4Address(&isIPv4);
+ if (!isIPv4) {
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::NotIPv4,
+ QKnxNetIpEndpointConnection::tr("Only IPv4 addresses as local control endpoint "
+ "supported."));
+ return false;
+ }
+
+ setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Starting);
m_channelId = -1;
@@ -327,49 +557,67 @@ void QKnxNetIpEndpointConnectionPrivate::setup()
m_errorString = QString();
m_error = QKnxNetIpEndpointConnection::Error::None;
- if (m_tcpSocket) {
- QObject::connect(m_tcpSocket, &QIODevice::readyRead, [&]() {
+ m_routeBack.hostProtocol = hp;
+
+ m_sessionId = 0;
+ m_sequenceNumber = 0;
+ m_waitForAuthentication = false;
+
+ setupTimer();
+
+ QKnxPrivate::clearSocket(&m_tcpSocket);
+ QKnxPrivate::clearSocket(&m_udpSocket);
+
+ Q_Q(QKnxNetIpEndpointConnection);
+ QAbstractSocket *socket = nullptr;
+ if (hp == QKnxNetIp::HostProtocol::TCP_IPv4) {
+ socket = m_tcpSocket = new QTcpSocket(q_func());
+ QObject::connect(m_tcpSocket, &QTcpSocket::readyRead, q, [&]() {
+ if (m_tcpSocket->bytesAvailable() < QKnxNetIpFrameHeader::HeaderSize10)
+ return;
m_rxBuffer += QKnxByteArray::fromByteArray(m_tcpSocket->readAll());
- int bufferSize = m_rxBuffer.size();
- bool continueProcessingRxBuffer = false;
- do {
- // TODO: AN184 v03 KNXnet-IP Core v2 AS, 2.2.3.2.3.2
- processReceivedFrame(m_remoteControlEndpoint.address,
- m_remoteControlEndpoint.port);
- continueProcessingRxBuffer = (m_rxBuffer.size() < bufferSize);
- bufferSize = m_rxBuffer.size();
- } while (continueProcessingRxBuffer);
- });
- using overload = void (QTcpSocket::*)(QTcpSocket::SocketError);
- QObject::connect(m_tcpSocket,
- static_cast<overload>(&QTcpSocket::error), [&](QTcpSocket::SocketError) {
- setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
- m_tcpSocket->errorString());
+ // TODO: AN184 v03 KNXnet-IP Core v2 AS, 2.2.3.2.3.2
+ auto header = QKnxNetIpFrameHeader::fromBytes(m_rxBuffer);
+ if (header.isValid() && m_rxBuffer.size() < header.totalSize())
+ return;
- Q_Q(QKnxNetIpEndpointConnection);
- q->disconnectFromHost();
+ auto frame = QKnxNetIpFrame::fromBytes(m_rxBuffer);
+ m_rxBuffer.remove(0, header.totalSize());
+ processReceivedFrame(frame);
});
- } else if (m_udpSocket) {
- QObject::connect(m_udpSocket, &QUdpSocket::readyRead, [&]() {
+ } else if (hp == QKnxNetIp::HostProtocol::UDP_IPv4) {
+ socket = m_udpSocket = new QUdpSocket(q_func());
+ QObject::connect(m_udpSocket, &QUdpSocket::readyRead, q, [&]() {
while (m_udpSocket && m_udpSocket->state() == QUdpSocket::BoundState
&& m_udpSocket->hasPendingDatagrams()) {
- auto datagram = m_udpSocket->receiveDatagram();
- m_rxBuffer += QKnxByteArray::fromByteArray(datagram.data());
- processReceivedFrame(datagram.senderAddress(), datagram.senderPort());
+ auto tmp = m_udpSocket->receiveDatagram();
+ auto frame = QKnxNetIpFrame::fromBytes(QKnxByteArray::fromByteArray(tmp.data()));
+ if (processReceivedFrame(frame) != QKnxNetIp::ServiceType::ConnectResponse)
+ continue;
+
+ if (m_nat && m_remoteDataEndpoint.isNullOrLocal())
+ m_remoteDataEndpoint = { tmp.senderAddress(), quint16(tmp.senderPort())};
}
});
+ } else {
+ return false;
+ }
- using overload = void (QUdpSocket::*)(QUdpSocket::SocketError);
- QObject::connect(m_udpSocket,
- static_cast<overload>(&QUdpSocket::error), [&](QUdpSocket::SocketError) {
- setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
- m_udpSocket->errorString());
-
- Q_Q(QKnxNetIpEndpointConnection);
- q->disconnectFromHost();
+ if (socket) {
+ QObject::connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
+ q, [socket, this](QAbstractSocket::SocketError) {
+ setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
+ socket->errorString());
+ Q_Q(QKnxNetIpEndpointConnection);
+ q->disconnectFromHost();
});
+ } else {
+ return false;
}
+
+ setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
+ return true;
}
void QKnxNetIpEndpointConnectionPrivate::cleanup()
@@ -379,11 +627,26 @@ void QKnxNetIpEndpointConnectionPrivate::cleanup()
QKnxPrivate::clearTimer(&m_connectionStateTimer);
QKnxPrivate::clearTimer(&m_disconnectRequestTimer);
QKnxPrivate::clearTimer(&m_acknowledgeTimer);
+ QKnxPrivate::clearTimer(&m_secureTimer);
if (m_udpSocket) {
m_udpSocket->close();
QKnxPrivate::clearSocket(&m_udpSocket);
- } else {
+ } else if (m_tcpSocket) {
+ if (m_secureConfig.isValid()) {
+ auto secureStatusWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(QKnxNetIpSessionStatusProxy::builder()
+ .setStatus(QKnxNetIp::SecureSessionStatus::Close)
+ .create())
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ m_tcpSocket->write(secureStatusWrapper.bytes().toByteArray());
+ m_tcpSocket->waitForBytesWritten();
+ }
m_tcpSocket->close();
QKnxPrivate::clearSocket(&m_tcpSocket);
}
@@ -393,19 +656,37 @@ void QKnxNetIpEndpointConnectionPrivate::cleanup()
bool QKnxNetIpEndpointConnectionPrivate::sendCemiRequest()
{
- if (!m_waitForAcknowledgement && !m_tcpSocket) {
+ if (m_udpSocket) {
+ if (m_waitForAcknowledgement)
+ return false; // still waiting for an ACK from an previous request
+
m_waitForAcknowledgement = true;
m_udpSocket->writeDatagram(m_lastSendCemiRequest.bytes().toByteArray(),
- m_remoteDataEndpoint.address,
- m_remoteDataEndpoint.port);
+ m_remoteDataEndpoint.address,
+ m_remoteDataEndpoint.port);
m_cemiRequests++;
m_acknowledgeTimer->start(m_acknowledgeTimeout);
- } else if (m_tcpSocket) {
- m_tcpSocket->write(m_lastSendCemiRequest.bytes().toByteArray());
- m_waitForAcknowledgement = false;
+ return true;
}
- return !m_waitForAcknowledgement;
+
+ if (m_tcpSocket) {
+ if (m_secureConfig.isValid()) {
+ auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(m_lastSendCemiRequest)
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ m_tcpSocket->write(secureFrame.bytes().toByteArray());
+ } else {
+ m_tcpSocket->write(m_lastSendCemiRequest.bytes().toByteArray());
+ }
+ return true; // TCP connections do not send an ACK
+ }
+ return false;
}
void QKnxNetIpEndpointConnectionPrivate::sendStateRequest()
@@ -414,7 +695,19 @@ void QKnxNetIpEndpointConnectionPrivate::sendStateRequest()
.bytes().toHex();
if (m_tcpSocket) {
- m_tcpSocket->write(m_lastStateRequest.bytes().toByteArray());
+ if (m_secureConfig.isValid()) {
+ auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(m_lastStateRequest)
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ m_tcpSocket->write(secureFrame.bytes().toByteArray());
+ } else {
+ m_tcpSocket->write(m_lastStateRequest.bytes().toByteArray());
+ }
} else {
m_udpSocket->writeDatagram(m_lastStateRequest.bytes().toByteArray(),
m_remoteControlEndpoint.address, m_remoteControlEndpoint.port);
@@ -653,10 +946,13 @@ void QKnxNetIpEndpointConnectionPrivate::processConnectResponse(const QKnxNetIpF
m_remoteDataEndpoint = response.dataEndpoint();
m_lastStateRequest = QKnxNetIpConnectionStateRequestProxy::builder()
.setChannelId(m_channelId)
- .setControlEndpoint(m_nat ? m_natEndpoint : m_localEndpoint)
+ .setControlEndpoint(
+ m_tcpSocket ? m_routeBack : (m_nat ? m_routeBack : m_localEndpoint)
+ )
.create();
- QTimer::singleShot(0, [&]() { sendStateRequest(); });
+ Q_Q(QKnxNetIpEndpointConnection);
+ QTimer::singleShot(0, q, [&]() { sendStateRequest(); });
setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connected);
} else {
auto metaEnum = QMetaEnum::fromType<QKnxNetIp::Error>();
@@ -699,16 +995,26 @@ void QKnxNetIpEndpointConnectionPrivate::processDisconnectRequest(const QKnxNetI
QKnxNetIpDisconnectRequestProxy request(frame);
if (request.channelId() == m_channelId) {
- auto frame = QKnxNetIpDisconnectResponseProxy::builder()
+ auto responseFrame = QKnxNetIpDisconnectResponseProxy::builder()
.setChannelId(m_channelId)
.setStatus(QKnxNetIp::Error::None)
.create();
- qDebug() << "Sending disconnect response:" << frame;
+ qDebug() << "Sending disconnect response:" << responseFrame;
if (m_tcpSocket) {
- m_tcpSocket->write(frame.bytes().toByteArray());
+ if (m_secureConfig.isValid()) {
+ responseFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(m_sessionId)
+ .setSequenceNumber(m_sequenceNumber)
+ .setSerialNumber(m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(responseFrame)
+ .create(m_sessionKey);
+ ++m_sequenceNumber;
+ }
+ m_tcpSocket->write(responseFrame.bytes().toByteArray());
} else {
- m_udpSocket->writeDatagram(frame.bytes().toByteArray(),
- m_remoteControlEndpoint.address, m_remoteControlEndpoint.port);
+ m_udpSocket->writeDatagram(responseFrame.bytes().toByteArray(),
+ m_remoteControlEndpoint.address, m_remoteControlEndpoint.port);
}
Q_Q(QKnxNetIpEndpointConnection);
@@ -757,7 +1063,8 @@ void QKnxNetIpEndpointConnectionPrivate::setAndEmitErrorOccurred(
}
-// -- QKnxNetIpClient
+// -- QKnxNetIpEndpointConnection
+
/*!
Destroys a connection and disconnects from the server.
*/
@@ -879,8 +1186,8 @@ quint32 QKnxNetIpEndpointConnection::heartbeatTimeout() const
}
/*!
- Sets the value of the heartbeat timeout. Every \a msec a state request is sent over the
- connection to keep it alive.
+ Sets the value of the heartbeat timeout. Every \a msec a state request is
+ sent over the connection to keep it alive.
*/
void QKnxNetIpEndpointConnection::setHeartbeatTimeout(quint32 msec)
{
@@ -923,139 +1230,184 @@ void QKnxNetIpEndpointConnection::connectToHost(const QKnxNetIpHpai &controlEndp
}
/*!
- Establishes a connection to the host with \a address and \a port.
+ Establishes a connection to the host with \a address and \a port via UDP.
*/
void QKnxNetIpEndpointConnection::connectToHost(const QHostAddress &address, quint16 port)
{
Q_D(QKnxNetIpEndpointConnection);
- if (d->m_state != State::Disconnected)
- return;
-
- auto isIPv4 = false;
- address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as remote control "
- "endpoint supported."));
- return;
- }
- d->m_remoteControlEndpoint = Endpoint(address, port);
-
- isIPv4 = false;
- d->m_user.address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as local control "
- "endpoint supported."));
+ if (!d->initConnection(address, port, QKnxNetIp::HostProtocol::UDP_IPv4))
return;
- }
-
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Starting);
-
- QKnxPrivate::clearSocket(&(d->m_udpSocket));
- d->m_udpSocket = new QUdpSocket(this);
- if (!d->m_udpSocket->bind(d->m_user.address, d->m_user.port)) {
- d->setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::Network,
- QKnxNetIpEndpointConnection::tr("Could not bind endpoint: %1")
- .arg(d->m_udpSocket->errorString()));
- QKnxPrivate::clearSocket(&d->m_udpSocket);
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Disconnected);
+ if (!d->m_udpSocket->bind(d->m_user.address, d->m_user.port))
return;
- }
- d->m_localEndpoint = Endpoint(d->m_udpSocket->localAddress(), d->m_udpSocket->localPort());
+ d->m_localEndpoint = { d->m_udpSocket->localAddress(), d->m_udpSocket->localPort() };
d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Bound);
- d->setup();
-
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
-
auto request = QKnxNetIpConnectRequestProxy::builder()
- .setControlEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
- .setDataEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
+ .setControlEndpoint(d->m_nat ? d->m_routeBack : d->m_localEndpoint)
+ .setDataEndpoint(d->m_nat ? d->m_routeBack : d->m_localEndpoint)
.setRequestInformation(d->m_cri)
.create();
d->m_controlEndpointVersion = request.header().protocolVersion();
- qDebug() << "Sending connect request:" << request;
-
- d->m_connectRequestTimer->start(QKnxNetIp::ConnectRequestTimeout);
+ d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
+ qDebug() << "Sending connect request:" << request;
d->m_udpSocket->writeDatagram(request.bytes().toByteArray(),
d->m_remoteControlEndpoint.address, d->m_remoteControlEndpoint.port);
+
+ if (d->m_connectRequestTimer)
+ d->m_connectRequestTimer->start(QKnxNetIp::ConnectRequestTimeout);
}
/*!
\since 5.12
- Establishes a connection to the host with \a address, \a port and \a proto.
+ Establishes a connection to the host with \a address, \a port and \a protocol.
*/
void QKnxNetIpEndpointConnection::connectToHost(const QHostAddress &address, quint16 port,
- QKnxNetIp::HostProtocol proto)
+ QKnxNetIp::HostProtocol protocol)
{
- if (proto == QKnxNetIp::HostProtocol::Unknown)
- return;
+ if (protocol == QKnxNetIp::HostProtocol::UDP_IPv4)
+ return connectToHost(address, port);
- if (proto == QKnxNetIp::HostProtocol::UDP_IPv4) {
- connectToHost(address, port);
+ Q_D(QKnxNetIpEndpointConnection);
+ if (!d->initConnection(address, port, protocol))
return;
- }
- // establishing TCP connection
+ connect(d->m_tcpSocket, &QTcpSocket::connected, this, [&]() {
+ Q_D(QKnxNetIpEndpointConnection);
+ d->m_localEndpoint = { d->m_tcpSocket->localAddress(), d->m_tcpSocket->localPort(),
+ QKnxNetIp::HostProtocol::TCP_IPv4 };
+
+ auto request = QKnxNetIpConnectRequestProxy::builder()
+ .setControlEndpoint(d->m_routeBack)
+ .setDataEndpoint(d->m_routeBack)
+ .setRequestInformation(d->m_cri)
+ .create();
+ d->m_controlEndpointVersion = request.header().protocolVersion();
+
+ qDebug() << "Sending connect request:" << request;
+ d->m_tcpSocket->write(request.bytes().toByteArray());
+ d->m_connectRequestTimer->start(QKnxNetIp::ConnectRequestTimeout);
+ });
+ d->m_tcpSocket->connectToHost(address, port);
+}
+
+/*!
+ \since 5.13
+
+ Returns the serial number of the device using this connection. The default
+ value is set to \c {0x000000000000}.
+*/
+QKnxByteArray QKnxNetIpEndpointConnection::serialNumber() const
+{
+ Q_D(const QKnxNetIpEndpointConnection);
+ return d->m_serialNumber;
+}
+
+/*!
+ \since 5.13
+
+ Sets the serial number to \a serialNumber of the device using this
+ connection.
+
+ \note The serial number must contain exactly 6 bytes and cannot be changed
+ while the secure session is established.
+*/
+void QKnxNetIpEndpointConnection::setSerialNumber(const QKnxByteArray &serialNumber)
+{
Q_D(QKnxNetIpEndpointConnection);
- if (d->m_state != State::Disconnected)
- return;
+ if (d->m_state == QKnxNetIpEndpointConnection::State::Disconnected)
+ d->m_serialNumber = serialNumber;
+}
- auto isIPv4 = false;
- address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as remote control "
- "endpoint supported."));
- return;
- }
- d->m_remoteControlEndpoint = Endpoint(address, port);
- d->m_remoteControlEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
+/*!
+ \since 5.13
- isIPv4 = false;
- d->m_user.address.toIPv4Address(&isIPv4);
- if (!isIPv4) {
- d->setAndEmitErrorOccurred(Error::NotIPv4, tr("Only IPv4 addresses as local control "
- "endpoint supported."));
- return;
+ Returns the secure configuration used to establish the secure session.
+*/
+QKnxNetIpSecureConfiguration QKnxNetIpEndpointConnection::secureConfiguration() const
+{
+ Q_D(const QKnxNetIpEndpointConnection);
+ return d->m_secureConfig;
+}
+
+/*!
+ \since 5.13
+
+ Sets a secure configuration to be used to establish secure session
+ to \a {config}. The configuration cannot be changed while the secure
+ connection is established.
+*/
+void QKnxNetIpEndpointConnection::setSecureConfiguration(const QKnxNetIpSecureConfiguration &config)
+{
+ Q_D(QKnxNetIpEndpointConnection);
+ if (d->m_state == QKnxNetIpEndpointConnection::State::Disconnected) {
+ d->m_secureConfig = config;
+ d->updateCri(config.individualAddress());
}
+}
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Starting);
+/*!
+ \since 5.13
- QKnxPrivate::clearSocket(&(d->m_tcpSocket));
+ Establishes a connection to the KNXnet/IP control endpoint \a controlEndpoint.
- d->m_tcpSocket = new QTcpSocket(this);
- d->setup();
+ \sa setSerialNumber(), setSecureConfiguration()
+*/
+void QKnxNetIpEndpointConnection::connectToHostEncrypted(const QKnxNetIpHpai &controlEndpoint)
+{
+ const QKnxNetIpHpaiProxy proxy(controlEndpoint);
+ if (proxy.isValid() && proxy.hostProtocol() == QKnxNetIp::HostProtocol::TCP_IPv4)
+ connectToHost(proxy.hostAddress(), proxy.port());
+}
- d->setAndEmitStateChanged(QKnxNetIpEndpointConnection::State::Connecting);
+/*!
+ \since 5.13
- d->m_tcpSocket->abort();
- d->m_tcpSocket->connectToHost(address, port);
+ Establishes a secure session to the host with \a address and \a port.
+
+ \sa setSerialNumber(), setSecureConfiguration()
+*/
+void QKnxNetIpEndpointConnection::connectToHostEncrypted(const QHostAddress &address, quint16 port)
+{
+ Q_D(QKnxNetIpEndpointConnection);
+ if (!d->initConnection(address, port, QKnxNetIp::HostProtocol::TCP_IPv4))
+ return;
+
+ if (!d->m_secureConfig.isValid())
+ return d->setAndEmitErrorOccurred(Error::SecureConfig, tr("Invalid secure configuration."));
- d->m_localEndpoint = Endpoint(d->m_tcpSocket->localAddress(),
- d->m_tcpSocket->localPort());
- d->m_localEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
- d->m_natEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
- d->m_remoteDataEndpoint.code = QKnxNetIp::HostProtocol::TCP_IPv4;
+ if (d->m_serialNumber.size() != 6)
+ return d->setAndEmitErrorOccurred(Error::SerialNumber, tr("Invalid device serial number."));
connect(d->m_tcpSocket, &QTcpSocket::connected, this, [&]() {
Q_D(QKnxNetIpEndpointConnection);
- auto request = QKnxNetIpConnectRequestProxy::builder()
- .setControlEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
- .setDataEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
- .setRequestInformation(d->m_cri)
+ d->m_localEndpoint = Endpoint(d->m_tcpSocket->localAddress(),
+ d->m_tcpSocket->localPort(), QKnxNetIp::HostProtocol::TCP_IPv4);
+
+ auto request = QKnxNetIpSessionRequestProxy::builder()
+ .setControlEndpoint(d->m_routeBack)
+ .setPublicKey(d->m_secureConfig.d->publicKey.bytes())
.create();
d->m_controlEndpointVersion = request.header().protocolVersion();
- qDebug() << "Sending connect request:" << request;
+ qDebug() << "Sending secure session request:" << request;
d->m_tcpSocket->write(request.bytes().toByteArray());
- });
- // TODO: Implement connect request timeout.
+ QObject::connect(d->m_secureTimer, &QTimer::timeout, this, [&]() {
+ Q_D(QKnxNetIpEndpointConnection);
+ d->m_secureTimer->stop();
+ d->setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error::AuthFailed,
+ QKnxNetIpEndpointConnection::tr("Could not establish secure session."));
+ disconnectFromHost();
+ });
+ d->m_secureTimer->start(QKnxNetIp::SecureSessionRequestTimeout);
+ });
+ d->m_tcpSocket->connectToHost(address, port);
}
/*!
@@ -1076,12 +1428,24 @@ void QKnxNetIpEndpointConnection::disconnectFromHost()
} else {
auto frame = QKnxNetIpDisconnectRequestProxy::builder()
.setChannelId(d->m_channelId)
- .setControlEndpoint(d->m_nat ? d->m_natEndpoint : d->m_localEndpoint)
+ .setControlEndpoint(d->m_nat ? d->m_routeBack : d->m_localEndpoint)
.create();
qDebug() << "Sending disconnect request:" << frame;
if (d->m_tcpSocket) {
- d->m_tcpSocket->write(frame.bytes().toByteArray());
+ if (d->m_secureConfig.isValid()) {
+ auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(d->m_sessionId)
+ .setSequenceNumber(d->m_sequenceNumber)
+ .setSerialNumber(d->m_serialNumber)
+ // .setMessageTag(0x0000) TODO: Do we need an API for this?
+ .setEncapsulatedFrame(frame)
+ .create(d->m_sessionKey);
+ ++(d->m_sequenceNumber);
+ d->m_tcpSocket->write(secureFrame.bytes().toByteArray());
+ } else {
+ d->m_tcpSocket->write(frame.bytes().toByteArray());
+ }
} else {
d->m_udpSocket->writeDatagram(frame.bytes().toByteArray(),
d->m_remoteControlEndpoint.address, d->m_remoteControlEndpoint.port);
@@ -1092,6 +1456,9 @@ void QKnxNetIpEndpointConnection::disconnectFromHost()
}
}
+/*!
+ \internal
+*/
QKnxNetIpEndpointConnection::QKnxNetIpEndpointConnection(QKnxNetIpEndpointConnectionPrivate &dd,
QObject *parent)
: QObject(dd, parent)
diff --git a/src/knx/netip/qknxnetipendpointconnection.h b/src/knx/netip/qknxnetipendpointconnection.h
index 5677805..4c94b78 100644
--- a/src/knx/netip/qknxnetipendpointconnection.h
+++ b/src/knx/netip/qknxnetipendpointconnection.h
@@ -32,8 +32,10 @@
#include <QtKnx/qtknxglobal.h>
#include <QtKnx/qknxnetipcri.h>
-#include <QtKnx/qknxnetiphpai.h>
#include <QtKnx/qknxnetipframe.h>
+#include <QtKnx/qknxnetiphpai.h>
+#include <QtKnx/qknxnetipsecureconfiguration.h>
+
#include <QtNetwork/qudpsocket.h>
QT_BEGIN_NAMESPACE
@@ -67,6 +69,11 @@ public:
Acknowledge,
Heartbeat,
Cemi,
+ SecureConfig,
+ SerialNumber,
+ AuthFailed,
+ Timeout,
+ Close,
Unknown = 0x80
};
Q_ENUM(Error)
@@ -111,6 +118,15 @@ public:
void connectToHost(const QHostAddress &address, quint16 port);
void connectToHost(const QHostAddress &address, quint16 port, QKnxNetIp::HostProtocol proto);
+ QKnxByteArray serialNumber() const;
+ void setSerialNumber(const QKnxByteArray &serialNumber);
+
+ QKnxNetIpSecureConfiguration secureConfiguration() const;
+ void setSecureConfiguration(const QKnxNetIpSecureConfiguration &config);
+
+ void connectToHostEncrypted(const QKnxNetIpHpai &controlEndpoint);
+ void connectToHostEncrypted(const QHostAddress &address, quint16 port);
+
void disconnectFromHost();
protected:
diff --git a/src/knx/netip/qknxnetipendpointconnection_p.h b/src/knx/netip/qknxnetipendpointconnection_p.h
index 71f4279..81c9bb6 100644
--- a/src/knx/netip/qknxnetipendpointconnection_p.h
+++ b/src/knx/netip/qknxnetipendpointconnection_p.h
@@ -42,12 +42,15 @@
//
#include <QtCore/qtimer.h>
-#include <QtKnx/qknxaddress.h>
+
#include <QtKnx/qtknxglobal.h>
-#include <QtKnx/qknxnetipendpointconnection.h>
-#include <QtNetwork/qhostaddress.h>
+#include <QtKnx/qknxaddress.h>
#include <QtKnx/qknxdevicemanagementframe.h>
#include <QtKnx/qknxlinklayerframe.h>
+#include <QtKnx/qknxnetipendpointconnection.h>
+#include <QtKnx/qknxnetipsecureconfiguration.h>
+
+#include <QtNetwork/qhostaddress.h>
#include <private/qobject_p.h>
@@ -79,6 +82,11 @@ struct Endpoint final
: address(addr)
, port(p)
{}
+ Endpoint(const QHostAddress &addr, quint16 p, QKnxNetIp::HostProtocol c)
+ : address(addr)
+ , port(p)
+ , hostProtocol(c)
+ {}
explicit Endpoint(const QKnxNetIpHpaiProxy &hpai)
: address(hpai.hostAddress())
, port(hpai.port())
@@ -90,7 +98,7 @@ struct Endpoint final
Endpoint &operator=(const QKnxNetIpHpai &s)
{
const QKnxNetIpHpaiProxy hpai(s);
- address = hpai.hostAddress(); port = hpai.port();
+ hostProtocol = hpai.hostProtocol(); address = hpai.hostAddress(); port = hpai.port();
return *this;
}
operator QKnxNetIpHpai() const
@@ -98,13 +106,17 @@ struct Endpoint final
return QKnxNetIpHpaiProxy::builder()
.setHostAddress(address)
.setPort(port)
- .setHostProtocol(code)
+ .setHostProtocol(hostProtocol)
.create();
}
+ bool isNullOrLocal() const
+ {
+ return address.isNull() || address == QHostAddress::Any || address.isLoopback() || port == 0;
+ }
- QHostAddress address { QHostAddress::LocalHost };
+ QHostAddress address { QHostAddress::Null };
quint16 port { 0 };
- QKnxNetIp::HostProtocol code { QKnxNetIp::HostProtocol::UDP_IPv4 };
+ QKnxNetIp::HostProtocol hostProtocol { QKnxNetIp::HostProtocol::UDP_IPv4 };
};
class Q_KNX_EXPORT QKnxNetIpEndpointConnectionPrivate : public QObjectPrivate
@@ -121,14 +133,14 @@ public:
{}
~QKnxNetIpEndpointConnectionPrivate() override = default;
- void setup();
void setupTimer();
+ bool initConnection(const QHostAddress &address, quint16 port, QKnxNetIp::HostProtocol hp);
void cleanup();
bool sendCemiRequest();
void sendStateRequest();
- void processReceivedFrame(const QHostAddress &address, int port);
+ QKnxNetIp::ServiceType processReceivedFrame(const QKnxNetIpFrame &frame);
virtual void process(const QKnxLinkLayerFrame &frame);
virtual void process(const QKnxDeviceManagementFrame &frame);
@@ -155,14 +167,40 @@ public:
void setAndEmitStateChanged(QKnxNetIpEndpointConnection::State newState);
void setAndEmitErrorOccurred(QKnxNetIpEndpointConnection::Error newError, const QString &message);
- void setCri(const QKnxNetIpCri &cri) { m_cri = cri; }
+ QKnxNetIpCri cri() const { return m_cri; }
+ void updateCri(QKnxNetIp::TunnelLayer layer)
+ {
+ QKnxNetIpCriProxy proxy(m_cri);
+ m_cri = proxy.builder()
+ .setTunnelLayer(layer)
+ .setIndividualAddress(proxy.individualAddress())
+ .create();
+ }
+
+ void updateCri(const QKnxAddress &ia)
+ {
+ QKnxNetIpCriProxy proxy(m_cri);
+ if (proxy.connectionType() == QKnxNetIp::ConnectionType::DeviceManagement)
+ return;
+
+ if (ia.isValid()) {
+ m_cri = QKnxNetIpCriProxy::builder()
+ .setTunnelLayer(proxy.tunnelLayer())
+ .setIndividualAddress(ia)
+ .create();
+ } else {
+ m_cri = proxy.builder()
+ .setTunnelLayer(proxy.tunnelLayer())
+ .create();
+ }
+ }
private:
QKnxNetIpCri m_cri;
Endpoint m_remoteDataEndpoint;
Endpoint m_remoteControlEndpoint;
- Endpoint m_natEndpoint { QHostAddress::AnyIPv4 };
+ Endpoint m_routeBack { QHostAddress::AnyIPv4 };
Endpoint m_localEndpoint { QHostAddress::LocalHost };
int m_channelId { -1 };
@@ -203,6 +241,17 @@ private:
QKnxByteArray m_rxBuffer;
UserProperties m_user;
+
+ quint16 m_sessionId { 0 };
+ quint48 m_sequenceNumber { 0 };
+ bool m_waitForAuthentication { false };
+
+ QKnxByteArray m_sessionKey;
+ QTimer *m_secureTimer { nullptr };
+ QKnxNetIpSecureConfiguration m_secureConfig;
+
+ // TODO: We need some kind of device configuration class as well.
+ QKnxByteArray m_serialNumber { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
};
QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetiproutingindication.cpp b/src/knx/netip/qknxnetiproutingindication.cpp
index c465181..d84e732 100644
--- a/src/knx/netip/qknxnetiproutingindication.cpp
+++ b/src/knx/netip/qknxnetiproutingindication.cpp
@@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE
if (!proxy.isValid())
return;
- auto linkFrame = proxy.linkLayerFrame();
+ auto linkFrame = proxy.cemi();
\endcode
\sa builder(), QKnxNetIpRoutingLostMessageProxy, QKnxNetIpRoutingBusyProxy,
diff --git a/src/knx/netip/qknxnetiproutingsystembroadcast.cpp b/src/knx/netip/qknxnetiproutingsystembroadcast.cpp
index 66e8d6c..232a50e 100644
--- a/src/knx/netip/qknxnetiproutingsystembroadcast.cpp
+++ b/src/knx/netip/qknxnetiproutingsystembroadcast.cpp
@@ -67,7 +67,7 @@ QT_BEGIN_NAMESPACE
if (!proxy.isValid())
return;
- auto linkFrame = proxy.linkLayerFrame();
+ auto linkFrame = proxy.cemi();
\endcode
\sa builder(), QKnxNetIpRoutingSystemBroadcastProxy,
diff --git a/src/knx/netip/qknxnetipsecureconfiguration.cpp b/src/knx/netip/qknxnetipsecureconfiguration.cpp
new file mode 100644
index 0000000..8b2d03b
--- /dev/null
+++ b/src/knx/netip/qknxnetipsecureconfiguration.cpp
@@ -0,0 +1,472 @@
+/****************************************************************************
+**
+** 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 "qknxcryptographicengine.h"
+#include "qknxnetipsecureconfiguration.h"
+
+#include <QtCore/qfile.h>
+
+#include "private/qknxkeyring_p.h"
+#include "private/qknxnetipsecureconfiguration_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace QKnxPrivate
+{
+ static QKnxNetIpSecureConfiguration fromInterface(const QKnx::Ets::Keyring::QKnxInterface &iface,
+ const QKnxByteArray &pwHash, const QKnxByteArray &createdHash)
+ {
+ QKnxNetIpSecureConfiguration s;
+ s.setUserId(QKnxNetIp::SecureUserId(iface.UserID));
+ s.setPrivateKey(QKnxSecureKey::generatePrivateKey());
+ s.setHost({ QKnxAddress::Type::Individual, iface.Host });
+ s.setUserPassword(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash,
+ createdHash, iface.Password).toByteArray());
+ s.setIndividualAddress({ QKnxAddress::Type::Individual, iface.IndividualAddress });
+ s.setDeviceAuthenticationCode(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash,
+ createdHash, iface.Authentication).toByteArray());
+ return s;
+ }
+
+ static QKnxNetIpSecureConfiguration fromDevice(const QKnx::Ets::Keyring::QKnxDevice &device,
+ const QKnxByteArray &pwHash, const QKnxByteArray &createdHash)
+ {
+ QKnxNetIpSecureConfiguration s;
+ s.setUserId(QKnxNetIp::SecureUserId::Management);
+ s.setPrivateKey(QKnxSecureKey::generatePrivateKey());
+ s.setHost({ QKnxAddress::Type::Individual, device.IndividualAddress });
+ s.setUserPassword(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash,
+ createdHash, device.ManagementPassword).toByteArray());
+ s.setIndividualAddress({ QKnxAddress::Type::Individual, device.IndividualAddress });
+ s.setDeviceAuthenticationCode(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash,
+ createdHash, device.Authentication).toByteArray());
+ return s;
+ }
+
+ static QVector<QKnxNetIpSecureConfiguration> fromKeyring(QKnxNetIpSecureConfiguration::Type type,
+ const QKnxAddress &ia, const QString &filePath, const QByteArray &password, bool validate)
+ {
+ QFile file;
+ file.setFileName(filePath);
+ if (!file.open(QIODevice::ReadOnly))
+ return {};
+
+ QXmlStreamReader reader(&file);
+ QKnx::Ets::Keyring::QKnxKeyring keyring;
+
+ const auto pwHash = QKnxCryptographicEngine::keyringPasswordHash(password);
+ if (validate) {
+ if (!keyring.validate(&reader, pwHash))
+ return {};
+ file.seek(0);
+ reader.setDevice(&file);
+ }
+
+ if (!keyring.parseElement(&reader, true))
+ return {};
+ const auto createdHash = QKnxCryptographicEngine::hashSha256(keyring.Created.toUtf8());
+
+ auto iaString = ia.toString();
+ QVector<QKnxNetIpSecureConfiguration> results;
+
+ if (type == QKnxNetIpSecureConfiguration::Type::Tunneling) {
+ if (keyring.Interface.isEmpty())
+ return {};
+
+ if (!iaString.isEmpty()) { // only a single interface is requested
+ for (const auto iface : qAsConst(keyring.Interface)) {
+ if (iaString != iface.IndividualAddress)
+ continue;
+ return { QKnxPrivate::fromInterface(iface, pwHash, createdHash) };
+ }
+ } else {
+ for (const auto iface : qAsConst(keyring.Interface))
+ results.append(QKnxPrivate::fromInterface(iface, pwHash, createdHash));
+ }
+ }
+
+ if (type == QKnxNetIpSecureConfiguration::Type::DeviceManagement) {
+ if (keyring.Devices.isEmpty())
+ return {};
+
+ const auto devices = keyring.Devices.value(0).Device;
+ if (!iaString.isEmpty()) { // only a single device is requested
+ for (const auto device : devices) {
+ if (iaString != device.IndividualAddress)
+ continue;
+ return { QKnxPrivate::fromDevice(device, pwHash, createdHash) };
+ }
+ } else {
+ for (const auto device : devices)
+ results.append(QKnxPrivate::fromDevice(device, pwHash, createdHash));
+ }
+ }
+
+ return results;
+ }
+}
+
+/*!
+ \since 5.13
+ \inmodule QtKnx
+
+ \class QKnxNetIpSecureConfiguration
+ \ingroup qtknx-general-classes
+
+ \brief The QKnxNetIpSecureConfiguration class holds configuration
+ options used for the secure session authentication process.
+
+ It holds information such as secure key, user ID and password, device
+ authentication code, and so on.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+*/
+
+/*!
+ \enum QKnxNetIpSecureConfiguration::Type
+
+ This enum holds the type of secure configuration that can be constructed
+ from an ETS exported keyring (*.knxkeys) file.
+
+ \value Tunneling
+ KNXnet/IP secure tunneling configuration.
+ \value DeviceManagement
+ KNXnet/IP secure device management configuration.
+*/
+
+/*!
+ Constructs a new, empty, invalid secure configuration.
+
+ \sa isNull(), isValid()
+*/
+QKnxNetIpSecureConfiguration::QKnxNetIpSecureConfiguration()
+ : d(new QKnxNetIpSecureConfigurationPrivate)
+{}
+
+/*!
+ Releases any resources held by the secure configuration.
+*/
+QKnxNetIpSecureConfiguration::~QKnxNetIpSecureConfiguration() = default;
+
+/*!
+ Constructs a vector of secure configurations for the given type
+ \a type from an ETS exported \a keyring (*.knxkeys) file that was
+ encrypted with the given password \a password. Set the \a validate
+ argument to \c true to verify that all data in the keyring file is
+ trustworthy, \c false to omit the check.
+
+ \note If an error occurred, no or invalid information for \a type
+ was found in the keyring file, the returned vector can be empty.
+*/
+QVector<QKnxNetIpSecureConfiguration> QKnxNetIpSecureConfiguration::fromKeyring(Type type,
+ const QString &keyring, const QByteArray &password, bool validate)
+{
+ return QKnxPrivate::fromKeyring(type, {}, keyring, password, validate);
+}
+
+/*!
+ Constructs a secure configurations for the given type \a type and the
+ given individual address \a ia from an ETS exported \a keyring (*.knxkeys)
+ file that was encrypted with the given password \a password.
+ Set the \a validate argument to \c true to verify that all data in the
+ keyring file is trustworthy, \c false to omit the check.
+
+ \note If an error occurred, no or invalid information for \a type or \a ia
+ was found in the keyring file;
+ the function returns a \l {default-constructed value} which can be invalid.
+*/
+QKnxNetIpSecureConfiguration QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration::Type type,
+ const QKnxAddress &ia, const QString &keyring, const QByteArray &password, bool validate)
+{
+ return QKnxPrivate::fromKeyring(type, ia, keyring, password, validate).value(0, {});
+}
+
+/*!
+ Returns \c true if this is a default constructed secure configuration;
+ otherwise returns \c false. A secure configuration is considered \c null
+ if it contains no initialized values.
+*/
+bool QKnxNetIpSecureConfiguration::isNull() const
+{
+ return d->privateKey.isNull() && d->userId == QKnxNetIp::SecureUserId::Reserved
+ && d->userPassword.isNull() && d->deviceAuthenticationCode.isNull();
+}
+
+/*!
+ Returns \c true if the secure configuration contains initialized values and
+ is in itself valid, otherwise returns \c false.
+
+ A valid secure configuration consists of at least a valid user ID,
+ a valid \l {QKnxSecureKey} {secure key}, and sensible device authentication
+ code.
+*/
+bool QKnxNetIpSecureConfiguration::isValid() const
+{
+ if (isNull())
+ return false;
+
+ return d->privateKey.type() == QKnxSecureKey::Type::Private && d->privateKey.isValid()
+ && QKnxNetIp::isSecureUserId(d->userId) && (!d->deviceAuthenticationCode.isEmpty());
+}
+
+/*!
+ Returns the host address of the secure KNX device this secure configuration
+ targets. The host address can be empty and is not required to establish a
+ secure session.
+
+ The purpose of this field is more to help GUI applications to indicate
+ which KNX secure device can be used with this secure configuration.
+*/
+QKnxAddress QKnxNetIpSecureConfiguration::host() const
+{
+ return d->host;
+}
+
+/*!
+ Sets the secure configurations host address to \a hostAddress. The host
+ address can be empty and is not required to establish a secure session.
+*/
+void QKnxNetIpSecureConfiguration::setHost(const QKnxAddress &hostAddress)
+{
+ d->host = hostAddress;
+}
+
+/*!
+ Returns the public \l {QKnxSecureKey} {secure key} used to establish the
+ secure session. The public key is derived from the given private key.
+*/
+QKnxSecureKey QKnxNetIpSecureConfiguration::publicKey() const
+{
+ return d->publicKey;
+}
+
+/*!
+ Returns the private \l {QKnxSecureKey} {secure key} used to establish the
+ secure session.
+*/
+QKnxSecureKey QKnxNetIpSecureConfiguration::privateKey() const
+{
+ return d->privateKey;
+}
+
+/*!
+ Set the \l {QKnxSecureKey} {secure key} used to establish the secure
+ connection to \a key and returns \c true on success; \c false otherwise.
+*/
+bool QKnxNetIpSecureConfiguration::setPrivateKey(const QKnxSecureKey &key)
+{
+ auto valid = key.isValid() && key.type() == QKnxSecureKey::Type::Private;
+ if (valid) {
+ d->privateKey = key;
+ d->publicKey = QKnxSecureKey::publicKeyFromPrivate(key);
+ }
+ return valid;
+}
+
+/*!
+ Returns the user ID used in the KNXnet/IP session authentication frame.
+*/
+QKnxNetIp::SecureUserId QKnxNetIpSecureConfiguration::userId() const
+{
+ return d->userId;
+}
+
+/*!
+ Sets the user ID used in the KNXnet/IP session authentication frame
+ to \a userId and returns \c true on success; \c false otherwise.
+
+ \note A userId() with the value \c QKnxNetIp::SecureUserId::Reserved or
+ equal to or more than \c QKnxNetIp::SecureUserId::Invalid is considered
+ invalid according to the KNX application note AN159.
+*/
+bool QKnxNetIpSecureConfiguration::setUserId(QKnxNetIp::SecureUserId userId)
+{
+ auto valid = QKnxNetIp::isSecureUserId(userId);
+ if (valid)
+ d->userId = userId;
+ return valid;
+}
+
+/*!
+ Returns the user password used to authenticate the user while establishing
+ the secure session as an array of bytes.
+*/
+QByteArray QKnxNetIpSecureConfiguration::userPassword() const
+{
+ return d->userPassword;
+}
+
+/*!
+ Sets the user password to authenticate the user while establishing the
+ secure session to \a userPassword. Returns \c true on success; \c false
+ otherwise.
+*/
+void QKnxNetIpSecureConfiguration::setUserPassword(const QByteArray &userPassword)
+{
+ d->userPassword = userPassword;
+}
+
+/*!
+ Returns the requested individual address for the secure session.
+*/
+QKnxAddress QKnxNetIpSecureConfiguration::individualAddress() const
+{
+ return d->ia;
+}
+
+/*!
+ Sets the requested individual address of the secure session to \a address.
+ Returns \c true on success; \c false otherwise.
+
+ \note To request any of the freely available addresses for the secure
+ session, or to reset the requested one, pass an invalid \a address to
+ the function.
+*/
+bool QKnxNetIpSecureConfiguration::setIndividualAddress(const QKnxAddress &address)
+{
+ if ((address.type() == QKnxAddress::Type::Individual) || (!address.isValid()))
+ d->ia = address;
+ return d->ia == address;
+}
+
+/*!
+ Returns the device authentication code to establish the secure session
+ as an array of bytes.
+*/
+QByteArray QKnxNetIpSecureConfiguration::deviceAuthenticationCode() const
+{
+ return d->deviceAuthenticationCode;
+}
+
+/*!
+ Sets the device authentication code used to establish the secure session
+ to \a authenticationCode. Returns \c true on success; \c false otherwise.
+
+ \note The device authentication code cannot be empty.
+*/
+bool QKnxNetIpSecureConfiguration::setDeviceAuthenticationCode(const QByteArray &authenticationCode)
+{
+ auto valid = !authenticationCode.isEmpty();
+ if (valid)
+ d->deviceAuthenticationCode = authenticationCode;
+ return valid;
+}
+
+/*!
+ Returns \c true if the keep alive flag is set; \c false otherwise. By
+ default this is set to \c false.
+*/
+bool QKnxNetIpSecureConfiguration::isSecureSessionKeepAliveSet() const
+{
+ return d->keepAlive;
+}
+
+/*!
+ Determines whether the connection should be kept alive. Set \a keepAlive to
+ \c true to keep a secure session alive even if there is no traffic for more
+ than \l {QKnx::NetIp::SecureSessionTimeout} {60 seconds}.
+*/
+void QKnxNetIpSecureConfiguration::setKeepSecureSessionAlive(bool keepAlive)
+{
+ d->keepAlive = keepAlive;
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+QKnxNetIpSecureConfiguration::QKnxNetIpSecureConfiguration(const QKnxNetIpSecureConfiguration &other)
+ : d(other.d)
+{}
+
+/*!
+ Assigns the specified \a other to this secure configuration.
+*/
+QKnxNetIpSecureConfiguration &QKnxNetIpSecureConfiguration::operator=(const QKnxNetIpSecureConfiguration &other)
+{
+ d = other.d;
+ return *this;
+}
+
+/*!
+ Move-constructs a secure configuration, making it point to the same secure
+ configuration that \a other was pointing to.
+*/
+QKnxNetIpSecureConfiguration::QKnxNetIpSecureConfiguration(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW
+ : d(other.d)
+{
+ other.d = nullptr;
+}
+
+/*!
+ Move-assigns \a other to this secure configuration.
+*/
+QKnxNetIpSecureConfiguration &
+ QKnxNetIpSecureConfiguration::operator=(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW
+{
+ swap(other);
+ return *this;
+}
+
+/*!
+ Swaps \a other with this secure configuration. This operation is very fast
+ and never fails.
+*/
+void QKnxNetIpSecureConfiguration::swap(QKnxNetIpSecureConfiguration &other) Q_DECL_NOTHROW
+{
+ d.swap(other.d);
+}
+
+/*!
+ Returns \c true if this secure configuration and the given \a other are
+ equal; otherwise returns \c false.
+*/
+bool QKnxNetIpSecureConfiguration::operator==(const QKnxNetIpSecureConfiguration &other) const
+{
+ return d == other.d || (d->privateKey == other.d->privateKey
+ && d->publicKey == other.d->publicKey
+ && d->host == other.d->host
+ && d->userId == other.d->userId
+ && d->userPassword == other.d->userPassword
+ && d->ia == other.d->ia
+ && d->deviceAuthenticationCode == other.d->deviceAuthenticationCode
+ && d->keepAlive == other.d->keepAlive);
+}
+
+/*!
+ Returns \c true if this secure configuration and the given \a other are not
+ equal; otherwise returns \c false.
+*/
+bool QKnxNetIpSecureConfiguration::operator!=(const QKnxNetIpSecureConfiguration &other) const
+{
+ return !operator==(other);
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetipsecureconfiguration.h b/src/knx/netip/qknxnetipsecureconfiguration.h
new file mode 100644
index 0000000..72b136a
--- /dev/null
+++ b/src/knx/netip/qknxnetipsecureconfiguration.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QKNXNETIPSECURECONFIGURATION_H
+#define QKNXNETIPSECURECONFIGURATION_H
+
+#include <QtCore/qshareddata.h>
+
+#include <QtKnx/qknxaddress.h>
+#include <QtKnx/qknxnetip.h>
+#include <QtKnx/qknxsecurekey.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxNetIpSecureConfigurationPrivate;
+class Q_KNX_EXPORT QKnxNetIpSecureConfiguration
+{
+public:
+ enum class Type : quint8
+ {
+ Tunneling = 0x00,
+ DeviceManagement = 001
+ };
+
+ QKnxNetIpSecureConfiguration();
+ ~QKnxNetIpSecureConfiguration();
+
+ static QVector<QKnxNetIpSecureConfiguration> fromKeyring(QKnxNetIpSecureConfiguration::Type type,
+ const QString &keyring, const QByteArray &password, bool validate);
+
+ static QKnxNetIpSecureConfiguration fromKeyring(QKnxNetIpSecureConfiguration::Type type,
+ const QKnxAddress &ia, const QString &keyring, const QByteArray &password, bool validate);
+
+ bool isNull() const;
+ bool isValid() const;
+
+ QKnxSecureKey publicKey() const;
+
+ QKnxAddress host() const;
+ void setHost(const QKnxAddress &host);
+
+ QKnxSecureKey privateKey() const;
+ bool setPrivateKey(const QKnxSecureKey &key);
+
+ QKnxNetIp::SecureUserId userId() const;
+ bool setUserId(QKnxNetIp::SecureUserId userId);
+
+ QByteArray userPassword() const;
+ void setUserPassword(const QByteArray &userPassword);
+
+ QKnxAddress individualAddress() const;
+ bool setIndividualAddress(const QKnxAddress &address);
+
+ QByteArray deviceAuthenticationCode() const;
+ bool setDeviceAuthenticationCode(const QByteArray &authenticationCode);
+
+ bool isSecureSessionKeepAliveSet() const;
+ void setKeepSecureSessionAlive(bool keepAlive);
+
+ QKnxNetIpSecureConfiguration(const QKnxNetIpSecureConfiguration &other);
+ QKnxNetIpSecureConfiguration &operator=(const QKnxNetIpSecureConfiguration &other);
+
+ QKnxNetIpSecureConfiguration(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW;
+ QKnxNetIpSecureConfiguration &operator=(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW;
+
+ void swap(QKnxNetIpSecureConfiguration &other) Q_DECL_NOTHROW;
+
+ bool operator==(const QKnxNetIpSecureConfiguration &other) const;
+ bool operator!=(const QKnxNetIpSecureConfiguration &other) const;
+
+private:
+ friend class QKnxNetIpEndpointConnection;
+ friend class QKnxNetIpEndpointConnectionPrivate;
+ QSharedDataPointer<QKnxNetIpSecureConfigurationPrivate> d;
+};
+Q_DECLARE_SHARED(QKnxNetIpSecureConfiguration)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/netip/qknxnetipsecureconfiguration_p.h b/src/knx/netip/qknxnetipsecureconfiguration_p.h
new file mode 100644
index 0000000..1cdbd3f
--- /dev/null
+++ b/src/knx/netip/qknxnetipsecureconfiguration_p.h
@@ -0,0 +1,68 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef QKNXNETIPSECURECONFIGURATION_P_H
+#define QKNXNETIPSECURECONFIGURATION_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 <QtKnx/qknxaddress.h>
+#include <QtKnx/qknxnetip.h>
+#include <QtKnx/qknxsecurekey.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxNetIpSecureConfigurationPrivate : public QSharedData
+{
+public:
+ QKnxNetIpSecureConfigurationPrivate() = default;
+ ~QKnxNetIpSecureConfigurationPrivate() = default;
+
+ QKnxSecureKey privateKey;
+ QKnxSecureKey publicKey;
+ QKnxAddress host;
+ QKnxNetIp::SecureUserId userId { QKnxNetIp::SecureUserId::Reserved };
+ QByteArray userPassword;
+ QKnxAddress ia;
+ QByteArray deviceAuthenticationCode;
+ bool keepAlive { false };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/netip/qknxnetipsecurewrapper.cpp b/src/knx/netip/qknxnetipsecurewrapper.cpp
index 0639eee..a100604 100644
--- a/src/knx/netip/qknxnetipsecurewrapper.cpp
+++ b/src/knx/netip/qknxnetipsecurewrapper.cpp
@@ -178,6 +178,14 @@ QKnxNetIpSecureWrapperProxy::Builder QKnxNetIpSecureWrapperProxy::builder()
return QKnxNetIpSecureWrapperProxy::Builder();
}
+/*!
+ Returns a builder object to create a KNXnet/IP secure wrapper frame.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder QKnxNetIpSecureWrapperProxy::secureBuilder()
+{
+ return QKnxNetIpSecureWrapperProxy::SecureBuilder();
+}
+
/*!
\class QKnxNetIpSecureWrapperProxy::Builder
@@ -280,7 +288,8 @@ QKnxNetIpSecureWrapperProxy::Builder &
This field contains an arbitrary value to differentiate two KNXnet/IP
secure wrapper multicast frames sent by one KNXnet/IP device within the
same millisecond (thus the same timer value).
- For unicast connections this field is ignored and must be set to \c 0x0000.
+
+ \note For unicast connections this field is ignored and must be set to \c 0x0000.
*/
QKnxNetIpSecureWrapperProxy::Builder &
QKnxNetIpSecureWrapperProxy::Builder::setMessageTag(quint16 tag)
@@ -330,7 +339,7 @@ QKnxNetIpFrame QKnxNetIpSecureWrapperProxy::Builder::create() const
return { QKnxNetIp::ServiceType::SecureWrapper };
}
- // TODO: introspect the frame and reject secure wrapper frames if possible at all
+ // TODO: introspect the frame and reject secure wrapper frames - if possible at all
return { QKnxNetIp::ServiceType::SecureWrapper, QKnxUtils::QUint16::bytes(d_ptr->m_sessionId)
+ QKnxUtils::QUint48::bytes(d_ptr->m_seqNumber) + d_ptr->m_serial
@@ -354,4 +363,200 @@ QKnxNetIpSecureWrapperProxy::Builder &
return *this;
}
+/*!
+ \class QKnxNetIpSecureWrapperProxy::SecureBuilder
+
+ \inmodule QtKnx
+ \inheaderfile QKnxNetIpSecureWrapperProxy
+
+ \brief The QKnxNetIpSecureWrapperProxy::SecureBuilder class provides the
+ means to create a KNXnet/IP secure wrapper frame.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+
+ The advantage of using this builder over the default one is that it can
+ take away the burden to do the encryption of the encapsulated frame and
+ the calculation the message authentication code (MAC).
+
+ \note To use this class OpenSSL must be supported on your target system.
+
+ This frame will be sent during secure KNXnet/IP communication and includes
+ a fully encrypted KNXnet/IP frame as well as information needed to decrypt
+ the encapsulated frame and for ensuring data integrity and freshness.
+
+ The common way to create a secure wrapper frame is:
+
+ \code
+ QKnxNetIpFrame frame = QKnxNetIpFrame::fromBytes(...); // create frame
+
+ auto netIpFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSequenceNumber(15021976)
+ .setSerialNumber(QKnxByteArray::fromHex("0123456789AB"))
+ .setEncapsulatedFrame(frame)
+ .create(sessionKey);
+ \endcode
+
+ \sa QKnxCryptographicEngine
+*/
+
+/*!
+ Creates a new empty secure wrapper frame builder object.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder::SecureBuilder()
+ : d_ptr(new QKnxNetIpSecureWrapperPrivate)
+{}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder::~SecureBuilder() = default;
+
+/*!
+ Sets the secure session ID to \a sessionId and returns a reference to the
+ builder. By default value is set to \c 0x0000.
+
+ For multicast connections the fixed identifier \c 0x0000 must be used. For
+ unicast connections the ID was established during a previous successful
+ secure session setup procedure.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder &
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::setSecureSessionId(quint16 sessionId)
+{
+ d_ptr->m_sessionId = sessionId;
+ return *this;
+}
+
+/*!
+ Sets the sequence number to \a seqNumber and returns a reference to the
+ builder.
+
+ For unicast connections it is a monotonically increasing sequence number
+ assigned by the sender; incremented by the sender after each frame sent.
+ For multicast connections this is the device's current multicast timer
+ value in millisecond resolution.
+
+ \note The size of a sequence number is limited to 48 bits, so the maximum
+ number can be \c 281474976710655. Passing a larger value will result in
+ creating an invalid frame.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder &
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::setSequenceNumber(quint48 seqNumber)
+{
+ d_ptr->m_seqNumber = seqNumber;
+ return *this;
+}
+
+/*!
+ Sets the serial number to \a serialNumber of the device sending the frame
+ and returns a reference to the builder.
+
+ \note The serial number must contain exactly 6 bytes.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder &
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::setSerialNumber(const QKnxByteArray &serialNumber)
+{
+ d_ptr->m_serial = serialNumber;
+ return *this;
+}
+
+/*!
+ Sets the message tag of the generic KNXnet/IP secure wrapper frame to \a tag
+ and returns a reference to the builder. By default value is set to \c 0x0000.
+
+ This field contains an arbitrary value to differentiate two KNXnet/IP
+ secure wrapper multicast frames sent by one KNXnet/IP device within the
+ same millisecond (thus the same timer value).
+
+ \note For unicast connections this field is ignored and must be set to \c 0x0000.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder &
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::setMessageTag(quint16 tag)
+{
+ d_ptr->m_tag = tag;
+ return *this;
+}
+
+/*!
+ Sets the encapsulated KNXnet/IP frame to \a frame and returns a reference
+ to the builder.
+
+ \note A secure wrapper frame cannot be encapsulated in another secure
+ wrapper frame and will result in creating an invalid frame.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder &
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::setEncapsulatedFrame(const QKnxNetIpFrame &frame)
+{
+ if (frame.serviceType() != QKnxNetIp::ServiceType::SecureWrapper)
+ d_ptr->m_unencryptedFrame = frame;
+ return *this;
+}
+
+/*!
+ Creates and returns a KNXnet/IP secure wrapper frame. During creation the
+ encapsulated frame gets encrypted and the corresponding MAC computed.
+ The given session key \a sessionKey takes part of the encryption process.
+ Both values, the encrypted frame and the MAC are appended to the KNXnet/IP
+ secure wrapper frame.
+
+ \note The returned frame may be invalid depending on the values used during
+ setup.
+
+ \sa isValid()
+*/
+QKnxNetIpFrame
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::create(const QKnxByteArray &sessionKey) const
+{
+#if QT_CONFIG(opensslv11)
+ if (sessionKey.isEmpty() || d_ptr->m_seqNumber > Q_UINT48_MAX || d_ptr->m_serial.size() != 6
+ || !d_ptr->m_unencryptedFrame.isValid()) {
+ return { QKnxNetIp::ServiceType::SecureWrapper };
+ }
+
+ auto encryptedFrame = QKnxCryptographicEngine::encryptSecureWrapperPayload(sessionKey,
+ d_ptr->m_unencryptedFrame, d_ptr->m_seqNumber, d_ptr->m_serial, d_ptr->m_tag);
+ if (encryptedFrame.isEmpty())
+ return {};
+
+ auto builder = QKnxNetIpSecureWrapperProxy::builder();
+ auto frame = builder
+ .setSecureSessionId(d_ptr->m_sessionId)
+ .setSequenceNumber(d_ptr->m_seqNumber)
+ .setSerialNumber(d_ptr->m_serial)
+ .setMessageTag(d_ptr->m_tag)
+ .setEncapsulatedFrame(encryptedFrame)
+ .setMessageAuthenticationCode(QKnxByteArray(16, 0x00)) // dummy MAC to get a proper header
+ .create();
+
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(sessionKey,
+ frame.header(), d_ptr->m_sessionId, d_ptr->m_unencryptedFrame.bytes(), d_ptr->m_seqNumber,
+ d_ptr->m_serial, d_ptr->m_tag);
+ mac = QKnxCryptographicEngine::encryptMessageAuthenticationCode(sessionKey, mac,
+ d_ptr->m_seqNumber, d_ptr->m_serial, d_ptr->m_tag);
+
+ return builder.setMessageAuthenticationCode(mac).create();
+#else
+ Q_UNUSED(sessionKey)
+ return { QKnxNetIp::ServiceType::SecureWrapper };
+#endif
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder::SecureBuilder(const SecureBuilder &other)
+ : d_ptr(other.d_ptr)
+{}
+
+/*!
+ Assigns the specified \a other to this object.
+*/
+QKnxNetIpSecureWrapperProxy::SecureBuilder &
+ QKnxNetIpSecureWrapperProxy::SecureBuilder::operator=(const SecureBuilder &other)
+{
+ d_ptr = other.d_ptr;
+ return *this;
+}
+
QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetipsecurewrapper.h b/src/knx/netip/qknxnetipsecurewrapper.h
index a88c4de..16a6a66 100644
--- a/src/knx/netip/qknxnetipsecurewrapper.h
+++ b/src/knx/netip/qknxnetipsecurewrapper.h
@@ -77,6 +77,28 @@ public:
};
static QKnxNetIpSecureWrapperProxy::Builder builder();
+ class Q_KNX_EXPORT SecureBuilder final
+ {
+ public:
+ SecureBuilder();
+ ~SecureBuilder();
+
+ SecureBuilder &setSecureSessionId(quint16 sessionId);
+ SecureBuilder &setSequenceNumber(quint48 seqNumber);
+ SecureBuilder &setSerialNumber(const QKnxByteArray &serialNumber);
+ SecureBuilder &setMessageTag(quint16 tag);
+ SecureBuilder &setEncapsulatedFrame(const QKnxNetIpFrame &frame);
+
+ QKnxNetIpFrame create(const QKnxByteArray &sessionKey) const;
+
+ SecureBuilder(const SecureBuilder &other);
+ SecureBuilder &operator=(const SecureBuilder &other);
+
+ private:
+ QSharedDataPointer<QKnxNetIpSecureWrapperPrivate> d_ptr;
+ };
+ static QKnxNetIpSecureWrapperProxy::SecureBuilder secureBuilder();
+
private:
const QKnxNetIpFrame &m_frame;
};
diff --git a/src/knx/netip/qknxnetipserverdescriptionagent.cpp b/src/knx/netip/qknxnetipserverdescriptionagent.cpp
index 96532ef..5735ed7 100644
--- a/src/knx/netip/qknxnetipserverdescriptionagent.cpp
+++ b/src/knx/netip/qknxnetipserverdescriptionagent.cpp
@@ -41,15 +41,18 @@ QT_BEGIN_NAMESPACE
\brief The QKnxNetIpServerDescriptionAgent class establishes a point-to-point
connection with a KNXnet/IP server and requests its description.
- First, the \l QKnxNetIpServerDiscoveryAgent class is used to choose a
- server, as illustrated by the following code snippet:
+ First, the \l QKnxNetIpServerDiscoveryAgent class is used to search the
+ network for available KNXnet/IP devices. After successful discovery of
+ such a device, the server needs to be chosen to fetch the description.
+ This can be been done as illustrated by the following code snippet:
\code
- QKnxNetIpServerDescriptionAgent agent;
- QKnxAddress clientLocalAddress = ...
- agent.setLocalAddress(clientLocalAddress);
- QKnxNetIpServerInfo server = agent.discoveredServers()[0]; // for example
- agent.start(server);
+ QKnxNetIpServerDescriptionAgent descriptionAgent;
+ QHostAddress clientLocalAddress = ...
+ descriptionAgent.setLocalAddress(clientLocalAddress);
+
+ auto server = discoveryAgent.discoveredServers()[0]; // for example
+ descriptionAgent.start(server);
\endcode
When the description is received from the server, the
@@ -86,6 +89,8 @@ QT_BEGIN_NAMESPACE
A network error occurred.
\value NotIPv4
The network protocol used is not IPv4.
+ \value Timeout
+ A timeout occurred while waiting for the description response.
\value Unknown
An unknown error occurred.
*/
@@ -145,6 +150,7 @@ namespace QKnxPrivate
void QKnxNetIpServerDescriptionAgentPrivate::setupSocket()
{
usedPort = port;
+ timeoutHit = true;
QKnxPrivate::clearSocket(&socket);
Q_Q(QKnxNetIpServerDescriptionAgent);
@@ -255,6 +261,7 @@ void QKnxNetIpServerDescriptionAgentPrivate::setAndEmitStateChanged(
void QKnxNetIpServerDescriptionAgentPrivate::setAndEmitServerDescriptionReceived(
const QKnxNetIpServerInfo &description)
{
+ timeoutHit = false;
m_description = description;
Q_Q(QKnxNetIpServerDescriptionAgent);
@@ -301,8 +308,8 @@ QKnxNetIpServerDescriptionAgent::QKnxNetIpServerDescriptionAgent(const QHostAddr
Creates a KNXnet/IP server description agent with the host address
\a localAddress, the port number \a port, and the parent \a parent.
- \note If the port number is already bound by a different process, discovery
- will fail.
+ \note If the port number is already bound by a different process,
+ description requests will fail.
*/
QKnxNetIpServerDescriptionAgent::QKnxNetIpServerDescriptionAgent(const QHostAddress &localAddress,
quint16 port, QObject *parent)
@@ -361,8 +368,8 @@ quint16 QKnxNetIpServerDescriptionAgent::localPort() const
/*!
Sets the port number used by a description agent to \a port.
- \note If the port changes during discovery, the new port will not be used
- until the next run.
+ \note If the port changes during a description request, the new port will
+ not be used until the next run.
*/
void QKnxNetIpServerDescriptionAgent::setLocalPort(quint16 port)
{
@@ -383,8 +390,8 @@ QHostAddress QKnxNetIpServerDescriptionAgent::localAddress() const
/*!
Sets the host address of a description agent to \a address.
- \note If the address changes during discovery, the new address will not be
- used until the next run.
+ \note If the address changes during a description request, the new address
+ will not be used until the next run.
*/
void QKnxNetIpServerDescriptionAgent::setLocalAddress(const QHostAddress &address)
{
@@ -430,10 +437,10 @@ bool QKnxNetIpServerDescriptionAgent::natAware() const
}
/*!
- Sets whether the server discovery agent is using NAT to \a useNat.
+ Sets whether the server description agent is using NAT to \a useNat.
- \note If the setting changes during discovery, it will not be used until the
- next run.
+ \note If the setting changes during a description request, it will not be
+ used until the next run.
*/
void QKnxNetIpServerDescriptionAgent::setNatAware(bool useNat)
{
@@ -494,7 +501,7 @@ void QKnxNetIpServerDescriptionAgent::start(const QKnxNetIpHpai &server)
*/
void QKnxNetIpServerDescriptionAgent::start(const QKnxNetIpServerInfo &server)
{
- start(server.controlEndpointAddress(), server.controlEndpointPort());
+ start(server.endpoint());
}
/*!
@@ -523,6 +530,11 @@ void QKnxNetIpServerDescriptionAgent::stop()
QKnxPrivate::clearSocket(&(d->socket));
QKnxPrivate::clearTimer(&(d->receiveTimer));
+ if (d->timeoutHit) {
+ emit d->setAndEmitErrorOccurred(Error::Timeout, tr("A timeout occurred while waiting for "
+ "the description response."));
+ }
+
d->setAndEmitStateChanged(QKnxNetIpServerDescriptionAgent::State::NotRunning);
}
diff --git a/src/knx/netip/qknxnetipserverdescriptionagent.h b/src/knx/netip/qknxnetipserverdescriptionagent.h
index 292db19..917945d 100644
--- a/src/knx/netip/qknxnetipserverdescriptionagent.h
+++ b/src/knx/netip/qknxnetipserverdescriptionagent.h
@@ -60,6 +60,7 @@ public:
None,
Network,
NotIPv4,
+ Timeout,
Unknown = 0x80
};
Q_ENUM(Error)
diff --git a/src/knx/netip/qknxnetipserverdescriptionagent_p.h b/src/knx/netip/qknxnetipserverdescriptionagent_p.h
index 9d63e8c..bc50399 100644
--- a/src/knx/netip/qknxnetipserverdescriptionagent_p.h
+++ b/src/knx/netip/qknxnetipserverdescriptionagent_p.h
@@ -81,6 +81,8 @@ private:
quint8 ttl { 60 };
bool nat { false };
+
+ bool timeoutHit { true };
int timeout { QKnxNetIp::Timeout::DescriptionTimeout };
QString errorString;
diff --git a/src/knx/netip/qknxnetipserverdiscoveryagent.cpp b/src/knx/netip/qknxnetipserverdiscoveryagent.cpp
index 0fdc126..30c3d1d 100644
--- a/src/knx/netip/qknxnetipserverdiscoveryagent.cpp
+++ b/src/knx/netip/qknxnetipserverdiscoveryagent.cpp
@@ -390,7 +390,7 @@ void QKnxNetIpServerDiscoveryAgentPrivate::setupAndStartFrequencyTimer()
frequencyTimer->setSingleShot(false);
frequencyTimer->start(60000 / frequency);
- QObject::connect(receiveTimer, &QTimer::timeout, [&]() {
+ QObject::connect(frequencyTimer, &QTimer::timeout, [&]() {
Q_Q(QKnxNetIpServerDiscoveryAgent);
if (q->state() == QKnxNetIpServerDiscoveryAgent::State::Running) {
servers.clear();
diff --git a/src/knx/netip/qknxnetipservicefamiliesdib.cpp b/src/knx/netip/qknxnetipservicefamiliesdib.cpp
index 6394d47..5c95f5a 100644
--- a/src/knx/netip/qknxnetipservicefamiliesdib.cpp
+++ b/src/knx/netip/qknxnetipservicefamiliesdib.cpp
@@ -67,6 +67,29 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \since 5.13
+
+ Returns \c true if this object and the given \a other are equal; otherwise
+ returns \c false.
+*/
+bool QKnxServiceInfo::operator==(const QKnxServiceInfo &other) const
+{
+ return ServiceFamily == other.ServiceFamily && ServiceFamilyVersion == other.ServiceFamilyVersion;
+}
+
+/*!
+ \since 5.13
+
+ Returns \c true if this object and the given \a other are not equal;
+ otherwise returns \c false.
+*/
+bool QKnxServiceInfo::operator!=(const QKnxServiceInfo &other) const
+{
+ return !operator==(other);
+}
+
+
+/*!
\class QKnxNetIpServiceFamiliesDibProxy
\inmodule QtKnx
diff --git a/src/knx/netip/qknxnetipservicefamiliesdib.h b/src/knx/netip/qknxnetipservicefamiliesdib.h
index c686d27..1108208 100644
--- a/src/knx/netip/qknxnetipservicefamiliesdib.h
+++ b/src/knx/netip/qknxnetipservicefamiliesdib.h
@@ -39,6 +39,9 @@ struct Q_KNX_EXPORT QKnxServiceInfo
{
QKnxNetIp::ServiceFamily ServiceFamily;
quint8 ServiceFamilyVersion;
+
+ bool operator==(const QKnxServiceInfo &other) const;
+ bool operator!=(const QKnxServiceInfo &other) const;
};
class Q_KNX_EXPORT QKnxNetIpServiceFamiliesDibProxy final
diff --git a/src/knx/netip/qknxnetipsessionauthenticate.cpp b/src/knx/netip/qknxnetipsessionauthenticate.cpp
index 12e38b2..70d79b9 100644
--- a/src/knx/netip/qknxnetipsessionauthenticate.cpp
+++ b/src/knx/netip/qknxnetipsessionauthenticate.cpp
@@ -27,6 +27,7 @@
**
******************************************************************************/
+#include "qknxbuilderdata_p.h"
#include "qknxnetipsessionauthenticate.h"
#include "qknxutils.h"
@@ -104,8 +105,9 @@ QKnxNetIpSessionAuthenticateProxy::QKnxNetIpSessionAuthenticateProxy(const QKnxN
at least a valid header and a size in bytes corresponding to the total size
of the KNXnet/IP frame header.
- \note A userId() with the value \c 0x0000 or a value above \c 0x0080
- is considered invalid according to the KNX application note AN159.
+ \note A userId() with the value \c QKnxNetIp::SecureUserId::Reserved or
+ equal to or more than \c QKnxNetIp::SecureUserId::Invalid is considered
+ invalid according to the KNX application note AN159.
\note KNXnet/IP session authentication frames currently have a fixed size
of \c 24 bytes.
@@ -114,19 +116,16 @@ QKnxNetIpSessionAuthenticateProxy::QKnxNetIpSessionAuthenticateProxy(const QKnxN
*/
bool QKnxNetIpSessionAuthenticateProxy::isValid() const
{
- const auto id = userId();
return m_frame.isValid() && m_frame.serviceType() == QKnxNetIp::ServiceType::SessionAuthenticate
- && m_frame.size() == 24 && id > 0 && id < 0x80; // 0x00 and values above 0x80 are reserved
+ && m_frame.size() == 24 && QKnxNetIp::isSecureUserId(userId());
}
/*!
- Returns the user ID from the generic KNXnet/IP session authentication
- frame. The value of \c 0 is a reserved value and indicates an invalid
- user ID.
+ Returns the user ID from the generic KNXnet/IP session authentication frame.
*/
-quint8 QKnxNetIpSessionAuthenticateProxy::userId() const
+QKnxNetIp::SecureUserId QKnxNetIpSessionAuthenticateProxy::userId() const
{
- return m_frame.constData().value(1);
+ return static_cast<QKnxNetIp::SecureUserId> (m_frame.constData().value(1));
}
/*!
@@ -135,7 +134,7 @@ quint8 QKnxNetIpSessionAuthenticateProxy::userId() const
*/
QKnxByteArray QKnxNetIpSessionAuthenticateProxy::messageAuthenticationCode() const
{
- return m_frame.constData().mid(sizeof(quint16) + 32);
+ return m_frame.constData().mid(sizeof(quint16));
}
/*!
@@ -169,11 +168,10 @@ QKnxNetIpSessionAuthenticateProxy::Builder QKnxNetIpSessionAuthenticateProxy::bu
The common way to create a session authentication frame is:
\code
- quint16 mgmtLevelAccess = 0x0001;
auto auth = ... // create the full 128 bit CCM-MAC
auto netIpFrame = QKnxNetIpSessionAuthenticateProxy::builder()
- .setUserId(mgmtLevelAccess)
+ .setUserId(QKnxNetIp::SecureUserId::Management)
.setMessageAuthenticationCode(auth)
.create();
\endcode
@@ -181,18 +179,8 @@ QKnxNetIpSessionAuthenticateProxy::Builder QKnxNetIpSessionAuthenticateProxy::bu
\sa QKnxCryptographicEngine
*/
-class QKnxNetIpSessionAuthenticateBuilderPrivate : public QSharedData
-{
-public:
- QKnxNetIpSessionAuthenticateBuilderPrivate() = default;
- ~QKnxNetIpSessionAuthenticateBuilderPrivate() = default;
-
- quint16 m_id { 0 };
- QKnxByteArray m_authCode;
-};
-
/*!
- Creates a new empty session authenticate frame builder.
+ Creates a new empty session authentication frame builder.
*/
QKnxNetIpSessionAuthenticateProxy::Builder::Builder()
: d_ptr(new QKnxNetIpSessionAuthenticateBuilderPrivate)
@@ -207,11 +195,12 @@ QKnxNetIpSessionAuthenticateProxy::Builder::~Builder() = default;
Sets the user ID of the KNXnet/IP session authentication frame to \a userId
and returns a reference to the builder.
- \note A userId() with the value \c 0x0000 or a value above \c 0x0080
- is considered invalid according to the KNX application note AN159.
+ \note A userId() with the value \c QKnxNetIp::SecureUserId::Reserved or
+ equal to or more than \c QKnxNetIp::SecureUserId::Invalid is considered
+ invalid according to the KNX application note AN159.
*/
QKnxNetIpSessionAuthenticateProxy::Builder &
- QKnxNetIpSessionAuthenticateProxy::Builder::setUserId(quint16 userId)
+ QKnxNetIpSessionAuthenticateProxy::Builder::setUserId(QKnxNetIp::SecureUserId userId)
{
d_ptr->m_id = userId;
return *this;
@@ -263,4 +252,131 @@ QKnxNetIpSessionAuthenticateProxy::Builder &
return *this;
}
+
+/*!
+ \class QKnxNetIpSessionAuthenticateProxy::SecureBuilder
+
+ \inmodule QtKnx
+ \inheaderfile QKnxNetIpSessionAuthenticateProxy
+
+ \brief The QKnxNetIpSessionAuthenticateProxy::SecureBuilder class provides the
+ means to create a KNXnet/IP session authentication frame.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+
+ \note To use this class OpenSSL must be supported on your target system.
+
+ This frame will be sent by the KNXnet/IP secure client to the control
+ endpoint of the KNXnet/IP secure server after the Diffie-Hellman handshake
+ to authenticate the user against the server device.
+ The maximum time a KNXnet/IP secure client will wait for an authentication
+ status response of the KNXnet/IP secure server is 10 seconds.
+
+ The common way to create a session authentication frame is:
+
+ \code
+ auto netIpFrame = QKnxNetIpSessionAuthenticateProxy::secureBuilder()
+ .setUserId(QKnxNetIp::SecureUserId::Management)
+ .create(passwordHash, clientKey, serverKey);
+ \endcode
+
+ \sa QKnxCryptographicEngine
+*/
+
+/*!
+ Creates a new empty session authentication frame builder.
+*/
+QKnxNetIpSessionAuthenticateProxy::SecureBuilder::SecureBuilder()
+ : d_ptr(new QKnxNetIpSessionAuthenticateBuilderPrivate)
+{}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QKnxNetIpSessionAuthenticateProxy::SecureBuilder::~SecureBuilder() = default;
+
+/*!
+ Sets the user ID of the KNXnet/IP session authentication frame to \a userId
+ and returns a reference to the builder.
+
+ \note A userId() with the value \c QKnxNetIp::SecureUserId::Reserved or
+ equal to or more than \c QKnxNetIp::SecureUserId::Invalid is considered
+ invalid according to the KNX application note AN159.
+*/
+QKnxNetIpSessionAuthenticateProxy::SecureBuilder &
+ QKnxNetIpSessionAuthenticateProxy::SecureBuilder::setUserId(QKnxNetIp::SecureUserId userId)
+{
+ d_ptr->m_id = userId;
+ return *this;
+}
+
+/*!
+ Creates and returns a KNXnet/IP session authentication frame.
+
+ The function computes the AES128 CCM message authentication code (MAC)
+ with the given user session password \a sessionPassword, the Curve25519
+ client public key \a clientPublicKey, the Curve25519 server public key
+ \a serverPublicKey and appends it to the newly created frame.
+
+ \note The returned frame may be invalid depending on the values used during
+ setup.
+
+ \sa isValid()
+*/
+QKnxNetIpFrame QKnxNetIpSessionAuthenticateProxy::SecureBuilder::create(
+ const QByteArray &sessionPassword,
+ const QKnxByteArray &clientPublicKey,
+ const QKnxByteArray &serverPublicKey) const
+{
+#if QT_CONFIG(opensslv11)
+ if (!QKnxNetIp::isSecureUserId(d_ptr->m_id))
+ return { QKnxNetIp::ServiceType::SessionAuthenticate };
+
+ auto builder = QKnxNetIpSessionAuthenticateProxy::builder();
+ auto frame = builder
+ .setUserId(d_ptr->m_id)
+ .setMessageAuthenticationCode(QKnxByteArray(16, 0x00)) // dummy MAC to get a proper header
+ .create();
+
+ auto userPasswordHash = QKnxCryptographicEngine::userPasswordHash(sessionPassword);
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(userPasswordHash, frame.
+ header(), d_ptr->m_id, QKnxCryptographicEngine::XOR(clientPublicKey, serverPublicKey));
+ mac = QKnxCryptographicEngine::encryptMessageAuthenticationCode(userPasswordHash, mac);
+
+ return builder.setMessageAuthenticationCode(mac).create();
+#else
+ Q_UNUSED(sessionPassword)
+ Q_UNUSED(clientPublicKey)
+ Q_UNUSED(serverPublicKey)
+ return { QKnxNetIp::ServiceType::SessionAuthenticate };
+#endif
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+QKnxNetIpSessionAuthenticateProxy::SecureBuilder::SecureBuilder(const SecureBuilder &other)
+ : d_ptr(other.d_ptr)
+{}
+
+/*!
+ Assigns the specified \a other to this object.
+*/
+QKnxNetIpSessionAuthenticateProxy::SecureBuilder &
+ QKnxNetIpSessionAuthenticateProxy::SecureBuilder::operator=(const SecureBuilder &other)
+{
+ d_ptr = other.d_ptr;
+ return *this;
+}
+
+/*!
+ Returns a builder object to create a KNXnet/IP session authentication frame.
+*/
+QKnxNetIpSessionAuthenticateProxy::SecureBuilder QKnxNetIpSessionAuthenticateProxy::secureBuilder()
+{
+ return QKnxNetIpSessionAuthenticateProxy::SecureBuilder();
+}
+
QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetipsessionauthenticate.h b/src/knx/netip/qknxnetipsessionauthenticate.h
index 97884fd..23d5d71 100644
--- a/src/knx/netip/qknxnetipsessionauthenticate.h
+++ b/src/knx/netip/qknxnetipsessionauthenticate.h
@@ -47,7 +47,7 @@ public:
bool isValid() const;
- quint8 userId() const;
+ QKnxNetIp::SecureUserId userId() const;
QKnxByteArray messageAuthenticationCode() const;
class Q_KNX_EXPORT Builder final
@@ -56,7 +56,7 @@ public:
Builder();
~Builder();
- Builder &setUserId(quint16 userId);
+ Builder &setUserId(QKnxNetIp::SecureUserId userId);
Builder &setMessageAuthenticationCode(const QKnxByteArray &data);
QKnxNetIpFrame create() const;
@@ -69,6 +69,26 @@ public:
};
static QKnxNetIpSessionAuthenticateProxy::Builder builder();
+ class Q_KNX_EXPORT SecureBuilder final
+ {
+ public:
+ SecureBuilder();
+ ~SecureBuilder();
+
+ SecureBuilder &setUserId(QKnxNetIp::SecureUserId userId);
+
+ QKnxNetIpFrame create(const QByteArray &sessionPassword,
+ const QKnxByteArray &clientPublicKey,
+ const QKnxByteArray &serverPublicKey) const;
+
+ SecureBuilder(const SecureBuilder &other);
+ SecureBuilder &operator=(const SecureBuilder &other);
+
+ private:
+ QSharedDataPointer<QKnxNetIpSessionAuthenticateBuilderPrivate> d_ptr;
+ };
+ static QKnxNetIpSessionAuthenticateProxy::SecureBuilder secureBuilder();
+
private:
const QKnxNetIpFrame &m_frame;
};
diff --git a/src/knx/netip/qknxnetipsessionrequest.cpp b/src/knx/netip/qknxnetipsessionrequest.cpp
index 2f28860..3f1ed6d 100644
--- a/src/knx/netip/qknxnetipsessionrequest.cpp
+++ b/src/knx/netip/qknxnetipsessionrequest.cpp
@@ -185,7 +185,7 @@ public:
~QKnxNetIpSessionRequestBuilderPrivate() = default;
QKnxNetIpHpai m_hpai;
- QKnxByteArray m_publicKey;
+ QKnxByteArray m_serverPublicKey;
};
/*!
@@ -220,7 +220,7 @@ QKnxNetIpSessionRequestProxy::Builder &
QKnxNetIpSessionRequestProxy::Builder &
QKnxNetIpSessionRequestProxy::Builder::setPublicKey(const QKnxByteArray &publicKey)
{
- d_ptr->m_publicKey = publicKey;
+ d_ptr->m_serverPublicKey = publicKey;
return *this;
}
@@ -234,8 +234,8 @@ QKnxNetIpSessionRequestProxy::Builder &
*/
QKnxNetIpFrame QKnxNetIpSessionRequestProxy::Builder::create() const
{
- if (d_ptr->m_hpai.isValid() && d_ptr->m_publicKey.size() == 32)
- return { QKnxNetIp::ServiceType::SessionRequest, d_ptr->m_hpai.bytes() + d_ptr->m_publicKey };
+ if (d_ptr->m_hpai.isValid() && d_ptr->m_serverPublicKey.size() == 32)
+ return { QKnxNetIp::ServiceType::SessionRequest, d_ptr->m_hpai.bytes() + d_ptr->m_serverPublicKey };
return { QKnxNetIp::ServiceType::SessionRequest };
}
diff --git a/src/knx/netip/qknxnetipsessionresponse.cpp b/src/knx/netip/qknxnetipsessionresponse.cpp
index 341aea3..a7f0e83 100644
--- a/src/knx/netip/qknxnetipsessionresponse.cpp
+++ b/src/knx/netip/qknxnetipsessionresponse.cpp
@@ -27,6 +27,7 @@
**
******************************************************************************/
+#include "qknxbuilderdata_p.h"
#include "qknxnetipsessionresponse.h"
QT_BEGIN_NAMESPACE
@@ -66,7 +67,7 @@ QT_BEGIN_NAMESPACE
return;
auto endPoint = proxy.controlEndpoint();
- auto publicKey = proxy.publicKey();
+ auto serverPublicKey = proxy.publicKey();
auto authenticationCode = proxy.messageAuthenticationCode();
\endcode
@@ -171,12 +172,12 @@ QKnxNetIpSessionResponseProxy::Builder QKnxNetIpSessionResponseProxy::builder()
The common way to create a session response frame is:
\code
- auto publicKey = ... // create the public key
+ auto serverPublicKey = ... // create the public key
auto auth = ... // create the full 128 bit CCM-MAC
auto netIpFrame = QKnxNetIpSessionResponseProxy::builder()
.setSecureSessionId(0x1976)
- .setPublicKey(publicKey)
+ .setPublicKey(serverPublicKey)
.setMessageAuthenticationCode(auth)
.create();
\endcode
@@ -184,17 +185,6 @@ QKnxNetIpSessionResponseProxy::Builder QKnxNetIpSessionResponseProxy::builder()
\sa QKnxCryptographicEngine
*/
-class QKnxNetIpSessionResponseBuilderPrivate : public QSharedData
-{
-public:
- QKnxNetIpSessionResponseBuilderPrivate() = default;
- ~QKnxNetIpSessionResponseBuilderPrivate() = default;
-
- qint32 m_id { -1 };
- QKnxByteArray m_publicKey;
- QKnxByteArray m_authCode;
-};
-
/*!
Creates a new empty session response builder object.
*/
@@ -219,14 +209,15 @@ QKnxNetIpSessionResponseProxy::Builder &
}
/*!
- Sets the public key of the KNXnet/IP session response frame to \a publicKey
- and returns a reference to the builder. The public key needs to be generated
- using the Curve25519 algorithm and has a fixed size of \c 32 bytes.
+ Sets the public key of the KNXnet/IP session response frame to
+ \a serverPublicKey and returns a reference to the builder. The public key
+ needs to be generated using the Curve25519 algorithm and has a fixed size
+ of \c 32 bytes.
*/
QKnxNetIpSessionResponseProxy::Builder &
- QKnxNetIpSessionResponseProxy::Builder::setPublicKey(const QKnxByteArray &publicKey)
+ QKnxNetIpSessionResponseProxy::Builder::setPublicKey(const QKnxByteArray &serverPublicKey)
{
- d_ptr->m_publicKey = publicKey;
+ d_ptr->m_serverPublicKey = serverPublicKey;
return *this;
}
@@ -252,11 +243,11 @@ QKnxNetIpSessionResponseProxy::Builder &
*/
QKnxNetIpFrame QKnxNetIpSessionResponseProxy::Builder::create() const
{
- if (d_ptr->m_id < 0 || d_ptr->m_publicKey.size() != 32 || d_ptr->m_authCode.size() != 16)
+ if (d_ptr->m_id < 0 || d_ptr->m_serverPublicKey.size() != 32 || d_ptr->m_authCode.size() != 16)
return { QKnxNetIp::ServiceType::SessionResponse };
return { QKnxNetIp::ServiceType::SessionResponse, QKnxUtils::QUint16::bytes(d_ptr->m_id)
- + d_ptr->m_publicKey + d_ptr->m_authCode };
+ + d_ptr->m_serverPublicKey + d_ptr->m_authCode };
}
/*!
@@ -276,4 +267,142 @@ QKnxNetIpSessionResponseProxy::Builder &
return *this;
}
+
+/*!
+ \class QKnxNetIpSessionResponseProxy::SecureBuilder
+
+ \inmodule QtKnx
+ \inheaderfile QKnxNetIpSessionResponseProxy
+
+ \brief The QKnxNetIpSessionResponseProxy::SecureBuilder class provides the
+ means to create a KNXnet/IP session response frame.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+
+ \note To use this class OpenSSL must be supported on your target system.
+
+ This frame will be sent by the KNXnet/IP secure server to the KNXnet/IP
+ secure client control endpoint in response to a received secure session
+ request frame.
+
+ The common way to create a session response frame is:
+
+ \code
+ auto serverPublicKey = ... // create the public key
+ auto auth = ... // create the full 128 bit CCM-MAC
+
+ auto netIpFrame = QKnxNetIpSessionResponseProxy::secureBuilder()
+ .setSecureSessionId(0x1976)
+ .setPublicKey(serverPublicKey)
+ .create();
+ \endcode
+
+ \sa QKnxCryptographicEngine
+*/
+
+/*!
+ Creates a new empty session response builder object.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder::SecureBuilder()
+ : d_ptr(new QKnxNetIpSessionResponseBuilderPrivate)
+{}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder::~SecureBuilder() = default;
+
+/*!
+ Sets the secure session ID of the KNXnet/IP session response frame to
+ \a sessionId and returns a reference to the builder.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder &
+ QKnxNetIpSessionResponseProxy::SecureBuilder::setSecureSessionId(quint16 sessionId)
+{
+ d_ptr->m_id = sessionId;
+ return *this;
+}
+
+/*!
+ Sets the public key of the KNXnet/IP session response frame to
+ \a serverPublicKey and returns a reference to the builder. The public key
+ needs to be generated using the Curve25519 algorithm and has a fixed size
+ of \c 32 bytes.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder &
+ QKnxNetIpSessionResponseProxy::SecureBuilder::setPublicKey(const QKnxByteArray &serverPublicKey)
+{
+ d_ptr->m_serverPublicKey = serverPublicKey;
+ return *this;
+}
+
+/*!
+ Creates and returns a KNXnet/IP session response frame.
+
+ The function computes the AES128 CCM message authentication code (MAC)
+ with the given device password \a devicePassword and the Curve25519 client
+ public key \a clientPublicKey and appends it to the newly created frame.
+
+ \note The returned frame may be invalid depending on the values used during
+ setup.
+
+ \sa isValid()
+*/
+QKnxNetIpFrame QKnxNetIpSessionResponseProxy::SecureBuilder::create(const QByteArray &devicePassword,
+ const QKnxByteArray &clientPublicKey) const
+{
+#if QT_CONFIG(opensslv11)
+ if (d_ptr->m_id < 0 || clientPublicKey.size() != 32 || d_ptr->m_serverPublicKey.size() != 32)
+ return { QKnxNetIp::ServiceType::SessionResponse };
+
+ auto deviceAuthenticationCode =
+ QKnxCryptographicEngine::deviceAuthenticationCodeHash(devicePassword);
+ auto XOR_X_Y = QKnxCryptographicEngine::XOR(clientPublicKey, d_ptr->m_serverPublicKey);
+
+ auto builder = QKnxNetIpSessionResponseProxy::builder();
+ auto frame = builder
+ .setSecureSessionId(d_ptr->m_id)
+ .setPublicKey(d_ptr->m_serverPublicKey)
+ .setMessageAuthenticationCode(QKnxByteArray(16, 0x00)) // dummy MAC to get a proper header
+ .create();
+
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(deviceAuthenticationCode,
+ frame.header(), d_ptr->m_id, XOR_X_Y);
+ mac = QKnxCryptographicEngine::encryptMessageAuthenticationCode(deviceAuthenticationCode, mac);
+
+ return builder.setMessageAuthenticationCode(mac).create();
+#else
+ Q_UNUSED(devicePassword)
+ Q_UNUSED(clientPublicKey)
+ return { QKnxNetIp::ServiceType::SessionResponse };
+#endif
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder::SecureBuilder(const SecureBuilder &other)
+ : d_ptr(other.d_ptr)
+{}
+
+/*!
+ Assigns the specified \a other to this object.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder &
+ QKnxNetIpSessionResponseProxy::SecureBuilder::operator=(const SecureBuilder &other)
+{
+ d_ptr = other.d_ptr;
+ return *this;
+}
+
+/*!
+ Returns a builder object to create a KNXnet/IP session response frame.
+*/
+QKnxNetIpSessionResponseProxy::SecureBuilder QKnxNetIpSessionResponseProxy::secureBuilder()
+{
+ return QKnxNetIpSessionResponseProxy::SecureBuilder();
+}
+
QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetipsessionresponse.h b/src/knx/netip/qknxnetipsessionresponse.h
index c2eecf6..c6a5545 100644
--- a/src/knx/netip/qknxnetipsessionresponse.h
+++ b/src/knx/netip/qknxnetipsessionresponse.h
@@ -59,7 +59,7 @@ public:
~Builder();
Builder &setSecureSessionId(quint16 sessionId);
- Builder &setPublicKey(const QKnxByteArray &publicKey);
+ Builder &setPublicKey(const QKnxByteArray &serverPublicKey);
Builder &setMessageAuthenticationCode(const QKnxByteArray &data);
QKnxNetIpFrame create() const;
@@ -72,6 +72,26 @@ public:
};
static QKnxNetIpSessionResponseProxy::Builder builder();
+ class Q_KNX_EXPORT SecureBuilder final
+ {
+ public:
+ SecureBuilder();
+ ~SecureBuilder();
+
+ SecureBuilder &setSecureSessionId(quint16 sessionId);
+ SecureBuilder &setPublicKey(const QKnxByteArray &serverPublicKey);
+
+ QKnxNetIpFrame create(const QByteArray &devicePassword,
+ const QKnxByteArray &clientPublicKey) const;
+
+ SecureBuilder(const SecureBuilder &other);
+ SecureBuilder &operator=(const SecureBuilder &other);
+
+ private:
+ QSharedDataPointer<QKnxNetIpSessionResponseBuilderPrivate> d_ptr;
+ };
+ static QKnxNetIpSessionResponseProxy::SecureBuilder secureBuilder();
+
private:
const QKnxNetIpFrame &m_frame;
};
diff --git a/src/knx/netip/qknxnetiptimernotify.cpp b/src/knx/netip/qknxnetiptimernotify.cpp
index 69dd56d..05415e1 100644
--- a/src/knx/netip/qknxnetiptimernotify.cpp
+++ b/src/knx/netip/qknxnetiptimernotify.cpp
@@ -27,6 +27,7 @@
**
******************************************************************************/
+#include "qknxbuilderdata_p.h"
#include "qknxnetiptimernotify.h"
#include "qknxutils.h"
@@ -192,18 +193,6 @@ QKnxNetIpTimerNotifyProxy::Builder QKnxNetIpTimerNotifyProxy::builder()
\sa QKnxCryptographicEngine
*/
-class QKnxNetIpTimerNotifyBuilderPrivate : public QSharedData
-{
-public:
- QKnxNetIpTimerNotifyBuilderPrivate() = default;
- ~QKnxNetIpTimerNotifyBuilderPrivate() = default;
-
- quint64 m_timer { Q_UINT48_MAX + 1 };
- QKnxByteArray m_serial;
- qint32 m_tag { -1 };
- QKnxByteArray m_authCode;
-};
-
/*!
Creates a new empty timer notify frame builder object.
*/
@@ -305,4 +294,162 @@ QKnxNetIpTimerNotifyProxy::Builder &
return *this;
}
+
+/*!
+ \class QKnxNetIpTimerNotifyProxy::SecureBuilder
+
+ \inmodule QtKnx
+ \inheaderfile QKnxNetIpTimerNotifyProxy
+
+ \brief The QKnxNetIpTimerNotifyProxy::SecureBuilder class provides the
+ means to create a KNXnet/IP timer notify frame.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+
+ \note To use this class OpenSSL must be supported on your target system.
+
+ This frame will be sent during secure KNXnet/IP multicast group
+ communication to keep the multicast group member's timer values
+ synchronized. The frame is therefore sent to the KNXnet/IP routing
+ endpoint on port \c 3671 of the configured routing multicast address.
+
+ The common way to create a timer notify frame is:
+
+ \code
+ auto sessionId = ...
+ auto backboneKey = ... // the backbone key used
+
+ auto netIpFrame = QKnxNetIpTimerNotifyProxy::builder()
+ .setTimerValue(15021976)
+ .setSerialNumber(QKnxByteArray::fromHex("0123456789AB"))
+ .setMessageTag(quint16(QRandomGenerator::global()->generate())
+ .create(backboneKey, sessionId);
+ \endcode
+
+ \sa QKnxCryptographicEngine
+*/
+
+/*!
+ Creates a new empty timer notify frame builder object.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder::SecureBuilder()
+ : d_ptr(new QKnxNetIpTimerNotifyBuilderPrivate)
+{}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder::~SecureBuilder() = default;
+
+/*!
+ Sets the timer value to \a timerValue and returns a reference to the
+ builder.
+
+ \note The size of a timer value is limited to 48 bits, so the maximum
+ number can be \c 281474976710655. Passing a larger value will result in
+ creating an invalid frame.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder &
+ QKnxNetIpTimerNotifyProxy::SecureBuilder::setTimerValue(quint48 timerValue)
+{
+ d_ptr->m_timer = timerValue;
+ return *this;
+}
+
+/*!
+ Sets the serial number to \a serialNumber and returns a reference to the
+ builder.
+
+ \note The serial number must contain exactly 6 bytes.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder &
+ QKnxNetIpTimerNotifyProxy::SecureBuilder::setSerialNumber(const QKnxByteArray &serialNumber)
+{
+ d_ptr->m_serial = serialNumber;
+ return *this;
+}
+
+/*!
+ Sets the message tag of the generic KNXnet/IP timer notify frame to \a tag
+ and returns a reference to the builder.
+
+ In case of a periodic or initial notify the tag contains a random value. In
+ case of an update notify this is the value of the outdated frame triggering
+ the update.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder &
+ QKnxNetIpTimerNotifyProxy::SecureBuilder::setMessageTag(quint16 tag)
+{
+ d_ptr->m_tag = tag;
+ return *this;
+}
+
+/*!
+ Creates and returns a KNXnet/IP timer notify frame.
+
+ The function computes the AES128 CCM message authentication code (MAC)
+ with the given backbone key \a backboneKey and the session ID \a ssid and
+ appends it to the newly created frame.
+
+ \note The returned frame may be invalid depending on the values used during
+ setup.
+
+ \sa isValid()
+*/
+QKnxNetIpFrame
+ QKnxNetIpTimerNotifyProxy::SecureBuilder::create(const QKnxByteArray &backboneKey, quint16 ssid) const
+{
+#if QT_CONFIG(opensslv11)
+ if (d_ptr->m_timer > Q_UINT48_MAX || d_ptr->m_serial.size() != 6 || d_ptr->m_tag < 0)
+ return { QKnxNetIp::ServiceType::TimerNotify };
+
+ auto builder = QKnxNetIpTimerNotifyProxy::builder();
+ auto frame = builder
+ .setTimerValue(d_ptr->m_timer)
+ .setSerialNumber(d_ptr->m_serial)
+ .setMessageTag(d_ptr->m_tag)
+ .setMessageAuthenticationCode(QKnxByteArray(16, 0x00)) // dummy MAC to get a proper header
+ .create();
+
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(backboneKey,
+ frame.header(), ssid, {}, d_ptr->m_timer, d_ptr->m_serial, d_ptr->m_tag);
+
+ mac = QKnxCryptographicEngine::encryptMessageAuthenticationCode(backboneKey, mac,
+ d_ptr->m_timer, d_ptr->m_serial, d_ptr->m_tag);
+
+ return builder.setMessageAuthenticationCode(mac).create();
+#else
+ Q_UNUSED(backboneKey)
+ Q_UNUSED(ssid)
+ return { QKnxNetIp::ServiceType::TimerNotify };
+#endif
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder::SecureBuilder(const SecureBuilder &other)
+ : d_ptr(other.d_ptr)
+{}
+
+/*!
+ Assigns the specified \a other to this object.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder &
+ QKnxNetIpTimerNotifyProxy::SecureBuilder::operator=(const SecureBuilder &other)
+{
+ d_ptr = other.d_ptr;
+ return *this;
+}
+
+/*!
+ Returns a secure builder object to create a KNXnet/IP timer notify frame.
+*/
+QKnxNetIpTimerNotifyProxy::SecureBuilder QKnxNetIpTimerNotifyProxy::secureBuilder()
+{
+ return QKnxNetIpTimerNotifyProxy::SecureBuilder();
+}
+
QT_END_NAMESPACE
diff --git a/src/knx/netip/qknxnetiptimernotify.h b/src/knx/netip/qknxnetiptimernotify.h
index 5b94acf..d862216 100644
--- a/src/knx/netip/qknxnetiptimernotify.h
+++ b/src/knx/netip/qknxnetiptimernotify.h
@@ -73,6 +73,26 @@ public:
};
static QKnxNetIpTimerNotifyProxy::Builder builder();
+ class Q_KNX_EXPORT SecureBuilder final
+ {
+ public:
+ SecureBuilder();
+ ~SecureBuilder();
+
+ SecureBuilder &setTimerValue(quint48 timerValue);
+ SecureBuilder &setSerialNumber(const QKnxByteArray &serialNumber);
+ SecureBuilder &setMessageTag(quint16 tag);
+
+ QKnxNetIpFrame create(const QKnxByteArray &backboneKey, quint16 sessionId) const;
+
+ SecureBuilder(const SecureBuilder &other);
+ SecureBuilder &operator=(const SecureBuilder &other);
+
+ private:
+ QSharedDataPointer<QKnxNetIpTimerNotifyBuilderPrivate> d_ptr;
+ };
+ static QKnxNetIpTimerNotifyProxy::SecureBuilder secureBuilder();
+
private:
const QKnxNetIpFrame &m_frame;
};
diff --git a/src/knx/netip/qknxnetiptunnel.cpp b/src/knx/netip/qknxnetiptunnel.cpp
index ba6279b..5f382f3 100644
--- a/src/knx/netip/qknxnetiptunnel.cpp
+++ b/src/knx/netip/qknxnetiptunnel.cpp
@@ -111,68 +111,55 @@ public:
, m_layer(l)
{}
- void process(const QKnxLinkLayerFrame &frame) override
- {
- Q_Q(QKnxNetIpTunnel);
- emit q->frameReceived(frame);
- }
+ void process(const QKnxLinkLayerFrame &frame) override;
+ void processConnectResponse(const QKnxNetIpFrame &frame) override;
+ void processTunnelingFeatureFrame(const QKnxNetIpFrame &frame) override;
- void processConnectResponse(const QKnxNetIpFrame &frame) override
- {
- QKnxNetIpConnectResponseProxy response(frame);
- if (response.status() == QKnxNetIp::Error::NoMoreUniqueConnections) {
- Q_ASSERT_X(false, "QKnxNetIpTunnelPrivate::process", "NoMoreUniqueConnections "
- "error handling not implemented yet.");
- // TODO: Maybe implement 03_08_04 Tunneling v01.05.03 AS.pdf, paragraph 3.3
- }
+private:
+ QKnxAddress m_address;
+ QKnxNetIp::TunnelLayer m_layer { QKnxNetIp::TunnelLayer::Unknown };
+};
- Q_Q(QKnxNetIpTunnel);
- if (q->state() != QKnxNetIpTunnel::Connected) {
- const auto &crd = response.responseData();
- m_address = QKnxNetIpCrdProxy(crd).individualAddress();
- }
- QKnxNetIpEndpointConnectionPrivate::processConnectResponse(frame);
+void QKnxNetIpTunnelPrivate::process(const QKnxLinkLayerFrame &frame)
+{
+ Q_Q(QKnxNetIpTunnel);
+ emit q->frameReceived(frame);
+}
+
+void QKnxNetIpTunnelPrivate::processConnectResponse(const QKnxNetIpFrame &frame)
+{
+ QKnxNetIpConnectResponseProxy response(frame);
+ if (response.status() == QKnxNetIp::Error::NoMoreUniqueConnections) {
+ Q_ASSERT_X(false, "QKnxNetIpTunnelPrivate::process", "NoMoreUniqueConnections "
+ "error handling not implemented yet.");
+ // TODO: Maybe implement 03_08_04 Tunneling v01.05.03 AS.pdf, paragraph 3.3
}
- void processTunnelingFeatureFrame(const QKnxNetIpFrame &frame) override
- {
- Q_Q(QKnxNetIpTunnel);
- if (frame.serviceType() == QKnxNetIp::ServiceType::TunnelingFeatureInfo) {
- const QKnxNetIpTunnelingFeatureInfoProxy proxy(frame);
- if (proxy.isValid()) {
- emit q->tunnelingFeatureInfoReceived(proxy.featureIdentifier(),
- proxy.featureValue());
- }
- } else if (frame.serviceType() == QKnxNetIp::ServiceType::TunnelingFeatureResponse) {
- const QKnxNetIpTunnelingFeatureResponseProxy proxy(frame);
- if (proxy.isValid()) {
- emit q->tunnelingFeatureResponseReceived(proxy.featureIdentifier(),
- proxy.returnCode(), proxy.featureValue());
- }
- }
+ Q_Q(QKnxNetIpTunnel);
+ if (q->state() != QKnxNetIpTunnel::Connected) {
+ const auto &crd = response.responseData();
+ m_address = QKnxNetIpCrdProxy(crd).individualAddress();
}
+ QKnxNetIpEndpointConnectionPrivate::processConnectResponse(frame);
+}
- void updateCri()
- {
- if (m_criAddress.isValid()) {
- setCri(QKnxNetIpCriProxy::builder()
- .setTunnelLayer(m_layer)
- .setIndividualAddress(m_criAddress)
- .create()
- );
- } else {
- setCri(QKnxNetIpCriProxy::builder()
- .setTunnelLayer(m_layer)
- .create()
- );
+void QKnxNetIpTunnelPrivate::processTunnelingFeatureFrame(const QKnxNetIpFrame &frame)
+{
+ Q_Q(QKnxNetIpTunnel);
+ if (frame.serviceType() == QKnxNetIp::ServiceType::TunnelingFeatureInfo) {
+ const QKnxNetIpTunnelingFeatureInfoProxy proxy(frame);
+ if (proxy.isValid()) {
+ emit q->tunnelingFeatureInfoReceived(proxy.featureIdentifier(),
+ proxy.featureValue());
+ }
+ } else if (frame.serviceType() == QKnxNetIp::ServiceType::TunnelingFeatureResponse) {
+ const QKnxNetIpTunnelingFeatureResponseProxy proxy(frame);
+ if (proxy.isValid()) {
+ emit q->tunnelingFeatureResponseReceived(proxy.featureIdentifier(),
+ proxy.returnCode(), proxy.featureValue());
}
}
-
-private:
- QKnxAddress m_address;
- QKnxAddress m_criAddress;
- QKnxNetIp::TunnelLayer m_layer { QKnxNetIp::TunnelLayer::Unknown };
-};
+}
/*!
Creates a tunnel connection with the parent \a parent.
@@ -220,22 +207,17 @@ QKnxAddress QKnxNetIpTunnel::individualAddress() const
return d_func()->m_address;
}
+#if QT_DEPRECATED_SINCE(5, 13)
/*!
- Sets the requested individual address of the KNXnet/IP client extended
- connect request information (CRI) structure to \a address.
+ \obsolete
- If the \a address is a valid individual address, a extended CRI is used
- to send the connect request. To reset the behavior in favor of a standard
- CRI pass an invalid \a address to the function.
+ Use \l QKnxNetIpSecureConfiguration::setIndividualAddress() instead.
*/
void QKnxNetIpTunnel::setIndividualAddress(const QKnxAddress &address)
{
- if (address.type() != QKnxAddress::Type::Individual)
- return;
-
- d_func()->m_criAddress = address;
- d_func()->updateCri();
+ d_func()->updateCri(address);
}
+#endif
/*!
Returns the layer used for the tunnel connection.
@@ -259,7 +241,7 @@ void QKnxNetIpTunnel::setTunnelLayer(QKnxNetIp::TunnelLayer layer)
return;
d_func()->m_layer = layer;
- d_func()->updateCri();
+ d_func()->updateCri(layer);
}
/*!
diff --git a/src/knx/netip/qknxnetiptunnel.h b/src/knx/netip/qknxnetiptunnel.h
index 9ecb526..d16b027 100644
--- a/src/knx/netip/qknxnetiptunnel.h
+++ b/src/knx/netip/qknxnetiptunnel.h
@@ -54,7 +54,9 @@ public:
QKnxNetIp::TunnelLayer layer, QObject *parent = nullptr);
QKnxAddress individualAddress() const;
- void setIndividualAddress(const QKnxAddress &address);
+#if QT_DEPRECATED_SINCE(5, 13)
+ QT_DEPRECATED void setIndividualAddress(const QKnxAddress &address);
+#endif
QKnxNetIp::TunnelLayer layer() const;
void setTunnelLayer(QKnxNetIp::TunnelLayer layer);
diff --git a/src/knx/qknxnamespace.h b/src/knx/qknxnamespace.h
index dd97434..a5bb53a 100644
--- a/src/knx/qknxnamespace.h
+++ b/src/knx/qknxnamespace.h
@@ -39,9 +39,10 @@ namespace QKnx
{
Q_KNX_EXPORT Q_NAMESPACE
+ // ### Qt6: make this 16 bit
enum class EmiType : quint8
{
- Unknown,
+ Unknown = 0x00,
EMI1 = 0x01,
EMI2 = 0x02,
cEMI = 0x04
diff --git a/src/knx/ssl/qknxcryptographicengine.cpp b/src/knx/ssl/qknxcryptographicengine.cpp
new file mode 100644
index 0000000..c4ab26f
--- /dev/null
+++ b/src/knx/ssl/qknxcryptographicengine.cpp
@@ -0,0 +1,494 @@
+/******************************************************************************
+**
+** 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 "qknxcryptographicengine.h"
+#include "qknxnetipsessionauthenticate.h"
+#include "qknxnetipsecurewrapper.h"
+#include "qknxnetipsessionrequest.h"
+#include "qknxnetipsessionresponse.h"
+#include "qknxnetipsessionstatus.h"
+#include "qknxnetiptimernotify.h"
+
+#include "private/qknxssl_p.h"
+
+#include <QtCore/qcryptographichash.h>
+#include <QtCore/qmutex.h>
+
+#include <QtNetwork/qpassworddigestor.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QKnxCryptographicEngine
+
+ \since 5.12
+ \inmodule QtKnx
+ \ingroup qtknx-general-classes
+
+ \brief The QKnxCryptographicEngine class provides the means to handle all
+ KNXnet/IP security related tasks.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+
+ \section2 Calculating Message Authentication Codes
+
+ The computeMessageAuthenticationCode() function can be used to compute a
+ message authentication code (MAC) for a KNXnet/IP secure frame. The fields
+ that are used to compute the MAC depend on the type of the frame, such as
+ \e {session response frame}, \e {session authentication frame}, or
+ \e {timer notify frame}.
+
+ The example code shows how to compute the MAC for the most common secure
+ frames:
+
+ \code
+ auto dummyMac = QKnxByteArray(16, 000); // dummy to get a valid header
+
+ // Session Response Frame
+
+ quint16 secureSessionIdentifier = 0x0001;
+ auto responseBuilder = QKnxNetIpSessionResponseProxy::builder();
+
+ // create an intermediate frame to fetch a valid frame header
+ auto netIpFrame = responseBuilder
+ .setSecureSessionId(secureSessionIdentifier)
+ .setPublicKey(serverPublicKey)
+ .setMessageAuthenticationCode(dummyMac)
+ .create();
+
+ auto deviceAuthenticationHash =
+ QKnxCryptographicEngine::deviceAuthenticationCodeHash({ "trustme" });
+ auto XOR_X_Y = QKnxCryptographicEngine::XOR(clientPublicKey.bytes(), serverPublicKey.bytes());
+
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(deviceAuthenticationHash,
+ netIpFrame.header(), secureSessionIdentifier, XOR_X_Y);
+
+ // create the final frame including the computed MAC
+ netIpFrame = responseBuilder.
+ .setMessageAuthenticationCode(mac)
+ .create();
+
+
+ // Session Authenticate Frame
+
+ auto authenticateBuilder = QKnxNetIpSessionAuthenticateProxy::builder()'
+
+ // create an intermediate frame to fetch a valid frame header
+ netIpFrame = authenticateBuilder
+ .setUserId(QKnxNetIp::SecureUserId::Management)
+ .setMessageAuthenticationCode(dummyMac)
+ .create();
+
+ auto passwordHash = QKnxCryptographicEngine::userPasswordHash({ "secret" });
+
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(passwordHash,
+ netIpFrame.header(), userId, XOR_X_Y);
+
+ // create the final frame including the computed MAC
+ netIpFrame = responseBuilder.
+ .setMessageAuthenticationCode(mac)
+ .create();
+
+
+ // Timer Notify Frame
+
+ quint48 timerValue = 211938428830917;
+ auto serialNumber = QKnxByteArray::fromHex("00fa12345678");
+ quint16 messageTag = quint16(QRandomGenerator::global()->generate();
+
+ auto timerNotifyBuilder = QKnxNetIpTimerNotifyProxy::builder();
+
+ // create an intermediate frame to fetch a valid frame header
+ netIpFrame = timerNotifyBuilder
+ .setTimerValue(timerValue)
+ .setSerialNumber(serialNumber)
+ .setMessageTag(messageTag)
+ .setMessageAuthenticationCode(dummyMac)
+ .create();
+
+ QKnxByteArray dummyPayload;
+ quint16 dummySession = 0x0000;
+ auto backboneKey = QKnxByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
+
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(backboneKey,
+ netIpFrame.header(), dummySession, dummyPayload, timerValue, serialNumber, messageTag);
+
+ // create the final frame including the computed MAC
+ netIpFrame = responseBuilder.
+ .setMessageAuthenticationCode(mac)
+ .create();
+ \endcode
+*/
+
+/*!
+ \internal
+ \fn QKnxCryptographicEngine::QKnxCryptographicEngine()
+*/
+
+/*!
+ \internal
+ \fn QKnxCryptographicEngine::~QKnxCryptographicEngine()
+*/
+
+/*!
+ Determines if cryptography support is available. Returns \c true on success;
+ \c false otherwise.
+*/
+bool QKnxCryptographicEngine::supportsCryptography()
+{
+ return QKnxSsl::supportsCryptography();
+}
+
+/*!
+ Returns the OpenSSL version number of the OpenSSL library if OpenSSL is
+ available and used to provide cryptographic support; or \c 0 in any other
+ case.
+*/
+long QKnxCryptographicEngine::sslLibraryVersionNumber()
+{
+ return QKnxSsl::sslLibraryVersionNumber();
+}
+
+/*!
+ Returns the session key calculated from the given private key
+ \a privateKey and the peer's public key \a peerPublicKey if OpenSSL is
+ available and no error occurs;
+ otherwise returns a \l {default-constructed value} which can be empty.
+*/
+QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxSecureKey &privateKey,
+ const QKnxSecureKey &peerPublicKey)
+{
+ return sessionKey(QKnxSecureKey::sharedSecret(privateKey, peerPublicKey));
+}
+
+/*!
+ \overload sessionKey()
+*/
+QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxByteArray &privateKey,
+ const QKnxByteArray &peerPublicKey)
+{
+ return sessionKey(QKnxSecureKey::sharedSecret(privateKey, peerPublicKey));
+}
+
+/*!
+ Returns the session key computed from the given secret \a sharedSecret.
+*/
+QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxByteArray &sharedSecret)
+{
+ if (sharedSecret.isEmpty())
+ return {};
+
+ return QKnxByteArray::fromByteArray(QCryptographicHash::hash(sharedSecret.toByteArray(),
+ QCryptographicHash::Sha256)).mid(0, 16);
+}
+
+/*!
+ Returns the password hash derived from the user chosen password \a password.
+
+ \note The salt used in the password-based key derivation function (PBKDF2)
+ is set to \e {user-password.1.secure.ip.knx.org}.
+*/
+QKnxByteArray QKnxCryptographicEngine::userPasswordHash(const QByteArray &password)
+{
+ const auto hash = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Algorithm::Sha256,
+ password, QByteArray("user-password.1.secure.ip.knx.org"), 0x10000, 16);
+ return QKnxByteArray::fromByteArray(hash);
+}
+
+/*!
+ Returns the keyring password hash derived from the user chosen password
+ \a password.
+
+ \note The salt used in the password-based key derivation function (PBKDF2)
+ is set to \e {1.keyring.ets.knx.org}.
+*/
+QKnxByteArray QKnxCryptographicEngine::keyringPasswordHash(const QByteArray &password)
+{
+ const auto hash = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Algorithm::Sha256,
+ password, "1.keyring.ets.knx.org", 0x10000, 16);
+ return QKnxByteArray::fromByteArray(hash);
+}
+
+/*!
+ Returns the device authentication code hash derived from the user chosen
+ password \a password.
+
+ \note The salt used in the password-based key derivation function (PBKDF2)
+ is set to \e {device-authentication-code.1.secure.ip.knx.org}.
+*/
+QKnxByteArray QKnxCryptographicEngine::deviceAuthenticationCodeHash(const QByteArray &password)
+{
+ const auto hash = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Algorithm::Sha256,
+ password, "device-authentication-code.1.secure.ip.knx.org", 0x10000, 16);
+ return QKnxByteArray::fromByteArray(hash);
+}
+
+/*!
+ Returns the hash of \a data using the \c Sha256 algorithm.
+*/
+QKnxByteArray QKnxCryptographicEngine::hashSha256(const QByteArray &data)
+{
+ return QKnxByteArray::fromByteArray(QCryptographicHash::hash(data, QCryptographicHash::Sha256));
+}
+
+/*!
+ Performs a byte-wise XOR operation on the arguments \a left and \a right.
+ If the arguments are not equal in size, the function uses only the shorter
+ array for the operation. If \a adjust is set to \c true, the arrays are made
+ equal by padding them with \c 0x00 bytes.
+*/
+QKnxByteArray QKnxCryptographicEngine::XOR(const QKnxByteArray &left, const QKnxByteArray &right,
+ bool adjust)
+{
+ QKnxByteArray result(adjust ? qMax(left.size(), right.size()) : qMin(left.size(), right.size()),
+ Qt::Uninitialized);
+ for (int i = result.size() - 1; i >= 0; --i)
+ result.set(i, left.value(i, 0x00) ^ right.value(i, 0x00));
+ return result;
+}
+
+namespace QKnxPrivate
+{
+ static const quint8 iv[16] { 0x00 };
+
+ static QKnxByteArray b0(quint48 sequence, const QKnxByteArray &serial, quint16 tag, quint16 len)
+ {
+ return QKnxUtils::QUint48::bytes(sequence) + serial + QKnxUtils::QUint16::bytes(tag)
+ + QKnxUtils::QUint16::bytes(len);
+ }
+
+ static QKnxByteArray ctr0(quint48 sequence, const QKnxByteArray &serial, quint16 tag)
+ {
+ return QKnxPrivate::b0(sequence, serial, tag, 0xff00);
+ }
+
+ static QKnxByteArray processMAC(const QKnxByteArray &key, const QKnxByteArray &mac,
+ quint48 sequenceNumber, const QKnxByteArray &serialNumber, quint16 messageTag)
+ {
+ if (key.isEmpty() || mac.isEmpty())
+ return {};
+
+ auto Ctr0 = QKnxPrivate::ctr0(sequenceNumber,
+ (serialNumber.isEmpty() ? QKnxByteArray(6, 0x00) : serialNumber), messageTag);
+
+ return QKnxCryptographicEngine::XOR(QKnxSsl::doCrypt(key, { iv, 16 }, Ctr0,
+ QKnxSsl::Encrypt).right(16), mac);
+ }
+
+ static QKnxByteArray processPayload(const QKnxByteArray &key, const QKnxByteArray &payload,
+ quint48 sequenceNumber, const QKnxByteArray &serialNumber, quint16 messageTag)
+ {
+ if (key.isEmpty() || payload.isEmpty())
+ return {};
+
+ auto Ctr0 = QKnxPrivate::ctr0(sequenceNumber,
+ (serialNumber.isEmpty() ? QKnxByteArray(6, 0x00) : serialNumber), messageTag);
+
+ QKnxByteArray ctrArray;
+ for (int i = 0; i < (payload.size() + 15) >> 4; ++i) {
+ Ctr0.set(15, Ctr0.at(15) + 1);
+ ctrArray += QKnxSsl::doCrypt(key, { iv, 16 }, Ctr0, QKnxSsl::Encrypt).right(16);
+ }
+
+ return QKnxCryptographicEngine::XOR(ctrArray, payload, false);
+ }
+}
+
+/*!
+ Computes a message authentication code (MAC) using the given \a key,
+ \a header, and \a id for the given \a data. Returns an array of bytes that
+ represent the computed MAC or an empty byte array in case of an error.
+
+ \note The \a sequenceNumber, \a serialNumber, and \a messageTag values
+ are required to compute a valid MAC for KNXnet/IP secure wrapper frames.
+ For all other types of secure frames, the possibly given values are ignored
+ and \c 0 is used instead. For timer notify frames, \e {default-constructed}
+ values are used instead of the \a id and \a data values.
+
+ For an example of using this function, see
+ \l {Calculating Message Authentication Codes}.
+*/
+QKnxByteArray QKnxCryptographicEngine::computeMessageAuthenticationCode(const QKnxByteArray &key,
+ const QKnxNetIpFrameHeader &header, quint16 id, const QKnxByteArray &data,
+ quint48 sequenceNumber, const QKnxByteArray &serialNumber, quint16 messageTag)
+{
+ if (key.isEmpty() || !header.isValid())
+ return {};
+
+ auto sn = (serialNumber.isEmpty() ? QKnxByteArray(6, 0x00) : serialNumber);
+
+ QKnxByteArray B0, B;
+ if (header.serviceType() == QKnxNetIp::ServiceType::SecureWrapper) {
+ if (data.isEmpty())
+ return {};
+
+ const auto A = header.bytes() + QKnxUtils::QUint16::bytes(id);
+ B0 = QKnxPrivate::b0(sequenceNumber, sn, messageTag, quint16(data.size()));
+ B = B0 + QKnxUtils::QUint16::bytes(quint16(A.size())) + A + data;
+ } else if (header.serviceType() == QKnxNetIp::ServiceType::SessionResponse
+ || header.serviceType() == QKnxNetIp::ServiceType::SessionAuthenticate) {
+ if (data.isEmpty())
+ return {};
+
+ const auto A = header.bytes() + QKnxUtils::QUint16::bytes(id);
+ B0 = QKnxPrivate::b0(sequenceNumber, sn, messageTag, 0);
+ B = B0 + QKnxUtils::QUint16::bytes(quint16(A.size() + data.size())) + A + data;
+ } else if (header.serviceType() == QKnxNetIp::ServiceType::TimerNotify) {
+ const auto A = header.bytes();
+ B0 = QKnxPrivate::b0(sequenceNumber, sn, messageTag, 0);
+ B = B0 + QKnxUtils::QUint16::bytes(quint16(A.size())) + A;
+ }
+
+ if (B.isEmpty())
+ return {};
+ B.resize(B.size() + (16 - (B.size() % 16))); // pad to multiple of 16
+
+ return QKnxSsl::doCrypt(key, { QKnxPrivate::iv, 16 }, B, QKnxSsl::Encrypt).right(16);
+}
+
+/*!
+ Decrypts the given \a data with \a key and the initial vector \a iv. Returns
+ an array of bytes that represents the decrypted data.
+*/
+QKnxByteArray QKnxCryptographicEngine::decrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data)
+{
+ return QKnxSsl::doCrypt(key, iv, data, QKnxSsl::Decrypt);
+}
+
+/*!
+ Encrypts the given \a data with \a key and the initial vector \a iv. Returns
+ an array of bytes that represents the encrypted data.
+*/
+QKnxByteArray QKnxCryptographicEngine::encrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data)
+{
+ return QKnxSsl::doCrypt(key, iv, data, QKnxSsl::Encrypt);
+}
+
+/*!
+ Encrypts the given KNXnet/IP frame \a frame with the given key \a key,
+ sequence number \a sequenceNumber, serial number \a serialNumber, and
+ message tag \a messageTag. Returns an array of bytes that represent the
+ encrypted frame or an empty byte array in case of an error or invalid
+ KNXnet/IP frame \a frame.
+*/
+QKnxByteArray QKnxCryptographicEngine::encryptSecureWrapperPayload(const QKnxByteArray &key,
+ const QKnxNetIpFrame &frame, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
+ quint16 messageTag)
+{
+ if (!frame.isValid())
+ return {};
+ return QKnxPrivate::processPayload(key, frame.bytes(), sequenceNumber, serialNumber, messageTag);
+}
+
+/*!
+ Decrypts the given KNXnet/IP frame \a frame with the given key \a key,
+ sequence number \a sequenceNumber, serial number \a serialNumber, and
+ message tag \a messageTag. Returns an array of bytes that represent the
+ decrypted frame or an empty byte array in case of an error.
+*/
+QKnxByteArray QKnxCryptographicEngine::decryptSecureWrapperPayload(const QKnxByteArray &key,
+ const QKnxByteArray &frame, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
+ quint16 messageTag)
+{
+ return QKnxPrivate::processPayload(key, frame, sequenceNumber, serialNumber, messageTag);
+}
+
+/*!
+ Encrypts the given message authentication code (MAC) \a mac with the given
+ key \a key, sequence number \a sequenceNumber, serial number \a serialNumber,
+ and message tag \a messageTag. Returns an array of bytes that represent
+ the encrypted MAC or an empty byte array in case of an error.
+
+ \note The \a sequenceNumber, \a serialNumber and \a messageTag are mandatory
+ to properly encrypt the MAC for KNXnet/IP secure wrapper frame, for all other
+ secure frames the default value of \c 0 can be used.
+*/
+QKnxByteArray QKnxCryptographicEngine::encryptMessageAuthenticationCode(const QKnxByteArray &key,
+ const QKnxByteArray &mac, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
+ quint16 messageTag)
+{
+ return QKnxPrivate::processMAC(key, mac, sequenceNumber, serialNumber, messageTag);
+}
+
+/*!
+ Decrypts the given message authentication code (MAC) \a mac with the given
+ key \a key, sequence number \a sequenceNumber, serial number \a serialNumber,
+ and message tag \a messageTag. Returns an array of bytes that represent
+ the decrypted MAC or an empty byte array in case of an error.
+
+ \note The \a sequenceNumber, \a serialNumber and \a messageTag values are
+ required to properly decrypt the MAC for KNXnet/IP secure wrapper frame.
+ For all other secure frames, the default value of \c 0 can be used.
+
+*/
+QKnxByteArray QKnxCryptographicEngine::decryptMessageAuthenticationCode(const QKnxByteArray &key,
+ const QKnxByteArray &mac, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
+ quint16 messageTag)
+{
+ return QKnxPrivate::processMAC(key, mac, sequenceNumber, serialNumber, messageTag);
+}
+
+/*!
+ Decodes and decrypts a tool key \a toolKey that was stored in an ETS
+ keyring (*.knxkeys) file with the given password hash \a passwordHash and
+ created hash \a createdHash.
+
+ Returns an array of bytes that represent the decrypted tool key or an empty
+ byte array in case of an error.
+*/
+QKnxByteArray QKnxCryptographicEngine::decodeAndDecryptToolKey(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash, const QByteArray &toolKey)
+{
+ auto base64 = QKnxByteArray::fromByteArray(QByteArray::fromBase64(toolKey));
+ return QKnxSsl::doCrypt(passwordHash, createdHash, base64, QKnxSsl::Decrypt);
+}
+
+/*!
+ Decodes and decrypts a password \a password that was stored in an ETS
+ keyring (*.knxkeys) file with the given password hash \a passwordHash and
+ created hash \a createdHash.
+
+ Returns an array of bytes that represent the decrypted password or an empty
+ byte array in case of an error.
+*/
+QKnxByteArray QKnxCryptographicEngine::decodeAndDecryptPassword(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash, const QByteArray &password)
+{
+ const auto base64 = QKnxByteArray::fromByteArray(QByteArray::fromBase64(password));
+ const auto decoded = QKnxSsl::doCrypt(passwordHash, createdHash, base64, QKnxSsl::Decrypt);
+
+ quint8 paddedSize = decoded.value(decoded.size() - 1);
+ return decoded.mid(8, decoded.size() - paddedSize - 8);
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxcurve25519.h b/src/knx/ssl/qknxcryptographicengine.h
index 2d34e8b..eb48816 100644
--- a/src/knx/ssl/qknxcurve25519.h
+++ b/src/knx/ssl/qknxcryptographicengine.h
@@ -1,6 +1,6 @@
/******************************************************************************
**
-** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtKnx module.
@@ -27,88 +27,51 @@
**
******************************************************************************/
-#ifndef QKNXCURVE25519_H
-#define QKNXCURVE25519_H
+#ifndef QKNXCRYPTOGRAPHICENGINE_H
+#define QKNXCRYPTOGRAPHICENGINE_H
#include <QtCore/qshareddata.h>
#include <QtKnx/qknxbytearray.h>
-#include <QtKnx/qtknxglobal.h>
#include <QtKnx/qknxnetipframe.h>
+#include <QtKnx/qknxsecurekey.h>
QT_BEGIN_NAMESPACE
-class QKnxCurve25519KeyData;
-class QKnxCurve25519PrivateKey;
-
-class Q_KNX_EXPORT QKnxCurve25519PublicKey final
-{
-public:
- QKnxCurve25519PublicKey();
- ~QKnxCurve25519PublicKey();
-
- QKnxCurve25519PublicKey(const QKnxCurve25519PrivateKey &key);
-
- bool isNull() const;
- bool isValid() const;
-
- QKnxByteArray bytes() const;
- static QKnxCurve25519PublicKey fromBytes(const QKnxByteArray &data, quint16 index = 0);
-
- QKnxCurve25519PublicKey(const QKnxCurve25519PublicKey &other);
- QKnxCurve25519PublicKey &operator=(const QKnxCurve25519PublicKey &other);
-
-private:
- friend class QKnxCryptographicEngine;
- QSharedDataPointer<QKnxCurve25519KeyData> d_ptr;
-};
-
-class Q_KNX_EXPORT QKnxCurve25519PrivateKey final
-{
-public:
- QKnxCurve25519PrivateKey();
- ~QKnxCurve25519PrivateKey();
-
- bool isNull() const;
- bool isValid() const;
-
- QKnxByteArray bytes() const;
- static QKnxCurve25519PrivateKey fromBytes(const QKnxByteArray &data, quint16 index = 0);
-
- QKnxCurve25519PrivateKey(const QKnxCurve25519PrivateKey &other);
- QKnxCurve25519PrivateKey &operator=(const QKnxCurve25519PrivateKey &other);
-
-private:
- friend class QKnxCurve25519PublicKey;
- friend class QKnxCryptographicEngine;
- QSharedDataPointer<QKnxCurve25519KeyData> d_ptr;
-};
-
class Q_KNX_EXPORT QKnxCryptographicEngine final
{
public:
QKnxCryptographicEngine() = delete;
~QKnxCryptographicEngine() = default;
- static QKnxByteArray sharedSecret(const QKnxCurve25519PublicKey &pub,
- const QKnxCurve25519PrivateKey &priv);
+ static bool supportsCryptography();
+ static long sslLibraryVersionNumber();
+ static QKnxByteArray sessionKey(const QKnxSecureKey &privateKey,
+ const QKnxSecureKey &peerPublicKey);
+ static QKnxByteArray sessionKey(const QKnxByteArray &privateKey,
+ const QKnxByteArray &peerPublicKey);
static QKnxByteArray sessionKey(const QKnxByteArray &sharedSecret);
- static QKnxByteArray sessionKey(const QKnxCurve25519PublicKey &pub,
- const QKnxCurve25519PrivateKey &priv);
static QKnxByteArray userPasswordHash(const QByteArray &password);
+ static QKnxByteArray keyringPasswordHash(const QByteArray &password);
static QKnxByteArray deviceAuthenticationCodeHash(const QByteArray &password);
+ static QKnxByteArray hashSha256(const QByteArray &data);
static QKnxByteArray XOR(const QKnxByteArray &l, const QKnxByteArray &r, bool adjust = true);
- static QKnxByteArray calculateMessageAuthenticationCode(const QKnxByteArray &key,
- const QKnxNetIpFrameHeader &header,
- quint16 id,
- const QKnxByteArray &data,
- quint48 sequenceNumber = 0,
- const QKnxByteArray &serialNumber = {},
- quint16 messageTag = 0);
+ static QKnxByteArray computeMessageAuthenticationCode(const QKnxByteArray &key,
+ const QKnxNetIpFrameHeader &header,
+ quint16 id,
+ const QKnxByteArray &data,
+ quint48 sequenceNumber = 0,
+ const QKnxByteArray &serialNumber = {},
+ quint16 messageTag = 0);
+
+ static QKnxByteArray decrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data);
+ static QKnxByteArray encrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data);
static QKnxByteArray encryptSecureWrapperPayload(const QKnxByteArray &key,
const QKnxNetIpFrame &frame,
@@ -134,8 +97,12 @@ public:
const QKnxByteArray &serialNumber = {},
quint16 messageTag = 0);
- static QKnxByteArray pkcs5Pbkdf2HmacSha256(const QByteArray &password, const QKnxByteArray &salt,
- qint32 iterations, quint8 derivedKeyLength);
+ static QKnxByteArray decodeAndDecryptToolKey(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash,
+ const QByteArray &toolKey);
+ static QKnxByteArray decodeAndDecryptPassword(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash,
+ const QByteArray &password);
};
QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxcurve25519.cpp b/src/knx/ssl/qknxcurve25519.cpp
deleted file mode 100644
index 0881875..0000000
--- a/src/knx/ssl/qknxcurve25519.cpp
+++ /dev/null
@@ -1,819 +0,0 @@
-/******************************************************************************
-**
-** 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 "qknxcurve25519.h"
-#include "qknxcryptographicdata_p.h"
-
-#include "qknxnetipsessionauthenticate.h"
-#include "qknxnetipsecurewrapper.h"
-#include "qknxnetipsessionrequest.h"
-#include "qknxnetipsessionresponse.h"
-#include "qknxnetipsessionstatus.h"
-#include "qknxnetiptimernotify.h"
-
-#include <QtCore/qcryptographichash.h>
-#include <QMutex>
-
-QT_BEGIN_NAMESPACE
-
-bool QKnxOpenSsl::s_libraryLoaded = false;
-bool QKnxOpenSsl::s_libraryEnabled = false;
-
-Q_GLOBAL_STATIC(QKnxOpenSsl, qt_QKnxOpenSsl)
-Q_GLOBAL_STATIC_WITH_ARGS(QMutex, qt_knxOpenSslInitMutex, (QMutex::Recursive))
-
-/*!
- \internal
-*/
-bool QKnxOpenSsl::supportsSsl()
-{
- return ensureLibraryLoaded();
-}
-
-/*!
- \internal
-*/
-long QKnxOpenSsl::sslLibraryVersionNumber()
-{
- if (!supportsSsl())
- return 0;
- return q_OpenSSL_version_num();
-}
-
-/*!
- \internal
-*/
-QString QKnxOpenSsl::sslLibraryVersionString()
-{
- if (!supportsSsl())
- return QString();
-
- const char *versionString = q_OpenSSL_version(OPENSSL_VERSION);
- if (!versionString)
- return QString();
-
- return QString::fromLatin1(versionString);
-}
-
-/*!
- \internal
-*/
-long QKnxOpenSsl::sslLibraryBuildVersionNumber()
-{
- return OPENSSL_VERSION_NUMBER;
-}
-
-
-QString QKnxOpenSsl::sslLibraryBuildVersionString()
-{
- return QStringLiteral(OPENSSL_VERSION_TEXT);
-}
-
-/*!
- \internal
-*/
-bool QKnxOpenSsl::ensureLibraryLoaded()
-{
- if (!q_resolveOpenSslSymbols())
- return false;
-
- const QMutexLocker locker(qt_knxOpenSslInitMutex);
-
- if (!s_libraryLoaded) {
- s_libraryLoaded = true;
-
- // Initialize OpenSSL.
- if (q_OPENSSL_init_ssl(0, nullptr) != 1)
- return false;
- q_SSL_load_error_strings();
- q_OpenSSL_add_all_algorithms();
-
- // Initialize OpenSSL's random seed.
- if (!q_RAND_status()) {
- qWarning("Random number generator not seeded, disabling SSL support");
- return false;
- }
-
- if (q_EVP_PKEY_type(NID_X25519) == NID_undef) {
- qWarning("The X25519 algorithm is not supported, disabling SSL support");
- return false;
- }
- s_libraryEnabled = true;
- }
- return s_libraryEnabled;
-}
-
-
-/*!
- \inmodule QtKnx
- \class QKnxCurve25519PublicKey
-
- \since 5.12
- \ingroup qtknx-general-classes
-
- \brief The QKnxCurve25519PublicKey class represents the elliptic-curve
- public key to be used with the elliptic curve Diffie-Hellman (ECDH) key
- agreement scheme.
-
- This class is part of the Qt KNX module and currently available as a
- Technology Preview, and therefore the API and functionality provided
- by the class may be subject to change at any time without prior notice.
-*/
-
-/*!
- Constructs an empty invalid public key.
-*/
-QKnxCurve25519PublicKey::QKnxCurve25519PublicKey()
- : d_ptr(new QKnxCurve25519KeyData)
-{}
-
-/*!
- Destroys the public key and releases all allocated resources.
-*/
-QKnxCurve25519PublicKey::~QKnxCurve25519PublicKey() = default;
-
-/*!
- Creates a new public key with the given private key \a key.
-*/
-QKnxCurve25519PublicKey::QKnxCurve25519PublicKey(const QKnxCurve25519PrivateKey &key)
- : d_ptr(new QKnxCurve25519KeyData)
-{
- if (!key.d_ptr->m_evpPKey)
- return;
-
- if (!qt_QKnxOpenSsl->supportsSsl())
- return;
-
- q_EVP_PKEY_up_ref(key.d_ptr->m_evpPKey);
- d_ptr->m_evpPKey = key.d_ptr->m_evpPKey;
-}
-
-/*!
- Returns \c true if this is a default constructed public key; otherwise
- returns \c false. A public key is considered null if it contains no
- initialized values.
-*/
-bool QKnxCurve25519PublicKey::isNull() const
-{
- return d_ptr->m_evpPKey == nullptr;
-}
-
-/*!
- Returns \c true if OpenSSL is available and if the public key contains
- initialized values, otherwise returns \c false.
-*/
-bool QKnxCurve25519PublicKey::isValid() const
-{
- return qt_QKnxOpenSsl->supportsSsl() && !isNull();
-}
-
-/*!
- Returns an array of bytes that represent the Curve25519 raw public key.
-*/
-QKnxByteArray QKnxCurve25519PublicKey::bytes() const
-{
- if (!isValid())
- return {};
-
- size_t len = 32;
- QKnxByteArray ba(int(len), 0);
- if (q_EVP_PKEY_get_raw_public_key(d_ptr->m_evpPKey, ba.data(), &len) > 0)
- return ba; // preferred way
-
- ba.resize(q_i2d_PUBKEY(d_ptr->m_evpPKey, nullptr));
- auto tmp = ba.data();
- q_i2d_PUBKEY(d_ptr->m_evpPKey, &tmp);
- return ba.right(32);
-}
-
-/*!
- Constructs the public key from the byte array \a data starting at position
- \a index inside the array if OpenSSL is available and no error occurs;
- otherwise returns a \e {default-constructed key} which can be invalid.
-*/
-QKnxCurve25519PublicKey QKnxCurve25519PublicKey::fromBytes(const QKnxByteArray &data, quint16 index)
-{
- auto ba = data.mid(index, 32);
- if (ba.size() < 32)
- return {};
-
- if (!qt_QKnxOpenSsl->supportsSsl())
- return {};
-
- QKnxCurve25519PublicKey key;
- key.d_ptr->m_evpPKey = q_EVP_PKEY_new_raw_public_key(NID_X25519, nullptr, ba.constData(),
- ba.size()); // preferred way
- if (key.d_ptr->m_evpPKey)
- return key;
-
- key.d_ptr->m_evpPKey = q_EVP_PKEY_new();
- if (q_EVP_PKEY_set_type(key.d_ptr->m_evpPKey, NID_X25519) <= 0)
- return {};
-
- if (q_EVP_PKEY_set1_tls_encodedpoint(key.d_ptr->m_evpPKey, ba.constData(), ba.size()) <= 0)
- return {};
-
- return key;
-}
-
-/*!
- Constructs a copy of \a other.
-*/
-QKnxCurve25519PublicKey::QKnxCurve25519PublicKey(const QKnxCurve25519PublicKey &other)
- : d_ptr(other.d_ptr)
-{}
-
-/*!
- Assigns the specified \a other to this object.
-*/
-QKnxCurve25519PublicKey &QKnxCurve25519PublicKey::operator=(const QKnxCurve25519PublicKey &other)
-{
- d_ptr = other.d_ptr;
- return *this;
-}
-
-
-/*!
- \class QKnxCurve25519PrivateKey
-
- \inmodule QtKnx
-
- \since 5.12
- \ingroup qtknx-general-classes
-
- \brief The QKnxCurve25519PrivateKey class represents the elliptic-curve
- private key to be used with the elliptic curve Diffie-Hellman (ECDH) key
- agreement scheme.
-
- This class is part of the Qt KNX module and currently available as a
- Technology Preview, and therefore the API and functionality provided
- by the class may be subject to change at any time without prior notice.
-*/
-
-/*!
- Constructs a valid private key if OpenSSL is available and no error occurs.
-*/
-QKnxCurve25519PrivateKey::QKnxCurve25519PrivateKey()
- : d_ptr(new QKnxCurve25519KeyData)
-{
- if (!qt_QKnxOpenSsl->supportsSsl())
- return;
-
- if (auto *pctx = q_EVP_PKEY_CTX_new_id(NID_X25519, nullptr)) {
- q_EVP_PKEY_keygen_init(pctx);
- q_EVP_PKEY_keygen(pctx, &d_ptr->m_evpPKey);
- q_EVP_PKEY_CTX_free(pctx);
- }
-}
-
-/*!
- Destroys the private key and releases all allocated resources.
-*/
-QKnxCurve25519PrivateKey::~QKnxCurve25519PrivateKey() = default;
-
-/*!
- Returns \c true if this is a default constructed private key; otherwise
- returns \c false. A private key is considered null if it contains no
- initialized values.
-*/
-bool QKnxCurve25519PrivateKey::isNull() const
-{
- return d_ptr->m_evpPKey == nullptr;
-}
-
-/*!
- Returns \c true if OpenSSL is available and if the private key contains
- initialized values, otherwise returns \c false.
-*/
-bool QKnxCurve25519PrivateKey::isValid() const
-{
- return qt_QKnxOpenSsl->supportsSsl() && !isNull();
-}
-
-/*!
- Returns an array of bytes that represent the Curve25519 raw private key.
-*/
-QKnxByteArray QKnxCurve25519PrivateKey::bytes() const
-{
- if (!isValid())
- return {};
-
- size_t len = 32;
- QKnxByteArray ba(int(len), 0);
- if (q_EVP_PKEY_get_raw_private_key(d_ptr->m_evpPKey, ba.data(), &len) <= 0)
- return {}; // preferred, no other way possible
- return ba;
-}
-
-/*!
- Constructs the private key from the byte array \a data starting at position
- \a index inside the array if OpenSSL is available and no error occurs;
- otherwise returns a \e {default-constructed key} which can be invalid.
-*/
-QKnxCurve25519PrivateKey QKnxCurve25519PrivateKey::fromBytes(const QKnxByteArray &data, quint16 index)
-{
- auto ba = data.mid(index, 32);
- if (!qt_QKnxOpenSsl->supportsSsl() || ba.size() < 32)
- return {};
-
- QKnxCurve25519PrivateKey key;
- key.d_ptr->m_evpPKey = q_EVP_PKEY_new_raw_private_key(NID_X25519, nullptr, ba.constData(),
- ba.size()); // preferred way
- if (key.d_ptr->m_evpPKey)
- return key;
-
- static const auto pkcs8 = QKnxByteArray::fromHex("302e020100300506032b656e04220420");
- auto tmp = pkcs8 + ba; // PKCS #8 is a standard syntax for storing private key information
-
- BIO *bio = nullptr;
- if ((bio = q_BIO_new_mem_buf(reinterpret_cast<void *> (tmp.data()), tmp.size())))
- key.d_ptr->m_evpPKey = q_d2i_PrivateKey_bio(bio, nullptr);
- q_BIO_free(bio);
-
- return key;
-}
-
-/*!
- Constructs a copy of \a other.
-*/
-QKnxCurve25519PrivateKey::QKnxCurve25519PrivateKey(const QKnxCurve25519PrivateKey &other)
- : d_ptr(other.d_ptr)
-{}
-
-/*!
- Assigns the specified \a other to this object.
-*/
-QKnxCurve25519PrivateKey &QKnxCurve25519PrivateKey::operator=(const QKnxCurve25519PrivateKey &other)
-{
- d_ptr = other.d_ptr;
- return *this;
-}
-
-
-/*!
- \class QKnxCryptographicEngine
-
- \since 5.12
- \inmodule QtKnx
- \ingroup qtknx-general-classes
-
- \brief The QKnxCryptographicEngine class provides the means to handle all
- KNXnet/IP security related tasks.
-
- This class is part of the Qt KNX module and currently available as a
- Technology Preview, and therefore the API and functionality provided
- by the class may be subject to change at any time without prior notice.
-
- \section2 Calculating Message Authentication Codes
-
- The calculateMessageAuthenticationCode() function can be used to compute a
- message authentication code (MAC) for a KNXnet/IP secure frame. The fields
- that are used to calculate the MAC depend on the type of the frame, such
- as \e {session response frame}, \e {session authentication frame}, or
- \e {timer notify frame}.
-
- The example code shows how to calculate the MAC for the most common secure
- frames:
-
- \code
- auto dummyMac = QKnxByteArray(16, 000); // dummy to get a valid header
-
- // Session Response Frame
-
- quint16 secureSessionIdentifier = 0x0001;
- auto responseBuilder = QKnxNetIpSessionResponseProxy::builder();
-
- // create an intermediate frame to fetch a valid frame header
- auto netIpFrame = responseBuilder
- .setSecureSessionId(secureSessionIdentifier)
- .setPublicKey(serverPublicKey)
- .setMessageAuthenticationCode(dummyMac)
- .create();
-
- auto deviceAuthenticationHash =
- QKnxCryptographicEngine::deviceAuthenticationCodeHash({ "trustme" });
- auto XOR_X_Y = QKnxCryptographicEngine::XOR(clientPublicKey.bytes(), serverPublicKey.bytes());
-
- auto mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(deviceAuthenticationHash,
- netIpFrame.header(), secureSessionIdentifier, XOR_X_Y);
-
- // create the final frame including the calculated MAC
- netIpFrame = responseBuilder.
- .setMessageAuthenticationCode(mac)
- .create();
-
-
- // Session Authenticate Frame
-
- quint16 userId = 0x0001; // management level access
- auto authenticateBuilder = QKnxNetIpSessionAuthenticateProxy::builder()'
-
- // create an intermediate frame to fetch a valid frame header
- netIpFrame = authenticateBuilder
- .setUserId(userId)
- .setMessageAuthenticationCode(dummyMac)
- .create();
-
- auto passwordHash = QKnxCryptographicEngine::userPasswordHash({ "secret" });
-
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(passwordHash,
- netIpFrame.header(), userId, XOR_X_Y);
-
- // create the final frame including the calculated MAC
- netIpFrame = responseBuilder.
- .setMessageAuthenticationCode(mac)
- .create();
-
-
- // Timer Notify Frame
-
- quint48 timerValue = 211938428830917;
- auto serialNumber = QKnxByteArray::fromHex("00fa12345678");
- quint16 messageTag = quint16(QRandomGenerator::global()->generate();
-
- auto timerNotifyBuilder = QKnxNetIpTimerNotifyProxy::builder();
-
- // create an intermediate frame to fetch a valid frame header
- netIpFrame = timerNotifyBuilder
- .setTimerValue(timerValue)
- .setSerialNumber(serialNumber)
- .setMessageTag(messageTag)
- .setMessageAuthenticationCode(dummyMac)
- .create();
-
- QKnxByteArray dummyPayload;
- quint16 dummySession = 0x0000;
- auto backboneKey = QKnxByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
-
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(backboneKey,
- netIpFrame.header(), dummySession, dummyPayload, timerValue, serialNumber, messageTag);
-
- // create the final frame including the calculated MAC
- netIpFrame = responseBuilder.
- .setMessageAuthenticationCode(mac)
- .create();
- \endcode
-*/
-
-/*!
- \internal
- \fn QKnxCryptographicEngine::QKnxCryptographicEngine()
-*/
-
-/*!
- \internal
- \fn QKnxCryptographicEngine::~QKnxCryptographicEngine()
-*/
-
-/*!
- Derives and returns the shared secret from the given public peer key \a pub
- and the private key \a priv if OpenSSL is available and no error occurs;
- otherwise returns a \l {default-constructed value} which can be empty.
-*/
-QKnxByteArray QKnxCryptographicEngine::sharedSecret(const QKnxCurve25519PublicKey &pub,
- const QKnxCurve25519PrivateKey &priv)
-{
- if (pub.isNull() || priv.isNull())
- return {};
-
- if (!qt_QKnxOpenSsl->supportsSsl())
- return {};
-
- auto evpPKeyCtx = q_EVP_PKEY_CTX_new(priv.d_ptr->m_evpPKey, nullptr);
- if (!evpPKeyCtx)
- return {};
-
- struct ScopedFree final
- {
- ScopedFree(EVP_PKEY_CTX *key) : m_evpPKeyCtx(key) {}
- ~ScopedFree() { q_EVP_PKEY_CTX_free(m_evpPKeyCtx); }
- EVP_PKEY_CTX *m_evpPKeyCtx = nullptr;
- } _ { evpPKeyCtx };
-
- if (q_EVP_PKEY_derive_init(evpPKeyCtx) <= 0)
- return {};
-
- if (q_EVP_PKEY_derive_set_peer(evpPKeyCtx, pub.d_ptr->m_evpPKey) <= 0)
- return {};
-
- size_t keylen = 0;
- if (q_EVP_PKEY_derive(evpPKeyCtx, nullptr, &keylen) <= 0)
- return {};
-
- QKnxByteArray ba(int(keylen), 0);
- if (q_EVP_PKEY_derive(evpPKeyCtx, ba.data(), &keylen) <= 0)
- return {};
-
- return ba;
-}
-
-/*!
- Returns the session key calculated from the given secret \a sharedSecret.
-*/
-QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxByteArray &sharedSecret)
-{
- if (sharedSecret.isEmpty())
- return {};
-
- return QKnxByteArray::fromByteArray(QCryptographicHash::hash(sharedSecret.toByteArray(),
- QCryptographicHash::Sha256)).mid(0, 16);
-}
-
-/*!
- Returns the session key calculated from the given peer public key \a pub
- and the private key \a priv.
-*/
-QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxCurve25519PublicKey &pub,
- const QKnxCurve25519PrivateKey &priv)
-{
- return sessionKey(sharedSecret(pub, priv));
-}
-
-/*!
- Returns the password hash derived from the user chosen password \a password.
-
- \note The salt used in the Password-Based Key Derivation Function (PBKDF2)
- function is set to \e {user-password.1.secure.ip.knx.org}.
-
- \sa pkcs5Pbkdf2HmacSha256()
-*/
-QKnxByteArray QKnxCryptographicEngine::userPasswordHash(const QByteArray &password)
-{
- return pkcs5Pbkdf2HmacSha256(password,
- QKnxByteArray("user-password.1.secure.ip.knx.org", 33), 0x10000, 16);
-}
-
-/*!
- Returns the device authentication code hash derived from the user chosen
- password \a password.
-
- \note The salt used in the Password-Based Key Derivation Function (PBKDF2)
- function is set to \e {device-authentication-code.1.secure.ip.knx.org}.
-
- \sa pkcs5Pbkdf2HmacSha256()
-*/
-QKnxByteArray QKnxCryptographicEngine::deviceAuthenticationCodeHash(const QByteArray &password)
-{
- return pkcs5Pbkdf2HmacSha256(password,
- QKnxByteArray("device-authentication-code.1.secure.ip.knx.org", 46), 0x10000, 16);
-}
-
-/*!
- Performs a bytewise XOR operation on the arguments \a left and \a right.
- If the arguments are not equal in size, the function uses only the shorter
- array for the operation. If \a adjust is set to \c true, the arrays are made
- equal by padding them with \c 0x00 bytes.
-*/
-QKnxByteArray QKnxCryptographicEngine::XOR(const QKnxByteArray &left, const QKnxByteArray &right,
- bool adjust)
-{
- QKnxByteArray result(adjust ? qMax(left.size(), right.size()) : qMin(left.size(), right.size()),
- Qt::Uninitialized);
- for (int i = result.size() - 1; i >= 0; --i)
- result.set(i, left.value(i, 0x00) ^ right.value(i, 0x00));
- return result;
-}
-
-namespace QKnxPrivate
-{
- static QKnxByteArray b0(quint48 sequence, const QKnxByteArray &serial, quint16 tag, quint16 len)
- {
- return QKnxUtils::QUint48::bytes(sequence) + serial + QKnxUtils::QUint16::bytes(tag)
- + QKnxUtils::QUint16::bytes(len);
- }
-
- static QKnxByteArray ctr0(quint48 sequence, const QKnxByteArray &serial, quint16 tag)
- {
- return QKnxPrivate::b0(sequence, serial, tag, 0xff00);
- }
-
- static QKnxByteArray encrypt(const QKnxByteArray &key, const QKnxByteArray &data)
- {
- QSharedPointer<EVP_CIPHER_CTX> ctxPtr(q_EVP_CIPHER_CTX_new(), q_EVP_CIPHER_CTX_free);
- if (ctxPtr.isNull())
- return {};
-
- const auto ctx = ctxPtr.data();
- const auto c = q_EVP_aes_128_cbc();
- if (q_EVP_CipherInit_ex(ctx, c, nullptr, nullptr, nullptr, 0x01) <= 0)
- return {};
-
- if (q_EVP_CIPHER_CTX_set_padding(ctx, 0) <= 0)
- return {};
-
- Q_ASSERT(q_EVP_CIPHER_CTX_iv_length(ctx) == 16);
- Q_ASSERT(q_EVP_CIPHER_CTX_key_length(ctx) == 16);
-
- static const quint8 iv[16] { 0x00 };
- if (q_EVP_CipherInit_ex(ctx, nullptr, nullptr, key.constData(), iv, 0x01) <= 0)
- return {};
-
- int outl, offset = 0;
- QKnxByteArray out(data.size() + q_EVP_CIPHER_block_size(c), 0x00);
- if (q_EVP_CipherUpdate(ctx, out.data(), &outl, data.constData(), data.size()) <= 0)
- return {};
- offset += outl;
-
- if (q_EVP_CipherFinal_ex(ctx, out.data() + offset, &outl) <= 0)
- return {};
- offset += outl;
-
- return out.mid(offset - 16, 16);
- }
-
- static QKnxByteArray processMAC(const QKnxByteArray &key, const QKnxByteArray &mac,
- quint48 sequenceNumber, const QKnxByteArray &serialNumber, quint16 messageTag)
- {
- if (key.isEmpty() || mac.isEmpty())
- return {};
-
- auto Ctr0 = QKnxPrivate::ctr0(sequenceNumber,
- (serialNumber.isEmpty() ? QKnxByteArray(6, 0x00) : serialNumber), messageTag);
-
- return QKnxCryptographicEngine::XOR(QKnxPrivate::encrypt(key, Ctr0), mac);
- }
-
- static QKnxByteArray processPayload(const QKnxByteArray &key, const QKnxByteArray &payload,
- quint48 sequenceNumber, const QKnxByteArray &serialNumber, quint16 messageTag)
- {
- if (key.isEmpty() || payload.isEmpty())
- return {};
-
- auto Ctr0 = QKnxPrivate::ctr0(sequenceNumber,
- (serialNumber.isEmpty() ? QKnxByteArray(6, 0x00) : serialNumber), messageTag);
-
- QKnxByteArray ctrArray;
- for (int i = 0; i < (payload.size() + 15) >> 4; ++i) {
- Ctr0.set(15, Ctr0.at(15) + 1);
- ctrArray += QKnxPrivate::encrypt(key, Ctr0);
- }
-
- return QKnxCryptographicEngine::XOR(ctrArray, payload, false);
- }
-}
-
-/*!
- Computes a message authentication code (MAC) using the given \a key,
- \a header, and \a id for the given \a data. Returns an array of bytes that
- represent the calculated MAC or an empty byte array in case of an error.
-
- \note The \a sequenceNumber, \a serialNumber, and \a messageTag values
- are required to calculate a valid MAC for KNXnet/IP secure wrapper frames.
- For all other types of secure frames, the possibly given values are ignored
- and \c 0 is used instead. For timer notify frames, \e {default-constructed}
- values are used instead of the \a id and \a data values.
-
- For an example of using this function, see
- \l {Calculating Message Authentication Codes}.
-*/
-QKnxByteArray QKnxCryptographicEngine::calculateMessageAuthenticationCode(const QKnxByteArray &key,
- const QKnxNetIpFrameHeader &header, quint16 id, const QKnxByteArray &data,
- quint48 sequenceNumber, const QKnxByteArray &serialNumber, quint16 messageTag)
-{
- if (key.isEmpty() || !header.isValid())
- return {};
-
- auto sn = (serialNumber.isEmpty() ? QKnxByteArray(6, 0x00) : serialNumber);
-
- QKnxByteArray B0, B;
- if (header.serviceType() == QKnxNetIp::ServiceType::SecureWrapper) {
- if (data.isEmpty())
- return {};
-
- const auto A = header.bytes() + QKnxUtils::QUint16::bytes(id);
- B0 = QKnxPrivate::b0(sequenceNumber, sn, messageTag, data.size());
- B = B0 + QKnxUtils::QUint16::bytes(A.size()) + A + data;
- } else if (header.serviceType() == QKnxNetIp::ServiceType::SessionResponse
- || header.serviceType() == QKnxNetIp::ServiceType::SessionAuthenticate) {
- if (data.isEmpty())
- return {};
-
- const auto A = header.bytes() + QKnxUtils::QUint16::bytes(id);
- B0 = QKnxPrivate::b0(sequenceNumber, sn, messageTag, 0);
- B = B0 + QKnxUtils::QUint16::bytes(A.size() + data.size()) + A + data;
- } else if (header.serviceType() == QKnxNetIp::ServiceType::TimerNotify) {
- const auto A = header.bytes();
- B0 = QKnxPrivate::b0(sequenceNumber, sn, messageTag, 0);
- B = B0 + QKnxUtils::QUint16::bytes(A.size()) + A;
- }
-
- if (B.isEmpty())
- return {};
- B.resize(B.size() + (16 - (B.size() % 16))); // pad to multiple of 16
-
- return QKnxPrivate::encrypt(key, B);
-}
-
-/*!
- Encrypts the given KNXnet/IP frame \a frame with the given key \a key,
- sequence number \a sequenceNumber, serial number \a serialNumber, and
- message tag \a messageTag. Returns an array of bytes that represent the
- encrypted frame or an empty byte array in case of an error or invalid
- KNXnet/IP frame \a frame.
-*/
-QKnxByteArray QKnxCryptographicEngine::encryptSecureWrapperPayload(const QKnxByteArray &key,
- const QKnxNetIpFrame &frame, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
- quint16 messageTag)
-{
- if (!frame.isValid())
- return {};
- return QKnxPrivate::processPayload(key, frame.bytes(), sequenceNumber, serialNumber, messageTag);
-}
-
-/*!
- Decrypts the given KNXnet/IP frame \a frame with the given key \a key,
- sequence number \a sequenceNumber, serial number \a serialNumber, and
- message tag \a messageTag. Returns an array of bytes that represent the
- decrypted frame or an empty byte array in case of an error.
-*/
-QKnxByteArray QKnxCryptographicEngine::decryptSecureWrapperPayload(const QKnxByteArray &key,
- const QKnxByteArray &frame, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
- quint16 messageTag)
-{
- return QKnxPrivate::processPayload(key, frame, sequenceNumber, serialNumber, messageTag);
-}
-
-/*!
- Encrypts the given message authentication code (MAC) \a mac with the given
- key \a key, sequence number \a sequenceNumber, serial number \a serialNumber,
- and message tag \a messageTag. Returns an array of bytes that represent
- the encrypted MAC or an empty byte array in case of an error.
-
- \note The \a sequenceNumber, \a serialNumber and \a messageTag are mandatory
- to properly encrypt the MAC for KNXnet/IP secure wrapper frame, for all other
- secure frames the default value of \c 0 can be used.
-*/
-QKnxByteArray QKnxCryptographicEngine::encryptMessageAuthenticationCode(const QKnxByteArray &key,
- const QKnxByteArray &mac, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
- quint16 messageTag)
-{
- return QKnxPrivate::processMAC(key, mac, sequenceNumber, serialNumber, messageTag);
-}
-
-/*!
- Decrypts the given message authentication code (MAC) \a mac with the given
- key \a key, sequence number \a sequenceNumber, serial number \a serialNumber,
- and message tag \a messageTag. Returns an array of bytes that represent
- the decrypted MAC or an empty byte array in case of an error.
-
- \note The \a sequenceNumber, \a serialNumber and \a messageTag values are
- required to properly decrypt the MAC for KNXnet/IP secure wrapper frame.
- For all other secure frames, the default value of \c 0 can be used.
-
-*/
-QKnxByteArray QKnxCryptographicEngine::decryptMessageAuthenticationCode(const QKnxByteArray &key,
- const QKnxByteArray &mac, quint48 sequenceNumber, const QKnxByteArray &serialNumber,
- quint16 messageTag)
-{
- return QKnxPrivate::processMAC(key, mac, sequenceNumber, serialNumber, messageTag);
-}
-
-/*!
- Returns the hash code derived from the user chosen password \a password,
- with the given \a salt and \a iterations.
- The value of \a derivedKeyLength should be in the range \c 0 to \c 32.
-*/
-QKnxByteArray QKnxCryptographicEngine::pkcs5Pbkdf2HmacSha256(const QByteArray &password,
- const QKnxByteArray &salt, qint32 iterations, quint8 derivedKeyLength)
-{
- if (derivedKeyLength > 32)
- return {};
-
- if (!qt_QKnxOpenSsl->supportsSsl())
- return {};
-
- QKnxByteArray out(derivedKeyLength, 0x00);
- if (q_PKCS5_PBKDF2_HMAC(password.constData(), password.size(), salt.constData(), salt.size(),
- iterations, q_EVP_sha256(), out.size(), out.data()) <= 0) {
- return {};
- }
- return out;
-}
-
-QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxkeyring.cpp b/src/knx/ssl/qknxkeyring.cpp
new file mode 100644
index 0000000..ff31b7b
--- /dev/null
+++ b/src/knx/ssl/qknxkeyring.cpp
@@ -0,0 +1,515 @@
+/******************************************************************************
+**
+** 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 "qknxcryptographicengine.h"
+#include "private/qknxkeyring_p.h"
+#include "private/qknxssl_p.h"
+
+#include <QtCore/qcryptographichash.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qxmlstream.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+ \inmodule QtKnx
+ \namespace QKnx::Ets::Keyring
+*/
+
+namespace QKnx { namespace Ets { namespace Keyring {
+
+namespace QKnxPrivate
+{
+ void writeBytes(QByteArray *dest, const QStringRef &source)
+ {
+ dest->append(quint8(source.size()));
+ dest->append(source.toString().toUtf8());
+ }
+
+ void writeBytes(QByteArray *dest, const QByteArray &source)
+ {
+ dest->append(quint8(source.size()));
+ dest->append(source);
+ }
+
+ static void readElement(QXmlStreamReader *reader, QByteArray *bytes)
+ {
+ bytes->append(0x01);
+ QKnxPrivate::writeBytes(bytes, reader->name());
+
+ auto attributes = reader->attributes();
+ std::sort(attributes.begin(), attributes.end(),
+ [](const QXmlStreamAttribute &lhs, const QXmlStreamAttribute &rhs) {
+ return lhs.name() < rhs.name();
+ });
+
+ for (auto attribute : qAsConst(attributes)) {
+ QKnxPrivate::writeBytes(bytes, attribute.name());
+ QKnxPrivate::writeBytes(bytes, attribute.value());
+ }
+
+ while (!reader->atEnd()) {
+ auto token = reader->readNext();
+ if (token == QXmlStreamReader::StartElement)
+ QKnxPrivate::readElement(reader, bytes);
+ if (token == QXmlStreamReader::EndElement)
+ bytes->append(0x02);
+ }
+ }
+
+ static bool fetchAttr(const QXmlStreamAttributes &attributes, const QString &attrName,
+ QStringRef *value, QXmlStreamReader *reader)
+ {
+ if (!value || !reader)
+ return false;
+
+ *value = attributes.value(attrName);
+ if (value->isNull())
+ reader->raiseError(QKnxKeyring::tr("Invalid or empty attribute '%1'.").arg(attrName));
+ return !reader->hasError();
+ }
+
+ static bool setString(const QString &name, const QStringRef &attr, int maxSize,
+ QString *field, QXmlStreamReader *reader, bool pedantic)
+ {
+ if (!reader || !field)
+ return false;
+
+ if (pedantic && attr.size() > maxSize) {
+ reader->raiseError(QKnxKeyring::tr("Pedantic error: Invalid value for attribute '%1', "
+ "maximum length is %2 characters, got: '%3'.").arg(name).arg(maxSize).arg(attr.size()));
+ } else {
+ *field = attr.toString();
+ }
+ return !reader->hasError();
+ }
+
+ bool setString(const QString &name, const QStringRef &attr, const QStringList &list,
+ QString *field, QXmlStreamReader *reader, bool pedantic)
+ {
+ if (!reader || !field)
+ return false;
+
+ if (pedantic && !list.contains(attr.toString())) {
+ reader->raiseError(QKnxKeyring::tr("Pedantic error: Invalid value for attribute '%1', "
+ "expected '%2', got: '%3'.").arg(name, list.join(QLatin1String(", "))));
+ } else {
+ *field = attr.toString();
+ }
+ return !reader->hasError();
+ }
+}
+
+const char oneBlockBase64[29] = "^[A-Za-z0-9\\+/]{21}[AQgw]==$";
+// const char twoBlockBase64[40] = "^[A-Za-z0-9\\+/]{42}[AEIMQUYcgkosw048]=$";
+
+const char multicast[128] = "^2(2[4-9]|3[0-9])\\.((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|"
+ "[0-9])\\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$";
+
+const char individualAddress[74] = "^((1[0-5]|[0-9])\\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]"
+ "[0-9]|[0-9])$";
+
+bool QKnxBackbone::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Backbone")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("MulticastAddress"), &attr, reader))
+ return false;
+ MulticastAddress = attr.toString();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Latency"), &attr, reader))
+ return false;
+ Latency = attr.toUShort();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Key"), &attr, reader))
+ return false;
+ Key = attr.toUtf8();
+
+ if (pedantic) {
+ if (!QRegularExpression(QLatin1String(multicast)).match(MulticastAddress).hasMatch()) {
+ reader->raiseError(tr("The 'MulticastAddress' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(MulticastAddress));
+ return false;
+ }
+
+ if (Latency > 8000) {
+ reader->raiseError(tr("The 'Latency' attribute is invalid. The MaxInclusive "
+ "constraint failed, got: '%1', MaxInclusive: '8000'.").arg(Latency));
+ return false;
+ }
+
+ if (!QRegularExpression(QLatin1String(oneBlockBase64)).match(attr).hasMatch()) {
+ reader->raiseError(tr("The 'Key' attribute is invalid. The Pattern constraint "
+ "failed, got: '%1'.").arg(QString::fromUtf8(Key)));
+ return false;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Backbone>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxInterface::QKnxGroup::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Group")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Address"), &attr, reader))
+ return false;
+ Address = attr.toUShort();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Senders"), &attr, reader))
+ return false;
+ Senders = attr.toString().split(QLatin1Char(' ')).toVector();
+ if (pedantic) {
+ QRegularExpression regExp;
+ regExp.setPattern(QLatin1String(individualAddress));
+ for (auto sender : qAsConst(Senders)) {
+ if (!regExp.match(sender).hasMatch()) {
+ reader->raiseError(tr("The 'Senders' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(sender));
+ return false;
+ }
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Group>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxInterface::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Interface")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attribute
+ if (!QKnxPrivate::fetchAttr(attrs, QLatin1String("Type"), &attr, reader))
+ return false;
+ if (!QKnxPrivate::setString(QLatin1String("Type"), attr, {
+ QStringLiteral("Backbone"),
+ QStringLiteral("Tunneling"),
+ QStringLiteral("USB")
+ }, &Type, reader, pedantic)) return false;
+
+ // optional attributes, TODO: pedantic
+ Host = attrs.value(QStringLiteral("Host")).toString();
+ IndividualAddress = attrs.value(QStringLiteral("IndividualAddress")).toString();
+ UserID = attrs.value(QStringLiteral("UserID")).toUShort();
+ Password = attrs.value(QStringLiteral("Password")).toUtf8();
+ Authentication = attrs.value(QStringLiteral("Authentication")).toUtf8();
+
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Group")) {
+ QKnxInterface::QKnxGroup group;
+ if (!group.parseElement(reader, pedantic))
+ return false;
+ Group.append(group);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("Interface"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Interface>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxGroupAddresses::QKnxGroup::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Group")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Address"), &attr, reader))
+ return false;
+ Address = attr.toUShort();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Key"), &attr, reader))
+ return false;
+ Key = attr.toUtf8();
+ if (pedantic && !QRegularExpression(QLatin1String(oneBlockBase64)).match(attr).hasMatch()) {
+ reader->raiseError(tr("The 'Key' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(QString::fromUtf8(Key)));
+ return false;
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Group>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxGroupAddresses::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("GroupAddresses")) {
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Group")) {
+ QKnxGroupAddresses::QKnxGroup group;
+ if (!group.parseElement(reader, pedantic))
+ return false;
+ Group.append(group);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("GroupAddresses"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <GroupAddresses>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError() && Group.size() >= 1;
+}
+
+bool QKnxDevice::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Device")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attribute
+ if (!QKnxPrivate::fetchAttr(attrs, QLatin1String("IndividualAddress"), &attr, reader))
+ return false;
+ IndividualAddress = attr.toString();
+ if (pedantic && !QRegularExpression(QLatin1String(individualAddress))
+ .match(IndividualAddress).hasMatch()) {
+ reader->raiseError(tr("The 'IndividualAddress' attribute is invalid. The "
+ "Pattern constraint failed, got: '%1'.").arg(IndividualAddress));
+ return false;
+ }
+
+ // optional attributes, TODO: pedantic
+ ToolKey = attrs.value(QStringLiteral("ToolKey")).toUtf8();
+ SequenceNumber = attrs.value(QStringLiteral("SequenceNumber")).toULongLong();
+ ManagementPassword = attrs.value(QStringLiteral("ManagementPassword")).toUtf8();
+ Authentication = attrs.value(QStringLiteral("Authentication")).toUtf8();
+ } else {
+ reader->raiseError(tr("Expected element <Device>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxDevices::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Devices")) {
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Device")) {
+ QKnxDevice device;
+ if (!device.parseElement(reader, pedantic))
+ return false;
+ Device.append(device);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("Devices"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Devices>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError() && Device.size() >= 1;
+}
+
+bool QKnxKeyring::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->readNextStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Keyring")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QLatin1String("Project"), &attr, reader))
+ return false;
+ if (!QKnxPrivate::setString(QLatin1String("Project"), attr, 255, &Project, reader, pedantic))
+ return false;
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("CreatedBy"), &attr, reader))
+ return false;
+ CreatedBy = attr.toString();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Created"), &attr, reader))
+ return false;
+ Created = attr.toString();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Signature"), &attr, reader))
+ return false;
+ Signature = attr.toUtf8();
+ if (pedantic) {
+ if (!QRegularExpression(QLatin1String(oneBlockBase64)).match(attr).hasMatch()) {
+ reader->raiseError(tr("The 'Signature' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(attr));
+ return false;
+ }
+ }
+
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Backbone")) {
+ if (pedantic && Backbone.size() >= 1) {
+ reader->raiseError(tr("Pedantic error: Encountered element <Backbone> "
+ "more than once."));
+ return false;
+ }
+ QKnxBackbone backbone;
+ if (!backbone.parseElement(reader, pedantic))
+ return false;
+ Backbone.append(backbone);
+ } else if (reader->name() == QStringLiteral("Interface")) {
+ QKnxInterface interface;
+ if (!interface.parseElement(reader, pedantic))
+ return false;
+ Interface.append(interface);
+ } else if (reader->name() == QStringLiteral("GroupAddresses")) {
+ if (pedantic && GroupAddresses.size() >= 1) {
+ reader->raiseError(tr("Pedantic error: Encountered element "
+ "<GroupAddresses> more than once."));
+ return false;
+ }
+ QKnxGroupAddresses groupAddresses;
+ if (!groupAddresses.parseElement(reader, pedantic))
+ return false;
+ GroupAddresses.append(groupAddresses);
+ } else if (reader->name() == QStringLiteral("Devices")) {
+ if (pedantic && Devices.size() >= 1) {
+ reader->raiseError(tr("Pedantic error: Encountered element "
+ "<Devices> more than once."));
+ return false;
+ }
+ QKnxDevices devices;
+ if (!devices.parseElement(reader, pedantic))
+ return false;
+ Devices.append(devices);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("Keyring"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Keyring>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxKeyring::validate(QXmlStreamReader *reader, const QKnxByteArray &pwHash) const
+{
+ if (!reader || !reader->readNextStartElement())
+ return false;
+
+ if (reader->name() != QStringLiteral("Keyring"))
+ return false;
+
+ QByteArray bytes;
+ bytes.append(0x01);
+
+ QKnxPrivate::writeBytes(&bytes, reader->name());
+
+ auto attributes = reader->attributes();
+ if (attributes.isEmpty())
+ return false;
+
+ auto signature = attributes.value({}, QStringLiteral("Signature")).toUtf8();
+ if (signature.isEmpty())
+ return false;
+
+ std::sort(attributes.begin(), attributes.end(),
+ [](const QXmlStreamAttribute &lhs, const QXmlStreamAttribute &rhs) {
+ return lhs.name() < rhs.name();
+ });
+
+ for (auto attribute : attributes) {
+ if (attribute.name() == QStringLiteral("xmlns"))
+ continue;
+ if (attribute.name() == QStringLiteral("Signature"))
+ continue;
+
+ QKnxPrivate::writeBytes(&bytes, attribute.name());
+ QKnxPrivate::writeBytes(&bytes, attribute.value());
+ }
+
+ if (reader->readNextStartElement())
+ QKnxPrivate::readElement(reader, &bytes);
+ QKnxPrivate::writeBytes(&bytes, pwHash.toByteArray().toBase64());
+
+ return QKnxCryptographicEngine::hashSha256(bytes).left(16)
+ == QKnxByteArray::fromByteArray(QByteArray::fromBase64(signature));
+}
+
+}}} // QKnx::Ets::Keyring
+
+QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxkeyring_p.h b/src/knx/ssl/qknxkeyring_p.h
new file mode 100644
index 0000000..752131b
--- /dev/null
+++ b/src/knx/ssl/qknxkeyring_p.h
@@ -0,0 +1,160 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef QKNXKEYRING_P_H
+#define QKNXKEYRING_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/qcoreapplication.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qxmlstream.h>
+
+#include <QtKnx/qtknxglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QKnx { namespace Ets { namespace Keyring {
+
+struct Q_KNX_EXPORT QKnxBackbone
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxBackbone)
+
+public:
+ QString MulticastAddress; // mandatory, pattern 2(2[4-9]|3[0-9])\.((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
+ quint16 Latency; // mandatory, min. value 0, 8000 value max.
+ QByteArray Key; // mandatory, Base64 encoded, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxInterface
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxInterface)
+
+public:
+ QString Type; // mandatory, Backbone, Tunneling, USB
+
+ QString Host; // optional
+ QString IndividualAddress; // optional, pattern ((1[0-5]|[0-9])\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
+ quint8 UserID; // optional
+ QByteArray Password; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+ QByteArray Authentication; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+
+ struct Q_KNX_EXPORT QKnxGroup
+ {
+ Q_DECLARE_TR_FUNCTIONS(QKnxGroup)
+
+ public:
+ quint16 Address; // mandatory
+ QVector<QString> Senders; // mandatory
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+ };
+ QVector<QKnxGroup> Group; // 0..n
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxGroupAddresses
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxGroupAddresses)
+
+public:
+ struct Q_KNX_EXPORT QKnxGroup
+ {
+ Q_DECLARE_TR_FUNCTIONS(QKnxGroup)
+
+ public:
+ quint16 Address; // mandatory
+ QByteArray Key; // mandatory, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+ };
+ QVector<QKnxGroup> Group; // 1..n
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxDevice
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxDevice)
+
+public:
+ QString IndividualAddress; //mandatory, pattern ((1[0-5]|[0-9])\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
+ QByteArray ToolKey; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+ quint48 SequenceNumber; // optional
+ QByteArray ManagementPassword; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+ QByteArray Authentication; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxDevices
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxDevices)
+
+public:
+ QVector<QKnxDevice> Device; // 0..n
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxKeyring
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxKeyring)
+
+public:
+ QString Project; // mandatory, 50 character max.
+ QString Created; // mandatory, xs:dateTime
+ QString CreatedBy; // mandatory
+ QByteArray Signature; // mandatory, Base64 encoded, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+
+ QVector<QKnxBackbone> Backbone; // 0..1
+ QVector<QKnxInterface> Interface; // 0..n
+ QVector<QKnxGroupAddresses> GroupAddresses; // 0..1
+ QVector<QKnxDevices> Devices; // 0..1
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+ bool validate(QXmlStreamReader *reader, const QKnxByteArray &pwHash) const;
+};
+
+}}} // QKnx::Ets::Keyring
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/ssl/qknxsecurekey.cpp b/src/knx/ssl/qknxsecurekey.cpp
new file mode 100644
index 0000000..5aadbf9
--- /dev/null
+++ b/src/knx/ssl/qknxsecurekey.cpp
@@ -0,0 +1,389 @@
+/******************************************************************************
+**
+** 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 "qknxsecurekey.h"
+#include "qknxcryptographicengine.h"
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
+#if QT_CONFIG(opensslv11)
+# include "private/qsslsocket_openssl_symbols_p.h"
+# include "private/qsslsocket_openssl11_symbols_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QKnxSecureKeyData : public QSharedData
+{
+public:
+ QKnxSecureKeyData() = default;
+ ~QKnxSecureKeyData()
+ {
+#if QT_CONFIG(opensslv11)
+ if (m_evpPKey)
+ q_EVP_PKEY_free(m_evpPKey);
+#endif
+ }
+
+ bool isTypeValid() const
+ {
+ return m_type >= QKnxSecureKey::Type::Private && m_type < QKnxSecureKey::Type::Invalid;
+ }
+
+#if QT_CONFIG(opensslv11)
+ EVP_PKEY *m_evpPKey{ nullptr };
+#endif
+ QKnxSecureKey::Type m_type{ QKnxSecureKey::Type::Invalid };
+};
+
+/*!
+ \since 5.13
+ \inmodule QtKnx
+
+ \class QKnxSecureKey
+ \ingroup qtknx-general-classes
+
+ \brief The QKnxSecureKey class represents the elliptic-curve secure key to
+ be used with the elliptic curve Diffie-Hellman (ECDH) key agreement scheme.
+
+ This class is part of the Qt KNX module and currently available as a
+ Technology Preview, and therefore the API and functionality provided
+ by the class may be subject to change at any time without prior notice.
+*/
+
+/*!
+ \enum QKnxSecureKey::Type
+
+ Describes the types of keys QKnxSecureKey supports.
+
+ \value Private A private key.
+ \value Public A public key.
+ \value Invalid An invalid key, please do not use.
+*/
+
+/*!
+ Constructs an empty invalid secure key.
+*/
+QKnxSecureKey::QKnxSecureKey()
+ : d_ptr(new QKnxSecureKeyData)
+{}
+
+/*!
+ Destroys the secure key and releases all allocated resources.
+*/
+QKnxSecureKey::~QKnxSecureKey() = default;
+
+/*!
+ Returns the type of the key: \l {QKnxSecureKey::Public}{Public} or
+ \l {QKnxSecureKey::Private}{Private}.
+*/
+QKnxSecureKey::Type QKnxSecureKey::type() const
+{
+ return d_ptr->m_type;
+}
+
+/*!
+ Returns \c true if this is a default constructed secure key; otherwise
+ returns \c false. A secure key is considered \c null if it contains no
+ initialized values.
+*/
+bool QKnxSecureKey::isNull() const
+{
+#if QT_CONFIG(opensslv11)
+ return d_ptr->m_evpPKey == nullptr;
+#else
+ return true;
+#endif
+}
+
+/*!
+ Returns \c true if OpenSSL is available and if the key contains initialized
+ values, otherwise returns \c false.
+*/
+bool QKnxSecureKey::isValid() const
+{
+#if QT_CONFIG(opensslv11)
+ return !isNull() && d_ptr->isTypeValid() && QKnxCryptographicEngine::supportsCryptography();
+#else
+ return false;
+#endif
+}
+
+/*!
+ Returns an array of bytes that represent the Curve25519 raw secure key.
+
+ \note The function will return an empty byte array for the private
+ key unless OpenSLL with version 1.1.1a is used as back-end.
+*/
+QKnxByteArray QKnxSecureKey::bytes() const
+{
+#if QT_CONFIG(opensslv11)
+ if (!isValid())
+ return {};
+
+ if (d_ptr->m_type == Type::Private) {
+ size_t len = 32;
+ QKnxByteArray ba(int(len), 0);
+ if (q_EVP_PKEY_get_raw_private_key(d_ptr->m_evpPKey, ba.data(), &len) <= 0)
+ return {}; // preferred, no other way possible
+ return ba;
+ }
+
+ size_t len = 32;
+ QKnxByteArray pub(32, Qt::Uninitialized);
+ if (q_EVP_PKEY_get_raw_public_key(d_ptr->m_evpPKey, pub.data(), &len) > 0)
+ return pub; // preferred way
+
+ pub.resize(q_i2d_PUBKEY(d_ptr->m_evpPKey, nullptr));
+ auto tmp = pub.data();
+ q_i2d_PUBKEY(d_ptr->m_evpPKey, &tmp);
+ return pub.right(32);
+#else
+ return {};
+#endif
+}
+
+/*!
+ Constructs the Curve25519 secure key from the byte array \a data starting
+ at position \a index inside the array and sets the key type to \a type if
+ OpenSSL is available and no error occurs;
+ otherwise returns a \e {default-constructed key} which can be invalid.
+*/
+QKnxSecureKey QKnxSecureKey::fromBytes(QKnxSecureKey::Type type, const QKnxByteArray &data,
+ quint16 index)
+{
+#if QT_CONFIG(opensslv11)
+ auto ba = data.mid(index, 32);
+ if (ba.size() < 32)
+ return {};
+
+ if (!QKnxCryptographicEngine::supportsCryptography())
+ return {};
+
+ QKnxSecureKey key;
+ key.d_ptr->m_type = type;
+
+ if (type == Type::Private) {
+ key.d_ptr->m_evpPKey = q_EVP_PKEY_new_raw_private_key(NID_X25519, nullptr, ba.constData(),
+ ba.size()); // preferred way
+ if (key.d_ptr->m_evpPKey)
+ return key;
+
+ static const auto pkcs8 = QKnxByteArray::fromHex("302e020100300506032b656e04220420");
+ auto tmp = pkcs8 + ba; // PKCS #8 is a standard syntax for storing private key information
+
+ BIO *bio = nullptr;
+ if ((bio = q_BIO_new_mem_buf(reinterpret_cast<void *> (tmp.data()), tmp.size())))
+ key.d_ptr->m_evpPKey = q_d2i_PrivateKey_bio(bio, nullptr);
+ q_BIO_free(bio);
+ return key;
+ }
+
+ if (type == Type::Public) {
+ key.d_ptr->m_evpPKey = q_EVP_PKEY_new_raw_public_key(NID_X25519, nullptr, ba.constData(),
+ ba.size()); // preferred way
+ if (key.d_ptr->m_evpPKey)
+ return key;
+
+ key.d_ptr->m_evpPKey = q_EVP_PKEY_new();
+ if (q_EVP_PKEY_set_type(key.d_ptr->m_evpPKey, NID_X25519) <= 0)
+ return {};
+
+ if (q_EVP_PKEY_set1_tls_encodedpoint(key.d_ptr->m_evpPKey, ba.constData(), ba.size()) <= 0)
+ return {};
+ return key;
+ }
+#else
+ Q_UNUSED(type)
+ Q_UNUSED(data)
+ Q_UNUSED(index)
+#endif
+ return {};
+}
+
+/*!
+ Returns a new valid private key if OpenSSL is available and no error occurs.
+*/
+QKnxSecureKey QKnxSecureKey::generatePrivateKey()
+{
+ QKnxSecureKey key;
+#if QT_CONFIG(opensslv11)
+ if (!QKnxCryptographicEngine::supportsCryptography())
+ return key;
+
+ if (auto *pctx = q_EVP_PKEY_CTX_new_id(NID_X25519, nullptr)) {
+ q_EVP_PKEY_keygen_init(pctx);
+ key.d_ptr->m_type = Type::Private;
+ q_EVP_PKEY_keygen(pctx, &key.d_ptr->m_evpPKey);
+ q_EVP_PKEY_CTX_free(pctx);
+ }
+#endif
+ return key;
+}
+
+/*!
+ Returns a new valid public key with the given private key \a privateKey if
+ OpenSSL is available and no error occurs.
+*/
+QKnxSecureKey QKnxSecureKey::publicKeyFromPrivate(const QKnxSecureKey &privateKey)
+{
+ QKnxSecureKey key;
+#if QT_CONFIG(opensslv11)
+ if (privateKey.type() == QKnxSecureKey::Type::Private && privateKey.isValid()) {
+ q_EVP_PKEY_up_ref(privateKey.d_ptr->m_evpPKey);
+ key.d_ptr->m_type = Type::Public;
+ key.d_ptr->m_evpPKey = privateKey.d_ptr->m_evpPKey;
+ }
+#else
+ Q_UNUSED(privateKey)
+#endif
+ return key;
+}
+
+/*!
+ \overload publicKeyFromPrivate()
+*/
+QKnxSecureKey QKnxSecureKey::publicKeyFromPrivate(const QKnxByteArray &privateKey)
+{
+ return QKnxSecureKey::publicKeyFromPrivate(QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private,
+ privateKey));
+}
+
+/*!
+ Sets \a privateKey to a new valid private key and \a publicKey to a new
+ valid public key derived from the freshly generated private key if OpenSSL
+ is available and no error occurs.
+*/
+void QKnxSecureKey::generateKeys(QKnxSecureKey *privateKey, QKnxSecureKey *publicKey)
+{
+ if (!privateKey || !publicKey)
+ return;
+
+ *privateKey = generatePrivateKey();
+ *publicKey = publicKeyFromPrivate(*privateKey);
+}
+
+/*!
+ Derives and returns the shared secret from the given private key
+ \a privateKey and the peer's public key \a peerPublicKey if OpenSSL
+ is available and no error occurs;
+ otherwise returns a \l {default-constructed value} which can be empty.
+*/
+QKnxByteArray QKnxSecureKey::sharedSecret(const QKnxSecureKey &privateKey,
+ const QKnxSecureKey &peerPublicKey)
+{
+#if QT_CONFIG(opensslv11)
+ if (privateKey.type() != QKnxSecureKey::Type::Private || !privateKey.isValid())
+ return {};
+
+ if (peerPublicKey.type() != QKnxSecureKey::Type::Public || !peerPublicKey.isValid())
+ return {};
+
+ auto evpPKeyCtx = q_EVP_PKEY_CTX_new(privateKey.d_ptr->m_evpPKey, nullptr);
+ if (!evpPKeyCtx)
+ return {};
+
+ struct ScopedFree final
+ {
+ ScopedFree(EVP_PKEY_CTX *key) : m_evpPKeyCtx(key) {}
+ ~ScopedFree() { q_EVP_PKEY_CTX_free(m_evpPKeyCtx); }
+ EVP_PKEY_CTX *m_evpPKeyCtx = nullptr;
+ } _{ evpPKeyCtx };
+
+ if (q_EVP_PKEY_derive_init(evpPKeyCtx) <= 0)
+ return {};
+
+ if (q_EVP_PKEY_derive_set_peer(evpPKeyCtx, peerPublicKey.d_ptr->m_evpPKey) <= 0)
+ return {};
+
+ size_t keylen = 0;
+ if (q_EVP_PKEY_derive(evpPKeyCtx, nullptr, &keylen) <= 0)
+ return {};
+
+ QKnxByteArray ba(int(keylen), 0);
+ if (q_EVP_PKEY_derive(evpPKeyCtx, ba.data(), &keylen) <= 0)
+ return {};
+ return ba;
+#else
+ Q_UNUSED(privateKey)
+ Q_UNUSED(peerPublicKey)
+ return {};
+#endif
+}
+
+/*!
+ \overload sharedSecret()
+*/
+QKnxByteArray QKnxSecureKey::sharedSecret(const QKnxByteArray &privateKey,
+ const QKnxByteArray &peerPublicKey)
+{
+ return sharedSecret(QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private, privateKey),
+ QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, peerPublicKey));
+}
+
+
+/*!
+ Constructs a copy of \a other.
+*/
+QKnxSecureKey::QKnxSecureKey(const QKnxSecureKey &other)
+ : d_ptr(other.d_ptr)
+{}
+
+/*!
+ Assigns the specified \a other to this object.
+*/
+QKnxSecureKey &QKnxSecureKey::operator=(const QKnxSecureKey &other)
+{
+ d_ptr = other.d_ptr;
+ return *this;
+}
+
+/*!
+ Returns \c true if this key and the given \a other key are equal; otherwise
+ returns \c false.
+*/
+bool QKnxSecureKey::operator==(const QKnxSecureKey &other) const
+{
+ return d_ptr == other.d_ptr
+#if QT_CONFIG(opensslv11)
+ || (d_ptr->m_evpPKey == other.d_ptr->m_evpPKey && d_ptr->m_type == other.d_ptr->m_type)
+#endif
+ || (bytes() == other.bytes());
+}
+
+/*!
+ Returns \c true if this key and the given \a other key are not equal;
+ otherwise returns \c false.
+*/
+bool QKnxSecureKey::operator!=(const QKnxSecureKey &other) const
+{
+ return !operator==(other);
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxsecurekey.h b/src/knx/ssl/qknxsecurekey.h
new file mode 100644
index 0000000..2fbe0cd
--- /dev/null
+++ b/src/knx/ssl/qknxsecurekey.h
@@ -0,0 +1,86 @@
+/******************************************************************************
+**
+** 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$
+**
+******************************************************************************/
+
+#ifndef QKNXSECUREKEY_H
+#define QKNXSECUREKEY_H
+
+#include <QtCore/qshareddata.h>
+
+#include <QtKnx/qknxbytearray.h>
+#include <QtKnx/qtknxglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxSecureKeyData;
+class Q_KNX_EXPORT QKnxSecureKey final
+{
+public:
+ enum class Type : quint8 {
+ Private,
+ Public,
+ Invalid
+ };
+ QKnxSecureKey::Type type() const;
+
+ QKnxSecureKey();
+ ~QKnxSecureKey();
+
+ bool isNull() const;
+ bool isValid() const;
+
+ QKnxByteArray bytes() const;
+ static QKnxSecureKey fromBytes(QKnxSecureKey::Type type,
+ const QKnxByteArray &data,
+ quint16 index = 0);
+
+ static QKnxSecureKey generatePrivateKey();
+
+ static QKnxSecureKey publicKeyFromPrivate(const QKnxSecureKey &privateKey);
+ static QKnxSecureKey publicKeyFromPrivate(const QKnxByteArray &privateKey);
+
+ static void generateKeys(QKnxSecureKey *privateKey, QKnxSecureKey *publicKey);
+
+ static QKnxByteArray sharedSecret(const QKnxSecureKey &privateKey,
+ const QKnxSecureKey &peerPublicKey);
+ static QKnxByteArray sharedSecret(const QKnxByteArray &privateKey,
+ const QKnxByteArray &peerPublicKey);
+
+ QKnxSecureKey(const QKnxSecureKey &other);
+ QKnxSecureKey &operator=(const QKnxSecureKey &other);
+
+ bool operator==(const QKnxSecureKey &other) const;
+ bool operator!=(const QKnxSecureKey &other) const;
+
+private:
+ QSharedDataPointer<QKnxSecureKeyData> d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/ssl/qknxssl_openssl.cpp b/src/knx/ssl/qknxssl_openssl.cpp
new file mode 100644
index 0000000..a110efb
--- /dev/null
+++ b/src/knx/ssl/qknxssl_openssl.cpp
@@ -0,0 +1,178 @@
+/******************************************************************************
+**
+** 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 "qknxssl_p.h"
+
+#include <private/qtnetworkglobal_p.h>
+
+#if QT_CONFIG(opensslv11)
+# include "private/qsslsocket_openssl_symbols_p.h"
+# include "private/qsslsocket_openssl11_symbols_p.h"
+#endif
+
+#include <QtCore/qmutex.h>
+
+QT_BEGIN_NAMESPACE
+
+class QKnxOpenSsl
+{
+public:
+ static bool supportsSsl();
+ static long sslLibraryVersionNumber();
+
+protected:
+ static bool ensureLibraryLoaded();
+
+private:
+ static bool s_libraryLoaded;
+ static bool s_libraryEnabled;
+};
+
+bool QKnxOpenSsl::s_libraryLoaded = false;
+bool QKnxOpenSsl::s_libraryEnabled = false;
+
+Q_GLOBAL_STATIC(QKnxOpenSsl, qt_QKnxOpenSsl)
+Q_GLOBAL_STATIC_WITH_ARGS(QMutex, qt_knxOpenSslInitMutex, (QMutex::Recursive))
+
+/*!
+ \internal
+*/
+bool QKnxOpenSsl::supportsSsl()
+{
+#if QT_CONFIG(opensslv11)
+ if (!q_resolveOpenSslSymbols())
+ return false;
+
+ const QMutexLocker locker(qt_knxOpenSslInitMutex);
+ if (!s_libraryLoaded) {
+ s_libraryLoaded = true;
+
+ // Initialize OpenSSL.
+ if (q_OPENSSL_init_ssl(0, nullptr) != 1)
+ return false;
+ q_SSL_load_error_strings();
+ q_OpenSSL_add_all_algorithms();
+
+ // Initialize OpenSSL's random seed.
+ if (!q_RAND_status()) {
+ qWarning("Random number generator not seeded, disabling SSL support");
+ return false;
+ }
+
+ if (q_EVP_PKEY_type(NID_X25519) == NID_undef) {
+ qWarning("The X25519 algorithm is not supported, disabling SSL support");
+ return false;
+ }
+ s_libraryEnabled = true;
+ }
+ return s_libraryEnabled;
+#else
+ Q_UNUSED(qt_knxOpenSslInitMutex)
+ return false;
+#endif
+}
+
+/*!
+ \internal
+*/
+long QKnxOpenSsl::sslLibraryVersionNumber()
+{
+#if QT_CONFIG(opensslv11)
+ if (supportsSsl())
+ return q_OpenSSL_version_num();
+#endif
+ return 0;
+}
+
+/*!
+ \internal
+*/
+bool QKnxSsl::supportsCryptography()
+{
+ return qt_QKnxOpenSsl->supportsSsl();
+}
+
+/*!
+ \internal
+*/
+long QKnxSsl::sslLibraryVersionNumber()
+{
+ return qt_QKnxOpenSsl->sslLibraryVersionNumber();
+}
+
+/*!
+ \internal
+*/
+QKnxByteArray QKnxSsl::doCrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data, Mode mode)
+{
+#if QT_CONFIG(opensslv11)
+ if (!qt_QKnxOpenSsl->supportsSsl())
+ return {};
+
+ QSharedPointer<EVP_CIPHER_CTX> ctxPtr(q_EVP_CIPHER_CTX_new(), q_EVP_CIPHER_CTX_free);
+ if (ctxPtr.isNull())
+ return {};
+ q_EVP_CIPHER_CTX_reset(ctxPtr.data());
+
+ const auto ctx = ctxPtr.data();
+ const auto c = q_EVP_aes_128_cbc();
+ if (q_EVP_CipherInit_ex(ctx, c, nullptr, nullptr, nullptr, mode) <= 0)
+ return {};
+
+ if (q_EVP_CIPHER_CTX_set_padding(ctx, 0) <= 0)
+ return {};
+
+ Q_ASSERT(q_EVP_CIPHER_CTX_iv_length(ctx) == 16);
+ Q_ASSERT(q_EVP_CIPHER_CTX_key_length(ctx) == 16);
+
+ if (q_EVP_CipherInit_ex(ctx, nullptr, nullptr, key.constData(), iv.constData(), mode) <= 0)
+ return {};
+
+ int outl, offset = 0;
+ QKnxByteArray out(data.size() + q_EVP_CIPHER_block_size(c), 0x00);
+ if (q_EVP_CipherUpdate(ctx, out.data(), &outl, data.constData(), data.size()) <= 0)
+ return {};
+ offset += outl;
+
+ if (q_EVP_CipherFinal_ex(ctx, out.data() + offset, &outl) <= 0)
+ return {};
+ offset += outl;
+
+ return out.left(offset);
+#else
+ Q_UNUSED(key)
+ Q_UNUSED(iv)
+ Q_UNUSED(data)
+ Q_UNUSED(mode)
+ return {};
+#endif
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxcryptographicdata_p.h b/src/knx/ssl/qknxssl_p.h
index e963779..5de059e 100644
--- a/src/knx/ssl/qknxcryptographicdata_p.h
+++ b/src/knx/ssl/qknxssl_p.h
@@ -1,6 +1,6 @@
/******************************************************************************
**
-** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtKnx module.
@@ -27,8 +27,8 @@
**
******************************************************************************/
-#ifndef QKNXCRYPTOGRAPHICDATA_P_H
-#define QKNXCRYPTOGRAPHICDATA_P_H
+#ifndef QKNXSSL_P_H
+#define QKNXSSL_P_H
//
// W A R N I N G
@@ -41,39 +41,24 @@
// We mean it.
//
-#include <QtKnx/qtknxglobal.h>
-#include <QtKnx/private/qsslsocket_openssl_symbols_p.h>
-#include <QtKnx/private/qsslsocket_openssl11_symbols_p.h>
+#include <QtKnx/qknxbytearray.h>
QT_BEGIN_NAMESPACE
-class Q_KNX_EXPORT QKnxOpenSsl
+class QKnxSsl
{
public:
- static bool supportsSsl();
- static long sslLibraryVersionNumber();
- static QString sslLibraryVersionString();
- static long sslLibraryBuildVersionNumber();
- static QString sslLibraryBuildVersionString();
-
-protected:
- static bool ensureLibraryLoaded();
-
-private:
- static bool s_libraryLoaded;
- static bool s_libraryEnabled;
-};
-
-class QKnxCurve25519KeyData : public QSharedData
-{
-public:
- QKnxCurve25519KeyData() = default;
- ~QKnxCurve25519KeyData()
+ enum Mode
{
- q_EVP_PKEY_free(m_evpPKey);
- }
+ Decrypt = 0x00,
+ Encrypt = 0x01
+ };
+
+ static bool supportsCryptography();
+ static long sslLibraryVersionNumber();
- EVP_PKEY *m_evpPKey { nullptr };
+ static QKnxByteArray doCrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data, Mode mode);
};
QT_END_NAMESPACE
diff --git a/src/knx/ssl/qssl.cpp b/src/knx/ssl/qssl.cpp
deleted file mode 100644
index 7cfa6c3..0000000
--- a/src/knx/ssl/qssl.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtNetwork module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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 Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or 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.GPL2 and 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-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qssl_p.h"
-
-QT_BEGIN_NAMESPACE
-
-Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
-
-/*! \namespace QSsl
-
- \brief The QSsl namespace declares enums common to all SSL classes in Qt Network.
- \since 4.3
-
- \ingroup network
- \ingroup ssl
- \inmodule QtNetwork
-*/
-
-/*!
- \enum QSsl::KeyType
-
- Describes the two types of keys QSslKey supports.
-
- \value PrivateKey A private key.
- \value PublicKey A public key.
-*/
-
-/*!
- \enum QSsl::KeyAlgorithm
-
- Describes the different key algorithms supported by QSslKey.
-
- \value Rsa The RSA algorithm.
- \value Dsa The DSA algorithm.
- \value Ec The Elliptic Curve algorithm
- \value Opaque A key that should be treated as a 'black box' by QSslKey.
-
- The opaque key facility allows applications to add support for facilities
- such as PKCS#11 that Qt does not currently offer natively.
-*/
-
-/*!
- \enum QSsl::EncodingFormat
-
- Describes supported encoding formats for certificates and keys.
-
- \value Pem The PEM format.
- \value Der The DER format.
-*/
-
-/*!
- \enum QSsl::AlternativeNameEntryType
-
- Describes the key types for alternative name entries in QSslCertificate.
-
- \value EmailEntry An email entry; the entry contains an email address that
- the certificate is valid for.
-
- \value DnsEntry A DNS host name entry; the entry contains a host name
- entry that the certificate is valid for. The entry may contain wildcards.
-
- \note In Qt 4, this enum was called \c {AlternateNameEntryType}. That name
- is deprecated in Qt 5.
-
- \sa QSslCertificate::subjectAlternativeNames()
-*/
-
-/*!
- \typedef QSsl::AlternateNameEntryType
- \obsolete
-
- Use QSsl::AlternativeNameEntryType instead.
-*/
-
-/*!
- \enum QSsl::SslProtocol
-
- Describes the protocol of the cipher.
-
- \value SslV3 SSLv3. When using the WinRT backend this option will also enable TLSv1.0
- \value SslV2 SSLv2. Note, SSLv2 support was removed in OpenSSL 1.1.
- \value TlsV1_0 TLSv1.0
- \value TlsV1_0OrLater TLSv1.0 and later versions. This option is not available when using the WinRT backend due to platform limitations.
- \value TlsV1 Obsolete, means the same as TlsV1_0
- \value TlsV1_1 TLSv1.1. When using the WinRT backend this option will also enable TLSv1.0.
- \value TlsV1_1OrLater TLSv1.1 and later versions. This option is not available when using the WinRT backend due to platform limitations.
- \value TlsV1_2 TLSv1.2. When using the WinRT backend this option will also enable TLSv1.0 and TLSv1.1.
- \value TlsV1_2OrLater TLSv1.2 and later versions. This option is not available when using the WinRT backend due to platform limitations.
- \value UnknownProtocol The cipher's protocol cannot be determined.
- \value AnyProtocol The socket understands SSLv2, SSLv3, TLSv1.0 and all
- supported later versions of TLS. This value is used by QSslSocket only.
- \value TlsV1SslV3 On the client side, this will send
- a TLS 1.0 Client Hello, enabling TLSv1_0 and SSLv3 connections.
- On the server side, this will enable both SSLv3 and TLSv1_0 connections.
- \value SecureProtocols The default option, using protocols known to be secure;
- currently behaves similar to TlsV1Ssl3 except denying SSLv3 connections that does
- not upgrade to TLS.
-
- \note most servers understand both SSL and TLS, but it is recommended to use
- TLS only for security reasons. However, SSL and TLS are not compatible with
- each other: if you get unexpected handshake failures, verify that you chose
- the correct setting for your protocol.
-*/
-
-/*!
- \enum QSsl::SslOption
-
- Describes the options that can be used to control the details of
- SSL behaviour. These options are generally used to turn features off
- to work around buggy servers.
-
- \value SslOptionDisableEmptyFragments Disables the insertion of empty
- fragments into the data when using block ciphers. When enabled, this
- prevents some attacks (such as the BEAST attack), however it is
- incompatible with some servers.
- \value SslOptionDisableSessionTickets Disables the SSL session ticket
- extension. This can cause slower connection setup, however some servers
- are not compatible with the extension.
- \value SslOptionDisableCompression Disables the SSL compression
- extension. When enabled, this allows the data being passed over SSL to
- be compressed, however some servers are not compatible with this
- extension.
- \value SslOptionDisableServerNameIndication Disables the SSL server
- name indication extension. When enabled, this tells the server the virtual
- host being accessed allowing it to respond with the correct certificate.
- \value SslOptionDisableLegacyRenegotiation Disables the older insecure
- mechanism for renegotiating the connection parameters. When enabled, this
- option can allow connections for legacy servers, but it introduces the
- possibility that an attacker could inject plaintext into the SSL session.
- \value SslOptionDisableSessionSharing Disables SSL session sharing via
- the session ID handshake attribute.
- \value SslOptionDisableSessionPersistence Disables storing the SSL session
- in ASN.1 format as returned by QSslConfiguration::sessionTicket(). Enabling
- this feature adds memory overhead of approximately 1K per used session
- ticket.
- \value SslOptionDisableServerCipherPreference Disables selecting the cipher
- chosen based on the servers preferences rather than the order ciphers were
- sent by the client. This option is only relevant to server sockets, and is
- only honored by the OpenSSL backend.
-
- By default, SslOptionDisableEmptyFragments is turned on since this causes
- problems with a large number of servers. SslOptionDisableLegacyRenegotiation
- is also turned on, since it introduces a security risk.
- SslOptionDisableCompression is turned on to prevent the attack publicised by
- CRIME.
- SslOptionDisableSessionPersistence is turned on to optimize memory usage.
- The other options are turned off.
-
- \note Availability of above options depends on the version of the SSL
- backend in use.
-*/
-
-
-QT_END_NAMESPACE
diff --git a/src/knx/ssl/qssl.h b/src/knx/ssl/qssl.h
deleted file mode 100644
index c2a468c..0000000
--- a/src/knx/ssl/qssl.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtNetwork module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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 Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or 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.GPL2 and 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-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#ifndef QSSL_H
-#define QSSL_H
-
-#include <QtNetwork/qtnetworkglobal.h>
-#include <QtCore/QFlags>
-
-QT_BEGIN_NAMESPACE
-
-
-namespace QSsl {
- enum KeyType {
- PrivateKey,
- PublicKey
- };
-
- enum EncodingFormat {
- Pem,
- Der
- };
-
- enum KeyAlgorithm {
- Opaque,
- Rsa,
- Dsa,
- Ec
- };
-
- enum AlternativeNameEntryType {
- EmailEntry,
- DnsEntry
- };
-
-#if QT_DEPRECATED_SINCE(5,0)
- typedef AlternativeNameEntryType AlternateNameEntryType;
-#endif
-
- enum SslProtocol {
- SslV3,
- SslV2,
- TlsV1_0,
-#if QT_DEPRECATED_SINCE(5,0)
- TlsV1 = TlsV1_0,
-#endif
- TlsV1_1,
- TlsV1_2,
- AnyProtocol,
- TlsV1SslV3,
- SecureProtocols,
-
- TlsV1_0OrLater,
- TlsV1_1OrLater,
- TlsV1_2OrLater,
-
- UnknownProtocol = -1
- };
-
- enum SslOption {
- SslOptionDisableEmptyFragments = 0x01,
- SslOptionDisableSessionTickets = 0x02,
- SslOptionDisableCompression = 0x04,
- SslOptionDisableServerNameIndication = 0x08,
- SslOptionDisableLegacyRenegotiation = 0x10,
- SslOptionDisableSessionSharing = 0x20,
- SslOptionDisableSessionPersistence = 0x40,
- SslOptionDisableServerCipherPreference = 0x80
- };
- Q_DECLARE_FLAGS(SslOptions, SslOption)
-}
-
-Q_DECLARE_OPERATORS_FOR_FLAGS(QSsl::SslOptions)
-
-QT_END_NAMESPACE
-
-#endif // QSSL_H
diff --git a/src/knx/ssl/qssl_p.h b/src/knx/ssl/qssl_p.h
deleted file mode 100644
index 83ccdc7..0000000
--- a/src/knx/ssl/qssl_p.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtNetwork module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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 Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or 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.GPL2 and 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-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#ifndef QSSL_P_H
-#define QSSL_P_H
-
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists for the convenience
-// of qsslcertificate.cpp. This header file may change from version to version
-// without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtNetwork/private/qtnetworkglobal_p.h>
-#include <QtCore/QLoggingCategory>
-
-QT_BEGIN_NAMESPACE
-
-Q_DECLARE_LOGGING_CATEGORY(lcSsl)
-
-QT_END_NAMESPACE
-
-#endif // QSSL_P_H
diff --git a/src/knx/ssl/qsslsocket_openssl11_symbols_p.h b/src/knx/ssl/qsslsocket_openssl11_symbols_p.h
index d8b8b3b..0fe4ad3 100644
--- a/src/knx/ssl/qsslsocket_openssl11_symbols_p.h
+++ b/src/knx/ssl/qsslsocket_openssl11_symbols_p.h
@@ -151,7 +151,5 @@ int q_EVP_PKEY_get_raw_private_key(const EVP_PKEY *pkey, unsigned char *priv, si
EVP_PKEY *q_EVP_PKEY_new_raw_private_key(int type, ENGINE *e, const unsigned char *priv, size_t len);
const EVP_CIPHER *q_EVP_aes_128_cbc(void);
-int q_PKCS5_PBKDF2_HMAC(const char *pass, int passlen, const unsigned char *salt, int saltlen,
- int iter, const EVP_MD *digest, int keylen, unsigned char *out);
#endif
diff --git a/src/knx/ssl/qsslsocket_openssl_symbols.cpp b/src/knx/ssl/qsslsocket_openssl_symbols.cpp
index 7502dee..c89cafb 100644
--- a/src/knx/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/knx/ssl/qsslsocket_openssl_symbols.cpp
@@ -54,7 +54,6 @@
**
****************************************************************************/
-#include "qssl_p.h"
#include "qsslsocket_openssl_symbols_p.h"
#ifdef Q_OS_WIN
@@ -118,6 +117,8 @@ QT_BEGIN_NAMESPACE
possibly with a different version of OpenSSL.
*/
+Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
+
#ifndef QT_LINKED_OPENSSL
namespace {
@@ -579,8 +580,6 @@ DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG)
DEFINEFUNC(const EVP_CIPHER *, EVP_aes_128_cbc, DUMMYARG, DUMMYARG, return nullptr, return)
DEFINEFUNC2(int, EVP_CIPHER_CTX_set_padding, EVP_CIPHER_CTX *x, x, int padding, padding, return 0, return)
- DEFINEFUNC8(int, PKCS5_PBKDF2_HMAC, const char *pass, pass, int passlen, passlen, const unsigned char *salt, salt, \
- int saltlen, saltlen, int iter, iter, const EVP_MD *digest, digest, int keylen, keylen, unsigned char *out, out, return 0, return)
DEFINEFUNC(const EVP_MD *, EVP_sha256, DUMMYARG, DUMMYARG, return nullptr, return)
#endif
@@ -1304,7 +1303,6 @@ bool q_resolveOpenSslSymbols()
RESOLVEFUNC(EVP_aes_128_cbc)
RESOLVEFUNC(EVP_CIPHER_CTX_set_padding)
- RESOLVEFUNC(PKCS5_PBKDF2_HMAC)
RESOLVEFUNC(EVP_sha256)
#endif
diff --git a/src/knx/ssl/qsslsocket_openssl_symbols_p.h b/src/knx/ssl/qsslsocket_openssl_symbols_p.h
index 49a7e95..c5ae895 100644
--- a/src/knx/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/knx/ssl/qsslsocket_openssl_symbols_p.h
@@ -103,10 +103,13 @@
#include <openssl/dh.h>
#endif
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qglobal.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcSsl)
+
#define DUMMYARG
#if !defined QT_LINKED_OPENSSL
diff --git a/src/knx/ssl/ssl.pri b/src/knx/ssl/ssl.pri
index 2a0938f..35f2730 100644
--- a/src/knx/ssl/ssl.pri
+++ b/src/knx/ssl/ssl.pri
@@ -1,16 +1,17 @@
-qtConfig(opensslv11) { # OpenSSL 1.1 support is required.
- HEADERS += ssl/qssl.h \
- ssl/qssl_p.h \
- ssl/qknxcurve25519.h \
- ssl/qknxcryptographicdata_p.h
+HEADERS += ssl/qknxcryptographicengine.h \
+ ssl/qknxsecurekey.h \
+ ssl/qknxssl_p.h \
+ ssl/qknxkeyring_p.h
- SOURCES += ssl/qssl.cpp \
- ssl/qknxcurve25519.cpp
+SOURCES += ssl/qknxcryptographicengine.cpp \
+ ssl/qknxsecurekey.cpp \
+ ssl/qknxssl_openssl.cpp \
+ ssl/qknxkeyring.cpp
- HEADERS += ssl/qsslsocket_openssl_symbols_p.h
+qtConfig(opensslv11) { # OpenSSL 1.1 support is required.
SOURCES += ssl/qsslsocket_openssl_symbols.cpp
-
- HEADERS += ssl/qsslsocket_openssl11_symbols_p.h
+ HEADERS += ssl/qsslsocket_openssl_symbols_p.h \
+ ssl/qsslsocket_openssl11_symbols_p.h
QMAKE_CXXFLAGS += -DOPENSSL_API_COMPAT=0x10100000L
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index be105c4..2d09f65 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -46,7 +46,5 @@ SUBDIRS += cmake \
qknxnetipsecuredservicefamiliesdib \
qknxnetipsessionrequest \
qknxnetipsessionresponse \
- qknxnetiprouter
-
-QT_FOR_CONFIG += network
-qtConfig(opensslv11):SUBDIRS+=qknxcryptographicengine
+ qknxnetiprouter \
+ qknxcryptographicengine
diff --git a/tests/auto/qknxcryptographicengine/qknxcryptographicengine.pro b/tests/auto/qknxcryptographicengine/qknxcryptographicengine.pro
index e91f20d..5b9b9bb 100644
--- a/tests/auto/qknxcryptographicengine/qknxcryptographicengine.pro
+++ b/tests/auto/qknxcryptographicengine/qknxcryptographicengine.pro
@@ -1,6 +1,6 @@
TARGET = tst_qknxcryptographicengine
-QT = core testlib knx knx-private
+QT = core testlib knx
CONFIG += testcase c++11
CONFIG -= app_bundle
diff --git a/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp b/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp
index eb03cfd..8ab0b8c 100644
--- a/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp
+++ b/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp
@@ -28,14 +28,14 @@
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
-#include <QtKnx/qknxcurve25519.h>
+#include <QtKnx/qknxcryptographicengine.h>
#include <QtKnx/qknxlinklayerframebuilder.h>
#include <QtKnx/qknxnetiproutingindication.h>
#include <QtKnx/qknxnetipsecurewrapper.h>
#include <QtKnx/qknxnetipsessionauthenticate.h>
#include <QtKnx/qknxnetipsessionresponse.h>
#include <QtKnx/qknxnetipsessionstatus.h>
-#include <QtKnx/private/qknxcryptographicdata_p.h>
+#include <QtKnx/qknxnetiptimernotify.h>
#include <QtTest/qtest.h>
QT_BEGIN_NAMESPACE
@@ -60,95 +60,101 @@ private slots:
void testPublicKey()
{
- QKnxCurve25519PublicKey key;
+ QKnxSecureKey key;
QCOMPARE(key.isNull(), true);
QCOMPARE(key.isValid(), false);
QCOMPARE(key.bytes(), QKnxByteArray());
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Invalid);
- if (QKnxOpenSsl::sslLibraryVersionNumber() < 0x1010000fL)
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
return;
auto bytes = QKnxByteArray::fromHex("bdf099909923143ef0a5de0b3be3687b"
"c5bd3cf5f9e6f901699cd870ec1ff824");
- key = QKnxCurve25519PublicKey::fromBytes(bytes);
+ key = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, bytes);
QCOMPARE(key.isNull(), false);
QCOMPARE(key.isValid(), true);
QCOMPARE(key.bytes(), bytes);
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Public);
bytes = QKnxByteArray::fromHex("0aa227b4fd7a32319ba9960ac036ce0e"
"5c4507b5ae55161f1078b1dcfb3cb631");
- key = QKnxCurve25519PublicKey::fromBytes(bytes);
+ key = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, bytes);
QCOMPARE(key.isNull(), false);
QCOMPARE(key.isValid(), true);
QCOMPARE(key.bytes(), bytes);
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Public);
// derive public key from private key
- key = { QKnxCurve25519PrivateKey::fromBytes(QKnxByteArray::fromHex("b8fabd62665d8b9e8a9d"
- "8b1f4bca42c8c2789a6110f50e9dd785b3ede883f378")) };
+ key = QKnxSecureKey::publicKeyFromPrivate(QKnxByteArray::fromHex("b8fabd62665d8b9e8a9d"
+ "8b1f4bca42c8c2789a6110f50e9dd785b3ede883f378"));
QCOMPARE(key.isNull(), false);
QCOMPARE(key.isValid(), true);
QCOMPARE(key.bytes(), bytes);
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Public);
}
void testPrivateKey()
{
- if (QKnxOpenSsl::sslLibraryVersionNumber() >= 0x1010000fL) {
- QKnxCurve25519PrivateKey key;
- QCOMPARE(key.isNull(), false);
- QCOMPARE(key.isValid(), true);
- if (QKnxOpenSsl::sslLibraryVersionNumber() > 0x1010008fL)
- QCOMPARE(key.bytes().size(), 32);
-
- auto bytes = QKnxByteArray::fromHex("b8fabd62665d8b9e8a9d8b1f4bca42c8"
- "c2789a6110f50e9dd785b3ede883f378");
- key = QKnxCurve25519PrivateKey::fromBytes(bytes);
- QCOMPARE(key.isNull(), false);
- QCOMPARE(key.isValid(), true);
- if (QKnxOpenSsl::sslLibraryVersionNumber() > 0x1010008fL)
- QCOMPARE(key.bytes(), bytes);
-
- bytes = QKnxByteArray::fromHex("68c1744813f4e65cf10cca671caa1336"
- "a796b4ac40cc5cf2655674225c1e5264");
- key = QKnxCurve25519PrivateKey::fromBytes(bytes);
- QCOMPARE(key.isNull(), false);
- QCOMPARE(key.isValid(), true);
- if (QKnxOpenSsl::sslLibraryVersionNumber() > 0x1010008fL)
- QCOMPARE(key.bytes(), bytes);
-
- } else {
- QKnxCurve25519PrivateKey key;
- QCOMPARE(key.isNull(), true);
- QCOMPARE(key.isValid(), false);
- QCOMPARE(key.bytes(), QKnxByteArray());
- }
+ QKnxSecureKey key;
+ QCOMPARE(key.isNull(), true);
+ QCOMPARE(key.isValid(), false);
+ QCOMPARE(key.bytes(), QKnxByteArray());
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Invalid);
+
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
+ return;
+
+ key = QKnxSecureKey::generatePrivateKey();
+ QCOMPARE(key.isNull(), false);
+ QCOMPARE(key.isValid(), true);
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() >= 0x1010101fL)
+ QCOMPARE(key.bytes().size(), 32);
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Private);
+
+ auto bytes = QKnxByteArray::fromHex("b8fabd62665d8b9e8a9d8b1f4bca42c8"
+ "c2789a6110f50e9dd785b3ede883f378");
+ key = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private, bytes);
+ QCOMPARE(key.isNull(), false);
+ QCOMPARE(key.isValid(), true);
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() >= 0x1010101fL)
+ QCOMPARE(key.bytes(), bytes);
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Private);
+
+ bytes = QKnxByteArray::fromHex("68c1744813f4e65cf10cca671caa1336"
+ "a796b4ac40cc5cf2655674225c1e5264");
+ key = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private, bytes);
+ QCOMPARE(key.isNull(), false);
+ QCOMPARE(key.isValid(), true);
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() >= 0x1010101fL)
+ QCOMPARE(key.bytes(), bytes);
+ QCOMPARE(key.type(), QKnxSecureKey::Type::Private);
}
void testSharedSecret()
{
- if (QKnxOpenSsl::sslLibraryVersionNumber() < 0x1010000fL)
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
return;
auto pubBytes = QKnxByteArray::fromHex("0aa227b4fd7a32319ba9960ac036ce0e"
"5c4507b5ae55161f1078b1dcfb3cb631");
- auto pubKey = QKnxCurve25519PublicKey::fromBytes(pubBytes);
+ auto pubKey = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, pubBytes);
auto privBytes = QKnxByteArray::fromHex("68c1744813f4e65cf10cca671caa1336"
"a796b4ac40cc5cf2655674225c1e5264");
- auto privKey = QKnxCurve25519PrivateKey::fromBytes(privBytes);
+ auto privKey = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private, privBytes);
- auto secret = QKnxCryptographicEngine::sharedSecret(pubKey, privKey);
+ auto secret = QKnxSecureKey::sharedSecret(privKey, pubKey);
QCOMPARE(secret, QKnxByteArray::fromHex("d801525217618f0da90a4ff22148aee0"
"ff4c19b430e8081223ffe99c81a98b05"));
pubBytes = QKnxByteArray::fromHex("bdf099909923143ef0a5de0b3be3687b"
"c5bd3cf5f9e6f901699cd870ec1ff824");
- pubKey = QKnxCurve25519PublicKey::fromBytes(pubBytes);
privBytes = QKnxByteArray::fromHex("b8fabd62665d8b9e8a9d8b1f4bca42c8"
"c2789a6110f50e9dd785b3ede883f378");
- privKey = QKnxCurve25519PrivateKey::fromBytes(privBytes);
- secret = QKnxCryptographicEngine::sharedSecret(pubKey, privKey);
+ secret = QKnxSecureKey::sharedSecret(privBytes, pubBytes);
QCOMPARE(secret, QKnxByteArray::fromHex("d801525217618f0da90a4ff22148aee0"
"ff4c19b430e8081223ffe99c81a98b05"));
}
@@ -174,20 +180,23 @@ private slots:
void testMessageAuthenticationCode()
{
- if (QKnxOpenSsl::sslLibraryVersionNumber() < 0x1010000fL)
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
return;
/* This test more or less follows KNX AN156 - Annex A */
- auto clientPublicKey = QKnxCurve25519PublicKey::fromBytes(QKnxByteArray::fromHex("0aa227b4"
- "fd7a32319ba9960ac036ce0e5c4507b5ae55161f1078b1dcfb3cb631"));
+ auto clientPublicKey = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public,
+ QKnxByteArray::fromHex("0aa227b4fd7a32319ba9960ac036ce0e5c4507b5ae55161f1078b1dcfb3c"
+ "b631"));
- auto serverPrivateKey = QKnxCurve25519PrivateKey::fromBytes(QKnxByteArray::fromHex("68c174"
- "4813f4e65cf10cca671caa1336a796b4ac40cc5cf2655674225c1e5264"));
- auto serverPublicKey = QKnxCurve25519PublicKey::fromBytes(QKnxByteArray::fromHex("bdf09990"
- "9923143ef0a5de0b3be3687bc5bd3cf5f9e6f901699cd870ec1ff824"));
+ auto serverPrivateKey = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private,
+ QKnxByteArray::fromHex("68c1744813f4e65cf10cca671caa1336a796b4ac40cc5cf2655674225c1e"
+ "5264"));
+ auto serverPublicKey = QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public,
+ QKnxByteArray::fromHex("bdf099909923143ef0a5de0b3be3687bc5bd3cf5f9e6f901699cd870ec1f"
+ "f824"));
- QCOMPARE(serverPublicKey.bytes(), QKnxCurve25519PublicKey(serverPrivateKey).bytes());
+ QCOMPARE(serverPublicKey, QKnxSecureKey::publicKeyFromPrivate(serverPrivateKey));
/* Session Response */
@@ -204,7 +213,7 @@ private slots:
QCOMPARE(XOR_X_Y,
QKnxByteArray::fromHex("b752be246459260f6b0c4801fbd5a67599f83b4057b3ef1e79e469ac17234e15"));
- auto mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(deviceAuthenticationCode,
+ auto mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(deviceAuthenticationCode,
knxNetIpSecureHeader, secureSessionIdentifier, XOR_X_Y);
QCOMPARE(mac, QKnxByteArray::fromHex("da3dc6af79896aa6ee7573d69950c283"));
@@ -212,10 +221,11 @@ private slots:
mac);
QCOMPARE(encMac, QKnxByteArray::fromHex("a922505aaa436163570bd5494c2df2a3"));
- auto decMac = QKnxCryptographicEngine::decryptMessageAuthenticationCode(deviceAuthenticationCode, encMac);
+ auto decMac = QKnxCryptographicEngine::decryptMessageAuthenticationCode(deviceAuthenticationCode,
+ encMac);
QCOMPARE(decMac, mac);
- auto sharedSecret = QKnxCryptographicEngine::sharedSecret(clientPublicKey, serverPrivateKey);
+ auto sharedSecret = QKnxSecureKey::sharedSecret(serverPrivateKey, clientPublicKey);
QCOMPARE(sharedSecret,
QKnxByteArray::fromHex("d801525217618f0da90a4ff22148aee0ff4c19b430e8081223ffe99c81a98b05"));
@@ -230,10 +240,9 @@ private slots:
QCOMPARE(passwordHash, QKnxByteArray::fromHex("03fcedb66660251ec81a1a716901696a"));
knxNetIpSecureHeader = QKnxNetIpFrameHeader::fromBytes(QKnxByteArray::fromHex("061009530018"));
- quint16 userId = 0x0001;
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(passwordHash,
- knxNetIpSecureHeader, userId, XOR_X_Y);
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(passwordHash,
+ knxNetIpSecureHeader, QKnxNetIp::SecureUserId::Management, XOR_X_Y);
QCOMPARE(mac, QKnxByteArray::fromHex("741669f5e32bff6fa2edf51c52d4bd8f"));
encMac = QKnxCryptographicEngine::encryptMessageAuthenticationCode(passwordHash, mac);
@@ -248,11 +257,11 @@ private slots:
knxNetIpSecureHeader = QKnxNetIpFrameHeader::fromBytes(QKnxByteArray::fromHex("06100950003e"));
auto frame = QKnxNetIpSessionAuthenticateProxy::builder()
- .setUserId(userId)
+ .setUserId(QKnxNetIp::SecureUserId::Management)
.setMessageAuthenticationCode(encMac)
.create();
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(sessionKey,
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(sessionKey,
knxNetIpSecureHeader, secureSessionIdentifier, frame.bytes(), sequenceNumber,
serialNumber, messageTag);
QCOMPARE(mac, QKnxByteArray::fromHex("602280d0896beaa7106e7248f67f2eef"));
@@ -282,7 +291,7 @@ private slots:
.setStatus(QKnxNetIp::SecureSessionStatus::AuthenticationSuccess)
.create();
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(sessionKey,
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(sessionKey,
knxNetIpSecureHeader, secureSessionIdentifier, frame.bytes(), sequenceNumber,
serialNumber, messageTag);
QCOMPARE(mac, QKnxByteArray::fromHex("a8ed2796a566cd60b91a4de5c1144cbc"));
@@ -318,7 +327,7 @@ private slots:
knxNetIpSecureHeader = QKnxNetIpFrameHeader::fromBytes(QKnxByteArray::fromHex("061009500037"));
secureSessionIdentifier = 0x0000;
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(backboneKey,
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(backboneKey,
knxNetIpSecureHeader, secureSessionIdentifier, frame.bytes(), timerValue,
serialNumber, messageTag);
QCOMPARE(mac, QKnxByteArray::fromHex("bd0a294b952554b23539204c2271d26b"));
@@ -343,7 +352,7 @@ private slots:
knxNetIpSecureHeader = QKnxNetIpFrameHeader::fromBytes(QKnxByteArray::fromHex("061009550024"));
- mac = QKnxCryptographicEngine::calculateMessageAuthenticationCode(backboneKey,
+ mac = QKnxCryptographicEngine::computeMessageAuthenticationCode(backboneKey,
knxNetIpSecureHeader, secureSessionIdentifier, {}, timerValue,
serialNumber, messageTag);
QCOMPARE(mac, QKnxByteArray::fromHex("21631241bc1f784d6d03da070580464a"));
@@ -358,10 +367,155 @@ private slots:
}
- void cleanupTestCase()
- {}
-};
+ void testSecureWrapperFrame()
+ {
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
+ return;
+
+ auto sessionAuthenticate = QKnxNetIpSessionAuthenticateProxy::builder()
+ .setUserId(QKnxNetIp::SecureUserId::Management)
+ .setMessageAuthenticationCode(QKnxByteArray::fromHex("1f1d59ea9f12a152e5d9727f08462cde"))
+ .create();
+
+ quint48 sequenceNumber = 0x000000000000;
+ auto serialNumber = QKnxByteArray::fromHex("00fa12345678");
+ quint16 messageTag = 0xaffe;
+ quint16 secureSessionIdentifier = 0x0001;
+ auto sessionKey = QKnxByteArray::fromHex("289426c2912535ba98279a4d1843c487");
+
+ auto secureWrapper = QKnxNetIpSecureWrapperProxy::secureBuilder()
+ .setSecureSessionId(secureSessionIdentifier)
+ .setSequenceNumber(sequenceNumber)
+ .setSerialNumber(serialNumber)
+ .setMessageTag(messageTag)
+ .setEncapsulatedFrame(sessionAuthenticate)
+ .create(sessionKey);
+ QCOMPARE(secureWrapper.isValid(), true);
+
+ const QKnxNetIpSecureWrapperProxy proxy(secureWrapper);
+ QCOMPARE(proxy.isValid(), true);
+ QCOMPARE(proxy.secureSessionId(), secureSessionIdentifier);
+ QCOMPARE(proxy.sequenceNumber(), sequenceNumber);
+ QCOMPARE(proxy.serialNumber(), serialNumber);
+ QCOMPARE(proxy.messageTag(), messageTag);
+ QCOMPARE(proxy.messageAuthenticationCode(),
+ QKnxByteArray::fromHex("52dba8e7e4bd80bd7d868a3ae78749de"));
+ QCOMPARE(proxy.encapsulatedFrame(),
+ QKnxByteArray::fromHex("7915a4f36e6e4208d28b4a207d8f35c0d138c26a7b5e7169"));
+ }
+
+ void testTimerNotifyFrame()
+ {
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
+ return;
+
+ quint48 timerValue = 211938428830917;
+ auto serialNumber = QKnxByteArray::fromHex("00fa12345678");
+ quint16 messageTag = 0xaffe;
+ auto mac = QKnxByteArray::fromHex("ee7b9b3083deb1570eb38d073adad985");
+
+ auto timerNotify = QKnxNetIpTimerNotifyProxy::builder()
+ .setTimerValue(timerValue)
+ .setSerialNumber(serialNumber)
+ .setMessageTag(messageTag)
+ .setMessageAuthenticationCode(mac)
+ .create();
+
+ QKnxNetIpTimerNotifyProxy proxy(timerNotify);
+ QCOMPARE(proxy.isValid(), true);
+ QCOMPARE(proxy.timerValue(), timerValue);
+ QCOMPARE(proxy.serialNumber(), serialNumber);
+ QCOMPARE(proxy.messageTag(), messageTag);
+ QCOMPARE(proxy.messageAuthenticationCode(), mac);
+
+ quint16 sessionId = 0x0000;
+ auto backboneKey = QKnxByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
+
+ auto secureTimerNotify = QKnxNetIpTimerNotifyProxy::secureBuilder()
+ .setTimerValue(timerValue)
+ .setSerialNumber(serialNumber)
+ .setMessageTag(messageTag)
+ .create(backboneKey, sessionId);
+
+ QKnxNetIpTimerNotifyProxy proxy2(secureTimerNotify);
+ QCOMPARE(proxy2.isValid(), true);
+ QCOMPARE(proxy2.timerValue(), timerValue);
+ QCOMPARE(proxy2.serialNumber(), serialNumber);
+ QCOMPARE(proxy2.messageTag(), messageTag);
+ QCOMPARE(proxy2.messageAuthenticationCode(), mac);
+ }
+
+ void testSessionResponseFrame()
+ {
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
+ return;
+
+ auto clientPublicKey = QKnxByteArray::fromHex("0aa227b4"
+ "fd7a32319ba9960ac036ce0e5c4507b5ae55161f1078b1dcfb3cb631");
+ auto serverPublicKey = QKnxByteArray::fromHex("bdf09990"
+ "9923143ef0a5de0b3be3687bc5bd3cf5f9e6f901699cd870ec1ff824");
+
+ quint16 secureSessionIdentifier = 0x0001;
+ const QByteArray deviceAuthenticationPassword { "trustme" };
+ auto mac = QKnxByteArray::fromHex("a922505aaa436163570bd5494c2df2a3");
+
+ auto sessionResponse = QKnxNetIpSessionResponseProxy::builder()
+ .setSecureSessionId(secureSessionIdentifier)
+ .setPublicKey(serverPublicKey)
+ .setMessageAuthenticationCode(mac)
+ .create();
+
+ QKnxNetIpSessionResponseProxy proxy(sessionResponse);
+ QCOMPARE(proxy.isValid(), true);
+ QCOMPARE(proxy.secureSessionId(), secureSessionIdentifier);
+ QCOMPARE(proxy.publicKey(), serverPublicKey);
+ QCOMPARE(proxy.messageAuthenticationCode(), mac);
+
+ sessionResponse = QKnxNetIpSessionResponseProxy::secureBuilder()
+ .setSecureSessionId(secureSessionIdentifier)
+ .setPublicKey(serverPublicKey)
+ .create(deviceAuthenticationPassword, clientPublicKey);
+
+ QKnxNetIpSessionResponseProxy proxy2(sessionResponse);
+ QCOMPARE(proxy2.isValid(), true);
+ QCOMPARE(proxy2.secureSessionId(), secureSessionIdentifier);
+ QCOMPARE(proxy2.publicKey(), serverPublicKey);
+ QCOMPARE(proxy2.messageAuthenticationCode(), mac);
+ }
+
+ void testSessionAuthenticateFrame()
+ {
+ if (QKnxCryptographicEngine::sslLibraryVersionNumber() < 0x1010000fL)
+ return;
+ auto clientPublicKey = QKnxByteArray::fromHex("0aa227b4"
+ "fd7a32319ba9960ac036ce0e5c4507b5ae55161f1078b1dcfb3cb631");
+ auto serverPublicKey = QKnxByteArray::fromHex("bdf09990"
+ "9923143ef0a5de0b3be3687bc5bd3cf5f9e6f901699cd870ec1ff824");
+
+ const QByteArray password { "secret" };
+ auto mac = QKnxByteArray::fromHex("1f1d59ea9f12a152e5d9727f08462cde");
+
+ auto sessionAuth = QKnxNetIpSessionAuthenticateProxy::builder()
+ .setUserId(QKnxNetIp::SecureUserId::Management)
+ .setMessageAuthenticationCode(mac)
+ .create();
+
+ QKnxNetIpSessionAuthenticateProxy proxy(sessionAuth);
+ QCOMPARE(proxy.isValid(), true);
+ QCOMPARE(proxy.userId(), QKnxNetIp::SecureUserId::Management);
+ QCOMPARE(proxy.messageAuthenticationCode(), mac);
+
+ auto sessionAuth2 = QKnxNetIpSessionAuthenticateProxy::secureBuilder()
+ .setUserId(QKnxNetIp::SecureUserId::Management)
+ .create(password, clientPublicKey, serverPublicKey);
+
+ QKnxNetIpSessionAuthenticateProxy proxy2(sessionAuth2);
+ QCOMPARE(proxy2.isValid(), true);
+ QCOMPARE(proxy2.userId(), QKnxNetIp::SecureUserId::Management);
+ QCOMPARE(proxy2.messageAuthenticationCode(), mac);
+ }
+};
QTEST_APPLESS_MAIN(tst_qknxcryptographicengine)
diff --git a/tests/auto/qknxdatapointtype/tst_qknxdatapointtype.cpp b/tests/auto/qknxdatapointtype/tst_qknxdatapointtype.cpp
index 428bd37..8387ae1 100644
--- a/tests/auto/qknxdatapointtype/tst_qknxdatapointtype.cpp
+++ b/tests/auto/qknxdatapointtype/tst_qknxdatapointtype.cpp
@@ -127,6 +127,10 @@ void tst_QKnxDatapointType::dpt1_1Bit()
QCOMPARE(dpt1Bit.isValid(), true);
QCOMPARE(dpt1Bit.bytes(), QKnxByteArray({ 0x00 }));
QCOMPARE(dpt1Bit.type(), QKnxDatapointType::Type::Dpt1_1Bit);
+ QCOMPARE(dpt1Bit.minimum(), QVariant::fromValue(false));
+ QCOMPARE(dpt1Bit.maximum(), QVariant::fromValue(true));
+ QCOMPARE(dpt1Bit.minimumText(), QString("false"));
+ QCOMPARE(dpt1Bit.maximumText(), QString("true"));
dpt1Bit.setBit(true);
QCOMPARE(dpt1Bit.bit(), true);
@@ -144,6 +148,10 @@ void tst_QKnxDatapointType::dpt1_1Bit()
QCOMPARE(dptSwitch.value(), QKnxSwitch::State::Off);
QCOMPARE(dptSwitch.bytes(), QKnxByteArray({ 0x00 }));
QCOMPARE(dptSwitch.type(), QKnxDatapointType::Type::DptSwitch);
+ QCOMPARE(dptSwitch.minimum(), QVariant::fromValue(false));
+ QCOMPARE(dptSwitch.maximum(), QVariant::fromValue(true));
+ QCOMPARE(dptSwitch.minimumText(), QString("Off"));
+ QCOMPARE(dptSwitch.maximumText(), QString("On"));
dptSwitch.setValue(QKnxSwitch::State::On);
QCOMPARE(dptSwitch.isValid(), true);
@@ -187,6 +195,10 @@ void tst_QKnxDatapointType::dpt1_1Bit()
QCOMPARE(dptWindowDoor.value(), QKnxWindowDoor::State::Closed);
QCOMPARE(dptWindowDoor.bytes(), QKnxByteArray({ 0x00 }));
QCOMPARE(dptWindowDoor.type(), QKnxDatapointType::Type::DptWindowDoor);
+ QCOMPARE(dptWindowDoor.minimum(), QVariant::fromValue(false));
+ QCOMPARE(dptWindowDoor.maximum(), QVariant::fromValue(true));
+ QCOMPARE(dptWindowDoor.minimumText(), QString("Closed"));
+ QCOMPARE(dptWindowDoor.maximumText(), QString("Open"));
}
void tst_QKnxDatapointType::dpt2_1BitControlled()
diff --git a/tests/auto/qknxnetipdescriptionresponse/tst_qknxnetipdescriptionresponse.cpp b/tests/auto/qknxnetipdescriptionresponse/tst_qknxnetipdescriptionresponse.cpp
index bfa5a85..e1a863a 100644
--- a/tests/auto/qknxnetipdescriptionresponse/tst_qknxnetipdescriptionresponse.cpp
+++ b/tests/auto/qknxnetipdescriptionresponse/tst_qknxnetipdescriptionresponse.cpp
@@ -156,15 +156,15 @@ void tst_QKnxNetIpDescriptionResponse::testSupportedFamiliesAccessor()
.create();
QKnxNetIpDescriptionResponseProxy descriptionResponse(frame);
- auto familie = descriptionResponse.supportedFamilies();
- const QKnxNetIpServiceFamiliesDibProxy view(familie);
+ auto family = descriptionResponse.supportedFamilies();
+ const QKnxNetIpServiceFamiliesDibProxy view(family);
QCOMPARE(view.isValid(), m_sf.isValid());
QCOMPARE(view.descriptionType(), QKnxNetIpServiceFamiliesDibProxy(m_sf).descriptionType());
- QCOMPARE(familie.size(), m_sf.size());
- QCOMPARE(familie.bytes(), m_sf.bytes());
- QCOMPARE(familie.data().size(), m_sf.data().size());
- QCOMPARE(familie.data(), m_sf.data());
+ QCOMPARE(family.size(), m_sf.size());
+ QCOMPARE(family.bytes(), m_sf.bytes());
+ QCOMPARE(family.data().size(), m_sf.data().size());
+ QCOMPARE(family.data(), m_sf.data());
}
void tst_QKnxNetIpDescriptionResponse::testSupportedFamiliesVersions()
diff --git a/tests/auto/qknxnetiprouter/tst_qknxnetiprouter.cpp b/tests/auto/qknxnetiprouter/tst_qknxnetiprouter.cpp
index ae435a2..3474b61 100644
--- a/tests/auto/qknxnetiprouter/tst_qknxnetiprouter.cpp
+++ b/tests/auto/qknxnetiprouter/tst_qknxnetiprouter.cpp
@@ -286,7 +286,7 @@ void tst_QKnxNetIpRouter::test_routing_sends_indications()
indicationSentEmitted = true;
QKnxNetIpRoutingIndicationProxy indicationSent(frame);
QVERIFY(indicationSent.isValid());
- QCOMPARE(indicationSent.linkLayerFrame().bytes(), frameSent.bytes());
+ QCOMPARE(indicationSent.cemi().bytes(), frameSent.bytes());
});
auto indication = QKnxNetIpRoutingIndicationProxy::builder()
.setCemi(frameSent)
diff --git a/tests/auto/qknxnetipsearchrequest/tst_qknxnetipsearchrequest.cpp b/tests/auto/qknxnetipsearchrequest/tst_qknxnetipsearchrequest.cpp
index 2abb15b..a97ca50 100644
--- a/tests/auto/qknxnetipsearchrequest/tst_qknxnetipsearchrequest.cpp
+++ b/tests/auto/qknxnetipsearchrequest/tst_qknxnetipsearchrequest.cpp
@@ -32,16 +32,6 @@
#include <QtKnx/qknxnetipsrp.h>
#include <QtKnx/QKnxServiceInfo>
-#if QT_VERSION < QT_VERSION_CHECK(5,13,0)
- QT_BEGIN_NAMESPACE
- bool operator==(const QKnxServiceInfo &lhs, const QKnxServiceInfo &rhs)
- {
- return (lhs.ServiceFamily == rhs.ServiceFamily)
- && (lhs.ServiceFamilyVersion == rhs.ServiceFamilyVersion);
- }
- QT_END_NAMESPACE
-#endif
-
char *toString(const QKnxByteArray &ba)
{
using QTest::toString;