summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--examples/mqtt/mqtt.pro1
-rw-r--r--examples/mqtt/quickpublication/main.cpp70
-rw-r--r--examples/mqtt/quickpublication/main.qml221
-rw-r--r--examples/mqtt/quickpublication/qml.qrc5
-rw-r--r--examples/mqtt/quickpublication/qmlmqttclient.cpp64
-rw-r--r--examples/mqtt/quickpublication/qmlmqttclient.h68
-rw-r--r--examples/mqtt/quickpublication/quickpublication.pro32
-rw-r--r--examples/mqtt/quicksubscription/main.qml7
-rw-r--r--examples/mqtt/websocketsubscription/main.cpp2
-rw-r--r--src/mqtt/doc/src/index.qdoc4
-rw-r--r--src/mqtt/doc/src/overview.qdoc56
-rw-r--r--src/mqtt/mqtt.pro17
-rw-r--r--src/mqtt/qmqttauthenticationproperties.cpp149
-rw-r--r--src/mqtt/qmqttauthenticationproperties.h73
-rw-r--r--src/mqtt/qmqttclient.cpp338
-rw-r--r--src/mqtt/qmqttclient.h30
-rw-r--r--src/mqtt/qmqttclient_p.h6
-rw-r--r--src/mqtt/qmqttconnection.cpp1163
-rw-r--r--src/mqtt/qmqttconnection_p.h36
-rw-r--r--src/mqtt/qmqttconnectionproperties.cpp651
-rw-r--r--src/mqtt/qmqttconnectionproperties.h166
-rw-r--r--src/mqtt/qmqttconnectionproperties_p.h93
-rw-r--r--src/mqtt/qmqttcontrolpacket.cpp45
-rw-r--r--src/mqtt/qmqttcontrolpacket_p.h4
-rw-r--r--src/mqtt/qmqttglobal.h51
-rw-r--r--src/mqtt/qmqttmessage.cpp38
-rw-r--r--src/mqtt/qmqttmessage.h2
-rw-r--r--src/mqtt/qmqttmessage_p.h74
-rw-r--r--src/mqtt/qmqttpublishproperties.cpp302
-rw-r--r--src/mqtt/qmqttpublishproperties.h117
-rw-r--r--src/mqtt/qmqttpublishproperties_p.h75
-rw-r--r--src/mqtt/qmqttsubscription.cpp81
-rw-r--r--src/mqtt/qmqttsubscription.h12
-rw-r--r--src/mqtt/qmqttsubscription_p.h7
-rw-r--r--src/mqtt/qmqttsubscriptionproperties.cpp168
-rw-r--r--src/mqtt/qmqttsubscriptionproperties.h84
-rw-r--r--src/mqtt/qmqtttopicfilter.cpp25
-rw-r--r--src/mqtt/qmqtttopicfilter.h2
-rw-r--r--src/mqtt/qmqtttype.cpp465
-rw-r--r--src/mqtt/qmqtttype.h75
-rw-r--r--tests/auto/auto.pro4
-rw-r--r--tests/auto/conformance/tst_conformance.cpp104
-rw-r--r--tests/auto/qmqttclient/tst_qmqttclient.cpp406
-rw-r--r--tests/auto/qmqttconnectionproperties/qmqttconnectionproperties.pro17
-rw-r--r--tests/auto/qmqttconnectionproperties/tst_qmqttconnectionproperties.cpp411
-rw-r--r--tests/auto/qmqttlastwillproperties/qmqttlastwillproperties.pro17
-rw-r--r--tests/auto/qmqttlastwillproperties/tst_qmqttlastwillproperties.cpp203
-rw-r--r--tests/auto/qmqttpublishproperties/qmqttpublishproperties.pro17
-rw-r--r--tests/auto/qmqttpublishproperties/tst_qmqttpublishproperties.cpp218
-rw-r--r--tests/auto/qmqttsubscription/tst_qmqttsubscription.cpp202
-rw-r--r--tests/auto/qmqttsubscriptionproperties/qmqttsubscriptionproperties.pro17
-rw-r--r--tests/auto/qmqttsubscriptionproperties/tst_qmqttsubscriptionproperties.cpp128
-rw-r--r--tests/auto/qmqtttopicfilter/tst_qmqtttopicfilter.cpp8
-rw-r--r--tests/benchmarks/qmqttclient/tst_qmqttclient.cpp4
-rw-r--r--tests/common/broker_connection.h27
56 files changed, 6368 insertions, 296 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 56c0f31..81caa27 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,4 +1,4 @@
load(qt_build_config)
CONFIG += warning_clean
-MODULE_VERSION = 5.11.2
+MODULE_VERSION = 5.12.0
diff --git a/examples/mqtt/mqtt.pro b/examples/mqtt/mqtt.pro
index 767ed5e..c734248 100644
--- a/examples/mqtt/mqtt.pro
+++ b/examples/mqtt/mqtt.pro
@@ -4,4 +4,5 @@ SUBDIRS += \
subscriptions
qtHaveModule(quick): SUBDIRS += quicksubscription
+qtHaveModule(quick): SUBDIRS += quickpublication
qtHaveModule(websockets): SUBDIRS += websocketsubscription
diff --git a/examples/mqtt/quickpublication/main.cpp b/examples/mqtt/quickpublication/main.cpp
new file mode 100644
index 0000000..a1bd557
--- /dev/null
+++ b/examples/mqtt/quickpublication/main.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $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 "qmlmqttclient.h"
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QLoggingCategory>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+
+ qmlRegisterType<QmlMqttClient>("MqttClient", 1, 0, "MqttClient");
+
+ engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/examples/mqtt/quickpublication/main.qml b/examples/mqtt/quickpublication/main.qml
new file mode 100644
index 0000000..b018e68
--- /dev/null
+++ b/examples/mqtt/quickpublication/main.qml
@@ -0,0 +1,221 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Window 2.2
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.1
+import MqttClient 1.0
+
+Window {
+ visible: true
+ width: 640
+ height: 480
+ title: qsTr("Qt Quick MQTT Publish Example")
+ id: root
+
+ MqttClient {
+ id: client
+ hostname: hostnameField.text
+ port: portField.text
+ }
+
+ ListModel {
+ id: messageModel
+ }
+
+ function addMessage(payload)
+ {
+ messageModel.insert(0, {"payload" : payload})
+
+ if (messageModel.count >= 100)
+ messageModel.remove(99)
+ }
+
+ GridLayout {
+ anchors.fill: parent
+ columns: 2
+
+ Label {
+ text: "Hostname:"
+ enabled: client.state === MqttClient.Disconnected
+ }
+
+ TextField {
+ id: hostnameField
+ Layout.fillWidth: true
+ text: "test.mosquitto.org"
+ placeholderText: "<Enter host running MQTT broker>"
+ enabled: client.state === MqttClient.Disconnected
+ }
+
+ Label {
+ text: "Port:"
+ enabled: client.state === MqttClient.Disconnected
+ }
+
+ TextField {
+ id: portField
+ Layout.fillWidth: true
+ text: "1883"
+ placeholderText: "<Port>"
+ inputMethodHints: Qt.ImhDigitsOnly
+ enabled: client.state === MqttClient.Disconnected
+ }
+
+ Button {
+ id: connectButton
+ Layout.columnSpan: 2
+ text: client.state === MqttClient.Connected ? "Disconnect" : "Connect"
+ onClicked: {
+ if (client.state === MqttClient.Connected)
+ client.disconnectFromHost()
+ else
+ client.connectToHost()
+ }
+ }
+
+ RowLayout {
+ enabled: client.state === MqttClient.Connected
+ Layout.columnSpan: 2
+ Layout.fillWidth: true
+
+ Label {
+ text: "Topic:"
+ }
+
+ TextField {
+ id: pubField
+ placeholderText: "<Publication topic>"
+ }
+
+ Label {
+ text: "Message:"
+ }
+
+ TextField {
+ id: msgField
+ placeholderText: "<Publication message>"
+ }
+ }
+
+ RowLayout {
+ enabled: client.state === MqttClient.Connected
+ Layout.columnSpan: 2
+ Layout.fillWidth: true
+
+ Label {
+ text: "QoS:"
+ }
+
+ ComboBox {
+ id: qosItems
+ editable: false
+ model: [0, 1, 2]
+ }
+
+ CheckBox {
+ id: retain
+ checked: false
+ text: "Retain"
+ }
+
+ Button {
+ id: pubButton
+ text: "Publish"
+ onClicked: {
+ if (pubField.text.length === 0) {
+ console.log("No payload to send. Skipping publish...")
+ return
+ }
+ client.publish(pubField.text, msgField.text, qosItems.currentText, retain.checked)
+ addMessage(msgField.text)
+ }
+ }
+ }
+
+ ListView {
+ id: messageView
+ model: messageModel
+ height: 300
+ width: 200
+ Layout.fillHeight: true
+ delegate: Rectangle {
+ width: 150
+ height: 30
+ color: index % 2 ? "#DDDDDD" : "#888888"
+ radius: 5
+ Text {
+ text: payload
+ anchors.centerIn: parent
+ }
+ }
+ }
+
+ Label {
+ function stateToString(value) {
+ if (value === 0)
+ return "Disconnected"
+ else if (value === 1)
+ return "Connecting"
+ else if (value === 2)
+ return "Connected"
+ else
+ return "Unknown"
+ }
+
+ Layout.columnSpan: 2
+ Layout.fillWidth: true
+ color: "#333333"
+ text: "Status:" + stateToString(client.state) + "(" + client.state + ")"
+ enabled: client.state === MqttClient.Connected
+ }
+ }
+}
diff --git a/examples/mqtt/quickpublication/qml.qrc b/examples/mqtt/quickpublication/qml.qrc
new file mode 100644
index 0000000..5f6483a
--- /dev/null
+++ b/examples/mqtt/quickpublication/qml.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/mqtt/quickpublication/qmlmqttclient.cpp b/examples/mqtt/quickpublication/qmlmqttclient.cpp
new file mode 100644
index 0000000..28f69fc
--- /dev/null
+++ b/examples/mqtt/quickpublication/qmlmqttclient.cpp
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $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 "qmlmqttclient.h"
+#include <QDebug>
+#include <QMqttTopicName>
+
+QmlMqttClient::QmlMqttClient(QObject *parent)
+ : QMqttClient(parent)
+{
+}
+
+int QmlMqttClient::publish(const QString &topic, const QString &message, int qos, bool retain)
+{
+ auto result = QMqttClient::publish(QMqttTopicName(topic), message.toUtf8(), qos, retain);
+ return result;
+}
diff --git a/examples/mqtt/quickpublication/qmlmqttclient.h b/examples/mqtt/quickpublication/qmlmqttclient.h
new file mode 100644
index 0000000..85b6532
--- /dev/null
+++ b/examples/mqtt/quickpublication/qmlmqttclient.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $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 QMLMQTTCLIENT_H
+#define QMLMQTTCLIENT_H
+
+#include <QtCore/QMap>
+#include <QtMqtt/QMqttClient>
+
+class QmlMqttClient : public QMqttClient
+{
+ Q_OBJECT
+public:
+ QmlMqttClient(QObject *parent = nullptr);
+
+ Q_INVOKABLE int publish(const QString &topic, const QString &message, int qos = 0, bool retain = false);
+private:
+ Q_DISABLE_COPY(QmlMqttClient)
+};
+
+#endif // QMLMQTTCLIENT_H
diff --git a/examples/mqtt/quickpublication/quickpublication.pro b/examples/mqtt/quickpublication/quickpublication.pro
new file mode 100644
index 0000000..20bdcc7
--- /dev/null
+++ b/examples/mqtt/quickpublication/quickpublication.pro
@@ -0,0 +1,32 @@
+TEMPLATE = app
+
+QT += qml quick mqtt
+CONFIG += c++11
+
+SOURCES += main.cpp \
+ qmlmqttclient.cpp
+
+HEADERS += \
+ qmlmqttclient.h
+
+RESOURCES += qml.qrc
+
+# Additional import path used to resolve QML modules in Qt Creator's code model
+QML_IMPORT_PATH =
+
+# Additional import path used to resolve QML modules just for Qt Quick Designer
+QML_DESIGNER_IMPORT_PATH =
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+target.path = $$[QT_INSTALL_EXAMPLES]/mqtt/quickpublication
+INSTALLS += target
diff --git a/examples/mqtt/quicksubscription/main.qml b/examples/mqtt/quicksubscription/main.qml
index d42eddd..7b930e2 100644
--- a/examples/mqtt/quicksubscription/main.qml
+++ b/examples/mqtt/quicksubscription/main.qml
@@ -58,7 +58,7 @@ Window {
visible: true
width: 640
height: 480
- title: qsTr("Hello World")
+ title: qsTr("Qt Quick MQTT Subscription Example")
id: root
property var tempSubscription: 0
@@ -93,7 +93,6 @@ Window {
TextField {
id: hostnameField
Layout.fillWidth: true
-// text: "broker.hivemq.com"
text: "test.mosquitto.org"
placeholderText: "<Enter host running MQTT broker>"
enabled: client.state === MqttClient.Disconnected
@@ -143,8 +142,10 @@ Window {
id: subButton
text: "Subscribe"
onClicked: {
- if (subField.text.length === 0)
+ if (subField.text.length === 0) {
+ console.log("No topic specified to subscribe to.")
return
+ }
tempSubscription = client.subscribe(subField.text)
tempSubscription.messageReceived.connect(addMessage)
}
diff --git a/examples/mqtt/websocketsubscription/main.cpp b/examples/mqtt/websocketsubscription/main.cpp
index 03ca84e..a91fc89 100644
--- a/examples/mqtt/websocketsubscription/main.cpp
+++ b/examples/mqtt/websocketsubscription/main.cpp
@@ -99,7 +99,7 @@ int main(int argc, char *argv[])
} else if (versionString == "3") {
clientsub.setVersion(3);
} else {
- qWarning() << "Unknown MQTT version";
+ qInfo() << "Unknown MQTT version";
return -2;
}
diff --git a/src/mqtt/doc/src/index.qdoc b/src/mqtt/doc/src/index.qdoc
index fd50391..92aacbb 100644
--- a/src/mqtt/doc/src/index.qdoc
+++ b/src/mqtt/doc/src/index.qdoc
@@ -41,8 +41,8 @@
The Qt MQTT module provides a standard compliant implementation of the MQTT
protocol specification. It enables applications to act as telemetry displays
- and devices to publish telemetry data. The supported versions are MQTT 3.1
- and MQTT 3.1.1.
+ and devices to publish telemetry data. The supported versions are MQTT 3.1,
+ MQTT 3.1.1, and MQTT 5.0.
\note Qt MQTT is part of the Qt for Automation offering and not Qt. For further
details please see \l {Qt for Automation}.
diff --git a/src/mqtt/doc/src/overview.qdoc b/src/mqtt/doc/src/overview.qdoc
index 64f5fab..5ba73c1 100644
--- a/src/mqtt/doc/src/overview.qdoc
+++ b/src/mqtt/doc/src/overview.qdoc
@@ -90,6 +90,61 @@
receive messages on the temperature of all rooms in all apartments in a
house.
+ \section1 Shared Subscriptions
+
+ \e {Shared subscriptions} describe a pool of subscribers to one topic
+ filter. Instead of all subscribers receiving a message, only one subscriber
+ receives it. This enables load balancing on multiple clients.
+ The format of a shared subscription is:
+
+ \badcode
+ $share/{sharename}/{topicfilter}
+ \endcode
+
+ For example, if \e {Client 1} and \e {Client 2} should share a subscription
+ to the topic \e {sensors/house/temperature}, the topic filter to subscribe
+ to is:
+
+ \badcode
+ $share/poolAB/sensors/house/temperature
+ \endcode
+
+ It is not defined in which order messages are distributed by the server.
+ This is a server specific option.
+
+ To identify whether a server supports shared subscriptions, see also
+ QMqttServerConnectionProperties::sharedSubscriptionSupported().
+
+ \section1 Topic Aliases
+
+ Structuring topics in a tree helps to separate data channels and provide a
+ logical order of information. However, this can lead to very long topic
+ names being used during the publication of messages, hence increasing
+ the size of each message.
+
+ The MQTT 5.0 protocol version introduced \e {topic aliases} to circumvent
+ this. Instead of the topic string, an integer value is sent. To create an
+ initial mapping between the client and the server, both the topic string and
+ the alias need to be part of a message. Thereafter, only the ID with an
+ empty topic is used.
+
+ This mapping can be changed at any time by using a topic alias with another
+ topic string. Note that this mapping does not necessarily apply to other
+ connections, such as connections from the server to other clients. Each
+ connection needs to create this mapping manually.
+
+ Qt MQTT provides an automated mechanism to help reduce data rates. After
+ QMqttClient creates a connection, information about topic aliases supported
+ by the server is stored. Subsequently, topic aliases are used in the
+ order the messages are published, until all available aliases are in use. A
+ user is always able to modify this mapping by using
+ QMqttPublishProperties::setTopicAlias() during publication.
+
+ When QMqttClient subscribes to a topic, the server can use topic aliases
+ as well, depending on the QMqttConnectionProperties::maximumTopicAlias()
+ value set by the client. The client automatically maps topic aliases and
+ transparently forwards messages to the user including the full topic string.
+
\section1 Security
The connections between the clients and the broker are secured by an
@@ -145,4 +200,5 @@
any previously retained message for its topic at the broker \e{must} be
discarded. The broker \e{should} store the last message, but \e{may}
also discard it. This depends on the implementation of the broker.
+
*/
diff --git a/src/mqtt/mqtt.pro b/src/mqtt/mqtt.pro
index c7866ba..aa6c99c 100644
--- a/src/mqtt/mqtt.pro
+++ b/src/mqtt/mqtt.pro
@@ -8,27 +8,40 @@ QT += core-private
QMAKE_DOCS = $$PWD/doc/qtmqtt.qdocconf
PUBLIC_HEADERS += \
+ qmqttauthenticationproperties.h \
qmqttglobal.h \
qmqttclient.h \
+ qmqttconnectionproperties.h \
qmqttmessage.h \
+ qmqttpublishproperties.h \
qmqttsubscription.h \
+ qmqttsubscriptionproperties.h \
qmqtttopicfilter.h \
- qmqtttopicname.h
+ qmqtttopicname.h \
+ qmqtttype.h
PRIVATE_HEADERS += \
qmqttclient_p.h \
qmqttconnection_p.h \
+ qmqttconnectionproperties_p.h \
qmqttcontrolpacket_p.h \
+ qmqttmessage_p.h \
+ qmqttpublishproperties_p.h \
qmqttsubscription_p.h
SOURCES += \
+ qmqttauthenticationproperties.cpp \
qmqttclient.cpp \
qmqttconnection.cpp \
+ qmqttconnectionproperties.cpp \
qmqttcontrolpacket.cpp \
qmqttmessage.cpp \
+ qmqttpublishproperties.cpp \
qmqttsubscription.cpp \
+ qmqttsubscriptionproperties.cpp \
qmqtttopicfilter.cpp \
- qmqtttopicname.cpp
+ qmqtttopicname.cpp \
+ qmqtttype.cpp
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
diff --git a/src/mqtt/qmqttauthenticationproperties.cpp b/src/mqtt/qmqttauthenticationproperties.cpp
new file mode 100644
index 0000000..0425939
--- /dev/null
+++ b/src/mqtt/qmqttauthenticationproperties.cpp
@@ -0,0 +1,149 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 "qmqttauthenticationproperties.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QMqttAuthenticationProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttAuthenticationProperties class represents configuration
+ options during the authentication process.
+
+ \note Authentication properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+class QMqttAuthenticationPropertiesData : public QSharedData
+{
+public:
+ QString authenticationMethod;
+ QByteArray authenticationData;
+ QString reason;
+ QMqttUserProperties userProperties;
+};
+
+/*!
+ \internal
+*/
+QMqttAuthenticationProperties::QMqttAuthenticationProperties() : data(new QMqttAuthenticationPropertiesData)
+{
+
+}
+
+/*!
+ \internal
+*/
+QMqttAuthenticationProperties::QMqttAuthenticationProperties(const QMqttAuthenticationProperties &) = default;
+
+QMqttAuthenticationProperties &QMqttAuthenticationProperties::operator=(const QMqttAuthenticationProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqttAuthenticationProperties::~QMqttAuthenticationProperties() = default;
+
+/*!
+ Returns the authentication method.
+*/
+QString QMqttAuthenticationProperties::authenticationMethod() const
+{
+ return data->authenticationMethod;
+}
+
+/*!
+ Sets the authentication method to \a method.
+*/
+void QMqttAuthenticationProperties::setAuthenticationMethod(const QString &method)
+{
+ data->authenticationMethod = method;
+}
+
+/*!
+ Returns the authentication data
+*/
+QByteArray QMqttAuthenticationProperties::authenticationData() const
+{
+ return data->authenticationData;
+}
+
+/*!
+ Sets the authentication data to \a adata.
+
+ Authentication data can only be used if an authentication method has
+ been specified.
+
+ \sa authenticationMethod()
+*/
+void QMqttAuthenticationProperties::setAuthenticationData(const QByteArray &adata)
+{
+ data->authenticationData = adata;
+}
+
+/*!
+ Returns the reason string. The reason string specifies the reason for
+ a disconnect.
+*/
+QString QMqttAuthenticationProperties::reason() const
+{
+ return data->reason;
+}
+
+/*!
+ Sets the reason string to \a r.
+*/
+void QMqttAuthenticationProperties::setReason(const QString &r)
+{
+ data->reason = r;
+}
+
+/*!
+ Returns the user properties.
+*/
+QMqttUserProperties QMqttAuthenticationProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+/*!
+ Sets the user properties to \a user.
+*/
+void QMqttAuthenticationProperties::setUserProperties(const QMqttUserProperties &user)
+{
+ data->userProperties = user;
+}
+
+QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttauthenticationproperties.h b/src/mqtt/qmqttauthenticationproperties.h
new file mode 100644
index 0000000..b587338
--- /dev/null
+++ b/src/mqtt/qmqttauthenticationproperties.h
@@ -0,0 +1,73 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTAUTHENTICATIONPROPERTIES_H
+#define QMQTTAUTHENTICATIONPROPERTIES_H
+
+#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqtttype.h>
+
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttAuthenticationPropertiesData;
+
+class Q_MQTT_EXPORT QMqttAuthenticationProperties
+{
+ Q_GADGET
+
+public:
+ QMqttAuthenticationProperties();
+ QMqttAuthenticationProperties(const QMqttAuthenticationProperties &);
+ QMqttAuthenticationProperties &operator=(const QMqttAuthenticationProperties &);
+ ~QMqttAuthenticationProperties();
+
+ QString authenticationMethod() const;
+ void setAuthenticationMethod(const QString &method);
+
+ QByteArray authenticationData() const;
+ void setAuthenticationData(const QByteArray &adata);
+
+ QString reason() const;
+ void setReason(const QString &r);
+
+ QMqttUserProperties userProperties() const;
+ void setUserProperties(const QMqttUserProperties &user);
+
+private:
+ QSharedDataPointer<QMqttAuthenticationPropertiesData> data;
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTAUTHENTICATIONPROPERTIES_H
diff --git a/src/mqtt/qmqttclient.cpp b/src/mqtt/qmqttclient.cpp
index 5150663..fd6ca5d 100644
--- a/src/mqtt/qmqttclient.cpp
+++ b/src/mqtt/qmqttclient.cpp
@@ -30,11 +30,14 @@
#include "qmqttclient.h"
#include "qmqttclient_p.h"
+#include <QtCore/QLoggingCategory>
#include <QtCore/QUuid>
#include <QtCore/QtEndian>
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcMqttClient, "qt.mqtt.client")
+
/*!
\class QMqttClient
@@ -207,6 +210,9 @@ QT_BEGIN_NAMESPACE
connection.
\value UnknownError
An unknown error occurred.
+ \value Mqtt5SpecificError
+ The error is related to MQTT protocol level 5. A reason code might
+ provide more details.
*/
/*!
@@ -219,6 +225,8 @@ QT_BEGIN_NAMESPACE
MQTT Standard 3.1
\value MQTT_3_1_1
MQTT Standard 3.1.1, publicly referred to as version 4
+ \value MQTT_5_0
+ MQTT Standard 5.0
*/
/*!
@@ -243,6 +251,15 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \fn QMqttClient::messageStatusChanged(qint32 id, QMqtt::MessageStatus s, const QMqttMessageStatusProperties &properties);
+ \since 5.12
+
+ This signal is emitted when the status for the message identified by \a id
+ changes. \a s specifies the new status of the message, and
+ \a properties specify additional properties provided by the server.
+*/
+
+/*!
\fn QMqttClient::messageSent(qint32 id)
Indicates that a message that was sent via the publish() function has been
@@ -269,6 +286,37 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \since 5.12
+ \fn QMqttClient::authenticationRequested(const QMqttAuthenticationProperties &p)
+
+ This signal is emitted after a client invoked QMqttClient::connectToHost or
+ QMqttClient::connectToHostEncrypted and before the connection is
+ established. In extended authentication, a broker might request additional
+ details which need to be provided by invoking QMqttClient::authenticate.
+ \a p specifies properties provided by the broker.
+
+ \note Extended authentication is part of the MQTT 5.0 standard and can
+ only be used when the client specifies MQTT_5_0 as ProtocolVersion.
+
+ \sa authenticationFinished(), authenticate()
+*/
+
+/*!
+ \since 5.12
+ \fn QMqttClient::authenticationFinished(const QMqttAuthenticationProperties &p)
+
+ This signal is emitted after extended authentication has finished. \a p
+ specifies available details on the authentication process.
+
+ After successful authentication QMqttClient::connected is emitted.
+
+ \note Extended authentication is part of the MQTT 5.0 standard and can
+ only be used when the client specifies MQTT_5_0 as ProtocolVersion.
+
+ \sa authenticationRequested(), authenticate()
+*/
+
+/*
Creates a new MQTT client instance with the specified \a parent.
*/
QMqttClient::QMqttClient(QObject *parent) : QObject(*(new QMqttClientPrivate(this)), parent)
@@ -293,7 +341,7 @@ void QMqttClient::setTransport(QIODevice *device, QMqttClient::TransportType tra
Q_D(QMqttClient);
if (d->m_state != Disconnected) {
- qWarning("Changing transport layer while connected is not possible");
+ qCDebug(lcMqttClient) << "Changing transport layer while connected is not possible.";
return;
}
d->m_connection.setTransport(device, transport);
@@ -319,12 +367,33 @@ QIODevice *QMqttClient::transport() const
*/
QMqttSubscription *QMqttClient::subscribe(const QMqttTopicFilter &topic, quint8 qos)
{
+ return subscribe(topic, QMqttSubscriptionProperties(), qos);
+}
+
+/*!
+ \since 5.12
+
+ Adds a new subscription to receive notifications on \a topic. The parameter
+ \a properties specifies additional subscription properties to be validated
+ by the broker. The parameter \a qos specifies the level at which security
+ messages are received. For more information about the available QoS levels,
+ see \l {Quality of Service}.
+
+ This function returns a pointer to a \l QMqttSubscription. If the same topic
+ is subscribed twice, the return value points to the same subscription
+ instance. The MQTT client is the owner of the subscription.
+
+ \note \a properties will only be passed to the broker when the client
+ specifies MQTT_5_0 as ProtocolVersion.
+*/
+QMqttSubscription *QMqttClient::subscribe(const QMqttTopicFilter &topic, const QMqttSubscriptionProperties &properties, quint8 qos)
+{
Q_D(QMqttClient);
if (d->m_state != QMqttClient::Connected)
return nullptr;
- return d->m_connection.sendControlSubscribe(topic, qos);
+ return d->m_connection.sendControlSubscribe(topic, qos, properties);
}
/*!
@@ -336,8 +405,26 @@ QMqttSubscription *QMqttClient::subscribe(const QMqttTopicFilter &topic, quint8
*/
void QMqttClient::unsubscribe(const QMqttTopicFilter &topic)
{
+ unsubscribe(topic, QMqttUnsubscriptionProperties());
+}
+
+/*!
+ \since 5.12
+
+ Unsubscribes from \a topic. No notifications will be sent to any of the
+ subscriptions made by calling subscribe(). \a properties specifies
+ additional user properties to be passed to the broker.
+
+ \note If a client disconnects from a broker without unsubscribing, the
+ broker will store all messages and publish them on the next reconnect.
+
+ \note \a properties will only be passed to the broker when the client
+ specifies MQTT_5_0 as ProtocolVersion.
+*/
+void QMqttClient::unsubscribe(const QMqttTopicFilter &topic, const QMqttUnsubscriptionProperties &properties)
+{
Q_D(QMqttClient);
- d->m_connection.sendControlUnsubscribe(topic);
+ d->m_connection.sendControlUnsubscribe(topic, properties);
}
/*!
@@ -348,9 +435,30 @@ void QMqttClient::unsubscribe(const QMqttTopicFilter &topic)
other clients to connect and receive the message.
Returns an ID that is used internally to identify the message.
- */
+*/
qint32 QMqttClient::publish(const QMqttTopicName &topic, const QByteArray &message, quint8 qos, bool retain)
{
+ return publish(topic, QMqttPublishProperties(), message, qos, retain);
+}
+
+/*!
+ \since 5.12
+
+ Publishes a \a message to the broker with the specified \a properties and
+ \a topic. \a qos specifies the level of security required for transferring
+ the message.
+
+ If \a retain is set to \c true, the message will stay on the broker for
+ other clients to connect and receive the message.
+
+ Returns an ID that is used internally to identify the message.
+
+ \note \a properties will only be passed to the broker when the client
+ specifies MQTT_5_0 as ProtocolVersion.
+*/
+qint32 QMqttClient::publish(const QMqttTopicName &topic, const QMqttPublishProperties &properties,
+ const QByteArray &message, quint8 qos, bool retain)
+{
Q_D(QMqttClient);
if (qos > 2)
return -1;
@@ -358,7 +466,7 @@ qint32 QMqttClient::publish(const QMqttTopicName &topic, const QByteArray &messa
if (d->m_state != QMqttClient::Connected)
return -1;
- return d->m_connection.sendControlPublish(topic, message, qos, retain);
+ return d->m_connection.sendControlPublish(topic, message, qos, retain, properties);
}
/*!
@@ -414,12 +522,12 @@ void QMqttClient::connectToHost(bool encrypted, const QString &sslPeerName)
Q_D(QMqttClient);
if (state() == QMqttClient::Connected) {
- qWarning("Already connected to a broker. Rejecting connection request.");
+ qCDebug(lcMqttClient) << "Already connected to a broker. Rejecting connection request.";
return;
}
if (!d->m_connection.ensureTransport(encrypted)) {
- qWarning("Could not ensure connection");
+ qCDebug(lcMqttClient) << "Could not ensure connection.";
d->setStateAndError(Disconnected, TransportInvalid);
return;
}
@@ -430,7 +538,7 @@ void QMqttClient::connectToHost(bool encrypted, const QString &sslPeerName)
d->m_connection.cleanSubscriptions();
if (!d->m_connection.ensureTransportOpen(sslPeerName)) {
- qWarning("Could not ensure that connection is open");
+ qCDebug(lcMqttClient) << "Could not ensure that connection is open.";
d->setStateAndError(Disconnected, TransportInvalid);
return;
}
@@ -450,11 +558,11 @@ void QMqttClient::disconnectFromHost()
switch (d->m_connection.internalState()) {
case QMqttConnection::BrokerConnected:
d->m_connection.sendControlDisconnect();
+ break;
case QMqttConnection::BrokerDisconnected:
- return;
+ break;
case QMqttConnection::BrokerConnecting:
case QMqttConnection::BrokerWaitForConnectAck:
- default:
d->m_connection.m_transport->close();
break;
}
@@ -508,6 +616,122 @@ bool QMqttClient::willRetain() const
return d->m_willRetain;
}
+/*!
+ \since 5.12
+
+ Sets the connection properties to \a prop. \l QMqttConnectionProperties
+ can be used to ask the server to use a specific feature set. After a
+ connection request the server response can be obtained by calling
+ \l QMqttClient::serverConnectionProperties.
+
+ \note The connection properties can only be set if the MQTT client is in the
+ \l Disconnected state.
+
+ \note QMqttConnectionProperties can only be used when the client specifies
+ MQTT_5_0 as ProtocolVersion.
+*/
+void QMqttClient::setConnectionProperties(const QMqttConnectionProperties &prop)
+{
+ Q_D(QMqttClient);
+ d->m_connectionProperties = prop;
+}
+
+/*!
+ \since 5.12
+
+ Returns the connection properties the client requests to the broker.
+
+ \note QMqttConnectionProperties can only be used when the client specifies
+ MQTT_5_0 as ProtocolVersion.
+*/
+QMqttConnectionProperties QMqttClient::connectionProperties() const
+{
+ Q_D(const QMqttClient);
+ return d->m_connectionProperties;
+}
+
+/*!
+ \since 5.12
+
+ Sets the last will properties to \a prop. QMqttLastWillProperties allows
+ to set additional features for the last will message stored at the broker.
+
+ \note The connection properties can only be set if the MQTT client is in the
+ \l Disconnected state.
+
+ \note QMqttLastWillProperties can only be used when the client specifies
+ MQTT_5_0 as ProtocolVersion.
+*/
+void QMqttClient::setLastWillProperties(const QMqttLastWillProperties &prop)
+{
+ Q_D(QMqttClient);
+ d->m_lastWillProperties = prop;
+}
+
+/*!
+ \since 5.12
+
+ Returns the last will properties.
+
+ \note QMqttLastWillProperties can only be used when the client specifies
+ MQTT_5_0 as ProtocolVersion.
+*/
+QMqttLastWillProperties QMqttClient::lastWillProperties() const
+{
+ Q_D(const QMqttClient);
+ return d->m_lastWillProperties;
+}
+
+/*!
+ \since 5.12
+
+ Returns the QMqttServerConnectionProperties the broker returned after a
+ connection attempt.
+
+ This can be used to verify that client side connection properties set by
+ QMqttClient::setConnectionProperties have been accepted by the broker. Also,
+ in case of a failed connection attempt, it can be used for connection
+ diagnostics.
+
+ \note QMqttServerConnectionProperties can only be used when the client
+ specifies MQTT_5_0 as ProtocolVersion.
+
+ \sa connectionProperties()
+*/
+QMqttServerConnectionProperties QMqttClient::serverConnectionProperties() const
+{
+ Q_D(const QMqttClient);
+ return d->m_serverConnectionProperties;
+}
+
+/*!
+ \since 5.12
+
+ Sends an authentication request to the broker. \a prop specifies
+ the required information to fulfill the authentication request.
+
+ This function should only be called after a
+ QMqttClient::authenticationRequested signal has been emitted.
+
+ \note Extended authentication is part of the MQTT 5.0 standard and can
+ only be used when the client specifies MQTT_5_0 as ProtocolVersion.
+
+ \sa authenticationRequested(), authenticationFinished()
+*/
+void QMqttClient::authenticate(const QMqttAuthenticationProperties &prop)
+{
+ Q_D(QMqttClient);
+ if (protocolVersion() != QMqttClient::MQTT_5_0) {
+ qCDebug(lcMqttClient) << "Authentication is only supported on protocol level 5.";
+ return;
+ }
+ if (state() == QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Cannot send authentication request while disconnected.";
+ return;
+ }
+ d->m_connection.sendControlAuthenticate(prop);
+}
+
QMqttClient::ClientError QMqttClient::error() const
{
Q_D(const QMqttClient);
@@ -535,6 +759,12 @@ quint16 QMqttClient::keepAlive() const
void QMqttClient::setHostname(const QString &hostname)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing hostname while connected is not possible.";
+ return;
+ }
+
if (d->m_hostname == hostname)
return;
@@ -545,6 +775,12 @@ void QMqttClient::setHostname(const QString &hostname)
void QMqttClient::setPort(quint16 port)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing port while connected is not possible.";
+ return;
+ }
+
if (d->m_port == port)
return;
@@ -555,11 +791,12 @@ void QMqttClient::setPort(quint16 port)
void QMqttClient::setClientId(const QString &clientId)
{
Q_D(QMqttClient);
- if (d->m_clientId == clientId)
- return;
- d->m_clientId = clientId;
- emit clientIdChanged(clientId);
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing client ID while connected is not possible.";
+ return;
+ }
+ d->setClientId(clientId);
}
void QMqttClient::setKeepAlive(quint16 keepAlive)
@@ -569,7 +806,7 @@ void QMqttClient::setKeepAlive(quint16 keepAlive)
return;
if (state() != QMqttClient::Disconnected) {
- qWarning("Trying to modify keepAlive while connected.");
+ qCDebug(lcMqttClient) << "Changing keepAlive while connected is not possible.";
return;
}
@@ -580,11 +817,16 @@ void QMqttClient::setKeepAlive(quint16 keepAlive)
void QMqttClient::setProtocolVersion(ProtocolVersion protocolVersion)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing protocol version while connected is not possible.";
+ return;
+ }
+
if (d->m_protocolVersion == protocolVersion)
return;
- // Only MQTT 3 and 4 are supported
- if (protocolVersion < 3 || protocolVersion > 4)
+ if (protocolVersion < 3 || protocolVersion > 5)
return;
d->m_protocolVersion = protocolVersion;
@@ -608,6 +850,12 @@ void QMqttClient::setState(ClientState state)
void QMqttClient::setUsername(const QString &username)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing username while connected is not possible.";
+ return;
+ }
+
if (d->m_username == username)
return;
@@ -618,6 +866,12 @@ void QMqttClient::setUsername(const QString &username)
void QMqttClient::setPassword(const QString &password)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing password while connected is not possible.";
+ return;
+ }
+
if (d->m_password == password)
return;
@@ -628,6 +882,12 @@ void QMqttClient::setPassword(const QString &password)
void QMqttClient::setCleanSession(bool cleanSession)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing clean session while connected is not possible.";
+ return;
+ }
+
if (d->m_cleanSession == cleanSession)
return;
@@ -638,6 +898,12 @@ void QMqttClient::setCleanSession(bool cleanSession)
void QMqttClient::setWillTopic(const QString &willTopic)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing will topic while connected is not possible.";
+ return;
+ }
+
if (d->m_willTopic == willTopic)
return;
@@ -648,6 +914,12 @@ void QMqttClient::setWillTopic(const QString &willTopic)
void QMqttClient::setWillQoS(quint8 willQoS)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing will qos while connected is not possible.";
+ return;
+ }
+
if (d->m_willQoS == willQoS)
return;
@@ -658,6 +930,12 @@ void QMqttClient::setWillQoS(quint8 willQoS)
void QMqttClient::setWillMessage(const QByteArray &willMessage)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing will message while connected is not possible.";
+ return;
+ }
+
if (d->m_willMessage == willMessage)
return;
@@ -668,6 +946,12 @@ void QMqttClient::setWillMessage(const QByteArray &willMessage)
void QMqttClient::setWillRetain(bool willRetain)
{
Q_D(QMqttClient);
+
+ if (state() != QMqttClient::Disconnected) {
+ qCDebug(lcMqttClient) << "Changing will retain while connected is not possible.";
+ return;
+ }
+
if (d->m_willRetain == willRetain)
return;
@@ -694,6 +978,15 @@ QMqttClientPrivate::QMqttClientPrivate(QMqttClient *c)
m_clientId.remove(QLatin1Char('}'));
m_clientId.remove(QLatin1Char('-'));
m_clientId.resize(23);
+#ifdef QT_BUILD_INTERNAL
+ // Some test servers require a username token
+ if (qEnvironmentVariableIsSet("QT_MQTT_TEST_USERNAME"))
+ m_username = qEnvironmentVariable("QT_MQTT_TEST_USERNAME");
+ if (qEnvironmentVariableIsSet("QT_MQTT_TEST_PASSWORD"))
+ m_password = qEnvironmentVariable("QT_MQTT_TEST_PASSWORD");
+ if (qEnvironmentVariableIsSet("QT_MQTT_TEST_CLIENTID"))
+ m_clientId = qEnvironmentVariable("QT_MQTT_TEST_CLIENTID");
+#endif
}
QMqttClientPrivate::~QMqttClientPrivate()
@@ -710,4 +1003,15 @@ void QMqttClientPrivate::setStateAndError(QMqttClient::ClientState s, QMqttClien
q->setError(e);
}
+void QMqttClientPrivate::setClientId(const QString &id)
+{
+ Q_Q(QMqttClient);
+
+ if (m_clientId == id)
+ return;
+
+ m_clientId = id;
+ emit q->clientIdChanged(id);
+}
+
QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttclient.h b/src/mqtt/qmqttclient.h
index 9b06ae7..92e114b 100644
--- a/src/mqtt/qmqttclient.h
+++ b/src/mqtt/qmqttclient.h
@@ -31,7 +31,11 @@
#define QTMQTTCLIENT_H
#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqttauthenticationproperties.h>
+#include <QtMqtt/qmqttconnectionproperties.h>
+#include <QtMqtt/qmqttpublishproperties.h>
#include <QtMqtt/qmqttsubscription.h>
+#include <QtMqtt/qmqttsubscriptionproperties.h>
#include <QtMqtt/qmqtttopicfilter.h>
#include <QtCore/QIODevice>
@@ -67,11 +71,13 @@ public:
// Qt states
TransportInvalid = 256,
ProtocolViolation,
- UnknownError
+ UnknownError,
+ Mqtt5SpecificError
};
enum ProtocolVersion {
MQTT_3_1 = 3,
- MQTT_3_1_1 = 4
+ MQTT_3_1_1 = 4,
+ MQTT_5_0 = 5
};
private:
@@ -99,10 +105,18 @@ public:
QIODevice *transport() const;
QMqttSubscription *subscribe(const QMqttTopicFilter &topic, quint8 qos = 0);
+ QMqttSubscription *subscribe(const QMqttTopicFilter &topic,
+ const QMqttSubscriptionProperties &properties, quint8 qos = 0);
void unsubscribe(const QMqttTopicFilter &topic);
+ void unsubscribe(const QMqttTopicFilter &topic, const QMqttUnsubscriptionProperties &properties);
Q_INVOKABLE qint32 publish(const QMqttTopicName &topic, const QByteArray &message = QByteArray(),
quint8 qos = 0, bool retain = false);
+ Q_INVOKABLE qint32 publish(const QMqttTopicName &topic, const QMqttPublishProperties &properties,
+ const QByteArray &message = QByteArray(),
+ quint8 qos = 0,
+ bool retain = false);
+
bool requestPing();
QString hostname() const;
@@ -129,10 +143,20 @@ public:
QByteArray willMessage() const;
bool willRetain() const;
+ void setConnectionProperties(const QMqttConnectionProperties &prop);
+ QMqttConnectionProperties connectionProperties() const;
+
+ void setLastWillProperties(const QMqttLastWillProperties &prop);
+ QMqttLastWillProperties lastWillProperties() const;
+
+ QMqttServerConnectionProperties serverConnectionProperties() const;
+
+ void authenticate(const QMqttAuthenticationProperties &prop);
Q_SIGNALS:
void connected();
void disconnected();
void messageReceived(const QByteArray &message, const QMqttTopicName &topic = QMqttTopicName());
+ void messageStatusChanged(qint32 id, QMqtt::MessageStatus s, const QMqttMessageStatusProperties &properties);
void messageSent(qint32 id);
void pingResponseReceived();
void brokerSessionRestored();
@@ -153,6 +177,8 @@ Q_SIGNALS:
void willMessageChanged(QByteArray willMessage);
void willRetainChanged(bool willRetain);
+ void authenticationRequested(const QMqttAuthenticationProperties &p);
+ void authenticationFinished(const QMqttAuthenticationProperties &p);
public Q_SLOTS:
void setHostname(const QString &hostname);
void setPort(quint16 port);
diff --git a/src/mqtt/qmqttclient_p.h b/src/mqtt/qmqttclient_p.h
index 2bec37e..2ce9f48 100644
--- a/src/mqtt/qmqttclient_p.h
+++ b/src/mqtt/qmqttclient_p.h
@@ -57,14 +57,13 @@ public:
QMqttClientPrivate(QMqttClient *c);
~QMqttClientPrivate() override;
void setStateAndError(QMqttClient::ClientState s, QMqttClient::ClientError e = QMqttClient::NoError);
+ void setClientId(const QString &id);
QMqttClient *m_client{nullptr};
QString m_hostname;
quint16 m_port{0};
QMqttConnection m_connection;
QString m_clientId; // auto-generated
quint16 m_keepAlive{60};
- // 3 == MQTT Standard 3.1
- // 4 == MQTT Standard 3.1.1
QMqttClient::ProtocolVersion m_protocolVersion{QMqttClient::MQTT_3_1_1};
QMqttClient::ClientState m_state{QMqttClient::Disconnected};
QMqttClient::ClientError m_error{QMqttClient::NoError};
@@ -75,6 +74,9 @@ public:
QString m_username;
QString m_password;
bool m_cleanSession{true};
+ QMqttConnectionProperties m_connectionProperties;
+ QMqttLastWillProperties m_lastWillProperties;
+ QMqttServerConnectionProperties m_serverConnectionProperties;
};
QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttconnection.cpp b/src/mqtt/qmqttconnection.cpp
index f010cad..fc0ac13 100644
--- a/src/mqtt/qmqttconnection.cpp
+++ b/src/mqtt/qmqttconnection.cpp
@@ -28,7 +28,10 @@
******************************************************************************/
#include "qmqttconnection_p.h"
+#include "qmqttconnectionproperties_p.h"
#include "qmqttcontrolpacket_p.h"
+#include "qmqttmessage_p.h"
+#include "qmqttpublishproperties_p.h"
#include "qmqttsubscription_p.h"
#include "qmqttclient_p.h"
@@ -44,6 +47,50 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcMqttConnection, "qt.mqtt.connection")
Q_LOGGING_CATEGORY(lcMqttConnectionVerbose, "qt.mqtt.connection.verbose");
+template<>
+quint32 QMqttConnection::readBufferTyped(qint64 *dataSize)
+{
+ if (dataSize)
+ *dataSize -= sizeof(quint32);
+ return qFromBigEndian<quint32>(reinterpret_cast<const quint32 *>(readBuffer(4).constData()));
+}
+
+template<>
+quint16 QMqttConnection::readBufferTyped(qint64 *dataSize)
+{
+ if (dataSize)
+ *dataSize -= sizeof(quint16);
+ return qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(readBuffer(2).constData()));
+}
+
+template<>
+quint8 QMqttConnection::readBufferTyped(qint64 *dataSize)
+{
+ quint8 result;
+ readBuffer(reinterpret_cast<char *>(&result), 1);
+ if (dataSize)
+ *dataSize -= sizeof(quint8);
+ return result;
+}
+
+template<>
+QString QMqttConnection::readBufferTyped(qint64 *dataSize)
+{
+ const quint16 size = readBufferTyped<quint16>(dataSize);
+ if (dataSize)
+ *dataSize -= size;
+ return QString::fromUtf8(reinterpret_cast<const char *>(readBuffer(size).constData()), size);
+}
+
+template<>
+QByteArray QMqttConnection::readBufferTyped(qint64 *dataSize)
+{
+ const quint16 size = readBufferTyped<quint16>(dataSize);
+ if (dataSize)
+ *dataSize -= size;
+ return QByteArray(reinterpret_cast<const char *>(readBuffer(size).constData()), size);
+}
+
QMqttConnection::QMqttConnection(QObject *parent) : QObject(parent)
{
m_pingTimer.setSingleShot(false);
@@ -97,7 +144,7 @@ bool QMqttConnection::ensureTransport(bool createSecureIfNeeded)
// We are asked to create a transport layer
if (m_clientPrivate->m_hostname.isEmpty() || m_clientPrivate->m_port == 0) {
- qWarning("Trying to create a transport layer, but no hostname is specified");
+ qCDebug(lcMqttConnection) << "No hostname specified, not able to create a transport layer.";
return false;
}
auto socket =
@@ -129,12 +176,14 @@ bool QMqttConnection::ensureTransportOpen(const QString &sslPeerName)
return sendControlConnect();
if (!m_transport->open(QIODevice::ReadWrite)) {
- qWarning("Could not open Transport IO device");
+ qCDebug(lcMqttConnection) << "Could not open Transport IO device.";
m_internalState = BrokerDisconnected;
return false;
}
return sendControlConnect();
- } else if (m_transportType == QMqttClient::AbstractSocket) {
+ }
+
+ if (m_transportType == QMqttClient::AbstractSocket) {
auto socket = dynamic_cast<QTcpSocket*>(m_transport);
Q_ASSERT(socket);
if (socket->state() == QAbstractSocket::ConnectedState)
@@ -154,12 +203,12 @@ bool QMqttConnection::ensureTransportOpen(const QString &sslPeerName)
socket->connectToHostEncrypted(m_clientPrivate->m_hostname, m_clientPrivate->m_port, sslPeerName);
if (!socket->waitForConnected()) {
- qWarning("Could not establish socket connection for transport");
+ qCDebug(lcMqttConnection) << "Could not establish socket connection for transport.";
return false;
}
if (!socket->waitForEncrypted()) {
- qWarning("Could not initiate encryption.");
+ qCDebug(lcMqttConnection) << "Could not initiate encryption.";
return false;
}
}
@@ -188,10 +237,10 @@ bool QMqttConnection::sendControlConnect()
packet.append("MQTT");
packet.append(char(4)); // Version 3.1.1
break;
- default:
- qCWarning(lcMqttConnection) << "Illegal MQTT Version";
- m_clientPrivate->setStateAndError(QMqttClient::Disconnected, QMqttClient::InvalidProtocolVersion);
- return false;
+ case QMqttClient::MQTT_5_0:
+ packet.append("MQTT");
+ packet.append(char(5)); // Version 5.0
+ break;
}
// 3.1.2.3 Connect Flags
@@ -203,7 +252,7 @@ bool QMqttConnection::sendControlConnect()
if (!m_clientPrivate->m_willMessage.isEmpty()) {
flags |= 1 << 2;
if (m_clientPrivate->m_willQoS > 2) {
- qWarning("Will QoS does not have a valid value");
+ qCDebug(lcMqttConnection) << "Invalid Will QoS specified.";
return false;
}
if (m_clientPrivate->m_willQoS == 1)
@@ -224,6 +273,9 @@ bool QMqttConnection::sendControlConnect()
// 3.1.2.10 Keep Alive
packet.append(m_clientPrivate->m_keepAlive);
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0)
+ packet.appendRaw(writeConnectProperties());
+
// 3.1.3 Payload
// 3.1.3.1 Client Identifier
const QByteArray clientStringArray = m_clientPrivate->m_clientId.toUtf8();
@@ -235,6 +287,9 @@ bool QMqttConnection::sendControlConnect()
}
if (!m_clientPrivate->m_willMessage.isEmpty()) {
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0)
+ packet.appendRaw(writeLastWillProperties());
+
packet.append(m_clientPrivate->m_willTopic.toUtf8());
packet.append(m_clientPrivate->m_willMessage);
}
@@ -246,7 +301,7 @@ bool QMqttConnection::sendControlConnect()
packet.append(m_clientPrivate->m_password.toUtf8());
if (!writePacketToTransport(packet)) {
- qWarning("Could not write CONNECT frame to transport");
+ qCDebug(lcMqttConnection) << "Could not write CONNECT frame to transport.";
return false;
}
@@ -255,7 +310,41 @@ bool QMqttConnection::sendControlConnect()
return true;
}
-qint32 QMqttConnection::sendControlPublish(const QMqttTopicName &topic, const QByteArray &message, quint8 qos, bool retain)
+bool QMqttConnection::sendControlAuthenticate(const QMqttAuthenticationProperties &properties)
+{
+ qCDebug(lcMqttConnection) << Q_FUNC_INFO;
+
+ QMqttControlPacket packet(QMqttControlPacket::AUTH);
+
+ switch (m_clientPrivate->m_state) {
+ case QMqttClient::Disconnected:
+ qCDebug(lcMqttConnection) << "Using AUTH while disconnected.";
+ return false;
+ case QMqttClient::Connecting:
+ qCDebug(lcMqttConnection) << "AUTH while connecting, set continuation flag.";
+ packet.append(char(0x18));
+ break;
+ case QMqttClient::Connected:
+ qCDebug(lcMqttConnection) << "AUTH while connected, initiate re-authentication.";
+ packet.append(char(0x19));
+ break;
+ }
+
+ packet.appendRaw(writeAuthenticationProperties(properties));
+
+ if (!writePacketToTransport(packet)) {
+ qCDebug(lcMqttConnection) << "Could not write AUTH frame to transport.";
+ return false;
+ }
+
+ return true;
+}
+
+qint32 QMqttConnection::sendControlPublish(const QMqttTopicName &topic,
+ const QByteArray &message,
+ quint8 qos,
+ bool retain,
+ const QMqttPublishProperties &properties)
{
qCDebug(lcMqttConnection) << Q_FUNC_INFO << topic << " Size:" << message.size() << " bytes."
<< "QoS:" << qos << " Retain:" << retain;
@@ -273,13 +362,55 @@ qint32 QMqttConnection::sendControlPublish(const QMqttTopicName &topic, const QB
header |= 0x01;
QSharedPointer<QMqttControlPacket> packet(new QMqttControlPacket(header));
- packet->append(topic.name().toUtf8());
+ // topic alias
+ QMqttPublishProperties publishProperties(properties);
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0) {
+ const quint16 topicAlias = publishProperties.topicAlias();
+ if (topicAlias > 0) { // User specified topic Alias
+ if (topicAlias > m_clientPrivate->m_serverConnectionProperties.maximumTopicAlias()) {
+ qCDebug(lcMqttConnection) << "TopicAlias publish: overflow.";
+ return -1;
+ }
+ if (m_publishAliases.at(topicAlias - 1) != topic) {
+ qCDebug(lcMqttConnection) << "TopicAlias publish: Assign:" << topicAlias << ":" << topic;
+ m_publishAliases[topicAlias - 1] = topic;
+ packet->append(topic.name().toUtf8());
+ } else {
+ qCDebug(lcMqttConnectionVerbose) << "TopicAlias publish: Reuse:" << topicAlias;
+ packet->append(quint16(0));
+ }
+ } else if (m_publishAliases.size() > 0) { // Automatic module alias assignment
+ int autoAlias = m_publishAliases.indexOf(topic);
+ if (autoAlias != -1) {
+ qCDebug(lcMqttConnectionVerbose) << "TopicAlias publish: Use auto alias:" << autoAlias;
+ packet->append(quint16(0));
+ publishProperties.setTopicAlias(quint16(autoAlias + 1));
+ } else {
+ autoAlias = m_publishAliases.indexOf(QMqttTopicName());
+ if (autoAlias != -1) {
+ qCDebug(lcMqttConnectionVerbose) << "TopicAlias publish: auto alias assignment:" << autoAlias;
+ m_publishAliases[autoAlias] = topic;
+ publishProperties.setTopicAlias(quint16(autoAlias) + 1);
+ } else
+ qCDebug(lcMqttConnectionVerbose) << "TopicAlias publish: alias storage full, using full topic";
+ packet->append(topic.name().toUtf8());
+ }
+ } else {
+ packet->append(topic.name().toUtf8());
+ }
+ } else { // ! MQTT_5_0
+ packet->append(topic.name().toUtf8());
+ }
quint16 identifier = 0;
if (qos > 0) {
identifier = unusedPacketIdentifier();
packet->append(identifier);
m_pendingMessages.insert(identifier, packet);
}
+
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0)
+ packet->appendRaw(writePublishProperties(publishProperties));
+
packet->appendRaw(message);
const bool written = writePacketToTransport(*packet.data());
@@ -324,11 +455,20 @@ bool QMqttConnection::sendControlPublishComp(quint16 id)
return writePacketToTransport(packet);
}
-QMqttSubscription *QMqttConnection::sendControlSubscribe(const QMqttTopicFilter &topic, quint8 qos)
+QMqttSubscription *QMqttConnection::sendControlSubscribe(const QMqttTopicFilter &topic,
+ quint8 qos,
+ const QMqttSubscriptionProperties &properties)
{
qCDebug(lcMqttConnection) << Q_FUNC_INFO << " Topic:" << topic << " qos:" << qos;
- if (m_activeSubscriptions.contains(topic))
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0) {
+ if (!topic.shareName().isEmpty()) {
+ const QMqttTopicFilter filter(topic.filter().section(QLatin1Char('/'), 2));
+ if (m_activeSubscriptions.contains(filter) && m_activeSubscriptions.value(filter)->shareName() == topic.shareName())
+ return m_activeSubscriptions[filter];
+ } else if (m_activeSubscriptions.contains(topic) && !m_activeSubscriptions.value(topic)->isShared())
+ return m_activeSubscriptions[topic];
+ } else if (m_activeSubscriptions.contains(topic))
return m_activeSubscriptions[topic];
// has to have 0010 as bits 3-0, maybe update SUBSCRIBE instead?
@@ -341,9 +481,12 @@ QMqttSubscription *QMqttConnection::sendControlSubscribe(const QMqttTopicFilter
packet.append(identifier);
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0)
+ packet.appendRaw(writeSubscriptionProperties(properties));
+
// Overflow protection
if (!topic.isValid()) {
- qWarning("Subscribed topic filter is not valid.");
+ qCDebug(lcMqttConnection) << "Invalid subscription topic filter.";
return nullptr;
}
@@ -361,6 +504,11 @@ QMqttSubscription *QMqttConnection::sendControlSubscribe(const QMqttTopicFilter
result->setClient(m_clientPrivate->m_client);
result->setQos(qos);
result->setState(QMqttSubscription::SubscriptionPending);
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0 && !topic.shareName().isEmpty()) {
+ result->setShareName(topic.shareName());
+ result->setShared(true);
+ result->setTopic(topic.filter().section(QLatin1Char('/'), 2));
+ }
if (!writePacketToTransport(packet)) {
delete result;
@@ -373,7 +521,7 @@ QMqttSubscription *QMqttConnection::sendControlSubscribe(const QMqttTopicFilter
return result;
}
-bool QMqttConnection::sendControlUnsubscribe(const QMqttTopicFilter &topic)
+bool QMqttConnection::sendControlUnsubscribe(const QMqttTopicFilter &topic, const QMqttUnsubscriptionProperties &properties)
{
qCDebug(lcMqttConnection) << Q_FUNC_INFO << " Topic:" << topic;
@@ -399,6 +547,10 @@ bool QMqttConnection::sendControlUnsubscribe(const QMqttTopicFilter &topic)
packet.append(identifier);
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0) {
+ packet.appendRaw(writeUnsubscriptionProperties(properties));
+ }
+
packet.append(topic.filter().toUtf8());
auto sub = m_activeSubscriptions[topic];
sub->setState(QMqttSubscription::UnsubscriptionPending);
@@ -422,7 +574,7 @@ bool QMqttConnection::sendControlPingRequest()
const QMqttControlPacket packet(QMqttControlPacket::PINGREQ);
if (!writePacketToTransport(packet)) {
- qWarning("Could not write DISCONNECT frame to transport");
+ qCDebug(lcMqttConnection) << "Failed to write PINGREQ to transport.";
return false;
}
return true;
@@ -436,9 +588,12 @@ bool QMqttConnection::sendControlDisconnect()
m_activeSubscriptions.clear();
+ m_receiveAliases.clear();
+ m_publishAliases.clear();
+
const QMqttControlPacket packet(QMqttControlPacket::DISCONNECT);
if (!writePacketToTransport(packet)) {
- qWarning("Could not write DISCONNECT frame to transport");
+ qCDebug(lcMqttConnection) << "Failed to write DISCONNECT to transport.";
return false;
}
m_internalState = BrokerDisconnected;
@@ -471,7 +626,7 @@ quint16 QMqttConnection::unusedPacketIdentifier() const
packetIdentifierCounter++;
if (lastIdentifier == packetIdentifierCounter) {
- qWarning("Can't generate unique packet identifier.");
+ qCDebug(lcMqttConnection) << "Could not generate unique packet identifier.";
break;
}
} while (m_pendingSubscriptionAck.contains(packetIdentifierCounter)
@@ -504,7 +659,7 @@ void QMqttConnection::transportConnectionEstablished()
}
if (!sendControlConnect()) {
- qWarning("Could not send CONNECT to broker");
+ qCDebug(lcMqttConnection) << "Failed to write CONNECT to transport.";
// ### Who disconnects now? Connection or client?
m_clientPrivate->setStateAndError(QMqttClient::Disconnected, QMqttClient::TransportInvalid);
}
@@ -513,6 +668,7 @@ void QMqttConnection::transportConnectionEstablished()
void QMqttConnection::transportConnectionClosed()
{
m_readBuffer.clear();
+ m_readPosition = 0;
m_pingTimer.stop();
m_clientPrivate->setStateAndError(QMqttClient::Disconnected, QMqttClient::TransportInvalid);
}
@@ -524,15 +680,40 @@ void QMqttConnection::transportReadReady()
processData();
}
-void QMqttConnection::readBuffer(char *data, qint64 size)
+void QMqttConnection::readBuffer(char *data, quint64 size)
+{
+ memcpy(data, m_readBuffer.constData() + m_readPosition, size);
+ m_readPosition += size;
+}
+
+qint32 QMqttConnection::readVariableByteInteger(qint32 *byteCount)
{
- memcpy(data, m_readBuffer.constData(), size);
- m_readBuffer = m_readBuffer.mid(size);
+ quint32 multiplier = 1;
+ qint32 msgLength = 0;
+ quint8 b = 0;
+ quint8 iteration = 0;
+ if (byteCount)
+ *byteCount = 0;
+ do {
+ b = readBufferTyped<quint8>();
+ msgLength += (b & 127) * multiplier;
+ multiplier *= 128;
+ iteration++;
+ if (iteration > 4) {
+ qCDebug(lcMqttConnection) << "Overflow trying to read variable integer.";
+ closeConnection(QMqttClient::ProtocolViolation);
+ return -1;
+ }
+ } while ((b & 128) != 0);
+ if (byteCount)
+ *byteCount += iteration;
+ return msgLength;
}
void QMqttConnection::closeConnection(QMqttClient::ClientError error)
{
m_readBuffer.clear();
+ m_readPosition = 0;
m_pingTimer.stop();
m_activeSubscriptions.clear();
m_internalState = BrokerDisconnected;
@@ -541,20 +722,665 @@ void QMqttConnection::closeConnection(QMqttClient::ClientError error)
m_clientPrivate->setStateAndError(QMqttClient::Disconnected, error);
}
-QByteArray QMqttConnection::readBuffer(qint64 size)
+QByteArray QMqttConnection::readBuffer(quint64 size)
{
- QByteArray res = m_readBuffer.left(size);
- m_readBuffer = m_readBuffer.mid(size);
+ QByteArray res(m_readBuffer.constData() + m_readPosition, int(size));
+ m_readPosition += size;
return res;
}
+void QMqttConnection::readAuthProperties(QMqttAuthenticationProperties &properties)
+{
+ qint64 propertyLength = readVariableByteInteger();
+ m_missingData = 0;
+
+ QMqttUserProperties userProperties;
+ while (propertyLength > 0) {
+ quint8 propertyId = readBufferTyped<quint8>();
+ propertyLength--;
+
+ switch (propertyId) {
+ case 0x15: { //3.15.2.2.2 Authentication Method
+ const QString method = readBufferTyped<QString>(&propertyLength);
+ properties.setAuthenticationMethod(method);
+ break;
+ }
+ case 0x16: { // 3.15.2.2.3 Authentication Data
+ const QByteArray data = readBufferTyped<QByteArray>(&propertyLength);
+ properties.setAuthenticationData(data);
+ break;
+ }
+ case 0x1F: { // 3.15.2.2.4 Reason String
+ const QString reasonString = readBufferTyped<QString>(&propertyLength);
+ properties.setReason(reasonString);
+ break;
+ }
+ case 0x26: { // 3.15.2.2.5 User property
+ const QString propertyName = readBufferTyped<QString>(&propertyLength);
+ const QString propertyValue = readBufferTyped<QString>(&propertyLength);
+
+ userProperties.append(QMqttStringPair(propertyName, propertyValue));
+ break;
+ }
+ default:
+ qCDebug(lcMqttConnection) << "Unknown property id in AUTH:" << propertyId;
+ break;
+ }
+ }
+ properties.setUserProperties(userProperties);
+}
+
+void QMqttConnection::readConnackProperties(QMqttServerConnectionProperties &properties)
+{
+ qint64 propertyLength = readVariableByteInteger();
+ m_missingData = 0;
+
+ properties.serverData->valid = true;
+
+ while (propertyLength > 0) {
+ quint8 propertyId = readBufferTyped<quint8>(&propertyLength);
+ switch (propertyId) {
+ case 0x11: { // 3.2.2.3.2 Session Expiry Interval
+ const quint32 expiryInterval = readBufferTyped<quint32>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::SessionExpiryInterval;
+ properties.setSessionExpiryInterval(expiryInterval);
+ break;
+ }
+ case 0x21: { // 3.2.2.3.3 Receive Maximum
+ const quint16 receiveMaximum = readBufferTyped<quint16>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::MaximumReceive;
+ properties.setMaximumReceive(receiveMaximum);
+ break;
+ }
+ case 0x24: { // 3.2.2.3.4 Maximum QoS Level
+ const quint8 maxQoS = readBufferTyped<quint8>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::MaximumQoS;
+ properties.serverData->maximumQoS = maxQoS;
+ break;
+ }
+ case 0x25: { // 3.2.2.3.5 Retain available
+ const quint8 retainAvailable = readBufferTyped<quint8>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::RetainAvailable;
+ properties.serverData->retainAvailable = retainAvailable == 1;
+ break;
+ }
+ case 0x27: { // 3.2.2.3.6 Maximum packet size
+ const quint32 maxPacketSize = readBufferTyped<quint32>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::MaximumPacketSize;
+ properties.setMaximumPacketSize(maxPacketSize);
+ break;
+ }
+ case 0x12: { // 3.2.2.3.7 Assigned clientId
+ const QString assignedClientId = readBufferTyped<QString>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::AssignedClientId;
+ m_clientPrivate->setClientId(assignedClientId);
+ break;
+ }
+ case 0x22: { // 3.2.2.3.8 Topic Alias Maximum
+ const quint16 topicAliasMaximum = readBufferTyped<quint16>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::MaximumTopicAlias;
+ properties.setMaximumTopicAlias(topicAliasMaximum);
+ break;
+ }
+ case 0x1F: { // 3.2.2.3.9 Reason String
+ const QString reasonString = readBufferTyped<QString>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::ReasonString;
+ properties.serverData->reasonString = reasonString;
+ break;
+ }
+ case 0x26: { // 3.2.2.3.10 User property
+ const QString propertyName = readBufferTyped<QString>(&propertyLength);
+ const QString propertyValue = readBufferTyped<QString>(&propertyLength);
+
+ properties.serverData->details |= QMqttServerConnectionProperties::UserProperty;
+ properties.data->userProperties.append(QMqttStringPair(propertyName, propertyValue));
+ break;
+ }
+ case 0x28: { // 3.2.2.3.11 Wildcard subscriptions available
+ const quint8 available = readBufferTyped<quint8>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::WildCardSupported;
+ properties.serverData->wildcardSupported = available == 1;
+ break;
+ }
+ case 0x29: { // 3.2.2.3.12 Subscription identifiers available
+ const quint8 available = readBufferTyped<quint8>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::SubscriptionIdentifierSupport;
+ properties.serverData->subscriptionIdentifierSupported = available == 1;
+ break;
+ }
+ case 0x2A: { // 3.2.2.3.13 Shared subscriptions available
+ const quint8 available = readBufferTyped<quint8>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::SharedSubscriptionSupport;
+ properties.serverData->sharedSubscriptionSupported = available == 1;
+ break;
+ }
+ case 0x13: { // 3.2.2.3.14 Server Keep Alive
+ const quint16 serverKeepAlive = readBufferTyped<quint16>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::ServerKeepAlive;
+ m_clientPrivate->m_client->setKeepAlive(serverKeepAlive);
+ break;
+ }
+ case 0x1A: { // 3.2.2.3.15 Response information
+ const QString responseInfo = readBufferTyped<QString>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::ResponseInformation;
+ properties.serverData->responseInformation = responseInfo;
+ break;
+ }
+ case 0x1C: { // 3.2.2.3.16 Server reference
+ const QString serverReference = readBufferTyped<QString>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::ServerReference;
+ properties.serverData->serverReference = serverReference;
+ break;
+ }
+ case 0x15: { // 3.2.2.3.17 Authentication method
+ const QString method = readBufferTyped<QString>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::AuthenticationMethod;
+ properties.data->authenticationMethod = method;
+ break;
+ }
+ case 0x16: { // 3.2.2.3.18 Authentication data
+ const QByteArray data = readBufferTyped<QByteArray>(&propertyLength);
+ properties.serverData->details |= QMqttServerConnectionProperties::AuthenticationData;
+ properties.data->authenticationData = data;
+ break;
+ }
+ default:
+ qCDebug(lcMqttConnection) << "Unknown property id in CONNACK:" << int(propertyId);
+ break;
+ }
+ }
+}
+
+void QMqttConnection::readMessageStatusProperties(QMqttMessageStatusProperties &properties)
+{
+ qint64 propertyLength = readVariableByteInteger();
+
+ m_missingData -= propertyLength;
+ while (propertyLength > 0) {
+ const quint8 propertyId = readBufferTyped<quint8>(&propertyLength);
+ switch (propertyId) {
+ case 0x1f: { // 3.4.2.2.2 Reason String
+ const QString content = readBufferTyped<QString>(&propertyLength);
+ properties.data->reasonString = content;
+ break;
+ }
+ case 0x26: { // 3.4.2.2.3 User Properites
+ const QString propertyName = readBufferTyped<QString>(&propertyLength);
+ const QString propertyValue = readBufferTyped<QString>(&propertyLength);
+ properties.data->userProperties.append(QMqttStringPair(propertyName, propertyValue));
+ break;
+ }
+ default:
+ qCDebug(lcMqttConnection) << "Unknown subscription property received.";
+ break;
+ }
+ }
+}
+
+void QMqttConnection::readPublishProperties(QMqttPublishProperties &properties)
+{
+ qint32 propertySize = 0;
+ qint64 propertyLength = readVariableByteInteger(&propertySize);
+ m_missingData -= propertySize;
+ m_missingData -= propertyLength;
+
+ QMqttUserProperties userProperties;
+ QList<quint32> subscriptionIds;
+
+ while (propertyLength > 0) {
+ const quint8 propertyId = readBufferTyped<quint8>(&propertyLength);
+ switch (propertyId) {
+ case 0x01: { // 3.3.2.3.2 Payload Format Indicator
+ const quint8 format = readBufferTyped<quint8>(&propertyLength);
+ if (format == 1)
+ properties.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);
+ break;
+ }
+ case 0x02: { // 3.3.2.3.3 Message Expiry Interval
+ const quint32 interval = readBufferTyped<quint32>(&propertyLength);
+ properties.setMessageExpiryInterval(interval);
+ break;
+ }
+ case 0x23: { // 3.3.2.3.4 Topic alias
+ const quint16 alias = readBufferTyped<quint16>(&propertyLength);
+ properties.setTopicAlias(alias);
+ break;
+ }
+ case 0x08: { // 3.3.2.3.5 Response Topic
+ const QString responseTopic = readBufferTyped<QString>(&propertyLength);
+ properties.setResponseTopic(responseTopic);
+ break;
+ }
+ case 0x09: { // 3.3.2.3.6 Correlation Data
+ const QByteArray data = readBufferTyped<QByteArray>(&propertyLength);
+ properties.setCorrelationData(data);
+ break;
+ }
+ case 0x26: { // 3.3.2.3.7 User property
+ const QString propertyName = readBufferTyped<QString>(&propertyLength);
+ const QString propertyValue = readBufferTyped<QString>(&propertyLength);
+ userProperties.append(QMqttStringPair(propertyName, propertyValue));
+ break;
+ }
+ case 0x0b: { // 3.3.2.3.8 Subscription Identifier
+ qint32 idSize = 0;
+ qint32 id = readVariableByteInteger(&idSize);
+ if (id < 0)
+ return; // readVariableByteInteger closes connection
+ propertyLength -= idSize;
+ subscriptionIds.append(quint32(id));
+ break;
+ }
+ case 0x03: { // 3.3.2.3.9 Content Type
+ const QString content = readBufferTyped<QString>(&propertyLength);
+ properties.setContentType(content);
+ break;
+ }
+ default:
+ qCDebug(lcMqttConnection) << "Unknown publish property received.";
+ break;
+ }
+ }
+ if (!userProperties.isEmpty())
+ properties.setUserProperties(userProperties);
+
+ if (!subscriptionIds.isEmpty())
+ properties.setSubscriptionIdentifiers(subscriptionIds);
+}
+
+void QMqttConnection::readSubscriptionProperties(QMqttSubscription *sub)
+{
+ qint32 bytes = 0;
+ qint64 propertyLength = readVariableByteInteger(&bytes);
+
+ m_missingData -= bytes;
+ while (propertyLength > 0) {
+ const quint8 propertyId = readBufferTyped<quint8>(&propertyLength);
+ switch (propertyId) {
+ case 0x1f: { // 3.9.2.1.2 Reason String
+ const QString content = readBufferTyped<QString>(&propertyLength);
+ sub->d_func()->m_reasonString = content;
+ break;
+ }
+ case 0x26: { // 3.9.2.1.3
+ const QString propertyName = readBufferTyped<QString>(&propertyLength);
+ const QString propertyValue = readBufferTyped<QString>(&propertyLength);
+
+ sub->d_func()->m_userProperties.append(QMqttStringPair(propertyName, propertyValue));
+ break;
+ }
+ default:
+ qCDebug(lcMqttConnection) << "Unknown subscription property received.";
+ break;
+ }
+ }
+}
+
+QByteArray QMqttConnection::writeConnectProperties()
+{
+ QMqttControlPacket properties;
+
+ // According to MQTT5 3.1.2.11 default values do not need to be included in the
+ // connect statement.
+
+ // 3.1.2.11.2
+ if (m_clientPrivate->m_connectionProperties.sessionExpiryInterval() != 0) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify sessionExpiryInterval";
+ properties.append(char(0x11));
+ properties.append(m_clientPrivate->m_connectionProperties.sessionExpiryInterval());
+ }
+
+ // 3.1.2.11.3
+ if (m_clientPrivate->m_connectionProperties.maximumReceive() != 65535) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify maximumReceive";
+ properties.append(char(0x21));
+ properties.append(m_clientPrivate->m_connectionProperties.maximumReceive());
+ }
+
+ // 3.1.2.11.4
+ if (m_clientPrivate->m_connectionProperties.maximumPacketSize() != std::numeric_limits<quint32>::max()) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify maximumPacketSize";
+ properties.append(char(0x27));
+ properties.append(m_clientPrivate->m_connectionProperties.maximumPacketSize());
+ }
+
+ // 3.1.2.11.5
+ if (m_clientPrivate->m_connectionProperties.maximumTopicAlias() != 0) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify maximumTopicAlias";
+ properties.append(char(0x22));
+ properties.append(m_clientPrivate->m_connectionProperties.maximumTopicAlias());
+ }
+
+ // 3.1.2.11.6
+ if (m_clientPrivate->m_connectionProperties.requestResponseInformation()) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify requestResponseInformation";
+ properties.append(char(0x19));
+ properties.append(char(1));
+ }
+
+ // 3.1.2.11.7
+ if (!m_clientPrivate->m_connectionProperties.requestProblemInformation()) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify requestProblemInformation";
+ properties.append(char(0x17));
+ properties.append(char(0));
+ }
+
+ // 3.1.2.11.8 Add User properties
+ auto userProperties = m_clientPrivate->m_connectionProperties.userProperties();
+ if (!userProperties.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ properties.append(char(0x26));
+ properties.append(prop.name().toUtf8());
+ properties.append(prop.value().toUtf8());
+ }
+ }
+
+ // 3.1.2.11.9 Add Authentication
+ const QString authenticationMethod = m_clientPrivate->m_connectionProperties.authenticationMethod();
+ if (!authenticationMethod.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: specify AuthenticationMethod:";
+ qCDebug(lcMqttConnectionVerbose) << " " << authenticationMethod;
+ properties.append(char(0x15));
+ properties.append(authenticationMethod.toUtf8());
+ // 3.1.2.11.10
+ const QByteArray authenticationData = m_clientPrivate->m_connectionProperties.authenticationData();
+ if (!authenticationData.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Connection Properties: Authentication Data:";
+ qCDebug(lcMqttConnectionVerbose) << " " << authenticationData;
+ properties.append(char(0x16));
+ properties.append(authenticationData);
+ }
+ }
+
+ return properties.serializePayload();
+}
+
+QByteArray QMqttConnection::writeLastWillProperties() const
+{
+ QMqttControlPacket properties;
+ const QMqttLastWillProperties &lastWillProperties = m_clientPrivate->m_lastWillProperties;
+ // Will Delay interval 3.1.3.2.2
+ if (lastWillProperties.willDelayInterval() > 0) {
+ const quint32 delay = lastWillProperties.willDelayInterval();
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: specify will delay interval:"
+ << delay;
+ properties.append(char(0x18));
+ properties.append(delay);
+ }
+
+ // Payload Format Indicator 3.1.3.2.3
+ if (lastWillProperties.payloadFormatIndicator() != QMqtt::PayloadFormatIndicator::Unspecified) {
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: payload format indicator specified";
+ properties.append(char(0x01));
+ properties.append(char(0x01)); // UTF8
+ }
+
+ // Message Expiry Interval 3.1.3.2.4
+ if (lastWillProperties.messageExpiryInterval() > 0) {
+ const quint32 interval = lastWillProperties.messageExpiryInterval();
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: Message Expiry interval:"
+ << interval;
+ properties.append(char(0x02));
+ properties.append(interval);
+ }
+
+ // Content Type 3.1.3.2.5
+ if (!lastWillProperties.contentType().isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: Content Type:"
+ << lastWillProperties.contentType();
+ properties.append(char(0x03));
+ properties.append(lastWillProperties.contentType().toUtf8());
+ }
+
+ // Response Topic 3.1.3.2.6
+ if (!lastWillProperties.responseTopic().isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: Response Topic:"
+ << lastWillProperties.responseTopic();
+ properties.append(char(0x08));
+ properties.append(lastWillProperties.responseTopic().toUtf8());
+ }
+
+ // Correlation Data 3.1.3.2.7
+ if (!lastWillProperties.correlationData().isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: Correlation Data:"
+ << lastWillProperties.correlationData();
+ properties.append(char(0x09));
+ properties.append(lastWillProperties.correlationData());
+ }
+
+ // User Properties 3.1.3.2.8
+ if (!lastWillProperties.userProperties().isEmpty()) {
+ auto userProperties = lastWillProperties.userProperties();
+ qCDebug(lcMqttConnectionVerbose) << "Last Will Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ properties.append(char(0x26));
+ properties.append(prop.name().toUtf8());
+ properties.append(prop.value().toUtf8());
+ }
+ }
+
+ return properties.serializePayload();
+}
+
+QByteArray QMqttConnection::writePublishProperties(const QMqttPublishProperties &properties)
+{
+ QMqttControlPacket packet;
+
+ // 3.3.2.3.2 Payload Indicator
+ if (properties.availableProperties() & QMqttPublishProperties::PayloadFormatIndicator &&
+ properties.payloadFormatIndicator() != QMqtt::PayloadFormatIndicator::Unspecified) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Payload Indicator:"
+ << (properties.payloadFormatIndicator() == QMqtt::PayloadFormatIndicator::UTF8Encoded ? 1 : 0);
+ packet.append(char(0x01));
+ switch (properties.payloadFormatIndicator()) {
+ case QMqtt::PayloadFormatIndicator::UTF8Encoded:
+ packet.append(char(0x01));
+ break;
+ default:
+ qCDebug(lcMqttConnection) << "Unknown payload indicator.";
+ break;
+ }
+ }
+
+ // 3.3.2.3.3 Message Expiry
+ if (properties.availableProperties() & QMqttPublishProperties::MessageExpiryInterval &&
+ properties.messageExpiryInterval() > 0) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Message Expiry :"
+ << properties.messageExpiryInterval();
+ packet.append(char(0x02));
+ packet.append(properties.messageExpiryInterval());
+ }
+
+ // 3.3.2.3.4 Topic alias
+ if (properties.availableProperties() & QMqttPublishProperties::TopicAlias &&
+ properties.topicAlias() > 0) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Topic Alias :"
+ << properties.topicAlias();
+ if (m_clientPrivate->m_serverConnectionProperties.availableProperties() & QMqttServerConnectionProperties::MaximumTopicAlias
+ && properties.topicAlias() > m_clientPrivate->m_serverConnectionProperties.maximumTopicAlias()) {
+ qCDebug(lcMqttConnection) << "Invalid topic alias specified: " << properties.topicAlias()
+ << " Maximum by server is:"
+ << m_clientPrivate->m_serverConnectionProperties.maximumTopicAlias();
+
+ } else {
+ packet.append(char(0x23));
+ packet.append(properties.topicAlias());
+ }
+ }
+
+ // 3.3.2.3.5 Response Topic
+ if (properties.availableProperties() & QMqttPublishProperties::ResponseTopic &&
+ !properties.responseTopic().isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Response Topic :"
+ << properties.responseTopic();
+ packet.append(char(0x08));
+ packet.append(properties.responseTopic().toUtf8());
+ }
+
+ // 3.3.2.3.6 Correlation Data
+ if (properties.availableProperties() & QMqttPublishProperties::CorrelationData &&
+ !properties.correlationData().isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Correlation Data :"
+ << properties.correlationData();
+ packet.append(char(0x09));
+ packet.append(properties.correlationData());
+ }
+
+ // 3.3.2.3.7 User Property
+ if (properties.availableProperties() & QMqttPublishProperties::UserProperty) {
+ auto userProperties = properties.userProperties();
+ if (!userProperties.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ packet.append(char(0x26));
+ packet.append(prop.name().toUtf8());
+ packet.append(prop.value().toUtf8());
+ }
+ }
+ }
+
+ // 3.3.2.3.8 Subscription Identifier
+ if (properties.availableProperties() & QMqttPublishProperties::SubscriptionIdentifier) {
+ for (auto id : properties.subscriptionIdentifiers()) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Subscription ID:" << id;
+ packet.append(char(0x0b));
+ packet.appendRawVariableInteger(id);
+ }
+ }
+
+ // 3.3.2.3.9 Content Type
+ if (properties.availableProperties() & QMqttPublishProperties::ContentType &&
+ !properties.contentType().isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Publish Properties: Content Type :"
+ << properties.contentType();
+ packet.append(char(0x03));
+ packet.append(properties.contentType().toUtf8());
+ }
+
+ return packet.serializePayload();
+}
+
+QByteArray QMqttConnection::writeSubscriptionProperties(const QMqttSubscriptionProperties &properties)
+{
+ QMqttControlPacket packet;
+
+ // 3.8.2.1.2 Subscription Identifier
+ if (properties.subscriptionIdentifier() > 0) {
+ qCDebug(lcMqttConnectionVerbose) << "Subscription Properties: Subscription Identifier";
+ packet.append(char(0x0b));
+ packet.appendRawVariableInteger(properties.subscriptionIdentifier());
+ }
+
+ // 3.8.2.1.3 User Property
+ auto userProperties = properties.userProperties();
+ if (!userProperties.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Subscription Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ packet.append(char(0x26));
+ packet.append(prop.name().toUtf8());
+ packet.append(prop.value().toUtf8());
+ }
+ }
+
+ return packet.serializePayload();
+}
+
+QByteArray QMqttConnection::writeUnsubscriptionProperties(const QMqttUnsubscriptionProperties &properties)
+{
+ QMqttControlPacket packet;
+
+ // 3.10.2.1.2
+ auto userProperties = properties.userProperties();
+ if (!userProperties.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Unsubscription Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ packet.append(char(0x26));
+ packet.append(prop.name().toUtf8());
+ packet.append(prop.value().toUtf8());
+ }
+ }
+
+ return packet.serializePayload();
+}
+
+QByteArray QMqttConnection::writeAuthenticationProperties(const QMqttAuthenticationProperties &properties)
+{
+ QMqttControlPacket packet;
+
+ // 3.15.2.2.2
+ if (!properties.authenticationMethod().isEmpty()) {
+ packet.append(char(0x15));
+ packet.append(properties.authenticationMethod().toUtf8());
+ }
+ // 3.15.2.2.3
+ if (!properties.authenticationData().isEmpty()) {
+ packet.append(char(0x16));
+ packet.append(properties.authenticationData());
+ }
+
+ // 3.15.2.2.4
+ if (!properties.reason().isEmpty()) {
+ packet.append(char(0x1F));
+ packet.append(properties.reason().toUtf8());
+ }
+
+ // 3.15.2.2.5
+ auto userProperties = properties.userProperties();
+ if (!userProperties.isEmpty()) {
+ qCDebug(lcMqttConnectionVerbose) << "Unsubscription Properties: specify user properties";
+ for (const auto &prop : userProperties) {
+ packet.append(char(0x26));
+ packet.append(prop.name().toUtf8());
+ packet.append(prop.value().toUtf8());
+ }
+ }
+
+ return packet.serializePayload();
+}
+
+void QMqttConnection::finalize_auth()
+{
+ qCDebug(lcMqttConnectionVerbose) << "Finalize AUTH";
+
+ const quint8 authReason = readBufferTyped<quint8>();
+ m_missingData--;
+ QMqttAuthenticationProperties authProperties;
+ // 3.15.2.1 - The Reason Code and Property Length can be omitted if the Reason Code
+ // is 0x00 (Success) and there are no Properties. In this case the AUTH has a
+ // Remaining Length of 0.
+ if (m_missingData == 0 && authReason != 0) {
+ qCDebug(lcMqttConnection) << "Received non success AUTH without properties.";
+ closeConnection(QMqttClient::ProtocolViolation);
+ return;
+ } else if (m_missingData > 0)
+ readAuthProperties(authProperties);
+
+ switch (authReason) {
+ case 0x00: // Success
+ emit m_clientPrivate->m_client->authenticationFinished(authProperties);
+ break;
+ case 0x18: // Continue Authentication
+ case 0x19: // Re-authenticate
+ emit m_clientPrivate->m_client->authenticationRequested(authProperties);
+ break;
+ default:
+ qCDebug(lcMqttConnection) << "Received illegal AUTH reason code:" << authReason;
+ closeConnection(QMqttClient::ProtocolViolation);
+ break;
+ }
+}
+
void QMqttConnection::finalize_connack()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize CONNACK";
- quint8 ackFlags;
- readBuffer((char*)&ackFlags, 1);
+
+ const quint8 ackFlags = readBufferTyped<quint8>(&m_missingData);
+
if (ackFlags > 1) { // MQTT-3.2.2.1
- qWarning("Unexpected CONNACK Flags set");
+ qCDebug(lcMqttConnection) << "Unexpected CONNACK Flags specified:" << QString::number(ackFlags);
+ readBuffer(quint64(m_missingData));
+ m_missingData = 0;
closeConnection(QMqttClient::ProtocolViolation);
return;
}
@@ -564,25 +1390,34 @@ void QMqttConnection::finalize_connack()
if (sessionPresent) {
emit m_clientPrivate->m_client->brokerSessionRestored();
if (m_clientPrivate->m_cleanSession)
- qWarning("Connected with a clean session, ack contains session present");
+ qCDebug(lcMqttConnection) << "Connected with a clean session, ack contains session present.";
} else {
// MQTT-4.1.0.-1 MQTT-4.1.0-2 Session not stored on broker side
// regardless whether cleanSession is false
cleanSubscriptions();
}
- quint8 connectResultValue;
- readBuffer((char*)&connectResultValue, 1);
- if (connectResultValue != 0) {
- qWarning("Connection has been rejected");
- // MQTT-3.2.2-5
- m_readBuffer.clear();
- m_transport->close();
- m_internalState = BrokerDisconnected;
- // Table 3.1, values 1-5
- m_clientPrivate->setStateAndError(QMqttClient::Disconnected, static_cast<QMqttClient::ClientError>(connectResultValue));
+ quint8 connectResultValue = readBufferTyped<quint8>(&m_missingData);
+ QMqttServerConnectionProperties serverProp;
+ serverProp.serverData->reasonCode = QMqtt::ReasonCode(connectResultValue);
+ if (connectResultValue != 0 && m_clientPrivate->m_protocolVersion != QMqttClient::MQTT_5_0) {
+ qCDebug(lcMqttConnection) << "Connection has been rejected.";
+ closeConnection(static_cast<QMqttClient::ClientError>(connectResultValue));
return;
}
+
+ // MQTT 5.0 has variable part != 2 in the header
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0) {
+ readConnackProperties(serverProp);
+ m_clientPrivate->m_serverConnectionProperties = serverProp;
+ m_receiveAliases.resize(m_clientPrivate->m_serverConnectionProperties.maximumTopicAlias());
+ m_publishAliases.resize(m_clientPrivate->m_connectionProperties.maximumTopicAlias());
+ if (connectResultValue != 0) {
+ closeConnection(QMqttClient::Mqtt5SpecificError);
+ return;
+ }
+ }
+
m_internalState = BrokerConnected;
m_clientPrivate->setStateAndError(QMqttClient::Connected);
@@ -592,42 +1427,43 @@ void QMqttConnection::finalize_connack()
void QMqttConnection::finalize_suback()
{
- quint16 id;
- readBuffer((char*)&id, 2);
- id = qFromBigEndian<quint16>(id);
+ const quint16 id = readBufferTyped<quint16>(&m_missingData);
if (!m_pendingSubscriptionAck.contains(id)) {
- qWarning("Received SUBACK for unknown subscription request");
+ qCDebug(lcMqttConnection) << "Received SUBACK for unknown subscription request.";
return;
}
- quint8 result;
- readBuffer((char*)&result, 1);
+
auto sub = m_pendingSubscriptionAck.take(id);
- qCDebug(lcMqttConnectionVerbose) << "Finalize SUBACK: id:" << id << "qos:" << result;
- if (result <= 2) {
- // The broker might have a different support level for QoS than what
- // the client requested
- if (result != sub->qos()) {
- sub->setQos(result);
- emit sub->qosChanged(result);
- }
- sub->setState(QMqttSubscription::Subscribed);
- } else if (result == 0x80) {
- qWarning() << "Subscription for id " << id << " failed.";
- sub->setState(QMqttSubscription::Error);
- } else {
- qWarning("Received invalid SUBACK result value");
- sub->setState(QMqttSubscription::Error);
+
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0)
+ readSubscriptionProperties(sub);
+
+ while (m_missingData > 0) {
+ quint8 reason = readBufferTyped<quint8>(&m_missingData);
+
+ sub->d_func()->m_reasonCode = QMqtt::ReasonCode(reason);
+ qCDebug(lcMqttConnectionVerbose) << "Finalize SUBACK: id:" << id << "qos:" << reason;
+ if (reason <= 2) {
+ // The broker might have a different support level for QoS than what
+ // the client requested
+ if (reason != sub->qos()) {
+ sub->setQos(reason);
+ emit sub->qosChanged(reason);
+ }
+ sub->setState(QMqttSubscription::Subscribed);
+ } else {
+ qCDebug(lcMqttConnection) << "Subscription for id " << id << " failed. Reason Code:" << reason;
+ sub->setState(QMqttSubscription::Error);
+ }
}
}
void QMqttConnection::finalize_unsuback()
{
- quint16 id;
- readBuffer((char*)&id, 2);
- id = qFromBigEndian<quint16>(id);
+ const quint16 id = readBufferTyped<quint16>(&m_missingData);
qCDebug(lcMqttConnectionVerbose) << "Finalize UNSUBACK: " << id;
if (!m_pendingUnsubscriptions.contains(id)) {
- qWarning("Received UNSUBACK for unknown request");
+ qCDebug(lcMqttConnection) << "Received UNSUBACK for unknown request.";
return;
}
auto sub = m_pendingUnsubscriptions.take(id);
@@ -638,17 +1474,39 @@ void QMqttConnection::finalize_unsuback()
void QMqttConnection::finalize_publish()
{
// String topic
- const quint16 topicLength = qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(readBuffer(2).constData()));
- const QMqttTopicName topic = QString::fromUtf8(reinterpret_cast<const char *>(readBuffer(topicLength).constData()));
+ QMqttTopicName topic = readBufferTyped<QString>(&m_missingData);
+ const int topicLength = topic.name().length();
quint16 id = 0;
- if (m_currentPublish.qos > 0) {
- id = qFromBigEndian<quint16>(reinterpret_cast<const quint16 *>(readBuffer(2).constData()));
+ if (m_currentPublish.qos > 0)
+ id = readBufferTyped<quint16>(&m_missingData);
+
+ QMqttPublishProperties publishProperties;
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0)
+ readPublishProperties(publishProperties);
+
+ const quint16 topicAlias = publishProperties.topicAlias();
+ if (topicAlias > 0) {
+ if (topicAlias > m_clientPrivate->m_serverConnectionProperties.maximumTopicAlias()) {
+ qCDebug(lcMqttConnection) << "TopicAlias receive: overflow.";
+ closeConnection(QMqttClient::ProtocolViolation);
+ } else if (topicLength == 0) { // New message on existing topic alias
+ if (m_receiveAliases.at(topicAlias - 1).name().isEmpty()) {
+ qCDebug(lcMqttConnection) << "TopicAlias receive: alias for unknown topic.";
+ closeConnection(QMqttClient::ProtocolViolation);
+ }
+ qCDebug(lcMqttConnectionVerbose) << "TopicAlias receive: Using " << topicAlias;
+ topic = m_receiveAliases.at(topicAlias - 1);
+ } else { // Resetting a topic alias
+ qCDebug(lcMqttConnection) << "TopicAlias receive: Resetting:" << topic.name() << " : " << topicAlias;
+ m_receiveAliases[topicAlias - 1] = topic;
+ }
}
// message
- qint64 payloadLength = m_missingData - (topicLength + 2) - (m_currentPublish.qos > 0 ? 2 : 0);
+ const quint64 payloadLength = quint64(m_missingData);
const QByteArray message = readBuffer(payloadLength);
+ m_missingData -= payloadLength;
qCDebug(lcMqttConnectionVerbose) << "Finalize PUBLISH: topic:" << topic
<< " payloadLength:" << payloadLength;;
@@ -657,6 +1515,13 @@ void QMqttConnection::finalize_publish()
QMqttMessage qmsg(topic, message, id, m_currentPublish.qos,
m_currentPublish.dup, m_currentPublish.retain);
+ qmsg.d->m_publishProperties = publishProperties;
+
+ if (id != 0) {
+ QMqttMessageStatusProperties statusProp;
+ statusProp.data->userProperties = publishProperties.userProperties();
+ emit m_clientPrivate->m_client->messageStatusChanged(id, QMqtt::MessageStatus::Published, statusProp);
+ }
for (auto sub = m_activeSubscriptions.constBegin(); sub != m_activeSubscriptions.constEnd(); sub++) {
if (sub.key().match(topic))
@@ -672,42 +1537,59 @@ void QMqttConnection::finalize_publish()
void QMqttConnection::finalize_pubAckRecComp()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize PUBACK/REC/COMP";
- quint16 id;
- readBuffer((char*)&id, 2);
- id = qFromBigEndian<quint16>(id);
-
+ const quint16 id = readBufferTyped<quint16>(&m_missingData);
+
+ QMqttMessageStatusProperties properties;
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0 && m_missingData > 0) {
+ // Reason Code (1byte)
+ const quint8 reasonCode = readBufferTyped<quint8>(&m_missingData);
+ properties.data->reasonCode = QMqtt::ReasonCode(reasonCode);
+ if (m_missingData > 0)
+ readMessageStatusProperties(properties);
+ }
if ((m_currentPacket & 0xF0) == QMqttControlPacket::PUBCOMP) {
qCDebug(lcMqttConnectionVerbose) << " PUBCOMP:" << id;
auto pendingRelease = m_pendingReleaseMessages.take(id);
if (!pendingRelease)
- qWarning("Received PUBCOMP for unknown released message");
+ qCDebug(lcMqttConnection) << "Received PUBCOMP for unknown released message.";
+ emit m_clientPrivate->m_client->messageStatusChanged(id, QMqtt::MessageStatus::Completed, properties);
emit m_clientPrivate->m_client->messageSent(id);
return;
}
auto pendingMsg = m_pendingMessages.take(id);
if (!pendingMsg) {
- qWarning() << QLatin1String("Received PUBACK for unknown message: ") << id;
+ qCDebug(lcMqttConnection) << "Received PUBACK for unknown message: " << id;
return;
}
if ((m_currentPacket & 0xF0) == QMqttControlPacket::PUBREC) {
qCDebug(lcMqttConnectionVerbose) << " PUBREC:" << id;
m_pendingReleaseMessages.insert(id, pendingMsg);
+ emit m_clientPrivate->m_client->messageStatusChanged(id, QMqtt::MessageStatus::Received, properties);
sendControlPublishRelease(id);
} else {
qCDebug(lcMqttConnectionVerbose) << " PUBACK:" << id;
+ emit m_clientPrivate->m_client->messageStatusChanged(id, QMqtt::MessageStatus::Acknowledged, properties);
emit m_clientPrivate->m_client->messageSent(id);
}
}
void QMqttConnection::finalize_pubrel()
{
- quint16 id;
- readBuffer((char*)&id, 2);
- id = qFromBigEndian<quint16>(id);
+ const quint16 id = readBufferTyped<quint16>(&m_missingData);
qCDebug(lcMqttConnectionVerbose) << "Finalize PUBREL:" << id;
+ QMqttMessageStatusProperties properties;
+ if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0 && m_missingData > 0) {
+ const quint8 reasonCode = readBufferTyped<quint8>(&m_missingData);
+ properties.data->reasonCode = QMqtt::ReasonCode(reasonCode);
+ if (m_missingData > 0)
+ readMessageStatusProperties(properties);
+ }
+
+ emit m_clientPrivate->m_client->messageStatusChanged(id, QMqtt::MessageStatus::Released, properties);
+
// ### TODO: send to our app now or not???
// See standard Figure 4.3 Method A or B ???
sendControlPublishComp(id);
@@ -716,23 +1598,26 @@ void QMqttConnection::finalize_pubrel()
void QMqttConnection::finalize_pingresp()
{
qCDebug(lcMqttConnectionVerbose) << "Finalize PINGRESP";
- quint8 v;
- readBuffer((char*)&v, 1);
+ const quint8 v = readBufferTyped<quint8>(&m_missingData);
+
if (v != 0) {
- qWarning("Received a PINGRESP with payload!");
+ qCDebug(lcMqttConnection) << "Received a PINGRESP including payload.";
closeConnection(QMqttClient::ProtocolViolation);
return;
}
emit m_clientPrivate->m_client->pingResponseReceived();
}
-void QMqttConnection::processData()
+bool QMqttConnection::processDataHelper()
{
if (m_missingData > 0) {
- if (m_readBuffer.size() < m_missingData)
- return;
+ if ((m_readBuffer.size() - m_readPosition) < m_missingData)
+ return false;
switch (m_currentPacket & 0xF0) {
+ case QMqttControlPacket::AUTH:
+ finalize_auth();
+ break;
case QMqttControlPacket::CONNACK:
finalize_connack();
break;
@@ -757,11 +1642,15 @@ void QMqttConnection::processData()
finalize_pubrel();
break;
default:
- qWarning("Unknown packet to finalize");
+ qCDebug(lcMqttConnection) << "Unknown packet to finalize.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
- m_missingData = 0;
+
+ Q_ASSERT(m_missingData == 0);
+
+ m_readBuffer = m_readBuffer.mid(m_readPosition);
+ m_readPosition = 0;
}
// MQTT-2.2 A fixed header of a control packet must be at least 2 bytes. If the payload is
@@ -769,48 +1658,48 @@ void QMqttConnection::processData()
switch (m_readBuffer.size()) {
case 0:
case 1:
- return;
+ return false;
case 2:
if ((m_readBuffer.at(1) & 128) != 0)
- return;
+ return false;
break;
case 3:
if ((m_readBuffer.at(1) & 128) != 0 && (m_readBuffer.at(2) & 128) != 0)
- return;
+ return false;
break;
case 4:
if ((m_readBuffer.at(1) & 128) != 0 && (m_readBuffer.at(2) & 128) != 0 && (m_readBuffer.at(3) & 128) != 0)
- return;
+ return false;
break;
default:
break;
}
- readBuffer((char*)&m_currentPacket, 1);
+ readBuffer(reinterpret_cast<char *>(&m_currentPacket), 1);
switch (m_currentPacket & 0xF0) {
case QMqttControlPacket::CONNACK: {
qCDebug(lcMqttConnectionVerbose) << "Received CONNACK";
if (m_internalState != BrokerWaitForConnectAck) {
- qWarning("Received CONNACK at unexpected time!");
+ qCDebug(lcMqttConnection) << "Received CONNACK at unexpected time.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
- quint8 payloadSize;
- readBuffer((char*)&payloadSize, 1);
- if (payloadSize != 2) {
- qWarning("Unexpected FRAME size in CONNACK");
- closeConnection(QMqttClient::ProtocolViolation);
- return;
+ qint32 payloadSize = readVariableByteInteger();
+ if (m_clientPrivate->m_protocolVersion != QMqttClient::MQTT_5_0) {
+ if (payloadSize != 2) {
+ qCDebug(lcMqttConnection) << "Unexpected FRAME size in CONNACK.";
+ closeConnection(QMqttClient::ProtocolViolation);
+ return false;
+ }
}
- m_missingData = 2;
+ m_missingData = payloadSize;
break;
}
case QMqttControlPacket::SUBACK: {
qCDebug(lcMqttConnectionVerbose) << "Received SUBACK";
- quint8 remaining;
- readBuffer((char*)&remaining, 1);
+ const quint8 remaining = readBufferTyped<quint8>();
m_missingData = remaining;
break;
}
@@ -822,26 +1711,12 @@ void QMqttConnection::processData()
if ((m_currentPublish.qos == 0 && m_currentPublish.dup != 0)
|| m_currentPublish.qos > 2) {
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
- // remaining length
- quint32 multiplier = 1;
- quint32 msgLength = 0;
- quint8 b = 0;
- quint8 iteration = 0;
- do {
- readBuffer((char*)&b, 1);
- msgLength += (b & 127) * multiplier;
- multiplier *= 128;
- iteration++;
- if (iteration > 4) {
- qWarning("Publish message is too big to handle");
- closeConnection(QMqttClient::ProtocolViolation);
- return;
- }
- } while ((b & 128) != 0);
- m_missingData = msgLength;
+ m_missingData = readVariableByteInteger();
+ if (m_missingData == -1)
+ return false; // Connection closed inside readVariableByteInteger
break;
}
case QMqttControlPacket::PINGRESP:
@@ -852,17 +1727,16 @@ void QMqttConnection::processData()
case QMqttControlPacket::PUBREL: {
qCDebug(lcMqttConnectionVerbose) << "Received PUBREL";
- char remaining;
- readBuffer(&remaining, 1);
+ const quint8 remaining = readBufferTyped<quint8>();
if (remaining != 0x02) {
- qWarning("Received 2 byte message with invalid remaining length");
+ qCDebug(lcMqttConnection) << "Received 2 byte message with invalid remaining length.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
if ((m_currentPacket & 0x0F) != 0x02) {
- qWarning("Malformed fixed header for PUBREL");
+ qCDebug(lcMqttConnection) << "Malformed fixed header for PUBREL.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
m_missingData = 2;
break;
@@ -874,39 +1748,44 @@ void QMqttConnection::processData()
case QMqttControlPacket::PUBCOMP: {
qCDebug(lcMqttConnectionVerbose) << "Received UNSUBACK/PUBACK/PUBREC/PUBCOMP";
if ((m_currentPacket & 0x0F) != 0) {
- qWarning("Malformed fixed header");
+ qCDebug(lcMqttConnection) << "Malformed fixed header for UNSUBACK/PUBACK/PUBREC/PUBCOMP.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
- char remaining;
- readBuffer(&remaining, 1);
- if (remaining != 0x02) {
- qWarning("Received 2 byte message with invalid remaining length");
+ const quint8 remaining = readBufferTyped<quint8>();
+ if (m_clientPrivate->m_protocolVersion != QMqttClient::MQTT_5_0 && remaining != 0x02) {
+ qCDebug(lcMqttConnection) << "Received 2 byte message with invalid remaining length.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
- m_missingData = 2;
+ m_missingData = remaining;
break;
}
default:
- qWarning("Received unknown command");
+ qCDebug(lcMqttConnection) << "Received unknown command.";
closeConnection(QMqttClient::ProtocolViolation);
- return;
+ return false;
}
/* set current command CONNACK - PINGRESP */
/* read command size */
/* calculate missing_data */
- processData(); // implicitly finishes and enqueues
- return;
+ return true; // reiterate. implicitly finishes and enqueues
+}
+
+void QMqttConnection::processData()
+{
+ while (processDataHelper())
+ ;
}
bool QMqttConnection::writePacketToTransport(const QMqttControlPacket &p)
{
const QByteArray writeData = p.serialize();
+ qCDebug(lcMqttConnectionVerbose) << Q_FUNC_INFO << " DataSize:" << writeData.size();
const qint64 res = m_transport->write(writeData.constData(), writeData.size());
if (Q_UNLIKELY(res == -1)) {
- qWarning("Could not write frame to transport");
+ qCDebug(lcMqttConnection) << "Could not write frame to transport.";
return false;
}
return true;
diff --git a/src/mqtt/qmqttconnection_p.h b/src/mqtt/qmqttconnection_p.h
index cf9c912..cfe1641 100644
--- a/src/mqtt/qmqttconnection_p.h
+++ b/src/mqtt/qmqttconnection_p.h
@@ -67,7 +67,7 @@ public:
BrokerConnected
};
- explicit QMqttConnection(QObject *parent = 0);
+ explicit QMqttConnection(QObject *parent = nullptr);
~QMqttConnection() override;
void setTransport(QIODevice *device, QMqttClient::TransportType transport);
@@ -77,13 +77,15 @@ public:
bool ensureTransportOpen(const QString &sslPeerName = QString());
bool sendControlConnect();
- qint32 sendControlPublish(const QMqttTopicName &topic, const QByteArray &message, quint8 qos = 0, bool retain = false);
+ bool sendControlAuthenticate(const QMqttAuthenticationProperties &properties);
+ qint32 sendControlPublish(const QMqttTopicName &topic, const QByteArray &message, quint8 qos = 0, bool retain = false,
+ const QMqttPublishProperties &properties = QMqttPublishProperties());
bool sendControlPublishAcknowledge(quint16 id);
bool sendControlPublishRelease(quint16 id);
bool sendControlPublishReceive(quint16 id);
bool sendControlPublishComp(quint16 id);
- QMqttSubscription *sendControlSubscribe(const QMqttTopicFilter &topic, quint8 qos = 0);
- bool sendControlUnsubscribe(const QMqttTopicFilter &topic);
+ QMqttSubscription *sendControlSubscribe(const QMqttTopicFilter &topic, quint8 qos, const QMqttSubscriptionProperties &properties);
+ bool sendControlUnsubscribe(const QMqttTopicFilter &topic, const QMqttUnsubscriptionProperties &properties);
bool sendControlPingRequest();
bool sendControlDisconnect();
@@ -106,7 +108,7 @@ public:
QMqttClientPrivate *m_clientPrivate{nullptr};
private:
Q_DISABLE_COPY(QMqttConnection)
- void someFuncToBeRemoved();
+ void finalize_auth();
void finalize_connack();
void finalize_suback();
void finalize_unsuback();
@@ -115,17 +117,32 @@ private:
void finalize_pubrel();
void finalize_pingresp();
void processData();
- void readBuffer(char *data, qint64 size);
+ bool processDataHelper();
+ void readBuffer(char *data, quint64 size);
+ qint32 readVariableByteInteger(qint32 *byteCount = nullptr);
+ void readAuthProperties(QMqttAuthenticationProperties &properties);
+ void readConnackProperties(QMqttServerConnectionProperties &properties);
+ void readMessageStatusProperties(QMqttMessageStatusProperties &properties);
+ void readPublishProperties(QMqttPublishProperties &properties);
+ void readSubscriptionProperties(QMqttSubscription *sub);
+ QByteArray writeConnectProperties();
+ QByteArray writeLastWillProperties() const;
+ QByteArray writePublishProperties(const QMqttPublishProperties &properties);
+ QByteArray writeSubscriptionProperties(const QMqttSubscriptionProperties &properties);
+ QByteArray writeUnsubscriptionProperties(const QMqttUnsubscriptionProperties &properties);
+ QByteArray writeAuthenticationProperties(const QMqttAuthenticationProperties &properties);
void closeConnection(QMqttClient::ClientError error);
- QByteArray readBuffer(qint64 size);
+ QByteArray readBuffer(quint64 size);
+ template<typename T> T readBufferTyped(qint64 *dataSize = nullptr);
QByteArray m_readBuffer;
+ int m_readPosition{0};
qint64 m_missingData{0};
struct PublishData {
quint8 qos;
bool dup;
bool retain;
};
- PublishData m_currentPublish;
+ PublishData m_currentPublish{0, false, false};
QMqttControlPacket::PacketType m_currentPacket{QMqttControlPacket::UNKNOWN};
bool writePacketToTransport(const QMqttControlPacket &p);
@@ -136,6 +153,9 @@ private:
QMap<quint16, QSharedPointer<QMqttControlPacket>> m_pendingReleaseMessages;
InternalConnectionState m_internalState{BrokerDisconnected};
QTimer m_pingTimer;
+
+ QVector<QMqttTopicName> m_receiveAliases;
+ QVector<QMqttTopicName> m_publishAliases;
};
QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttconnectionproperties.cpp b/src/mqtt/qmqttconnectionproperties.cpp
new file mode 100644
index 0000000..aae5b58
--- /dev/null
+++ b/src/mqtt/qmqttconnectionproperties.cpp
@@ -0,0 +1,651 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 "qmqttconnectionproperties.h"
+
+#include "qmqttconnectionproperties_p.h"
+
+#include <QtCore/QLoggingCategory>
+
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcMqttConnection)
+
+/*!
+ \class QMqttConnectionProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttConnectionProperties class represents configuration
+ options a QMqttClient can pass to the server when invoking
+ QMqttClient::connectToHost().
+
+ \note Connection properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+/*!
+ \class QMqttServerConnectionProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttServerConnectionProperties class represents configuration
+ options of a server a QMqttClient is connected to.
+
+ When a connection has been established the server might send additional
+ details about the connection properties. Use availableProperties() to
+ identify properties set by the server. If a property is not set by the
+ server, default values are assumed and can be obtained by invoking access
+ functions of this instance.
+
+ \note Connection properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+/*!
+ \class QMqttLastWillProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttLastWillProperties class represents configuration
+ options a QMqttClient can pass to the server when specifying the last will
+ during connecting to a server.
+
+ \note Last Will properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+/*!
+ \enum QMqttServerConnectionProperties::ServerPropertyDetail
+
+ This enum type specifies the available properties set by the
+ server or the client after establishing a connection.
+
+ \value None
+ No property has been specified.
+ \value SessionExpiryInterval
+ The number of seconds the server keeps the session after
+ a disconnect.
+ \value MaximumReceive
+ The maximum number of QoS 1 and 2 message the server is
+ capable of managing concurrently.
+ \value MaximumQoS
+ The maximum QoS level the server can understand.
+ \value RetainAvailable
+ Specifies whether retained messages are supported.
+ \value MaximumPacketSize
+ Specifies the maximum packet size including the message header
+ and properties.
+ \value AssignedClientId
+ Specifies whether the server assigned a client identifier.
+ \value MaximumTopicAlias
+ Specifies the maximum amount of topic aliases.
+ \value ReasonString
+ Specifies a string providing more details on connection state.
+ \value UserProperty
+ Specifies additional user properties.
+ \value WildCardSupported
+ Specifies whether the server supports wildcard subscriptions.
+ \value SubscriptionIdentifierSupport
+ Specifies whether the server supports subscription identifiers.
+ \value SharedSubscriptionSupport
+ Specifies whether the server supports shared subscriptions.
+ \value ServerKeepAlive
+ Specifies the number of seconds the server expects a keep alive
+ packet from the client.
+ \value ResponseInformation
+ Specifies the response information.
+ \value ServerReference
+ Specifies an alternative server address for the client to
+ connect to.
+ \value AuthenticationMethod
+ Specifies the authentication method.
+ \value AuthenticationData
+ Specifies the authentication data.
+*/
+
+/*!
+ \internal
+*/
+QMqttLastWillProperties::QMqttLastWillProperties() : data(new QMqttLastWillPropertiesData)
+{
+}
+
+/*!
+ \internal
+*/
+QMqttLastWillProperties::QMqttLastWillProperties(const QMqttLastWillProperties &) = default;
+
+QMqttLastWillProperties &QMqttLastWillProperties::operator=(const QMqttLastWillProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqttLastWillProperties::~QMqttLastWillProperties() = default;
+
+/*!
+ Returns the delay in seconds a last will message will be sent after
+ disconnecting from the server.
+*/
+quint32 QMqttLastWillProperties::willDelayInterval() const
+{
+ return data->willDelayInterval;
+}
+
+/*!
+ Returns the payload format indicator.
+*/
+QMqtt::PayloadFormatIndicator QMqttLastWillProperties::payloadFormatIndicator() const
+{
+ return data->formatIndicator;
+}
+
+/*!
+ Returns the lifetime of the last will message in seconds, starting from
+ the will delay interval.
+*/
+quint32 QMqttLastWillProperties::messageExpiryInterval() const
+{
+ return data->messageExpiryInterval;
+}
+
+/*!
+ Returns the content type of the last will message.
+*/
+QString QMqttLastWillProperties::contentType() const
+{
+ return data->contentType;
+}
+
+/*!
+ Returns the topic that subscribers to the last will message should respond
+ to.
+*/
+QString QMqttLastWillProperties::responseTopic() const
+{
+ return data->responseTopic;
+}
+
+/*!
+ Returns the correlation data to identify the request.
+*/
+QByteArray QMqttLastWillProperties::correlationData() const
+{
+ return data->correlationData;
+}
+
+/*!
+ Returns the user properties.
+*/
+QMqttUserProperties QMqttLastWillProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+/*!
+ Sets the will delay interval to \a delay.
+*/
+void QMqttLastWillProperties::setWillDelayInterval(quint32 delay)
+{
+ data->willDelayInterval = delay;
+}
+
+/*!
+ Sets the payload format indicator to \a p.
+*/
+void QMqttLastWillProperties::setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator p)
+{
+ data->formatIndicator = p;
+}
+
+/*!
+ Sets the message expiry interval to \a expiry.
+*/
+void QMqttLastWillProperties::setMessageExpiryInterval(quint32 expiry)
+{
+ data->messageExpiryInterval = expiry;
+}
+
+/*!
+ Sets the content type to \a content.
+*/
+void QMqttLastWillProperties::setContentType(const QString &content)
+{
+ data->contentType = content;
+}
+
+/*!
+ Sets the response topic to \a response.
+*/
+void QMqttLastWillProperties::setResponseTopic(const QString &response)
+{
+ data->responseTopic = response;
+}
+
+/*!
+ Sets the correlation data to \a correlation.
+*/
+void QMqttLastWillProperties::setCorrelationData(const QByteArray &correlation)
+{
+ data->correlationData = correlation;
+}
+
+/*!
+ Sets the user properties to \a properties.
+*/
+void QMqttLastWillProperties::setUserProperties(const QMqttUserProperties &properties)
+{
+ data->userProperties = properties;
+}
+
+/*!
+ \internal
+*/
+QMqttConnectionProperties::QMqttConnectionProperties() : data(new QMqttConnectionPropertiesData)
+{
+
+}
+
+/*!
+ \internal
+*/
+QMqttConnectionProperties::QMqttConnectionProperties(const QMqttConnectionProperties &) = default;
+
+QMqttConnectionProperties &QMqttConnectionProperties::operator=(const QMqttConnectionProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqttConnectionProperties::~QMqttConnectionProperties() = default;
+
+/*!
+ Sets the session expiry interval to \a expiry. The session expiry interval
+ specifies the number of seconds a server holds information on the client
+ state after a connection has been closed.
+
+ The default value is 0, which specifies that the session is closed when
+ the network connection ends. If the value is specified as maximum of
+ quint32, then the session does not expire.
+*/
+void QMqttConnectionProperties::setSessionExpiryInterval(quint32 expiry)
+{
+ data->sessionExpiryInterval = expiry;
+}
+
+/*!
+ Sets the maximum QoS level the client is allowed to receive to \a qos.
+
+ A maximum receive value of 0 is not allowed.
+*/
+void QMqttConnectionProperties::setMaximumReceive(quint16 qos)
+{
+ if (qos == 0) {
+ qCDebug(lcMqttConnection) << "Maximum Receive is not allowed to be 0.";
+ return;
+ }
+ data->maximumReceive = qos;
+}
+
+/*!
+ Sets the maximum packet size to \a packetSize. The maximum packet size
+ specifies the maximum size one packet can contain. This includes the
+ packet header and its properties.
+
+ If no maximum packet size is specified, no limit is imposed beyond the
+ limitations of the protocol itself.
+*/
+void QMqttConnectionProperties::setMaximumPacketSize(quint32 packetSize)
+{
+ if (packetSize == 0) {
+ qCDebug(lcMqttConnection) << "Packet size is not allowed to be 0.";
+ return;
+ }
+ data->maximumPacketSize = packetSize;
+}
+
+/*!
+ Sets the maximum topic alias to \a alias. The maximum topic alias specifies
+ the highest value that the client will accept from the server. The client
+ uses this value to limit the number of topic aliases it is willing to hold
+ for the connection.
+
+ The default value is 0. 0 indicates that the client does not accept any
+ topic aliases on this connection.
+*/
+void QMqttConnectionProperties::setMaximumTopicAlias(quint16 alias)
+{
+ data->maximumTopicAlias = alias;
+}
+
+/*!
+ Sets the request response information to \a response. A client uses this
+ to request the server to return response information after the connection
+ request has been handled.
+
+ The default value is \c false, which indicates that the client must not
+ return any response information. If the value is \c true, the server
+ may return response information, but is not enforced to do so.
+*/
+void QMqttConnectionProperties::setRequestResponseInformation(bool response)
+{
+ data->requestResponseInformation = response;
+}
+
+/*!
+ Sets the request problem information to \a problem. A client uses this
+ to request the server to return additional information in case of failure.
+ Types of failure include connection and message management on the server
+ side.
+
+ The default value is \c false, which indicates that the client must not
+ receive any problem information for anything but connection management.
+ The server still may send problem information for connection handling.
+ If the value is \c true, the server may return problem information.
+
+ Problem information is available in user properties or reason strings
+ of the property classes.
+*/
+void QMqttConnectionProperties::setRequestProblemInformation(bool problem)
+{
+ data->requestProblemInformation = problem;
+}
+
+/*!
+ Sets the user properties of the connection to \a properties.
+
+ The default value is to not send any user information.
+*/
+void QMqttConnectionProperties::setUserProperties(const QMqttUserProperties &properties)
+{
+ data->userProperties = properties;
+}
+
+/*!
+ Sets the authentication method to \a authMethod.
+
+ \sa authenticationData()
+*/
+void QMqttConnectionProperties::setAuthenticationMethod(const QString &authMethod)
+{
+ data->authenticationMethod = authMethod;
+}
+
+/*!
+ Sets the authentication data to \a authData.
+
+ Authentication data can only be used if an authentication method has
+ been specified.
+
+ \sa authenticationMethod()
+*/
+void QMqttConnectionProperties::setAuthenticationData(const QByteArray &authData)
+{
+ data->authenticationData = authData;
+}
+
+/*!
+ Returns the session expiry interval.
+*/
+quint32 QMqttConnectionProperties::sessionExpiryInterval() const
+{
+ return data->sessionExpiryInterval;
+}
+
+/*!
+ Returns the maximum QoS level the client can receive.
+*/
+quint16 QMqttConnectionProperties::maximumReceive() const
+{
+ return data->maximumReceive;
+}
+
+/*!
+ Returns the maximum packet size the client can receive.
+*/
+quint32 QMqttConnectionProperties::maximumPacketSize() const
+{
+ return data->maximumPacketSize;
+}
+
+/*!
+ Returns the maximum topic alias ID the client can use.
+*/
+quint16 QMqttConnectionProperties::maximumTopicAlias() const
+{
+ return data->maximumTopicAlias;
+}
+
+/*!
+ Returns whether the client should receive response information.
+*/
+bool QMqttConnectionProperties::requestResponseInformation() const
+{
+ return data->requestResponseInformation;
+}
+
+/*!
+ Returns whether the client should receive problem information.
+*/
+bool QMqttConnectionProperties::requestProblemInformation() const
+{
+ return data->requestProblemInformation;
+}
+
+/*!
+ Returns the user properties for the connection.
+*/
+QMqttUserProperties QMqttConnectionProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+/*!
+ Returns the authentication method.
+*/
+QString QMqttConnectionProperties::authenticationMethod() const
+{
+ return data->authenticationMethod;
+}
+
+/*!
+ Returns the authentication data.
+*/
+QByteArray QMqttConnectionProperties::authenticationData() const
+{
+ return data->authenticationData;
+}
+
+/*!
+ \internal
+*/
+QMqttServerConnectionProperties::QMqttServerConnectionProperties()
+ : QMqttConnectionProperties()
+ , serverData(new QMqttServerConnectionPropertiesData)
+{
+
+}
+
+/*!
+ \internal
+*/
+QMqttServerConnectionProperties::QMqttServerConnectionProperties(const QMqttServerConnectionProperties &rhs)
+ : QMqttConnectionProperties(rhs)
+ , serverData(rhs.serverData)
+{
+
+}
+
+QMqttServerConnectionProperties &QMqttServerConnectionProperties::operator=(const QMqttServerConnectionProperties &rhs)
+{
+ if (this != &rhs) {
+ serverData.operator=(rhs.serverData);
+ QMqttConnectionProperties::operator=(rhs);
+ }
+ return *this;
+}
+
+QMqttServerConnectionProperties::~QMqttServerConnectionProperties() = default;
+
+/*!
+ Returns the available properties specified by the server.
+*/
+QMqttServerConnectionProperties::ServerPropertyDetails QMqttServerConnectionProperties::availableProperties() const
+{
+ return serverData->details;
+}
+
+/*!
+ Returns \c true if the server provided properties as part of the connection
+ aknowledgment. Returns \c false if no properties have been provided.
+*/
+bool QMqttServerConnectionProperties::isValid() const
+{
+ return serverData->valid;
+}
+
+/*!
+ Returns the maximum QoS level the server is able to understand.
+ The default value is 2.
+*/
+quint8 QMqttServerConnectionProperties::maximumQoS() const
+{
+ return serverData->maximumQoS;
+}
+
+/*!
+ Returns \c true if the server accepts retained messages.
+ The default value is \c true.
+*/
+bool QMqttServerConnectionProperties::retainAvailable() const
+{
+ return serverData->retainAvailable;
+}
+
+/*!
+ Returns \c true if the server assigned a new client identifier to
+ the client.
+
+ \sa QMqttClient::clientId()
+*/
+bool QMqttServerConnectionProperties::clientIdAssigned() const
+{
+ return serverData->details & QMqttServerConnectionProperties::AssignedClientId;
+}
+
+/*!
+ Returns the reason string associated with this response.
+*/
+QString QMqttServerConnectionProperties::reason() const
+{
+ return serverData->reasonString;
+}
+
+/*!
+ Returns the reason code associated with this response.
+*/
+QMqtt::ReasonCode QMqttServerConnectionProperties::reasonCode() const
+{
+ return serverData->reasonCode;
+}
+
+/*!
+ Returns \c true if the server accepts subscriptions including wildcards.
+ The default value is \c true.
+*/
+bool QMqttServerConnectionProperties::wildcardSupported() const
+{
+ return serverData->wildcardSupported;
+}
+
+/*!
+ Returns \c true if the server accepts subscription identifiers.
+ Subscription identifiers can be passed to the server when creating
+ a new subscription.
+
+ The default value is \c true.
+
+ \sa QMqttSubscriptionProperties::setSubscriptionIdentifier()
+*/
+bool QMqttServerConnectionProperties::subscriptionIdentifierSupported() const
+{
+ return serverData->subscriptionIdentifierSupported;
+}
+
+/*!
+ Returns \c true if the server accepts shared subscriptions.
+ The default value is \c true.
+*/
+bool QMqttServerConnectionProperties::sharedSubscriptionSupported() const
+{
+ return serverData->sharedSubscriptionSupported;
+}
+
+
+/*!
+ Returns the number of seconds the server requested as keep alive. This
+ overwrites the keep alive being set from the client side.
+
+ \sa QMqttClient::setKeepAlive()
+*/
+quint16 QMqttServerConnectionProperties::serverKeepAlive() const
+{
+ return serverData->serverKeepAlive;
+}
+
+/*!
+ Returns the response information.
+*/
+QString QMqttServerConnectionProperties::responseInformation() const
+{
+ return serverData->responseInformation;
+}
+
+/*!
+ Returns a server address which can be used by the client alternatively
+ to connect to. Typically, this is used together with the reason
+ code \c 0x9c (Use another server) or \c 0x9c (Server moved).
+*/
+QString QMqttServerConnectionProperties::serverReference() const
+{
+ return serverData->serverReference;
+}
+
+QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttconnectionproperties.h b/src/mqtt/qmqttconnectionproperties.h
new file mode 100644
index 0000000..8741f76
--- /dev/null
+++ b/src/mqtt/qmqttconnectionproperties.h
@@ -0,0 +1,166 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTCONNECTIONPROPERTIES_H
+#define QMQTTCONNECTIONPROPERTIES_H
+
+#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqtttype.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttConnectionPropertiesData;
+class QMqttLastWillPropertiesData;
+class QMqttServerConnectionPropertiesData;
+
+class Q_MQTT_EXPORT QMqttLastWillProperties
+{
+ Q_GADGET
+public:
+ QMqttLastWillProperties();
+ QMqttLastWillProperties(const QMqttLastWillProperties &);
+ QMqttLastWillProperties &operator=(const QMqttLastWillProperties &);
+ ~QMqttLastWillProperties();
+
+ quint32 willDelayInterval() const;
+ QMqtt::PayloadFormatIndicator payloadFormatIndicator() const;
+ quint32 messageExpiryInterval() const;
+ QString contentType() const;
+ QString responseTopic() const;
+ QByteArray correlationData() const;
+ QMqttUserProperties userProperties() const;
+
+ void setWillDelayInterval(quint32 delay);
+ void setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator p);
+ void setMessageExpiryInterval(quint32 expiry);
+ void setContentType(const QString &content);
+ void setResponseTopic(const QString &response);
+ void setCorrelationData(const QByteArray &correlation);
+ void setUserProperties(const QMqttUserProperties &properties);
+
+protected:
+ QSharedDataPointer<QMqttLastWillPropertiesData> data;
+};
+
+class Q_MQTT_EXPORT QMqttConnectionProperties
+{
+ Q_GADGET
+public:
+ QMqttConnectionProperties();
+ QMqttConnectionProperties(const QMqttConnectionProperties &);
+ QMqttConnectionProperties &operator=(const QMqttConnectionProperties &);
+ ~QMqttConnectionProperties();
+
+ quint32 sessionExpiryInterval() const;
+ quint16 maximumReceive() const;
+ quint32 maximumPacketSize() const;
+ quint16 maximumTopicAlias() const;
+ bool requestResponseInformation() const;
+ bool requestProblemInformation() const;
+ QMqttUserProperties userProperties() const;
+ QString authenticationMethod() const;
+ QByteArray authenticationData() const;
+
+ void setSessionExpiryInterval(quint32 expiry);
+ void setMaximumReceive(quint16 qos);
+ void setMaximumPacketSize(quint32 packetSize);
+ void setMaximumTopicAlias(quint16 alias);
+ void setRequestResponseInformation(bool response);
+ void setRequestProblemInformation(bool problem);
+ void setUserProperties(const QMqttUserProperties &properties);
+ void setAuthenticationMethod(const QString &authMethod);
+ void setAuthenticationData(const QByteArray &authData);
+
+protected:
+ QSharedDataPointer<QMqttConnectionPropertiesData> data;
+};
+
+class Q_MQTT_EXPORT QMqttServerConnectionProperties
+ : public QMqttConnectionProperties
+{
+ Q_GADGET
+public:
+ enum ServerPropertyDetail : quint32 {
+ None = 0x00000000,
+ SessionExpiryInterval = 0x00000001,
+ MaximumReceive = 0x00000002,
+ MaximumQoS = 0x00000004,
+ RetainAvailable = 0x00000010,
+ MaximumPacketSize = 0x00000020,
+ AssignedClientId = 0x00000040,
+ MaximumTopicAlias = 0x00000080,
+ ReasonString = 0x00000100,
+ UserProperty = 0x00000200,
+ WildCardSupported = 0x00000400,
+ SubscriptionIdentifierSupport = 0x00000800,
+ SharedSubscriptionSupport = 0x00001000,
+ ServerKeepAlive = 0x00002000,
+ ResponseInformation = 0x00004000,
+ ServerReference = 0x00008000,
+ AuthenticationMethod = 0x00010000,
+ AuthenticationData = 0x00020000
+ };
+ Q_ENUM(ServerPropertyDetail)
+ Q_DECLARE_FLAGS(ServerPropertyDetails, ServerPropertyDetail)
+
+ QMqttServerConnectionProperties();
+ QMqttServerConnectionProperties(const QMqttServerConnectionProperties &);
+ QMqttServerConnectionProperties &operator=(const QMqttServerConnectionProperties &);
+ ~QMqttServerConnectionProperties();
+
+ ServerPropertyDetails availableProperties() const;
+
+ bool isValid() const;
+
+ quint8 maximumQoS() const;
+ bool retainAvailable() const;
+ bool clientIdAssigned() const;
+ QString reason() const;
+ QMqtt::ReasonCode reasonCode() const;
+ bool wildcardSupported() const;
+ bool subscriptionIdentifierSupported() const;
+ bool sharedSubscriptionSupported() const;
+ quint16 serverKeepAlive() const;
+ QString responseInformation() const;
+ QString serverReference() const;
+
+
+private:
+ friend class QMqttConnection;
+ QSharedDataPointer<QMqttServerConnectionPropertiesData> serverData;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QMqttServerConnectionProperties::ServerPropertyDetails)
+
+QT_END_NAMESPACE
+
+#endif // QMQTTCONNECTIONPROPERTIES_H
diff --git a/src/mqtt/qmqttconnectionproperties_p.h b/src/mqtt/qmqttconnectionproperties_p.h
new file mode 100644
index 0000000..561fdb3
--- /dev/null
+++ b/src/mqtt/qmqttconnectionproperties_p.h
@@ -0,0 +1,93 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTCONNECTIONPROPERTIES_P_H
+#define QMQTTCONNECTIONPROPERTIES_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qmqttconnectionproperties.h"
+
+QT_BEGIN_NAMESPACE
+
+class QMqttLastWillPropertiesData : public QSharedData
+{
+public:
+ QString contentType;
+ QString responseTopic;
+ QByteArray correlationData;
+ QMqttUserProperties userProperties;
+ quint32 willDelayInterval{0};
+ quint32 messageExpiryInterval{0};
+ QMqtt::PayloadFormatIndicator formatIndicator{QMqtt::PayloadFormatIndicator::Unspecified};
+};
+
+class QMqttConnectionPropertiesData : public QSharedData
+{
+public:
+ QMqttUserProperties userProperties;
+ QString authenticationMethod;
+ QByteArray authenticationData;
+ quint32 sessionExpiryInterval{0};
+ quint32 maximumPacketSize{std::numeric_limits<quint32>::max()};
+ quint16 maximumReceive{65535};
+ quint16 maximumTopicAlias{0};
+ bool requestResponseInformation{false};
+ bool requestProblemInformation{true};
+};
+
+class QMqttServerConnectionPropertiesData : public QSharedData
+{
+public:
+ QMqttServerConnectionProperties::ServerPropertyDetails details{QMqttServerConnectionProperties::None};
+ QString reasonString;
+ QString responseInformation;
+ QString serverReference;
+ quint16 serverKeepAlive{0};
+ quint8 maximumQoS{2};
+ QMqtt::ReasonCode reasonCode{QMqtt::ReasonCode::Success};
+ bool valid{false}; // Only set to true after CONNACK
+ bool retainAvailable{true};
+ bool wildcardSupported{true};
+ bool subscriptionIdentifierSupported{true};
+ bool sharedSubscriptionSupported{true};
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTCONNECTIONPROPERTIES_P_H
diff --git a/src/mqtt/qmqttcontrolpacket.cpp b/src/mqtt/qmqttcontrolpacket.cpp
index 3d0db8d..4eb3663 100644
--- a/src/mqtt/qmqttcontrolpacket.cpp
+++ b/src/mqtt/qmqttcontrolpacket.cpp
@@ -28,10 +28,14 @@
******************************************************************************/
#include "qmqttcontrolpacket_p.h"
+
#include <QtCore/QtEndian>
+#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcMqttClient)
+
QMqttControlPacket::QMqttControlPacket()
{
@@ -73,8 +77,14 @@ void QMqttControlPacket::append(quint16 value)
{
const quint16 msb = qToBigEndian<quint16>(value);
const char * msb_c = reinterpret_cast<const char*>(&msb);
- m_payload.append(msb_c[0]);
- m_payload.append(msb_c[1]);
+ m_payload.append(msb_c, 2);
+}
+
+void QMqttControlPacket::append(quint32 value)
+{
+ const quint32 msb = qToBigEndian<quint32>(value);
+ const char * msb_c = reinterpret_cast<const char*>(&msb);
+ m_payload.append(msb_c, 4);
}
void QMqttControlPacket::append(const QByteArray &data)
@@ -88,16 +98,40 @@ void QMqttControlPacket::appendRaw(const QByteArray &data)
m_payload.append(data);
}
+void QMqttControlPacket::appendRawVariableInteger(quint32 value)
+{
+ QByteArray data;
+ // Add length
+ if (value > 268435455)
+ qCDebug(lcMqttClient) << "Attempting to write variable integer overflow.";
+ do {
+ quint8 b = value % 128;
+ value /= 128;
+ if (value > 0)
+ b |= 0x80;
+ data.append(char(b));
+ } while (value > 0);
+ appendRaw(data);
+}
+
QByteArray QMqttControlPacket::serialize() const
{
// Create ByteArray
QByteArray data;
// Add Header
data.append(char(m_header));
+ data.append(serializePayload());
+
+ return data;
+}
+
+QByteArray QMqttControlPacket::serializePayload() const
+{
+ QByteArray data;
// Add length
- quint32 msgSize = m_payload.size();
- if (msgSize > 268435455) // 0xFFFFFF7F
- qWarning("Publishing a message bigger than maximum size!");
+ quint32 msgSize = quint32(m_payload.size());
+ if (msgSize > 268435455)
+ qCDebug(lcMqttClient) << "Publishing a message bigger than maximum size.";
do {
quint8 b = msgSize % 128;
msgSize /= 128;
@@ -107,7 +141,6 @@ QByteArray QMqttControlPacket::serialize() const
} while (msgSize > 0);
// Add payload
data.append(m_payload);
-
return data;
}
diff --git a/src/mqtt/qmqttcontrolpacket_p.h b/src/mqtt/qmqttcontrolpacket_p.h
index 48bf4cd..740b1c4 100644
--- a/src/mqtt/qmqttcontrolpacket_p.h
+++ b/src/mqtt/qmqttcontrolpacket_p.h
@@ -66,6 +66,7 @@ public:
PINGREQ = 0xC0,
PINGRESP = 0xD0,
DISCONNECT = 0xE0,
+ AUTH = 0xF0,
};
QMqttControlPacket();
@@ -79,10 +80,13 @@ public:
void append(char value);
void append(quint16 value);
+ void append(quint32 value);
void append(const QByteArray &data);
void appendRaw(const QByteArray &data);
+ void appendRawVariableInteger(quint32 value);
QByteArray serialize() const;
+ QByteArray serializePayload() const;
inline QByteArray payload() const { return m_payload; }
private:
quint8 m_header{UNKNOWN};
diff --git a/src/mqtt/qmqttglobal.h b/src/mqtt/qmqttglobal.h
index 720531b..8386a7c 100644
--- a/src/mqtt/qmqttglobal.h
+++ b/src/mqtt/qmqttglobal.h
@@ -44,6 +44,57 @@ QT_BEGIN_NAMESPACE
# define Q_MQTT_EXPORT
#endif
+namespace QMqtt
+{
+enum class PayloadFormatIndicator : quint8 {
+ Unspecified = 0,
+ UTF8Encoded = 1
+};
+
+enum class MessageStatus : quint8 {
+ Unknown = 0,
+ Published,
+ Acknowledged,
+ Received,
+ Released,
+ Completed
+};
+
+enum class ReasonCode : quint16 {
+ Success = 0,
+ SubscriptionQoSLevel0 = 0,
+ SubscriptionQoSLevel1 = 0x01,
+ SubscriptionQoSLevel2 = 0x02,
+ NoMatchingSubscriber = 0x10,
+ UnspecifiedError = 0x80,
+ MalformedPacket = 0x81,
+ ProtocolError = 0x82,
+ ImplementationSpecificError = 0x83,
+ UnsupportedProtocolVersion = 0x84,
+ InvalidClientId = 0x85,
+ InvalidUserNameOrPassword = 0x86,
+ NotAuthorized = 0x87,
+ ServerNotAvailable = 0x88,
+ ServerBusy = 0x89,
+ ClientBanned = 0x8A,
+ InvalidAuthenticationMethod = 0x8C,
+ InvalidTopicFilter = 0x8F,
+ InvalidTopicName = 0x90,
+ MessageIdInUse = 0x91,
+ MessageIdNotFound = 0x92,
+ PacketTooLarge = 0x95,
+ QuotaExceeded = 0x97,
+ InvalidPayloadFormat = 0x99,
+ RetainNotSupported = 0x9A,
+ QoSNotSupported = 0x9B,
+ UseAnotherServer = 0x9C,
+ ServerMoved = 0x9D,
+ SharedSubscriptionsNotSupported = 0x9E,
+ ExceededConnectionRate = 0x9F,
+ SubscriptionIdsNotSupported = 0xA1,
+ WildCardSubscriptionsNotSupported = 0xA2
+};
+}
QT_END_NAMESPACE
#endif //QTQMQTTGLOBAL_H
diff --git a/src/mqtt/qmqttmessage.cpp b/src/mqtt/qmqttmessage.cpp
index 25ed768..1f3b49e 100644
--- a/src/mqtt/qmqttmessage.cpp
+++ b/src/mqtt/qmqttmessage.cpp
@@ -28,6 +28,7 @@
******************************************************************************/
#include "qmqttmessage.h"
+#include "qmqttmessage_p.h"
QT_BEGIN_NAMESPACE
@@ -86,25 +87,6 @@ QT_BEGIN_NAMESPACE
live update. A broker can store only one retained message per topic.
*/
-class QMqttMessagePrivate : public QSharedData
-{
-public:
- bool operator==(const QMqttMessagePrivate &other) {
- return m_topic == other.m_topic &&
- m_payload == other.m_payload &&
- m_id == other.m_id &&
- m_qos == other.m_qos &&
- m_duplicate == other.m_duplicate &&
- m_retain == other.m_retain;
- }
- QMqttTopicName m_topic;
- QByteArray m_payload;
- quint16 m_id{0};
- quint8 m_qos{0};
- bool m_duplicate{false};
- bool m_retain{false};
-};
-
/*!
Creates a new MQTT message.
*/
@@ -181,6 +163,24 @@ bool QMqttMessage::retain() const
}
/*!
+ \since 5.12
+
+ Returns the publish properties received as part of the message.
+
+ \note This function only specifies the properties when a
+ publish message is received. Messages with a QoS value of
+ 1 or 2 can contain additional properties when a message is released.
+ Those can be obtained by the QMqttClient::messageStatusChanged signal.
+
+ \note This function will only provide valid data when the client
+ specifies QMqttClient::MQTT_5_0 as QMqttClient::ProtocolVersion.
+*/
+QMqttPublishProperties QMqttMessage::publishProperties() const
+{
+ return d->m_publishProperties;
+}
+
+/*!
\internal
Constructs a new MQTT message with \a content on the topic \a topic.
Furthermore the properties \a id, \a qos, \a dup, \a retain must be specified.
diff --git a/src/mqtt/qmqttmessage.h b/src/mqtt/qmqttmessage.h
index c011cbe..af36918 100644
--- a/src/mqtt/qmqttmessage.h
+++ b/src/mqtt/qmqttmessage.h
@@ -31,6 +31,7 @@
#define QMQTTMESSAGE_H
#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqttpublishproperties.h>
#include <QtMqtt/qmqtttopicname.h>
#include <QtCore/QObject>
@@ -66,6 +67,7 @@ public:
bool duplicate() const;
bool retain() const;
+ QMqttPublishProperties publishProperties() const;
private:
friend class QMqttConnection;
QMqttMessage(const QMqttTopicName &topic, const QByteArray &payload,
diff --git a/src/mqtt/qmqttmessage_p.h b/src/mqtt/qmqttmessage_p.h
new file mode 100644
index 0000000..ee27e9e
--- /dev/null
+++ b/src/mqtt/qmqttmessage_p.h
@@ -0,0 +1,74 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTMESSAGE_P_H
+#define QMQTTMESSAGE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qmqttglobal.h"
+#include "qmqtttopicname.h"
+#include "qmqttpublishproperties.h"
+
+#include <QtCore/QSharedData>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttMessagePrivate : public QSharedData
+{
+public:
+ bool operator==(const QMqttMessagePrivate &other) const {
+ return m_topic == other.m_topic
+ && m_payload == other.m_payload
+ && m_id == other.m_id
+ && m_qos == other.m_qos
+ && m_duplicate == other.m_duplicate
+ && m_retain == other.m_retain;
+ }
+ QMqttTopicName m_topic;
+ QByteArray m_payload;
+ quint16 m_id{0};
+ quint8 m_qos{0};
+ bool m_duplicate{false};
+ bool m_retain{false};
+ QMqttPublishProperties m_publishProperties;
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTMESSAGE_P_H
diff --git a/src/mqtt/qmqttpublishproperties.cpp b/src/mqtt/qmqttpublishproperties.cpp
new file mode 100644
index 0000000..77e2118
--- /dev/null
+++ b/src/mqtt/qmqttpublishproperties.cpp
@@ -0,0 +1,302 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 "qmqttpublishproperties.h"
+#include "qmqttpublishproperties_p.h"
+#include "qmqtttype.h"
+
+#include <QtCore/QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcMqttClient)
+
+/*!
+ \class QMqttPublishProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttPublishProperties class represents configuration
+ options for sending or receiving a message.
+
+ Invoking QMqttClient::publish() to send a message to a broker can
+ include QMqttPublishProperties to provide additional arguments on
+ how the message should be treated on the broker.
+
+ Furthermore receiving a message by an instantiated subscription
+ might contain publish properties which have been forwarded or
+ adapted by the server.
+
+ \note Publish properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+/*!
+ \enum QMqttPublishProperties::PublishPropertyDetail
+
+ This enum type specifies the available properties set by the
+ server or the client when creating a message.
+
+ \value None
+ No property has been specified.
+ \value PayloadFormatIndicator
+ The type of content of the message.
+ \value MessageExpiryInterval
+ The duration a message is valid.
+ \value TopicAlias
+ The topic alias for this message.
+ \value ResponseTopic
+ The topic the receipient should respond to.
+ \value CorrelationData
+ An identifier of the response message.
+ \value UserProperty
+ Additional properties set by the user.
+ \value SubscriptionIdentifier
+ An identifier of subscriptions matching the publication.
+ \value ContentType
+ A description of the content of the message.
+*/
+
+QMqttPublishProperties::QMqttPublishProperties() : data(new QMqttPublishPropertiesData)
+{
+}
+
+/*!
+ \internal
+*/
+QMqttPublishProperties::QMqttPublishProperties(const QMqttPublishProperties &) = default;
+
+/*!
+ \internal
+*/
+QMqttPublishProperties &QMqttPublishProperties::operator=(const QMqttPublishProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqttPublishProperties::~QMqttPublishProperties() = default;
+
+/*!
+ Returns the available properties specified in this instance. When a message
+ is created, it does not need to include all properties. This function
+ serves as an indicator of those properties which have been explicitly
+ set.
+*/
+QMqttPublishProperties::PublishPropertyDetails QMqttPublishProperties::availableProperties() const
+{
+ return data->details;
+}
+
+/*!
+ Returns the payload format indicator.
+*/
+QMqtt::PayloadFormatIndicator QMqttPublishProperties::payloadFormatIndicator() const
+{
+ return data->payloadIndicator;
+}
+
+/*!
+ Sets the payload format indicator to \a indicator.
+*/
+void QMqttPublishProperties::setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator indicator)
+{
+ data->details |= QMqttPublishProperties::PayloadFormatIndicator;
+ data->payloadIndicator = indicator;
+}
+
+/*!
+ Returns the message expiry interval. This value specifies the number
+ of seconds a server is allowed to forward the message. If the interval
+ expires, the server must delete the message and abort publishing it.
+*/
+quint32 QMqttPublishProperties::messageExpiryInterval() const
+{
+ return data->messageExpiry;
+}
+
+/*!
+ Sets the message expiry interval to \a interval.
+*/
+void QMqttPublishProperties::setMessageExpiryInterval(quint32 interval)
+{
+ data->details |= QMqttPublishProperties::MessageExpiryInterval;
+ data->messageExpiry = interval;
+}
+
+/*!
+ Returns the topic alias used for publishing a message.
+*/
+quint16 QMqttPublishProperties::topicAlias() const
+{
+ return data->topicAlias;
+}
+
+/*!
+ Sets the topic alias for publishing a message to \a alias. A topic alias
+ value must be greater than zero and less than the maximum topic alias
+ specified by the server.
+
+ \sa QMqttServerConnectionProperties::maximumTopicAlias()
+*/
+void QMqttPublishProperties::setTopicAlias(quint16 alias)
+{
+ if (alias == 0) {
+ qCDebug(lcMqttClient) << "A topic alias with value 0 is not allowed.";
+ return;
+ }
+ data->details |= QMqttPublishProperties::TopicAlias;
+ data->topicAlias = alias;
+}
+
+/*!
+ Returns the response topic a user should use as a follow up to
+ a request.
+*/
+QString QMqttPublishProperties::responseTopic() const
+{
+ return data->responseTopic;
+}
+
+/*!
+ Sets the response topic to \a topic.
+*/
+void QMqttPublishProperties::setResponseTopic(const QString &topic)
+{
+ data->details |= QMqttPublishProperties::ResponseTopic;
+ data->responseTopic = topic;
+}
+
+/*!
+ Returns the correlation data.
+*/
+QByteArray QMqttPublishProperties::correlationData() const
+{
+ return data->correlationData;
+}
+
+/*!
+ Sets the correlation data to \a correlation.
+*/
+void QMqttPublishProperties::setCorrelationData(const QByteArray &correlation)
+{
+ data->details |= QMqttPublishProperties::CorrelationData;
+ data->correlationData = correlation;
+}
+
+/*!
+ Returns the user properties of a message.
+*/
+QMqttUserProperties QMqttPublishProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+/*!
+ Sets the user properties of a message to \a properties.
+*/
+void QMqttPublishProperties::setUserProperties(const QMqttUserProperties &properties)
+{
+ data->details |= QMqttPublishProperties::UserProperty;
+ data->userProperties = properties;
+}
+
+/*!
+ Returns the subscription identifiers of subscriptions matching
+ the topic filter of the message.
+*/
+QList<quint32> QMqttPublishProperties::subscriptionIdentifiers() const
+{
+ return data->subscriptionIdentifier;
+}
+
+/*!
+ Sets the subscription identifiers to \a ids.
+*/
+void QMqttPublishProperties::setSubscriptionIdentifiers(const QList<quint32> &ids)
+{
+ if (ids.contains(0)) {
+ qCDebug(lcMqttClient) << "A subscription identifier with value 0 is not allowed.";
+ return;
+ }
+ data->details |= QMqttPublishProperties::SubscriptionIdentifier;
+ data->subscriptionIdentifier = ids;
+}
+
+/*!
+ Returns the content type of the message.
+*/
+QString QMqttPublishProperties::contentType() const
+{
+ return data->contentType;
+}
+
+/*!
+ Sets the content type of the message to \a type.
+*/
+void QMqttPublishProperties::setContentType(const QString &type)
+{
+ data->details |= QMqttPublishProperties::ContentType;
+ data->contentType = type;
+}
+
+QMqttMessageStatusProperties::QMqttMessageStatusProperties() : data(new QMqttMessageStatusPropertiesData)
+{
+
+}
+
+QMqttMessageStatusProperties &QMqttMessageStatusProperties::operator=(const QMqttMessageStatusProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqtt::ReasonCode QMqttMessageStatusProperties::reasonCode() const
+{
+ return data->reasonCode;
+}
+
+QString QMqttMessageStatusProperties::reason() const
+{
+ return data->reasonString;
+}
+
+QMqttUserProperties QMqttMessageStatusProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+QMqttMessageStatusProperties::~QMqttMessageStatusProperties() = default;
+QMqttMessageStatusProperties::QMqttMessageStatusProperties(const QMqttMessageStatusProperties &) = default;
+
+QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttpublishproperties.h b/src/mqtt/qmqttpublishproperties.h
new file mode 100644
index 0000000..a239b4d
--- /dev/null
+++ b/src/mqtt/qmqttpublishproperties.h
@@ -0,0 +1,117 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTPUBLISHPROPERTIES_H
+#define QMQTTPUBLISHPROPERTIES_H
+
+#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqtttype.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttPublishPropertiesData;
+class QMqttMessageStatusPropertiesData;
+
+class Q_MQTT_EXPORT QMqttPublishProperties
+{
+ Q_GADGET
+public:
+ enum PublishPropertyDetail : quint32 {
+ None = 0x00000000,
+ PayloadFormatIndicator = 0x00000001,
+ MessageExpiryInterval = 0x00000002,
+ TopicAlias = 0x00000004,
+ ResponseTopic = 0x00000008,
+ CorrelationData = 0x00000010,
+ UserProperty = 0x00000020,
+ SubscriptionIdentifier = 0x00000040,
+ ContentType = 0x00000080
+ };
+ Q_ENUM(PublishPropertyDetail)
+ Q_DECLARE_FLAGS(PublishPropertyDetails, PublishPropertyDetail)
+
+ QMqttPublishProperties();
+ QMqttPublishProperties(const QMqttPublishProperties &);
+ QMqttPublishProperties &operator=(const QMqttPublishProperties &);
+ ~QMqttPublishProperties();
+
+ PublishPropertyDetails availableProperties() const;
+
+ QMqtt::PayloadFormatIndicator payloadFormatIndicator() const;
+ void setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator indicator);
+
+ quint32 messageExpiryInterval() const;
+ void setMessageExpiryInterval(quint32 interval);
+
+ quint16 topicAlias() const;
+ void setTopicAlias(quint16 alias);
+
+ QString responseTopic() const;
+ void setResponseTopic(const QString &topic);
+
+ QByteArray correlationData() const;
+ void setCorrelationData(const QByteArray &correlation);
+
+ QMqttUserProperties userProperties() const;
+ void setUserProperties(const QMqttUserProperties &properties);
+
+ QList<quint32> subscriptionIdentifiers() const;
+ void setSubscriptionIdentifiers(const QList<quint32> &ids);
+
+ QString contentType() const;
+ void setContentType(const QString &type);
+private:
+ QSharedDataPointer<QMqttPublishPropertiesData> data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QMqttPublishProperties::PublishPropertyDetails)
+
+class Q_MQTT_EXPORT QMqttMessageStatusProperties
+{
+ Q_GADGET
+public:
+ QMqttMessageStatusProperties();
+ QMqttMessageStatusProperties(const QMqttMessageStatusProperties &);
+ QMqttMessageStatusProperties &operator=(const QMqttMessageStatusProperties &);
+ ~QMqttMessageStatusProperties();
+
+ QMqtt::ReasonCode reasonCode() const;
+ QString reason() const;
+ QMqttUserProperties userProperties() const;
+
+private:
+ friend class QMqttConnection;
+ QSharedDataPointer<QMqttMessageStatusPropertiesData> data;
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTPUBLISHPROPERTIES_H
diff --git a/src/mqtt/qmqttpublishproperties_p.h b/src/mqtt/qmqttpublishproperties_p.h
new file mode 100644
index 0000000..2fc9d21
--- /dev/null
+++ b/src/mqtt/qmqttpublishproperties_p.h
@@ -0,0 +1,75 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTPUBLISHPROPERTIES_P_H
+#define QMQTTPUBLISHPROPERTIES_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qmqttglobal.h"
+#include "qmqttpublishproperties.h"
+
+#include <QtCore/QSharedData>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttPublishPropertiesData : public QSharedData
+{
+public:
+ QString responseTopic;
+ QString contentType;
+ QByteArray correlationData;
+ quint32 messageExpiry{0};
+ QList<quint32> subscriptionIdentifier;
+ QMqttPublishProperties::PublishPropertyDetails details{QMqttPublishProperties::None};
+ quint16 topicAlias{0};
+ QMqtt::PayloadFormatIndicator payloadIndicator{QMqtt::PayloadFormatIndicator::Unspecified};
+ QMqttUserProperties userProperties;
+};
+
+class QMqttMessageStatusPropertiesData : public QSharedData
+{
+public:
+ QMqttUserProperties userProperties;
+ QString reasonString;
+ QMqtt::ReasonCode reasonCode{QMqtt::ReasonCode::Success};
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTPUBLISHPROPERTIES_P_H
diff --git a/src/mqtt/qmqttsubscription.cpp b/src/mqtt/qmqttsubscription.cpp
index a492796..35f3e52 100644
--- a/src/mqtt/qmqttsubscription.cpp
+++ b/src/mqtt/qmqttsubscription.cpp
@@ -86,6 +86,36 @@ QT_BEGIN_NAMESPACE
This signal is emitted when the new message \a msg has been received.
*/
+/*!
+ \property QMqttSubscription::reason
+ \since 5.12
+ \brief This property holds the reason string for the subscription.
+
+ A reason string is used by the server to provide additional information
+ about the subscription. It is optional for the server to send it.
+*/
+
+/*!
+ \property QMqttSubscription::reasonCode
+ \since 5.12
+ \brief This property holds the reason code for the subscription.
+
+ The reason code specifies the error type if a subscription has failed,
+ or the level of QoS for success.
+*/
+
+/*!
+ \property QMqttSubscription::shared
+ \since 5.12
+ \brief This property holds whether the subscription is shared.
+*/
+
+/*!
+ \property QMqttSubscription::shareName
+ \since 5.12
+ \brief This property holds the name of the shared subscription.
+*/
+
QMqttSubscription::QMqttSubscription(QObject *parent) : QObject(*(new QMqttSubscriptionPrivate), parent)
{
@@ -120,6 +150,45 @@ quint8 QMqttSubscription::qos() const
return d->m_qos;
}
+QString QMqttSubscription::reason() const
+{
+ Q_D(const QMqttSubscription);
+ return d->m_reasonString;
+}
+
+QMqtt::ReasonCode QMqttSubscription::reasonCode() const
+{
+ Q_D(const QMqttSubscription);
+ return d->m_reasonCode;
+}
+
+/*!
+ \since 5.12
+
+ Returns the user properties received from the broker when the subscription
+ has been accepted.
+
+ \note This function will only provide valid data when the client
+ specifies QMqttClient::MQTT_5_0 as QMqttClient::ProtocolVersion.
+*/
+QMqttUserProperties QMqttSubscription::userProperties() const
+{
+ Q_D(const QMqttSubscription);
+ return d->m_userProperties;
+}
+
+bool QMqttSubscription::isShared() const
+{
+ Q_D(const QMqttSubscription);
+ return d->m_shared;
+}
+
+QString QMqttSubscription::shareName() const
+{
+ Q_D(const QMqttSubscription);
+ return d->m_shareName;
+}
+
void QMqttSubscription::setState(QMqttSubscription::SubscriptionState state)
{
Q_D(QMqttSubscription);
@@ -161,6 +230,18 @@ void QMqttSubscription::setQos(quint8 qos)
d->m_qos = qos;
}
+void QMqttSubscription::setShared(bool s)
+{
+ Q_D(QMqttSubscription);
+ d->m_shared = s;
+}
+
+void QMqttSubscription::setShareName(const QString &name)
+{
+ Q_D(QMqttSubscription);
+ d->m_shareName = name;
+}
+
QMqttSubscriptionPrivate::QMqttSubscriptionPrivate()
: QObjectPrivate()
{
diff --git a/src/mqtt/qmqttsubscription.h b/src/mqtt/qmqttsubscription.h
index b2f6891..ad10d37 100644
--- a/src/mqtt/qmqttsubscription.h
+++ b/src/mqtt/qmqttsubscription.h
@@ -48,6 +48,10 @@ class Q_MQTT_EXPORT QMqttSubscription : public QObject
Q_PROPERTY(SubscriptionState state READ state NOTIFY stateChanged)
Q_PROPERTY(quint8 qos READ qos NOTIFY qosChanged)
Q_PROPERTY(QMqttTopicFilter topic READ topic)
+ Q_PROPERTY(QString reason READ reason)
+ Q_PROPERTY(QMqtt::ReasonCode reasonCode READ reasonCode)
+ Q_PROPERTY(bool shared READ isShared)
+ Q_PROPERTY(QString shareName READ shareName)
public:
~QMqttSubscription() override;
enum SubscriptionState {
@@ -61,6 +65,12 @@ public:
SubscriptionState state() const;
QMqttTopicFilter topic() const;
quint8 qos() const;
+ QString reason() const;
+ QMqtt::ReasonCode reasonCode() const;
+ QMqttUserProperties userProperties() const;
+
+ bool isShared() const;
+ QString shareName() const;
Q_SIGNALS:
void stateChanged(SubscriptionState state);
@@ -77,6 +87,8 @@ private:
void setTopic(const QMqttTopicFilter &topic);
void setClient(QMqttClient *client);
void setQos(quint8 qos);
+ void setShared(bool s);
+ void setShareName(const QString &name);
friend class QMqttConnection;
friend class QMqttClient;
explicit QMqttSubscription(QObject *parent = nullptr);
diff --git a/src/mqtt/qmqttsubscription_p.h b/src/mqtt/qmqttsubscription_p.h
index 89ffdf0..f56c404 100644
--- a/src/mqtt/qmqttsubscription_p.h
+++ b/src/mqtt/qmqttsubscription_p.h
@@ -53,9 +53,14 @@ public:
QMqttSubscriptionPrivate();
~QMqttSubscriptionPrivate() override = default;
QMqttClient *m_client{nullptr};
- QMqttSubscription::SubscriptionState m_state{QMqttSubscription::Unsubscribed};
QMqttTopicFilter m_topic;
+ QString m_reasonString;
+ QMqttUserProperties m_userProperties;
+ QString m_shareName;
+ QMqttSubscription::SubscriptionState m_state{QMqttSubscription::Unsubscribed};
+ QMqtt::ReasonCode m_reasonCode{QMqtt::ReasonCode::Success};
quint8 m_qos{0};
+ bool m_shared{false};
};
QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttsubscriptionproperties.cpp b/src/mqtt/qmqttsubscriptionproperties.cpp
new file mode 100644
index 0000000..08977a9
--- /dev/null
+++ b/src/mqtt/qmqttsubscriptionproperties.cpp
@@ -0,0 +1,168 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 "qmqttsubscriptionproperties.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QMqttSubscriptionProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttSubscriptionProperties class represents configuration
+ options a QMqttClient can pass to the server when subscribing to a
+ topic filter.
+
+ \note Subscription properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+/*!
+ \class QMqttUnsubscriptionProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttUnsubscriptionProperties class represents configuration
+ options a QMqttClient can pass to the server when unsubscribing from a
+ topic filter.
+
+ \note Unsubscription properties are part of the MQTT 5.0 specification and
+ cannot be used when connecting with a lower protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+*/
+
+class QMqttSubscriptionPropertiesData : public QSharedData
+{
+public:
+ quint32 subscriptionIdentifier{0};
+ QMqttUserProperties userProperties;
+};
+
+class QMqttUnsubscriptionPropertiesData : public QSharedData
+{
+public:
+ QMqttUserProperties userProperties;
+};
+
+/*!
+ \internal
+*/
+QMqttSubscriptionProperties::QMqttSubscriptionProperties() : data(new QMqttSubscriptionPropertiesData)
+{
+
+}
+
+/*!
+ \internal
+*/
+QMqttSubscriptionProperties::QMqttSubscriptionProperties(const QMqttSubscriptionProperties &) = default;
+
+QMqttSubscriptionProperties &QMqttSubscriptionProperties::operator=(const QMqttSubscriptionProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+QMqttSubscriptionProperties::~QMqttSubscriptionProperties() = default;
+
+/*!
+ Returns the user specified properties.
+*/
+QMqttUserProperties QMqttSubscriptionProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+/*!
+ Sets the user properties to \a user.
+*/
+void QMqttSubscriptionProperties::setUserProperties(const QMqttUserProperties &user)
+{
+ data->userProperties = user;
+}
+
+/*!
+ Returns the subscription identifier used to describe this subscription.
+*/
+quint32 QMqttSubscriptionProperties::subscriptionIdentifier() const
+{
+ return data->subscriptionIdentifier;
+}
+
+/*!
+ Sets the subscription identifier to \a id.
+*/
+void QMqttSubscriptionProperties::setSubscriptionIdentifier(quint32 id)
+{
+ data->subscriptionIdentifier = id;
+}
+
+/*!
+ \internal
+*/
+QMqttUnsubscriptionProperties::QMqttUnsubscriptionProperties() : data(new QMqttUnsubscriptionPropertiesData)
+{
+}
+
+/*!
+ \internal
+*/
+QMqttUnsubscriptionProperties &QMqttUnsubscriptionProperties::operator=(const QMqttUnsubscriptionProperties &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+/*!
+ Returns the user specified properties.
+*/
+QMqttUserProperties QMqttUnsubscriptionProperties::userProperties() const
+{
+ return data->userProperties;
+}
+
+/*!
+ Sets the user properties to \a user.
+*/
+void QMqttUnsubscriptionProperties::setUserProperties(const QMqttUserProperties &user)
+{
+ data->userProperties = user;
+}
+
+QMqttUnsubscriptionProperties::~QMqttUnsubscriptionProperties() = default;
+
+QMqttUnsubscriptionProperties::QMqttUnsubscriptionProperties(const QMqttUnsubscriptionProperties &) = default;
+
+QT_END_NAMESPACE
diff --git a/src/mqtt/qmqttsubscriptionproperties.h b/src/mqtt/qmqttsubscriptionproperties.h
new file mode 100644
index 0000000..3794609
--- /dev/null
+++ b/src/mqtt/qmqttsubscriptionproperties.h
@@ -0,0 +1,84 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTSUBSCRIPTIONPROPERTIES_H
+#define QMQTTSUBSCRIPTIONPROPERTIES_H
+
+#include <QtMqtt/qmqttglobal.h>
+#include <QtMqtt/qmqtttype.h>
+
+#include <QtCore/QHash>
+#include <QtCore/QObject>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttSubscriptionPropertiesData;
+class QMqttUnsubscriptionPropertiesData;
+
+class Q_MQTT_EXPORT QMqttSubscriptionProperties
+{
+ Q_GADGET
+
+public:
+ QMqttSubscriptionProperties();
+ QMqttSubscriptionProperties(const QMqttSubscriptionProperties &);
+ QMqttSubscriptionProperties &operator=(const QMqttSubscriptionProperties &);
+ ~QMqttSubscriptionProperties();
+
+ QMqttUserProperties userProperties() const;
+ void setUserProperties(const QMqttUserProperties &user);
+
+ quint32 subscriptionIdentifier() const;
+ void setSubscriptionIdentifier(quint32 id);
+private:
+ QSharedDataPointer<QMqttSubscriptionPropertiesData> data;
+};
+
+class Q_MQTT_EXPORT QMqttUnsubscriptionProperties
+{
+ Q_GADGET
+
+public:
+ QMqttUnsubscriptionProperties();
+ QMqttUnsubscriptionProperties(const QMqttUnsubscriptionProperties &);
+ QMqttUnsubscriptionProperties &operator=(const QMqttUnsubscriptionProperties &rhs);
+ ~QMqttUnsubscriptionProperties();
+
+ QMqttUserProperties userProperties() const;
+ void setUserProperties(const QMqttUserProperties &user);
+
+private:
+ QSharedDataPointer<QMqttUnsubscriptionPropertiesData> data;
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTSUBSCRIPTIONPROPERTIES_H
diff --git a/src/mqtt/qmqtttopicfilter.cpp b/src/mqtt/qmqtttopicfilter.cpp
index d2071c7..1e6bd9c 100644
--- a/src/mqtt/qmqtttopicfilter.cpp
+++ b/src/mqtt/qmqtttopicfilter.cpp
@@ -154,6 +154,24 @@ void QMqttTopicFilter::setFilter(const QString &filter)
}
/*!
+ \since 5.12
+
+ Returns the name of a share if the topic filter has been specified as
+ a shared subscription. The format of shared subscriptions is defined
+ as \c $share/sharename/topicfilter.
+*/
+QString QMqttTopicFilter::shareName() const
+{
+ QString result;
+ if (d->filter.startsWith(QLatin1String("$share/"))) {
+ // Has to have at least two /
+ // $share/<sharename>/topicfilter
+ result = d->filter.section(QLatin1Char('/'), 1, 1);
+ }
+ return result;
+}
+
+/*!
Returns \c true if the topic filter is valid according to the MQTT standard
section 4.7, or \c false otherwise.
*/
@@ -184,6 +202,13 @@ bool QMqttTopicFilter::isValid() const
singleLevelPosition = d->filter.indexOf(QLatin1Char('#'), singleLevelPosition + 1);
}
+ // Shared Subscription syntax
+ // $share/shareName/TopicFilter -- must have at least 2 '/'
+ if (d->filter.startsWith(QLatin1String("$share/"))) {
+ const int index = d->filter.indexOf(QLatin1Char('/'), 7);
+ if (index == -1 || index == 7)
+ return false;
+ }
return true;
}
diff --git a/src/mqtt/qmqtttopicfilter.h b/src/mqtt/qmqtttopicfilter.h
index 626cc6a..e926cf4 100644
--- a/src/mqtt/qmqtttopicfilter.h
+++ b/src/mqtt/qmqtttopicfilter.h
@@ -69,6 +69,8 @@ public:
QString filter() const;
void setFilter(const QString &filter);
+ QString shareName() const;
+
Q_REQUIRED_RESULT bool isValid() const;
Q_REQUIRED_RESULT bool match(const QMqttTopicName &name, MatchOptions matchOptions = NoMatchOption) const;
diff --git a/src/mqtt/qmqtttype.cpp b/src/mqtt/qmqtttype.cpp
new file mode 100644
index 0000000..373707b
--- /dev/null
+++ b/src/mqtt/qmqtttype.cpp
@@ -0,0 +1,465 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 "qmqtttype.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QMqttStringPair
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttStringPair class represents the string pair data type
+ of the MQTT 5.0 standard.
+
+ This data type is used to hold a name-value pair.
+*/
+
+/*!
+ \class QMqttUserProperties
+
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief The QMqttUserProperties class represents a list of QMqttStringPair
+ values which can be passed to the server.
+
+ User properties allow a client to pass additional data to the server or
+ other subscribers, which do not belong to the message payload.
+
+ On the other hand it also provides a server the flexibility to provide
+ further information to connected clients.
+
+ The server might add additional user properties to the ones provided.
+ However, the order of the provided properties is not changed during transfer.
+
+ \note User properties are part of the MQTT 5.0 specification and
+ cannot be used when using QtMqtt with an older protocol level. See
+ QMqttClient::ProtocolVersion for more information.
+
+ \sa QMqttAuthenticationProperties, QMqttConnectionProperties,
+ QMqttLastWillProperties, QMqttPublishProperties, QMqttSubscriptionProperties,
+ QMqttUnsubscriptionProperties, QMqttSubscription
+*/
+
+/*!
+ \namespace QMqtt
+ \inmodule QtMqtt
+ \since 5.12
+
+ \brief Contains miscellaneous identifiers used throughout the Qt MQTT module.
+*/
+
+/*!
+ \enum QMqtt::PayloadFormatIndicator
+ \since 5.12
+
+ The payload format provides information on the content of a message.
+ This can help other clients to handle the message faster.
+
+ \value Unspecified
+ The format is not specified.
+ \value UTF8Encoded
+ The payload of the message is formatted as UTF-8 Encoded
+ Character Data.
+*/
+
+/*!
+ \enum QMqtt::MessageStatus
+ \since 5.12
+
+ This enum type specifies the available states of a message. Depending
+ on the QoS and role of the client, different message statuses are
+ expected.
+
+ \value Unknown
+ The message status is unknown.
+ \value Published
+ The client received a message for one of its subscriptions. This
+ applies to all QoS levels.
+ \value Acknowledged
+ A message has been acknowledged. This applies to QoS 1 and states
+ that the message handling has been finished from the client side.
+ \value Received
+ A message has been received. This applies to QoS 2.
+ \value Released
+ A message has been released. This applies to QoS 2. For a publisher
+ the message handling has been finished.
+ \value Completed
+ A message has been completed. This applies to QoS 2 and states
+ that the message handling has been finished from the client side.
+*/
+
+/*!
+ \enum QMqtt::ReasonCode
+ \since 5.12
+
+ This enum type specifies the available error codes.
+
+ \value Success
+ The specified action has succeeded.
+ \value SubscriptionQoSLevel0
+ A subscription with QoS level 0 has been created.
+ \value SubscriptionQoSLevel1
+ A subscription with QoS level 1 has been created.
+ \value SubscriptionQoSLevel2
+ A subscription with QoS level 2 has been created.
+ \value NoMatchingSubscriber
+ The message has been accepted by the server, but there are no
+ subscribers to receive this message. A broker may send this
+ reason code instead of \l Success.
+ \value UnspecifiedError
+ An unspecified error occurred.
+ \value MalformedPacket
+ The packet sent to the server is invalid.
+ \value ProtocolError
+ A protocol error has occurred. In most cases, this will cause
+ the server to disconnect the client.
+ \value ImplementationSpecificError
+ The packet is valid, but the recipient rejects it.
+ \value UnsupportedProtocolVersion
+ The requested protocol version is not supported by the server.
+ \value InvalidClientId
+ The client ID is invalid.
+ \value InvalidUserNameOrPassword
+ The username or password specified is invalid.
+ \value NotAuthorized
+ The client is not authorized for the specified action.
+ \value ServerNotAvailable
+ The server to connect to is not available.
+ \value ServerBusy
+ The server to connect to is not available. The client is asked to
+ try at a later time.
+ \value ClientBanned
+ The client has been banned from the server.
+ \value InvalidAuthenticationMethod
+ The authentication method specified is invalid.
+ \value InvalidTopicFilter
+ The topic filter specified is invalid.
+ \value InvalidTopicName
+ The topic name specified is invalid.
+ \value MessageIdInUse
+ The message ID used in the previous packet is already in use.
+ \value MessageIdNotFound
+ The message ID used in the previous packet has not been found.
+ \value PacketTooLarge
+ The packet received is too large. See also
+ \l QMqttServerConnectionProperties::maximumPacketSize().
+ \value QuotaExceeded
+ An administratively imposed limit has been exceeded.
+ \value InvalidPayloadFormat
+ The payload format is invalid.
+ \value RetainNotSupported
+ The server does not support retained messages.
+ \value QoSNotSupported
+ The QoS level requested is not supported.
+ \value UseAnotherServer
+ The server the client tries to connect to is not available. See also
+ \l QMqttServerConnectionProperties::serverReference().
+ \value ServerMoved
+ The server the client tries to connect to has moved to a new address.
+ See also \l QMqttServerConnectionProperties::serverReference().
+ \value SharedSubscriptionsNotSupported
+ Shared subscriptions are not supported.
+ \value ExceededConnectionRate
+ The connection rate limit has been exceeded.
+ \value SubscriptionIdsNotSupported
+ Subscription IDs are not supported.
+ \value WildCardSubscriptionsNotSupported
+ Subscriptions using wildcards are not supported by the server.
+
+ Not all values are available in every use case. Especially, some servers
+ will reject a reason code not suited for a specific command. See below
+ table to highlight expected reason codes for specific actions.
+
+ \table
+ \header
+ \li Reason Code
+ \li Connect Properties
+ \li Subscription Properties
+ \li Message Properties
+ \row
+ \li Success
+ \li X
+ \li X
+ \li X
+ \row
+ \li SubscriptionQoSLevel0
+ \li
+ \li X
+ \li
+ \row
+ \li SubscriptionQoSLevel1
+ \li
+ \li X
+ \li
+ \row
+ \li SubscriptionQoSLevel2
+ \li
+ \li X
+ \li
+ \row
+ \li NoMatchingSubscriber
+ \li
+ \li
+ \li X
+ \row
+ \li UnspecifiedError
+ \li X
+ \li X
+ \li X
+ \row
+ \li MalformedPacket
+ \li X
+ \li
+ \li
+ \row
+ \li ProtocolError
+ \li X
+ \li
+ \li
+ \row
+ \li ImplementationSpecificError
+ \li X
+ \li X
+ \li X
+ \row
+ \li UnsupportedProtocolVersion
+ \li X
+ \li
+ \li
+ \row
+ \li InvalidClientId
+ \li X
+ \li
+ \li
+ \row
+ \li InvalidUserNameOrPassword
+ \li X
+ \li
+ \li
+ \row
+ \li NotAuthorized
+ \li X
+ \li X
+ \li X
+ \row
+ \li ServerNotAvailable
+ \li X
+ \li
+ \li
+ \row
+ \li ServerBusy
+ \li X
+ \li
+ \li
+ \row
+ \li ClientBanned
+ \li X
+ \li
+ \li
+ \row
+ \li InvalidAuthenticationMethod
+ \li X
+ \li
+ \li
+ \row
+ \li InvalidTopicFilter
+ \li
+ \li X
+ \li
+ \row
+ \li InvalidTopicName
+ \li X
+ \li
+ \li X
+ \row
+ \li MessageIdInUse
+ \li
+ \li X
+ \li X
+ \row
+ \li MessageIdNotFound
+ \li
+ \li
+ \li X
+ \row
+ \li PacketTooLarge
+ \li X
+ \li
+ \li
+ \row
+ \li QuotaExceeded
+ \li X
+ \li X
+ \li X
+ \row
+ \li InvalidPayloadFormat
+ \li X
+ \li
+ \li X
+ \row
+ \li RetainNotSupported
+ \li X
+ \li
+ \li
+ \row
+ \li QoSNotSupported
+ \li X
+ \li
+ \li
+ \row
+ \li UseAnotherServer
+ \li X
+ \li
+ \li
+ \row
+ \li ServerMoved
+ \li X
+ \li
+ \li
+ \row
+ \li SharedSubscriptionsNotSupported
+ \li
+ \li X
+ \li
+ \row
+ \li ExceededConnectionRate
+ \li X
+ \li
+ \li
+ \row
+ \li SubscriptionIdsNotSupported
+ \li
+ \li X
+ \li
+ \row
+ \li WildCardSubscriptionsNotSupported
+ \li
+ \li X
+ \li
+ \endtable
+*/
+
+class QMqttStringPairData : public QSharedData
+{
+public:
+ QMqttStringPairData() = default;
+ QMqttStringPairData(const QString &name, const QString &value);
+
+ bool operator==(const QMqttStringPairData &rhs) const;
+ QString m_name;
+ QString m_value;
+};
+
+QMqttStringPairData::QMqttStringPairData(const QString &name, const QString &value)
+ : m_name(name)
+ , m_value(value)
+{
+}
+
+bool QMqttStringPairData::operator==(const QMqttStringPairData &rhs) const
+{
+ return m_name == rhs.m_name && m_value == rhs.m_value;
+}
+
+QMqttStringPair::QMqttStringPair()
+ : data(new QMqttStringPairData)
+{
+
+}
+
+QMqttStringPair::QMqttStringPair(const QString &name, const QString &value)
+ : data(new QMqttStringPairData(name, value))
+{
+}
+
+QMqttStringPair::QMqttStringPair(const QMqttStringPair &) = default;
+
+QMqttStringPair::~QMqttStringPair() = default;
+
+/*!
+ Returns the name of the string pair.
+*/
+QString QMqttStringPair::name() const
+{
+ return data->m_name;
+}
+
+/*!
+ Sets the name to \a n.
+*/
+void QMqttStringPair::setName(const QString &n)
+{
+ data->m_name = n;
+}
+
+/*!
+ Returns the value of the string pair.
+*/
+QString QMqttStringPair::value() const
+{
+ return data->m_value;
+}
+
+/*!
+ Sets the value to \a v.
+*/
+void QMqttStringPair::setValue(const QString &v)
+{
+ data->m_value = v;
+}
+
+/*!
+ Returns \c true if this instance matches \a other.
+*/
+bool QMqttStringPair::operator==(const QMqttStringPair &other) const
+{
+ return *data.constData() == *other.data.constData();
+}
+
+QMqttStringPair &QMqttStringPair::operator=(const QMqttStringPair &rhs)
+{
+ if (this != &rhs)
+ data.operator=(rhs.data);
+ return *this;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, const QMqttStringPair &s)
+{
+ QDebugStateSaver saver(d);
+ d.nospace() << "QMqttStringPair(" << s.name() << " : " << s.value() << ')';
+ return d;
+}
+#endif
+
+QT_END_NAMESPACE
diff --git a/src/mqtt/qmqtttype.h b/src/mqtt/qmqtttype.h
new file mode 100644
index 0000000..d791d63
--- /dev/null
+++ b/src/mqtt/qmqtttype.h
@@ -0,0 +1,75 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt 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 QMQTTTYPE_H
+#define QMQTTTYPE_H
+
+#include <QtMqtt/qmqttglobal.h>
+
+#include <QtCore/QDebug>
+#include <QtCore/QPair>
+#include <QtCore/QSharedDataPointer>
+#include <QtCore/QString>
+#include <QtCore/QVector>
+
+QT_BEGIN_NAMESPACE
+
+class QMqttStringPairData;
+class Q_MQTT_EXPORT QMqttStringPair
+{
+public:
+ QMqttStringPair();
+ QMqttStringPair(const QString &name, const QString &value);
+ QMqttStringPair(const QMqttStringPair &);
+ ~QMqttStringPair();
+
+ QString name() const;
+ void setName(const QString &n);
+
+ QString value() const;
+ void setValue(const QString &v);
+
+ bool operator==(const QMqttStringPair &other) const;
+ QMqttStringPair &operator=(const QMqttStringPair &);
+private:
+ QSharedDataPointer<QMqttStringPairData> data;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_MQTT_EXPORT QDebug operator<<(QDebug d, const QMqttStringPair &s);
+#endif
+
+class Q_MQTT_EXPORT QMqttUserProperties : public QVector<QMqttStringPair>
+{
+public:
+};
+
+QT_END_NAMESPACE
+
+#endif // QMQTTTYPE_H
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index ecd1207..068e54a 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -2,8 +2,12 @@ TEMPLATE = subdirs
win32|if(linux:!cross_compile): SUBDIRS += cmake \
conformance \
+ qmqttconnectionproperties \
qmqttcontrolpacket \
qmqttclient \
+ qmqttlastwillproperties \
+ qmqttpublishproperties \
qmqttsubscription \
+ qmqttsubscriptionproperties \
qmqtttopicname \
qmqtttopicfilter
diff --git a/tests/auto/conformance/tst_conformance.cpp b/tests/auto/conformance/tst_conformance.cpp
index ca3cc61..d093637 100644
--- a/tests/auto/conformance/tst_conformance.cpp
+++ b/tests/auto/conformance/tst_conformance.cpp
@@ -50,15 +50,19 @@ private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
+ void basic_test_data();
void basic_test();
void retained_message_test_data();
void retained_message_test();
+ void will_message_test_data();
void will_message_test();
void zero_length_clientid_test_data();
void zero_length_clientid_test();
+ void offline_message_queueing_test_data();
void offline_message_queueing_test();
// overlapping_subscriptions_test // Skipped at the module emits multiple messages for each sub
// keepalive_test // The module handles sending ping requests
+ void subscribe_failure_test_data();
void subscribe_failure_test();
private:
QProcess m_brokerProcess;
@@ -83,9 +87,13 @@ void Tst_MqttConformance::cleanupTestCase()
{
}
+DefaultVersionTestData(Tst_MqttConformance::basic_test_data)
+
void Tst_MqttConformance::basic_test()
{
- QMqttClient client;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
@@ -96,20 +104,32 @@ void Tst_MqttConformance::basic_test()
client.disconnectFromHost();
QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Could not disconnect from broker");
+ // The MQTT 5 broker might provide topic alias by default. Hence, disable it.
+ if (mqttVersion == QMqttClient::MQTT_5_0) {
+ QMqttConnectionProperties p;
+ p.setMaximumTopicAlias(0);
+ client.setConnectionProperties(p);
+ }
+
client.connectToHost();
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker");
const QString topic(QLatin1String("Qt/conformance"));
- auto sub = client.subscribe(topic, 2);
+ auto sub = client.subscribe(topic, 1);
QTRY_VERIFY2(sub->state() == QMqttSubscription::Subscribed, "Could not subscribe");
int msgCount = 0;
- connect(sub, &QMqttSubscription::messageReceived, this, [&msgCount](QMqttMessage msg) {
- qDebug() << "Message received:" << msg.payload();
+ connect(sub, &QMqttSubscription::messageReceived, this, [&msgCount](QMqttMessage) {
msgCount++;
});
+ connect(&client, &QMqttClient::messageReceived, this, [](const QByteArray &message, const QMqttTopicName &topic)
+ {
+ Q_UNUSED(message)
+ Q_UNUSED(topic)
+ });
+
client.publish(topic, "qos 0", 0);
client.publish(topic, "qos 1", 1);
client.publish(topic, "qos 2", 2);
@@ -126,22 +146,28 @@ void Tst_MqttConformance::basic_test()
void Tst_MqttConformance::retained_message_test_data()
{
+ QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion");
QTest::addColumn<QStringList>("messages");
QTest::addColumn<int>("expectedMsgCount");
- const QStringList topics1{"qos 0", "qos 1", "qos 2"};
- const QStringList topics2{"", "", ""};
+ QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0};
+
+ for (int i = 0; i < 2; ++i) {
+ const QStringList topics1{"qos 0", "qos 1", "qos 2"};
+ const QStringList topics2{"", "", ""};
- QTest::newRow("receiveRetain") << topics1 << 3;
- QTest::newRow("clearRetain") << topics2 << 0;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":receiveRetain")) << versions[i] << topics1 << 3;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":clearRetain")) << versions[i] << topics2 << 0;
+ }
}
void Tst_MqttConformance::retained_message_test()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
QFETCH(QStringList, messages);
QFETCH(int, expectedMsgCount);
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
@@ -149,8 +175,8 @@ void Tst_MqttConformance::retained_message_test()
client.connectToHost();
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker");
- const QStringList topics{"Qt/tests/retain1", "Qt/tests/retain2", "Qt/tests2/retain1"};
- const QString subTop{"Qt/#"}; // ### TODO: The test suite uses {"Qt/+/+"}; but we do not support ++ yet.
+ const QStringList topics{"Qt/conformance/tests/retain1", "Qt/conformance/tests/retain2", "Qt/conformance/tests2/retain1"};
+ const QString subTop{"Qt/conformance/#"}; // ### TODO: The test suite uses {"Qt/+/+"}; but we do not support ++ yet.
client.publish(topics[0], messages[0].toLocal8Bit(), 0, true);
client.publish(topics[1], messages[1].toLocal8Bit(), 1, true);
@@ -181,14 +207,18 @@ void Tst_MqttConformance::retained_message_test()
QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Could not disconnect");
}
+DefaultVersionTestData(Tst_MqttConformance::will_message_test_data)
+
void Tst_MqttConformance::will_message_test()
{
- QMqttClient client;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
- const QString wTopic{"Qt/willtest"};
+ const QString wTopic{"Qt/conformance/willtest"};
const QByteArray wMessage{"client got lost"};
client.setWillMessage(wMessage);
@@ -199,7 +229,7 @@ void Tst_MqttConformance::will_message_test()
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker");
- QMqttClient recipient;
+ VersionClient(mqttVersion, recipient);
recipient.setHostname(m_testBroker);
recipient.setPort(m_port);
recipient.connectToHost();
@@ -221,17 +251,23 @@ void Tst_MqttConformance::will_message_test()
void Tst_MqttConformance::zero_length_clientid_test_data()
{
+ QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion");
QTest::addColumn<bool>("session");
- QTest::newRow("noncleanSession") << false;
- QTest::newRow("cleanSession") << true;
+ QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0};
+
+ for (int i = 0; i < 2; ++i) {
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":noncleanSession")) << versions[i] << false;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":cleanSession")) << versions[i] << true;
+ }
}
void Tst_MqttConformance::zero_length_clientid_test()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
QFETCH(bool, session);
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
@@ -242,7 +278,13 @@ void Tst_MqttConformance::zero_length_clientid_test()
QVERIFY2(client.state() == QMqttClient::Connecting, "Could not set state to connecting.");
if (!session) {
- QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Sessions with empty client should not be allowed.");
+ if (client.protocolVersion() == QMqttClient::MQTT_5_0) {
+ // For MQTT 5 the broker creates an ID and returns it in CONNACK
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+ QVERIFY(!client.clientId().isEmpty());
+ } else {
+ QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Sessions with empty client should not be allowed.");
+ }
} else {
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
client.disconnectFromHost();
@@ -250,9 +292,12 @@ void Tst_MqttConformance::zero_length_clientid_test()
}
}
+DefaultVersionTestData(Tst_MqttConformance::offline_message_queueing_test_data)
+
void Tst_MqttConformance::offline_message_queueing_test()
{
- QMqttClient client;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
@@ -260,23 +305,23 @@ void Tst_MqttConformance::offline_message_queueing_test()
client.connectToHost();
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
- const QString subTopic{"Qt/offline/#"};
+ const QString subTopic{"Qt/conformance/offline/#"};
auto sub = client.subscribe(subTopic, 2);
Q_UNUSED(sub);
client.disconnectFromHost();
QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Could not disconnect.");
- QMqttClient publisher;
+ VersionClient(mqttVersion, publisher);
publisher.setHostname(m_testBroker);
publisher.setPort(m_port);
publisher.connectToHost();
QTRY_VERIFY2(publisher.state() == QMqttClient::Connected, "Could not connect to broker.");
QSignalSpy pubCounter(&publisher, SIGNAL(messageSent(qint32)));
- publisher.publish(QLatin1String("Qt/offline/foo/bar"), "msg1", 1);
- publisher.publish(QLatin1String("Qt/offline/foo/bar2"), "msg2", 1);
- publisher.publish(QLatin1String("Qt/offline/foo2/bar"), "msg3", 1);
+ publisher.publish(QLatin1String("Qt/conformance/offline/foo/bar"), "msg1", 1);
+ publisher.publish(QLatin1String("Qt/conformance/offline/foo/bar2"), "msg2", 1);
+ publisher.publish(QLatin1String("Qt/conformance/offline/foo2/bar"), "msg3", 1);
QTRY_VERIFY2(pubCounter.size() == 3, "Could not publish all messages.");
publisher.disconnectFromHost();
@@ -287,17 +332,24 @@ void Tst_MqttConformance::offline_message_queueing_test()
client.connectToHost();
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+ // ### TODO: MQTT5 Investigate / Fixme
+ if (client.protocolVersion() == QMqttClient::MQTT_5_0)
+ QEXPECT_FAIL("", "Offline messages seem not supported with MQTT5", Continue);
QTRY_VERIFY2(receiveCounter.size() == 3, "Did not receive all offline messages.");
client.disconnectFromHost();
QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Could not disconnect.");
}
+DefaultVersionTestData(Tst_MqttConformance::subscribe_failure_test_data)
+
void Tst_MqttConformance::subscribe_failure_test()
{
- QMqttClient client;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ VersionClient(mqttVersion, client);
- const QByteArray forbiddenTopic{"nosubscribe"};
+ const QByteArray forbiddenTopic{"Qt/conformance/nosubscribe"};
// We do not have a test broker with forbidden topics.
QSKIP("Missing infrastructure to set forbidden topics");
diff --git a/tests/auto/qmqttclient/tst_qmqttclient.cpp b/tests/auto/qmqttclient/tst_qmqttclient.cpp
index b94bfd4..8aec3d5 100644
--- a/tests/auto/qmqttclient/tst_qmqttclient.cpp
+++ b/tests/auto/qmqttclient/tst_qmqttclient.cpp
@@ -46,18 +46,34 @@ public:
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
+ void getSetCheck_data();
void getSetCheck();
void sendReceive_data();
void sendReceive();
+ void retainMessage_data();
void retainMessage();
+ void willMessage_data();
void willMessage();
void compliantTopic_data();
void compliantTopic();
+ void subscribeLongTopic_data();
void subscribeLongTopic();
+ void dataIncludingZero_data();
void dataIncludingZero();
+ void publishLongTopic_data();
void publishLongTopic();
+ void reconnect_QTBUG65726_data();
void reconnect_QTBUG65726();
+ void openIODevice_QTBUG66955_data();
void openIODevice_QTBUG66955();
+ void staticProperties_QTBUG_67176_data();
+ void staticProperties_QTBUG_67176();
+ void authentication();
+ void messageStatus_data();
+ void messageStatus();
+ void messageStatusReceive_data();
+ void messageStatusReceive();
+ void subscriptionIdsOverlap();
private:
QProcess m_brokerProcess;
QString m_testBroker;
@@ -79,9 +95,12 @@ void Tst_QMqttClient::cleanupTestCase()
{
}
+DefaultVersionTestData(Tst_QMqttClient::getSetCheck_data)
+
void Tst_QMqttClient::getSetCheck()
{
- QMqttClient client;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+ VersionClient(mqttVersion, client);
QVERIFY(client.clientId().size() > 0);
const QString clientId = QLatin1String("testclient123");
@@ -101,15 +120,27 @@ void Tst_QMqttClient::getSetCheck()
client.setKeepAlive(10);
QCOMPARE(client.keepAlive(), quint16(10));
- QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1_1);
- client.setProtocolVersion(QMqttClient::ProtocolVersion(0));
- QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1_1);
- client.setProtocolVersion(QMqttClient::ProtocolVersion(5));
- QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1_1);
- client.setProtocolVersion(QMqttClient::MQTT_3_1);
- QCOMPARE(client.protocolVersion(), QMqttClient::MQTT_3_1);
-
+ // Available protocol versions
+ QMqttClient client2;
+ QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1_1);
+ client2.setProtocolVersion(QMqttClient::ProtocolVersion(0));
+ QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1_1);
+ client2.setProtocolVersion(QMqttClient::ProtocolVersion(6));
+ QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1_1);
+ client2.setProtocolVersion(QMqttClient::MQTT_3_1);
+ QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_3_1);
+ client2.setProtocolVersion(QMqttClient::MQTT_5_0);
+ QCOMPARE(client2.protocolVersion(), QMqttClient::MQTT_5_0);
+
+#ifdef QT_BUILD_INTERNAL
+ if (qEnvironmentVariableIsSet("QT_MQTT_TEST_USERNAME"))
+ QEXPECT_FAIL("", "Default username has been overwritten.", Continue);
+#endif
QCOMPARE(client.username(), QString());
+#ifdef QT_BUILD_INTERNAL
+ if (qEnvironmentVariableIsSet("QT_MQTT_TEST_PASSWORD"))
+ QEXPECT_FAIL("", "Default username has been overwritten.", Continue);
+#endif
QCOMPARE(client.password(), QString());
QCOMPARE(client.cleanSession(), true);
QCOMPARE(client.willTopic(), QString());
@@ -120,22 +151,29 @@ void Tst_QMqttClient::getSetCheck()
void Tst_QMqttClient::sendReceive_data()
{
+ QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion");
QTest::addColumn<QByteArray>("data");
- QTest::newRow("empty") << QByteArray();
- QTest::newRow("simple") << QByteArray("This is a test message");
- QByteArray d;
- d.fill('A', 500);
- QTest::newRow("big") << d;
- d.fill('B', (128 * 128 * 128) + 4);
- QTest::newRow("huge") << d;
+
+ QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0};
+
+ for (int i = 0; i < 2; ++i) {
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":empty")) << versions[i] << QByteArray();
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":simple")) << versions[i] << QByteArray("This is a test message");
+ QByteArray d;
+ d.fill('A', 500);
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":big")) << versions[i] << d;
+ d.fill('B', (128 * 128 * 128) + 4);
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":huge")) << versions[i] << d;
+ }
}
void Tst_QMqttClient::sendReceive()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
QFETCH(QByteArray, data);
- const QString testTopic = QLatin1String("Topic");
+ const QString testTopic = QLatin1String("Qt/QMqttClient/Topic");
- QMqttClient publisher;
+ VersionClient(mqttVersion, publisher);
publisher.setClientId(QLatin1String("publisher"));
publisher.setHostname(m_testBroker);
publisher.setPort(m_port);
@@ -143,7 +181,7 @@ void Tst_QMqttClient::sendReceive()
publisher.connectToHost();
QTRY_COMPARE(publisher.state(), QMqttClient::Connected);
- QMqttClient subscriber;
+ VersionClient(mqttVersion, subscriber);
subscriber.setClientId(QLatin1String("subscriber"));
subscriber.setHostname(m_testBroker);
subscriber.setPort(m_port);
@@ -162,19 +200,26 @@ void Tst_QMqttClient::sendReceive()
QTRY_COMPARE(sub->state(), QMqttSubscription::Subscribed);
+ if (subscriber.protocolVersion() == QMqttClient::MQTT_5_0 &&
+ (quint32)data.size() > subscriber.serverConnectionProperties().maximumPacketSize())
+ QSKIP("The MQTT 5 test broker does not support huge packages.", SkipOnce);
publisher.publish(testTopic, data, 1);
QTRY_VERIFY2(received, "Subscriber did not receive message");
QVERIFY2(verified, "Subscriber received different message");
}
+DefaultVersionTestData(Tst_QMqttClient::retainMessage_data)
+
void Tst_QMqttClient::retainMessage()
{
- const QString testTopic = QLatin1String("Topic2");
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ const QString testTopic = QLatin1String("Qt/QMqttClient/Topic2");
const QByteArray testMessage("retainedMessage");
// Publisher
- QMqttClient publisher;
+ VersionClient(mqttVersion, publisher);
publisher.setClientId(QLatin1String("publisher"));
publisher.setHostname(m_testBroker);
publisher.setPort(m_port);
@@ -197,7 +242,7 @@ void Tst_QMqttClient::retainMessage()
publisher.publish(testTopic, testMessage, 1, i == 1 ? true : false);
QTRY_COMPARE(publishSpy.count(), 1);
- QMqttClient sub;
+ VersionClient(mqttVersion, sub);
sub.setClientId(QLatin1String("SubA"));
sub.setHostname(m_testBroker);
sub.setPort(m_port);
@@ -213,19 +258,22 @@ void Tst_QMqttClient::retainMessage()
auto subscription = sub.subscribe(testTopic);
QTRY_COMPARE(subscription->state(), QMqttSubscription::Subscribed);
- QTest::qWait(5000);
- QVERIFY(msgCount == i);
+ QTRY_VERIFY(msgCount == i);
}
publisher.disconnect();
}
+DefaultVersionTestData(Tst_QMqttClient::willMessage_data)
+
void Tst_QMqttClient::willMessage()
{
- const QString willTopic = QLatin1String("will/topic");
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ const QString willTopic = QLatin1String("Qt/QMqttClient/will/topic");
const QByteArray willMessage("The client died....");
// Client A connects
- QMqttClient client1;
+ VersionClient(mqttVersion, client1);
client1.setHostname(m_testBroker);
client1.setPort(m_port);
client1.connectToHost();
@@ -247,7 +295,7 @@ void Tst_QMqttClient::willMessage()
QVERIFY(sock.waitForConnected());
for (int i = 1; i > 0; --i) {
- QMqttClient willClient;
+ VersionClient(mqttVersion, willClient);
if (i == 1)
willClient.setTransport(&sock, QMqttClient::AbstractSocket);
else {
@@ -277,13 +325,21 @@ void Tst_QMqttClient::willMessage()
void Tst_QMqttClient::compliantTopic_data()
{
+ QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion");
QTest::addColumn<QString>("topic");
- QTest::newRow("simple") << QString::fromLatin1("topic");
- QTest::newRow("subPath") << QString::fromLatin1("topic/subtopic");
- QString l;
- l.fill(QLatin1Char('T'), std::numeric_limits<std::uint16_t>::max());
- QTest::newRow("maxSize") << l;
+ QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0};
+
+ for (int i = 0; i < 2; ++i) {
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":simple")) << versions[i] << QString::fromLatin1("Qt/QMqttClient/topic");
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":subPath")) << versions[i] << QString::fromLatin1("Qt/QMqttClient/topic/subtopic");
+
+ if (versions[i] != QMqttClient::MQTT_5_0) {
+ QString l;
+ l.fill(QLatin1Char('T'), std::numeric_limits<std::uint16_t>::max());
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":maxSize")) << versions[i] << l;
+ }
+ }
}
void Tst_QMqttClient::compliantTopic()
@@ -324,9 +380,13 @@ void Tst_QMqttClient::compliantTopic()
QVERIFY2(verified, "Subscriber received different message");
}
+DefaultVersionTestData(Tst_QMqttClient::subscribeLongTopic_data)
+
void Tst_QMqttClient::subscribeLongTopic()
{
- QMqttClient subscriber;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ VersionClient(mqttVersion, subscriber);
subscriber.setClientId(QLatin1String("subscriber"));
subscriber.setHostname(m_testBroker);
subscriber.setPort(m_port);
@@ -340,14 +400,18 @@ void Tst_QMqttClient::subscribeLongTopic()
QCOMPARE(sub, nullptr);
}
+DefaultVersionTestData(Tst_QMqttClient::dataIncludingZero_data)
+
void Tst_QMqttClient::dataIncludingZero()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
QByteArray data;
const int dataSize = 200;
data.fill('A', dataSize);
data[100] = '\0';
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
@@ -357,7 +421,7 @@ void Tst_QMqttClient::dataIncludingZero()
bool received = false;
bool verified = false;
bool correctSize = false;
- const QString testTopic(QLatin1String("some/topic"));
+ const QString testTopic(QLatin1String("Qt/QMqttClient/some/topic"));
auto sub = client.subscribe(testTopic, 1);
QVERIFY(sub);
connect(sub, &QMqttSubscription::messageReceived, [&](QMqttMessage msg) {
@@ -375,9 +439,13 @@ void Tst_QMqttClient::dataIncludingZero()
QVERIFY2(correctSize, "Subscriber received message of different size");
}
+DefaultVersionTestData(Tst_QMqttClient::publishLongTopic_data)
+
void Tst_QMqttClient::publishLongTopic()
{
- QMqttClient publisher;
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ VersionClient(mqttVersion, publisher);
publisher.setClientId(QLatin1String("publisher"));
publisher.setHostname(m_testBroker);
publisher.setPort(m_port);
@@ -428,11 +496,15 @@ public:
bool connectionSuccess{false};
};
+DefaultVersionTestData(Tst_QMqttClient::reconnect_QTBUG65726_data)
+
void Tst_QMqttClient::reconnect_QTBUG65726()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
FakeServer server;
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setClientId(QLatin1String("bugclient"));
client.setHostname(QLatin1String("localhost"));
client.setPort(5726);
@@ -465,24 +537,280 @@ public:
if (data[0] == 0x10)
written = 1;
else
- qWarning() << "Received unknown/invalid data";
+ qDebug() << "Received unknown/invalid data";
return len;
}
QAtomicInt written{0};
};
+DefaultVersionTestData(Tst_QMqttClient::openIODevice_QTBUG66955_data)
+
void Tst_QMqttClient::openIODevice_QTBUG66955()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
IOTransport trans;
trans.open(QIODevice::ReadWrite);
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setTransport(&trans, QMqttClient::IODevice);
client.connectToHost();
QTRY_COMPARE(trans.written, 1);
}
+DefaultVersionTestData(Tst_QMqttClient::staticProperties_QTBUG_67176_data)
+
+void Tst_QMqttClient::staticProperties_QTBUG_67176()
+{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
+ VersionClient(mqttVersion, client);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ const QString clientId = client.clientId();
+ const quint16 keepAlive = client.keepAlive();
+ const bool clean = client.cleanSession();
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ client.setClientId(QLatin1String("someclient"));
+ QCOMPARE(client.clientId(), clientId);
+
+ client.setHostname(QLatin1String("some.domain.foo"));
+ QCOMPARE(client.hostname(), m_testBroker);
+
+ client.setPort(1234);
+ QCOMPARE(client.port(), m_port);
+
+ client.setKeepAlive(keepAlive + 10);
+ QCOMPARE(client.keepAlive(), keepAlive);
+
+ client.setProtocolVersion(QMqttClient::MQTT_3_1);
+ QCOMPARE(client.protocolVersion(), mqttVersion);
+
+ client.setUsername(QLatin1String("someUser"));
+ QCOMPARE(client.username(), QLatin1String());
+
+ client.setPassword(QLatin1String("somePassword"));
+ QCOMPARE(client.password(), QLatin1String());
+
+ client.setCleanSession(!clean);
+ QCOMPARE(client.cleanSession(), clean);
+}
+
+void Tst_QMqttClient::authentication()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ QMqttConnectionProperties connectionProperties;
+ connectionProperties.setAuthenticationMethod(QLatin1String("SCRAM-SHA-1"));
+ client.setConnectionProperties(connectionProperties);
+
+ connect(&client, &QMqttClient::authenticationRequested, [](const QMqttAuthenticationProperties &prop)
+ {
+ qDebug() << "Authentication requested:" << prop.authenticationMethod();
+ });
+
+ connect(&client, &QMqttClient::authenticationFinished, [](const QMqttAuthenticationProperties &prop)
+ {
+ qDebug() << "Authentication finished:" << prop.authenticationMethod();
+ });
+
+ // ### FIXME : There is no public test broker yet able to handle authentication methods
+ // Theoretically the broker should send an AUTH request, followed by AUTH call including
+ // authentication data. See 4.12 of MQTT v5 specs.
+ QSKIP("No broker available with enhanced authentication.");
+ client.connectToHost();
+ QTRY_COMPARE(client.state(), QMqttClient::Connected);
+}
+
+Q_DECLARE_METATYPE(QMqtt::MessageStatus)
+
+void Tst_QMqttClient::messageStatus_data()
+{
+ QTest::addColumn<int>("qos");
+ QTest::addColumn<QList<QMqtt::MessageStatus>>("expectedStatus");
+
+ QTest::newRow("QoS1") << 1 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Acknowledged);
+ QTest::newRow("QoS2") << 2 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Received
+ << QMqtt::MessageStatus::Completed);
+}
+
+void Tst_QMqttClient::messageStatus()
+{
+ QFETCH(int, qos);
+ QFETCH(QList<QMqtt::MessageStatus>, expectedStatus);
+
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ const QString topic = QLatin1String("Qt/client/statusCheck");
+
+ connect(&client, &QMqttClient::messageStatusChanged, [&expectedStatus](qint32,
+ QMqtt::MessageStatus s,
+ const QMqttMessageStatusProperties &)
+ {
+ QCOMPARE(s, expectedStatus.first());
+ expectedStatus.takeFirst();
+ });
+
+ QSignalSpy publishSpy(&client, &QMqttClient::messageSent);
+ client.publish(topic, QByteArray("someContent"), quint8(qos));
+ QTRY_VERIFY2(publishSpy.count() == 1, "Could not publish message");
+ QTRY_VERIFY2(expectedStatus.isEmpty(), "Did not receive all status updates.");
+}
+
+void Tst_QMqttClient::messageStatusReceive_data()
+{
+ QTest::addColumn<int>("qos");
+ QTest::addColumn<QList<QMqtt::MessageStatus>>("expectedStatus");
+
+ QTest::newRow("QoS1") << 1 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Published);
+ QTest::newRow("QoS2") << 2 << (QList<QMqtt::MessageStatus>() << QMqtt::MessageStatus::Published
+ << QMqtt::MessageStatus::Released);
+}
+
+void Tst_QMqttClient::messageStatusReceive()
+{
+ QFETCH(int, qos);
+ QFETCH(QList<QMqtt::MessageStatus>, expectedStatus);
+
+ QMqttClient publisher;
+ publisher.setProtocolVersion(QMqttClient::MQTT_5_0);
+ publisher.setHostname(m_testBroker);
+ publisher.setPort(m_port);
+
+ publisher.connectToHost();
+ QTRY_VERIFY2(publisher.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ QMqttClient subscriber;
+ subscriber.setProtocolVersion(QMqttClient::MQTT_5_0);
+ subscriber.setHostname(m_testBroker);
+ subscriber.setPort(m_port);
+
+ subscriber.connectToHost();
+ QTRY_VERIFY2(subscriber.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ const QString topic = QLatin1String("Qt/client/statusCheckReceive");
+
+ auto subscription = subscriber.subscribe(topic, quint8(qos));
+ QTRY_VERIFY2(subscription->state() == QMqttSubscription::Subscribed, "Could not subscribe to topic");
+ QVERIFY(subscription->qos() >= qos);
+
+ connect(&subscriber, &QMqttClient::messageStatusChanged, [&expectedStatus](qint32,
+ QMqtt::MessageStatus s,
+ const QMqttMessageStatusProperties &)
+ {
+ QCOMPARE(s, expectedStatus.first());
+ expectedStatus.takeFirst();
+ });
+
+ QSignalSpy publishSpy(&publisher, &QMqttClient::messageSent);
+ QSignalSpy receiveSpy(&subscriber, &QMqttClient::messageReceived);
+
+ publisher.publish(topic, QByteArray("someContent"), quint8(qos));
+ QTRY_VERIFY2(publishSpy.count() == 1, "Could not publish message");
+ QTRY_VERIFY2(receiveSpy.count() == 1, "Did not receive message");
+
+ QTRY_VERIFY2(expectedStatus.isEmpty(), "Did not receive all status updates.");
+}
+
+void Tst_QMqttClient::subscriptionIdsOverlap()
+{
+
+ // If the Server sends a single copy of the message it MUST include in the
+ // PUBLISH packet the Subscription Identifiers for all matching
+ // subscriptions which have a Subscription Identifiers, their order is not
+ // significant [MQTT-3.3.4-4].
+ // If the Server sends multiple PUBLISH packets it MUST send, in each of
+ // them, the Subscription Identifier of the matching subscription if it has
+ // a Subscription Identifier [MQTT-3.3.4-5].
+
+ const QString topic = QLatin1String("Qt/client/idcheck");
+ // Connect publisher
+ QMqttClient pub;
+ pub.setProtocolVersion(QMqttClient::MQTT_5_0);
+ pub.setHostname(m_testBroker);
+ pub.setPort(m_port);
+
+ pub.connectToHost();
+ QTRY_VERIFY2(pub.state() == QMqttClient::Connected, "Could not connect publisher.");
+
+ // Connect subA
+ QMqttClient subClientA;
+ subClientA.setProtocolVersion(QMqttClient::MQTT_5_0);
+ subClientA.setHostname(m_testBroker);
+ subClientA.setPort(m_port);
+
+ subClientA.connectToHost();
+ QTRY_VERIFY2(subClientA.state() == QMqttClient::Connected, "Could not connect subscriber A.");
+
+ QMqttSubscriptionProperties subAProp;
+ subAProp.setSubscriptionIdentifier(8);
+ auto subA = subClientA.subscribe(topic, subAProp, 1);
+ QTRY_VERIFY2(subA->state() == QMqttSubscription::Subscribed, "Could not subscibe A.");
+
+ int receiveACounter = 0;
+ connect(subA, &QMqttSubscription::messageReceived, [&receiveACounter](QMqttMessage msg) {
+ qDebug() << "Sub A received:" << msg.publishProperties().subscriptionIdentifiers();
+ // ### TODO: Wait for fix at https://github.com/eclipse/paho.mqtt.testing/issues/56
+ //QVERIFY(msg.publishProperties().subscriptionIdentifiers().size() == 1);
+ //QVERIFY(msg.publishProperties().subscriptionIdentifiers().at(0) == 8); // Use sub->id();
+ receiveACounter++;
+ });
+
+ // Connect subB
+ QMqttClient subClientB;
+ subClientB.setProtocolVersion(QMqttClient::MQTT_5_0);
+ subClientB.setHostname(m_testBroker);
+ subClientB.setPort(m_port);
+
+ subClientB.connectToHost();
+ QTRY_VERIFY2(subClientB.state() == QMqttClient::Connected, "Could not connect subscriber A.");
+
+ QMqttSubscriptionProperties subBProp;
+ subBProp.setSubscriptionIdentifier(9);
+ auto subB = subClientB.subscribe(topic, subBProp, 1);
+ QTRY_VERIFY2(subB->state() == QMqttSubscription::Subscribed, "Could not subscibe A.");
+
+ int receiveBCounter = 2;
+ connect(subB, &QMqttSubscription::messageReceived, [&receiveBCounter](QMqttMessage msg) {
+ qDebug() << "Sub B received:" << msg.publishProperties().subscriptionIdentifiers();
+ QVERIFY(msg.publishProperties().subscriptionIdentifiers().size() > 0);
+ receiveBCounter -= msg.publishProperties().subscriptionIdentifiers().size();
+ });
+
+ QMqttSubscriptionProperties subB2Prop;
+ subB2Prop.setSubscriptionIdentifier(14);
+ auto subB2 = subClientB.subscribe(topic + "/#", subB2Prop, 1);
+ QTRY_VERIFY2(subB2->state() == QMqttSubscription::Subscribed, "Could not subscibe A.");
+
+ int receiveB2Counter = 2;
+ connect(subB2, &QMqttSubscription::messageReceived, [&receiveB2Counter](QMqttMessage msg) {
+ qDebug() << "Sub B2 received:" << msg.publishProperties().subscriptionIdentifiers();
+ QVERIFY(msg.publishProperties().subscriptionIdentifiers().size() > 0);
+ receiveB2Counter -= msg.publishProperties().subscriptionIdentifiers().size();
+ });
+
+ QSignalSpy publishSpy(&pub, &QMqttClient::messageSent);
+ pub.publish(topic, "SomeData", 1);
+ QTRY_VERIFY2(publishSpy.count() == 1, "Could not finalize publication.");
+ QTRY_VERIFY2(receiveBCounter == 0, "Did not receive both messages.");
+ QTRY_VERIFY2(receiveB2Counter == 0, "Did not receive both messages.");
+ QTRY_VERIFY2(receiveACounter == 1, "Did not receive non-overlapping message.");
+}
+
QTEST_MAIN(Tst_QMqttClient)
#include "tst_qmqttclient.moc"
diff --git a/tests/auto/qmqttconnectionproperties/qmqttconnectionproperties.pro b/tests/auto/qmqttconnectionproperties/qmqttconnectionproperties.pro
new file mode 100644
index 0000000..4cd41d5
--- /dev/null
+++ b/tests/auto/qmqttconnectionproperties/qmqttconnectionproperties.pro
@@ -0,0 +1,17 @@
+CONFIG += testcase
+QT += network testlib mqtt
+QT -= gui
+QT_PRIVATE += mqtt-private
+
+TARGET = tst_qmqttconnectionproperties
+
+SOURCES += \
+ tst_qmqttconnectionproperties.cpp
+
+HEADERS += \
+ $$PWD/../../common/broker_connection.h
+
+INCLUDEPATH += \
+ $$PWD/../../common
+
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/qmqttconnectionproperties/tst_qmqttconnectionproperties.cpp b/tests/auto/qmqttconnectionproperties/tst_qmqttconnectionproperties.cpp
new file mode 100644
index 0000000..f08adcd
--- /dev/null
+++ b/tests/auto/qmqttconnectionproperties/tst_qmqttconnectionproperties.cpp
@@ -0,0 +1,411 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt module.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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 "broker_connection.h"
+
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QString>
+#include <QtTest/QtTest>
+#include <QtMqtt/QMqttClient>
+#include <QtMqtt/QMqttConnectionProperties>
+#include <QtMqtt/QMqttSubscription>
+#include <QtMqtt/private/qmqttconnectionproperties_p.h>
+
+class tst_QMqttConnectionProperties : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QMqttConnectionProperties();
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void getSet();
+ void receiveServerProperties();
+ void maximumPacketSize();
+ void maximumTopicAlias();
+ void maximumTopicAliasReceive();
+ void assignedClientId();
+ void userProperties();
+private:
+ QProcess m_brokerProcess;
+ QString m_testBroker;
+ quint16 m_port{1883};
+};
+
+tst_QMqttConnectionProperties::tst_QMqttConnectionProperties()
+{
+}
+
+void tst_QMqttConnectionProperties::initTestCase()
+{
+ m_testBroker = invokeOrInitializeBroker(&m_brokerProcess);
+ if (m_testBroker.isEmpty())
+ qFatal("No MQTT broker present to test against.");
+}
+
+void tst_QMqttConnectionProperties::cleanupTestCase()
+{
+}
+
+void tst_QMqttConnectionProperties::getSet()
+{
+ QMqttConnectionProperties p;
+
+ QVERIFY(p.userProperties().isEmpty());
+ QMqttUserProperties properties;
+ properties.append(QMqttStringPair(QLatin1String("someKey"), QLatin1String("someValue")));
+ p.setUserProperties(properties);
+ QCOMPARE(p.userProperties(), properties);
+
+ QVERIFY(p.authenticationMethod().isEmpty());
+ const QLatin1String authMethod("SomeAuthentication");
+ p.setAuthenticationMethod(authMethod);
+ QCOMPARE(p.authenticationMethod(), authMethod);
+
+ QVERIFY(p.authenticationData().isEmpty());
+ const QByteArray authData("AuthData123");
+ p.setAuthenticationData(authData);
+ QCOMPARE(p.authenticationData(), authData);
+
+ QCOMPARE(p.sessionExpiryInterval(), 0u);
+ p.setSessionExpiryInterval(1000);
+ QCOMPARE(p.sessionExpiryInterval(), 1000u);
+
+ QCOMPARE(p.maximumPacketSize(), std::numeric_limits<quint32>::max());
+ p.setMaximumPacketSize(0);
+ QVERIFY(p.maximumPacketSize() != 0u);
+ p.setMaximumPacketSize(500);
+ QCOMPARE(p.maximumPacketSize(), 500u);
+
+ QCOMPARE(p.maximumReceive(), 65535);
+ p.setMaximumReceive(0);
+ QVERIFY(p.maximumReceive() != 0u);
+ p.setMaximumReceive(30);
+ QCOMPARE(p.maximumReceive(), 30u);
+
+ QCOMPARE(p.maximumTopicAlias(), 0u);
+ p.setMaximumTopicAlias(5);
+ QCOMPARE(p.maximumTopicAlias(), 5u);
+
+ QCOMPARE(p.requestResponseInformation(), false);
+ p.setRequestResponseInformation(true);
+ QCOMPARE(p.requestResponseInformation(), true);
+
+ QCOMPARE(p.requestProblemInformation(), true);
+ p.setRequestProblemInformation(false);
+ QCOMPARE(p.requestProblemInformation(), false);
+}
+
+void tst_QMqttConnectionProperties::receiveServerProperties()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ QMqttServerConnectionProperties server = client.serverConnectionProperties();
+
+ QCOMPARE(server.isValid(), false);
+
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ server = client.serverConnectionProperties();
+
+ QCOMPARE(server.isValid(), true);
+
+ QMqttServerConnectionProperties::ServerPropertyDetails properties = server.availableProperties();
+ qDebug() << "Specified properties:" << properties;
+
+ if (properties & QMqttServerConnectionProperties::SessionExpiryInterval)
+ qDebug() << " SessionExpiryInterval:" << server.sessionExpiryInterval();
+ if (properties & QMqttServerConnectionProperties::MaximumReceive)
+ qDebug() << " MaximumReceive:" << server.maximumReceive();
+ if (properties & QMqttServerConnectionProperties::MaximumQoS)
+ qDebug() << " MaximumQoS:" << server.maximumQoS();
+ if (properties & QMqttServerConnectionProperties::RetainAvailable)
+ qDebug() << " RetainAvailable:" << server.retainAvailable();
+ if (properties & QMqttServerConnectionProperties::MaximumPacketSize)
+ qDebug() << " MaximumPacketSize:" << server.maximumPacketSize();
+ if (properties & QMqttServerConnectionProperties::AssignedClientId)
+ qDebug() << " AssignedClientId:" << server.clientIdAssigned();
+ if (properties & QMqttServerConnectionProperties::MaximumTopicAlias)
+ qDebug() << " MaximumTopicAlias:" << server.maximumTopicAlias();
+ if (properties & QMqttServerConnectionProperties::ReasonString)
+ qDebug() << " ReasonString:" << server.reason();
+ if (properties & QMqttServerConnectionProperties::UserProperty)
+ qDebug() << " UserProperty:" << server.userProperties();
+ if (properties & QMqttServerConnectionProperties::WildCardSupported)
+ qDebug() << " WildCard Support:" << server.wildcardSupported();
+ if (properties & QMqttServerConnectionProperties::SubscriptionIdentifierSupport)
+ qDebug() << " Subscription Identifier Support:" << server.subscriptionIdentifierSupported();
+ if (properties & QMqttServerConnectionProperties::SharedSubscriptionSupport)
+ qDebug() << " Shared Subscription Support:" << server.sharedSubscriptionSupported();
+ if (properties & QMqttServerConnectionProperties::ServerKeepAlive)
+ qDebug() << " Server KeepAlive:" << server.serverKeepAlive();
+ if (properties & QMqttServerConnectionProperties::ResponseInformation)
+ qDebug() << " ResponseInformation:" << server.responseInformation();
+ if (properties & QMqttServerConnectionProperties::ServerReference)
+ qDebug() << " Server Reference:" << server.serverReference();
+ if (properties & QMqttServerConnectionProperties::AuthenticationMethod)
+ qDebug() << " AuthenticationMethod:" << server.authenticationMethod();
+ if (properties & QMqttServerConnectionProperties::AuthenticationData)
+ qDebug() << " AuthenticationData:" << server.authenticationData();
+}
+
+void tst_QMqttConnectionProperties::maximumPacketSize()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ QMqttConnectionProperties props;
+ props.setMaximumPacketSize(500);
+ client.setConnectionProperties(props);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ QMqttServerConnectionProperties serverProperties = client.serverConnectionProperties();
+ if (serverProperties.availableProperties() & QMqttServerConnectionProperties::MaximumPacketSize) {
+ if (serverProperties.maximumPacketSize() < props.maximumPacketSize()) {
+ qDebug() << "Server accepts less data than required for this test.";
+ }
+ } else {
+ QSKIP("Server has no max packet size defined. Default is unlimited.");
+ }
+
+ const QString topic = QLatin1String("Qt/ConnectionProperties/some/Topic/maxSize");
+
+ auto sub = client.subscribe(topic, 1);
+ QTRY_COMPARE(sub->state(), QMqttSubscription::Subscribed);
+
+ QSignalSpy subscribeSpy(sub, SIGNAL(messageReceived(QMqttMessage)));
+
+ QByteArray shortData(100, 'd');
+ QByteArray overFlowData(1000, 'o');
+
+ QSignalSpy publishSpy(&client, SIGNAL(messageSent(qint32)));
+ client.publish(topic, shortData, 1);
+ QTRY_COMPARE(publishSpy.count(), 1);
+
+ QTRY_COMPARE(subscribeSpy.count(), 1);
+
+ client.publish(topic, overFlowData, 1);
+ QTRY_COMPARE(publishSpy.count(), 1);
+
+ // We defined maximum size to receive is 500, hence the message should not be sent back
+ // to us. Wait for some time and verify no message got sent to subscriber
+ QTest::qWait(3000);
+ QTRY_COMPARE(subscribeSpy.count(), 1);
+}
+
+void tst_QMqttConnectionProperties::maximumTopicAlias()
+{
+ const QByteArray msgContent("SomeContent");
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ const int userMaxTopicAlias = 9;
+ QMqttConnectionProperties userProperties;
+ userProperties.setMaximumTopicAlias(userMaxTopicAlias);
+ client.setConnectionProperties(userProperties);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ int publishTransportSize = 0;
+ auto transport = client.transport();
+ QSignalSpy transportSpy(transport, SIGNAL(bytesWritten(qint64))); //&QIODevice::bytesWritten);
+
+ const auto serverProperties = client.serverConnectionProperties();
+ const quint16 serverMaxAlias = serverProperties.maximumTopicAlias();
+ if (serverMaxAlias == 0)
+ QSKIP("Need to skip this test due to topic aliases not supported on server");
+
+ //qDebug() << "Server Max Alias:" << serverMaxAlias;
+ //QLoggingCategory::setFilterRules("qt.mqtt.connection*=true");
+
+ // Fill up the internal publish vector
+ const QLatin1String topicBase("Qt/connprop/alias/top");
+ for (quint16 i = 0; i < serverMaxAlias; ++i) {
+ QSignalSpy publishSpy(&client, SIGNAL(messageSent(qint32)));
+
+ QMqttTopicName topic(topicBase + QString::number(i));
+ client.publish(topic, msgContent, 1);
+ QTRY_VERIFY(publishSpy.count() == 1);
+
+ QVERIFY(transportSpy.count() == 1);
+ const int dataSize = transportSpy.at(0).at(0).toInt();
+ if (publishTransportSize == 0) {
+ publishTransportSize = dataSize;
+ } else {
+ QCOMPARE(dataSize, publishTransportSize);
+ }
+ transportSpy.clear();
+ }
+
+ // Verify non auto assignable defaults to old behavior
+ QSignalSpy fullSpy(&client, SIGNAL(messageSent(qint32)));
+ transportSpy.clear();
+ client.publish(QLatin1String("Qt/connprop/alias/full/with/long/topic/to/verify/bigger/size"), msgContent, 1);
+ QTRY_VERIFY(fullSpy.count() == 1);
+ QVERIFY(transportSpy.count() == 1);
+ QVERIFY(transportSpy.at(0).at(0).toInt() > publishTransportSize);
+
+ // Verify alias is used at sending second time
+ transportSpy.clear();
+ fullSpy.clear();
+
+ client.publish(topicBase + QLatin1String("0"), msgContent, 1);
+ QTRY_VERIFY(fullSpy.count() == 1);
+ QVERIFY(transportSpy.count() == 1);
+ int usageSize = transportSpy.at(0).at(0).toInt();
+ QVERIFY(usageSize < publishTransportSize);
+
+ // Manually overwrite topic alias 1
+ const QMqttTopicName overwrite(QLatin1String("Qt/connprop/alias/overwrite/with/long/topic/to/verify/reset"));
+ QMqttPublishProperties overProp;
+ overProp.setTopicAlias(2);
+ fullSpy.clear();
+ transportSpy.clear();
+ client.publish(overwrite, overProp, msgContent, 1);
+ QTRY_VERIFY(fullSpy.count() == 1);
+ QVERIFY(transportSpy.count() == 1);
+ const int overwriteSize = transportSpy.at(0).at(0).toInt();
+ QVERIFY(overwriteSize > publishTransportSize);
+ // After resend new alias should be used and msg size reduced
+ fullSpy.clear();
+ transportSpy.clear();
+ client.publish(overwrite, msgContent, 1);
+ QTRY_VERIFY(fullSpy.count() == 1);
+ QVERIFY(transportSpy.count() == 1);
+ usageSize = transportSpy.at(0).at(0).toInt();
+ QVERIFY(usageSize < overwriteSize);
+}
+
+void createTopicAliasClient(QMqttClient &client, const QString &hostname, quint16 port)
+{
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(hostname);
+ client.setPort(port);
+
+ const int userMaxTopicAlias = 9;
+ QMqttConnectionProperties userProperties;
+ userProperties.setMaximumTopicAlias(userMaxTopicAlias);
+ client.setConnectionProperties(userProperties);
+}
+
+void tst_QMqttConnectionProperties::maximumTopicAliasReceive()
+{
+ const QByteArray msgContent("SomeContent");
+ const QString topic("Qt/connprop/receive/alias");
+
+ QMqttClient client;
+ createTopicAliasClient(client, m_testBroker, m_port);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ QMqttClient subscriber;
+ createTopicAliasClient(subscriber, m_testBroker, m_port);
+
+ subscriber.connectToHost();
+ QTRY_VERIFY2(subscriber.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ auto sub = subscriber.subscribe(topic + "/#", 1);
+
+ int receiveCounter = 0;
+ connect(sub, &QMqttSubscription::messageReceived, [&receiveCounter](QMqttMessage msg) {
+ qDebug() << "Received message with alias:" << msg.publishProperties().topicAlias();
+ receiveCounter++;
+ });
+
+ QTRY_VERIFY2(sub->state() == QMqttSubscription::Subscribed, "Could not subscribe");
+
+ QSignalSpy publishSpy(&client, &QMqttClient::messageSent);
+ //QLoggingCategory::setFilterRules("qt.mqtt.connection*=true");
+ client.publish(topic, msgContent, 1);
+ QTRY_VERIFY2(publishSpy.count() == 1, "Could not publish");
+ QTRY_VERIFY2(receiveCounter == 1, "Did not receive initial message");
+
+ publishSpy.clear();
+ client.publish(topic, msgContent, 1);
+ QTRY_VERIFY2(publishSpy.count() == 1, "Could not publish");
+ QTRY_VERIFY2(receiveCounter == 2, "Did not receive second aliases message");
+}
+
+void tst_QMqttConnectionProperties::assignedClientId()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+ client.setClientId(QLatin1String(""));
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ QMqttServerConnectionProperties serverProperties = client.serverConnectionProperties();
+ QVERIFY2(serverProperties.availableProperties() & QMqttServerConnectionProperties::AssignedClientId, "Must contain client ID");
+
+ QVERIFY2(!client.clientId().isEmpty(), "Client ID must not be empty");
+}
+
+void tst_QMqttConnectionProperties::userProperties()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+ QMqttConnectionProperties p;
+ QMqttUserProperties userProperties;
+ userProperties.append(QMqttStringPair(QLatin1String("TestKey"), QLatin1String("TestValue")));
+ p.setUserProperties(userProperties);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ // ### TODO: We should a method to verify transmission of the user data
+ // Probably in conjunction with authentication
+
+ //QMqttServerConnectionProperties serverProperties = client.serverConnectionProperties();
+ //if (serverProperties.availableProperties() & QMqttServerConnectionProperties::UserProperty) {
+ // auto serverUserProps = serverProperties.userProperties();
+ // qDebug() << serverUserProps;
+ //} else
+ // qDebug() << "No user props available";
+}
+
+QTEST_MAIN(tst_QMqttConnectionProperties)
+
+#include "tst_qmqttconnectionproperties.moc"
diff --git a/tests/auto/qmqttlastwillproperties/qmqttlastwillproperties.pro b/tests/auto/qmqttlastwillproperties/qmqttlastwillproperties.pro
new file mode 100644
index 0000000..e61752e
--- /dev/null
+++ b/tests/auto/qmqttlastwillproperties/qmqttlastwillproperties.pro
@@ -0,0 +1,17 @@
+CONFIG += testcase
+QT += network testlib mqtt
+QT -= gui
+QT_PRIVATE += mqtt-private
+
+TARGET = tst_qmqttlastwillproperties
+
+SOURCES += \
+ tst_qmqttlastwillproperties.cpp
+
+HEADERS += \
+ $$PWD/../../common/broker_connection.h
+
+INCLUDEPATH += \
+ $$PWD/../../common
+
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/qmqttlastwillproperties/tst_qmqttlastwillproperties.cpp b/tests/auto/qmqttlastwillproperties/tst_qmqttlastwillproperties.cpp
new file mode 100644
index 0000000..77a46b5
--- /dev/null
+++ b/tests/auto/qmqttlastwillproperties/tst_qmqttlastwillproperties.cpp
@@ -0,0 +1,203 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt module.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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 "broker_connection.h"
+
+#include <QtCore/QString>
+#include <QtTest/QtTest>
+#include <QtMqtt/QMqttClient>
+#include <QtMqtt/QMqttConnectionProperties>
+
+class tst_QMqttLastWillProperties : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QMqttLastWillProperties();
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void getSet();
+ void payloadFormat();
+ void willDelay_data();
+ void willDelay();
+
+private:
+ QProcess m_brokerProcess;
+ QString m_testBroker;
+ quint16 m_port{1883};
+};
+
+tst_QMqttLastWillProperties::tst_QMqttLastWillProperties()
+{
+}
+
+void tst_QMqttLastWillProperties::initTestCase()
+{
+ m_testBroker = invokeOrInitializeBroker(&m_brokerProcess);
+ if (m_testBroker.isEmpty())
+ qFatal("No MQTT broker present to test against.");
+}
+
+void tst_QMqttLastWillProperties::cleanupTestCase()
+{
+}
+
+void tst_QMqttLastWillProperties::getSet()
+{
+ QMqttLastWillProperties properties;
+
+ QCOMPARE(properties.willDelayInterval(), 0u);
+ properties.setWillDelayInterval(50);
+ QCOMPARE(properties.willDelayInterval(), 50u);
+
+ QCOMPARE(properties.payloadFormatIndicator(), QMqtt::PayloadFormatIndicator::Unspecified);
+ properties.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);
+ QCOMPARE(properties.payloadFormatIndicator(), QMqtt::PayloadFormatIndicator::UTF8Encoded);
+
+ QCOMPARE(properties.messageExpiryInterval(), 0u);
+ properties.setMessageExpiryInterval(50);
+ QCOMPARE(properties.messageExpiryInterval(), 50u);
+
+ const QString content = QLatin1String("contentType");
+ const QString response = QLatin1String("Qt/some/responseTopic");
+ const QByteArray correlation(500, char('a'));
+
+ QCOMPARE(properties.contentType(), QString());
+ properties.setContentType(content);
+ QCOMPARE(properties.contentType(), content);
+
+ QCOMPARE(properties.responseTopic(), QString());
+ properties.setResponseTopic(response);
+ QCOMPARE(properties.responseTopic(), response);
+
+ QCOMPARE(properties.correlationData(), QByteArray());
+ properties.setCorrelationData(correlation);
+ QCOMPARE(properties.correlationData(), correlation);
+
+ QVERIFY(properties.userProperties().isEmpty());
+ QMqttUserProperties user;
+ user.append(QMqttStringPair(QLatin1String("SomeName"), QLatin1String("SomeValue")));
+ user.append(QMqttStringPair(QLatin1String("SomeName2"), QLatin1String("SomeValue2")));
+ properties.setUserProperties(user);
+ QCOMPARE(properties.userProperties(), user);
+}
+
+void tst_QMqttLastWillProperties::payloadFormat()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ client.setWillMessage(QString::fromLatin1("willmessage to utf8").toUtf8());
+ client.setWillTopic(QLatin1String("Qt/LastWillProperties/willtopic"));
+ client.setWillQoS(1);
+
+ QMqttLastWillProperties lastWill;
+ lastWill.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);
+
+ // The broker MAY verify the content is utf8
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+}
+
+void tst_QMqttLastWillProperties::willDelay_data()
+{
+ QTest::addColumn<int>("delay");
+ QTest::addColumn<int>("expiry");
+ QTest::newRow("delay == expiry") << 5 << 5;
+ QTest::newRow("delay < expiry") << 3 << 10; // will delay is send first
+ QTest::newRow("delay > expiry") << 10 << 3; // will is send at expiry
+ QTest::newRow("delay > expiry(0)") << 5 << 0; // No expiry, hence will is send immediately
+}
+
+void tst_QMqttLastWillProperties::willDelay()
+{
+ QFETCH(int, delay);
+ QFETCH(int, expiry);
+
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ const QString wTopic{"Qt/lastwillproperties/willdelay"};
+ const QByteArray wMessage{"client got lost"};
+
+ client.setWillMessage(wMessage);
+ client.setWillQoS(2);
+ client.setWillTopic(wTopic);
+
+ if (expiry > 0) {
+ // If a session expires first, then last will is send immediately
+ QMqttConnectionProperties connectionProperties;
+ connectionProperties.setSessionExpiryInterval(quint32(expiry));
+ client.setConnectionProperties(connectionProperties);
+ }
+
+ QMqttLastWillProperties lastWillProperties;
+ lastWillProperties.setWillDelayInterval(quint32(delay));
+ client.setLastWillProperties(lastWillProperties);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker");
+
+ QMqttClient recipient;
+ recipient.setProtocolVersion(QMqttClient::MQTT_5_0);
+ recipient.setHostname(m_testBroker);
+ recipient.setPort(m_port);
+ recipient.connectToHost();
+ QTRY_VERIFY2(recipient.state() == QMqttClient::Connected, "Could not connect to broker");
+
+ QTime delayTime;
+ bool receivedWill = false;
+ auto sub = recipient.subscribe(wTopic, 1);
+ connect(sub, &QMqttSubscription::messageReceived, this, [wMessage, &receivedWill](QMqttMessage m) {
+ if (m.payload() == wMessage)
+ receivedWill = true;
+ });
+ QTRY_VERIFY2(sub->state() == QMqttSubscription::Subscribed, "Could not subscribe.");
+
+ auto transport = client.transport();
+ transport->close(); // closing transport does not send DISCONNECT
+ delayTime = QTime::currentTime();
+ delayTime.start();
+
+ const int minimalWait = qMin(delay, expiry) * 1000;
+ const int maximumWait = 2 * (minimalWait == 0 ? 1000 : minimalWait);
+ QTRY_VERIFY2_WITH_TIMEOUT(receivedWill, "Did not receive a will message", delay * 1000 * 3);
+ const int elapsed = delayTime.elapsed();
+ QVERIFY(elapsed > minimalWait);
+ QVERIFY(elapsed < maximumWait);
+}
+
+QTEST_MAIN(tst_QMqttLastWillProperties)
+
+#include "tst_qmqttlastwillproperties.moc"
diff --git a/tests/auto/qmqttpublishproperties/qmqttpublishproperties.pro b/tests/auto/qmqttpublishproperties/qmqttpublishproperties.pro
new file mode 100644
index 0000000..c28480b
--- /dev/null
+++ b/tests/auto/qmqttpublishproperties/qmqttpublishproperties.pro
@@ -0,0 +1,17 @@
+CONFIG += testcase
+QT += network testlib mqtt
+QT -= gui
+QT_PRIVATE += mqtt-private
+
+TARGET = tst_qmqttpublishproperties
+
+SOURCES += \
+ tst_qmqttpublishproperties.cpp
+
+HEADERS += \
+ $$PWD/../../common/broker_connection.h
+
+INCLUDEPATH += \
+ $$PWD/../../common
+
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/qmqttpublishproperties/tst_qmqttpublishproperties.cpp b/tests/auto/qmqttpublishproperties/tst_qmqttpublishproperties.cpp
new file mode 100644
index 0000000..669fe5d
--- /dev/null
+++ b/tests/auto/qmqttpublishproperties/tst_qmqttpublishproperties.cpp
@@ -0,0 +1,218 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt module.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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 "broker_connection.h"
+
+#include <QtCore/QString>
+#include <QtTest/QtTest>
+#include <QtMqtt/QMqttClient>
+#include <QtMqtt/QMqttPublishProperties>
+#include <QtMqtt/QMqttSubscription>
+
+class tst_QMqttPublishProperties : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QMqttPublishProperties();
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void getSet();
+ void propertyConsistency();
+ void topicAlias();
+
+private:
+ QProcess m_brokerProcess;
+ QString m_testBroker;
+ quint16 m_port{1883};
+};
+
+tst_QMqttPublishProperties::tst_QMqttPublishProperties()
+{
+}
+
+void tst_QMqttPublishProperties::initTestCase()
+{
+ m_testBroker = invokeOrInitializeBroker(&m_brokerProcess);
+ if (m_testBroker.isEmpty())
+ qFatal("No MQTT broker present to test against.");
+}
+
+void tst_QMqttPublishProperties::cleanupTestCase()
+{
+}
+
+void tst_QMqttPublishProperties::getSet()
+{
+ QMqttPublishProperties p;
+
+ QCOMPARE(p.availableProperties(), 0);
+
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::PayloadFormatIndicator));
+ QCOMPARE(p.payloadFormatIndicator(), QMqtt::PayloadFormatIndicator::Unspecified);
+ p.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);
+ QCOMPARE(p.payloadFormatIndicator(), QMqtt::PayloadFormatIndicator::UTF8Encoded);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::PayloadFormatIndicator);
+
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::MessageExpiryInterval));
+ p.setMessageExpiryInterval(200);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::MessageExpiryInterval);
+ QCOMPARE(p.messageExpiryInterval(), 200u);
+
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::TopicAlias));
+ p.setTopicAlias(1);
+ QCOMPARE(p.topicAlias(), 1);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::TopicAlias);
+ p.setTopicAlias(0); // Zero is not allowed
+ QCOMPARE(p.topicAlias(), 1);
+
+ const QString responseTopic = QLatin1String("reply/to/this");
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::ResponseTopic));
+ QCOMPARE(p.responseTopic(), QString());
+ p.setResponseTopic(responseTopic);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::ResponseTopic);
+ QCOMPARE(p.responseTopic(), responseTopic);
+
+ const QByteArray data = QByteArray(1, char('c'));
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::CorrelationData));
+ QCOMPARE(p.correlationData(), QByteArray());
+ p.setCorrelationData(data);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::CorrelationData);
+ QCOMPARE(p.correlationData(), data);
+
+ const QString userKey = QLatin1String("UserName");
+ const QString userValue = QLatin1String("SomeValue");
+ QMqttUserProperties userProperty;
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::UserProperty));
+ QCOMPARE(p.userProperties(), userProperty);
+ userProperty.append(QMqttStringPair(userKey, userValue));
+ p.setUserProperties(userProperty);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::UserProperty);
+ QCOMPARE(p.userProperties(), userProperty);
+
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::SubscriptionIdentifier));
+ const QList<quint32> one{1};
+ p.setSubscriptionIdentifiers(one);
+ QCOMPARE(p.subscriptionIdentifiers(), one);
+
+ const QList<quint32> invalidZero{1, 0};
+ p.setSubscriptionIdentifiers(invalidZero);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::SubscriptionIdentifier);
+ QCOMPARE(p.subscriptionIdentifiers(), one);
+
+ const QString contentType = QLatin1String("MultimediaContent123");
+ QVERIFY(!(p.availableProperties() & QMqttPublishProperties::ContentType));
+ QCOMPARE(p.contentType(), QString());
+ p.setContentType(contentType);
+ QVERIFY(p.availableProperties() & QMqttPublishProperties::ContentType);
+ QCOMPARE(p.contentType(), contentType);
+}
+
+void tst_QMqttPublishProperties::propertyConsistency()
+{
+
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ QMqttClient client2;
+ client2.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client2.setHostname(m_testBroker);
+ client2.setPort(m_port);
+
+ client2.connectToHost();
+ QTRY_VERIFY2(client2.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+
+ const QByteArray correlation(10, 'c');
+ const QString responseTopic = QLatin1String("topic/to/reply");
+ const QString userKey1 = QLatin1String("UserName1");
+ const QString userValue1 = QLatin1String("SomeValue");
+ const QString userKey2 = QLatin1String("UserName2");
+ const QString userValue2 = QLatin1String("OtherValue");
+ QMqttUserProperties userProperty;
+ userProperty.append(QMqttStringPair(userKey1, userValue1));
+ userProperty.append(QMqttStringPair(userKey2, userValue2));
+ const QString content = QLatin1String("ContentType");
+
+ const QString testTopic = QLatin1String("Qt/PublishProperties/publish/consistent");
+
+ auto sub = client2.subscribe(testTopic, 1);
+ QTRY_COMPARE(sub->state(), QMqttSubscription::Subscribed);
+
+ QMqttPublishProperties pubProp;
+ pubProp.setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator::UTF8Encoded);
+ pubProp.setMessageExpiryInterval(60);
+ //pubProp.setTopicAlias(1);
+ pubProp.setResponseTopic(responseTopic);
+ pubProp.setCorrelationData(correlation);
+ pubProp.setUserProperties(userProperty);
+ pubProp.setContentType(content);
+
+ QSignalSpy publishSpy(&client, SIGNAL(messageSent(qint32)));
+ QSignalSpy subscribeSpy(sub, SIGNAL(messageReceived(QMqttMessage)));
+ client.publish(testTopic, pubProp, QByteArray("Some Content"), 1);
+
+ QTRY_COMPARE(publishSpy.count(), 1);
+ QTRY_COMPARE(subscribeSpy.count(), 1);
+
+ auto msg = subscribeSpy.at(0).at(0).value<QMqttMessage>();
+
+ QCOMPARE(msg.payload(), QByteArray("Some Content"));
+ QMqttPublishProperties receivalProp = msg.publishProperties();
+ QCOMPARE(pubProp.payloadFormatIndicator(), receivalProp.payloadFormatIndicator());
+ QVERIFY(pubProp.messageExpiryInterval() >= receivalProp.messageExpiryInterval()); // The broker MIGHT reduce
+ QCOMPARE(pubProp.responseTopic(), receivalProp.responseTopic());
+ QCOMPARE(pubProp.correlationData(), receivalProp.correlationData());
+
+ // Paho test server sends identical userProperties, Flespi adds additional properties (ie timestamp)
+ const auto userSend = pubProp.userProperties();
+ const auto userReceive = receivalProp.userProperties();
+ QVERIFY(userSend.size() <= userReceive.size());
+ for (auto it = userSend.constBegin(); it != userSend.constEnd(); ++it)
+ QVERIFY(userReceive.contains(*it));
+
+ QCOMPARE(pubProp.contentType(), receivalProp.contentType());
+}
+
+void tst_QMqttPublishProperties::topicAlias()
+{
+ // Get serverproperties
+ // Send data with higher alias than available
+ // Connection gets killed
+}
+
+QTEST_MAIN(tst_QMqttPublishProperties)
+
+#include "tst_qmqttpublishproperties.moc"
diff --git a/tests/auto/qmqttsubscription/tst_qmqttsubscription.cpp b/tests/auto/qmqttsubscription/tst_qmqttsubscription.cpp
index fda8ef6..78bb05c 100644
--- a/tests/auto/qmqttsubscription/tst_qmqttsubscription.cpp
+++ b/tests/auto/qmqttsubscription/tst_qmqttsubscription.cpp
@@ -47,8 +47,13 @@ private Q_SLOTS:
void getSetCheck();
void wildCards_data();
void wildCards();
+ void reconnect_data();
void reconnect();
+ void sharedConnection();
+ void sharedNonShared_data();
+ void sharedNonShared();
private:
+ void createAndSubscribe(QMqttClient *c, QMqttSubscription **sub, const QString &topic);
QProcess m_brokerProcess;
QString m_testBroker;
quint16 m_port{1883};
@@ -75,35 +80,41 @@ void Tst_QMqttSubscription::getSetCheck()
void Tst_QMqttSubscription::wildCards_data()
{
+ QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion");
QTest::addColumn<QString>("subscription");
QTest::addColumn<int>("expectedReceival");
- QTest::newRow("#") << "Qt/#" << 6;
- QTest::newRow("Qt/a/b/c/d/e/f") << "Qt/a/b/c/d/e/f" << 1;
- QTest::newRow("Qt/+/b/c/d/e/f") << "Qt/+/b/c/d/e/f" << 1;
- QTest::newRow("Qt/a/+/c/d/e/f") << "Qt/a/+/c/d/e/f" << 1;
- QTest::newRow("Qt/a/b/+/d/e/f") << "Qt/a/b/+/d/e/f" << 1;
- QTest::newRow("Qt/a/b/c/+/e/f") << "Qt/a/b/c/+/e/f" << 1;
- QTest::newRow("Qt/a/b/c/d/+/f") << "Qt/a/b/c/d/+/f" << 1;
- QTest::newRow("Qt/a/b/c/d/e/+") << "Qt/a/b/c/d/e/+" << 1;
- QTest::newRow("Qt/+/b/+/d/e/+") << "Qt/+/b/+/d/e/+" << 1;
- QTest::newRow("Qt/a/+") << "Qt/a/+" << 1;
- QTest::newRow("Qt/a/+/c") << "Qt/a/+/c" << 1;
+ QList<QMqttClient::ProtocolVersion> versions{QMqttClient::MQTT_3_1_1, QMqttClient::MQTT_5_0};
+
+ for (int i = 0; i < 2; ++i) {
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/#")) << versions[i] << "Qt/subscription/#" << 6;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/b/c/d/e/f")) << versions[i] << "Qt/subscription/a/b/c/d/e/f" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/+/b/c/d/e/f")) << versions[i] << "Qt/subscription/+/b/c/d/e/f" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/+/c/d/e/f")) << versions[i] << "Qt/subscription/a/+/c/d/e/f" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/b/+/d/e/f")) << versions[i] << "Qt/subscription/a/b/+/d/e/f" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/b/c/+/e/f")) << versions[i] << "Qt/subscription/a/b/c/+/e/f" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/b/c/d/+/f")) << versions[i] << "Qt/subscription/a/b/c/d/+/f" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/b/c/d/e/+")) << versions[i] << "Qt/subscription/a/b/c/d/e/+" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/+/b/+/d/e/+")) << versions[i] << "Qt/subscription/+/b/+/d/e/+" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/+")) << versions[i] << "Qt/subscription/a/+" << 1;
+ QTest::newRow(qPrintable(QString::number(versions[i]) + ":Qt/subscription/a/+/c")) << versions[i] << "Qt/subscription/a/+/c" << 1;
+ }
}
void Tst_QMqttSubscription::wildCards()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
QFETCH(QString, subscription);
QFETCH(int, expectedReceival);
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
client.connectToHost();
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
- QMqttClient publisher;
+ VersionClient(mqttVersion, publisher);
publisher.setHostname(m_testBroker);
publisher.setPort(m_port);
publisher.connectToHost();
@@ -115,12 +126,12 @@ void Tst_QMqttSubscription::wildCards()
QSignalSpy receivalSpy(sub, SIGNAL(messageReceived(QMqttMessage)));
QStringList topics;
- topics << "Qt/a"
- << "Qt/a/b"
- << "Qt/a/b/c"
- << "Qt/a/b/c/d"
- << "Qt/a/b/c/d/e"
- << "Qt/a/b/c/d/e/f";
+ topics << "Qt/subscription/a"
+ << "Qt/subscription/a/b"
+ << "Qt/subscription/a/b/c"
+ << "Qt/subscription/a/b/c/d"
+ << "Qt/subscription/a/b/c/d/e"
+ << "Qt/subscription/a/b/c/d/e/f";
for (auto t : topics) {
QSignalSpy spy(&publisher, SIGNAL(messageSent(qint32)));
@@ -142,11 +153,15 @@ void Tst_QMqttSubscription::wildCards()
QTRY_VERIFY2(publisher.state() == QMqttClient::Disconnected, "Could not disconnect.");
}
+DefaultVersionTestData(Tst_QMqttSubscription::reconnect_data)
+
void Tst_QMqttSubscription::reconnect()
{
+ QFETCH(QMqttClient::ProtocolVersion, mqttVersion);
+
// QTBUG-64042
// - Connect with clean session
- QMqttClient client;
+ VersionClient(mqttVersion, client);
client.setHostname(m_testBroker);
client.setPort(m_port);
@@ -155,7 +170,7 @@ void Tst_QMqttSubscription::reconnect()
QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
// - Subscribe to topic A
- const QString subscription("topics/resub");
+ const QString subscription("Qt/subscription/topics/resub");
auto sub = client.subscribe(subscription, 1);
QTRY_VERIFY2(sub->state() == QMqttSubscription::Subscribed, "Could not subscribe to topic.");
@@ -217,6 +232,151 @@ void Tst_QMqttSubscription::reconnect()
QTRY_VERIFY2(client.state() == QMqttClient::Disconnected, "Could not disconnect.");
}
+void Tst_QMqttSubscription::createAndSubscribe(QMqttClient *c, QMqttSubscription **sub, const QString &topic)
+{
+ c->setProtocolVersion(QMqttClient::MQTT_5_0);
+ c->setHostname(m_testBroker);
+ c->setPort(m_port);
+
+ c->connectToHost();
+ QTRY_VERIFY2(c->state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ *sub = c->subscribe(topic, 1);
+ QTRY_VERIFY2((*sub)->state() == QMqttSubscription::Subscribed, "Could not subscribe.");
+}
+
+void Tst_QMqttSubscription::sharedConnection()
+{
+ // Create / Connect publisher
+ QMqttClient sender;
+ sender.setProtocolVersion(QMqttClient::MQTT_5_0);
+ sender.setHostname(m_testBroker);
+ sender.setPort(m_port);
+ sender.connectToHost();
+ QTRY_VERIFY2(sender.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ // Create GroupA
+ const int groupSizeA = 2;
+ const QString groupTopicA{QLatin1String("$share/groupA/shared/sub")};
+ QMqttClient listenersA[groupSizeA];
+ QMqttSubscription *subsA[groupSizeA];
+ int messageCounterA[groupSizeA] = {0};
+ int messageSumA = 0;
+ // listenerAx: $share/groupA/Qt/Subscription/shared_check/#
+ for (int i = 0; i < groupSizeA; ++i) {
+ createAndSubscribe(&listenersA[i], &subsA[i], groupTopicA);
+ QCOMPARE(subsA[i]->isShared(), true);
+ QCOMPARE(subsA[i]->shareName(), QLatin1String("groupA"));
+ connect(subsA[i], &QMqttSubscription::messageReceived, [i, &messageCounterA, &messageSumA]() {
+ messageCounterA[i]++;
+ messageSumA++;
+ //qDebug() << "A Got message:" << i << ":" << messageCounterA[i];
+ });
+ }
+
+ // Create GroupB
+ const int groupSizeB = 5;
+ const QString groupTopicB{QLatin1String("$share/groupB/shared/#")};
+ QMqttClient listenersB[groupSizeB];
+ QMqttSubscription *subsB[groupSizeB];
+ int messageCounterB[groupSizeB] = {0};
+ int messageSumB = 0;
+ // listenerBx: $share/groupB/Qt/Subscription/shared_check/#
+ for (int i = 0; i < groupSizeB; ++i) {
+ createAndSubscribe(&listenersB[i], &subsB[i], groupTopicB);
+ QCOMPARE(subsB[i]->isShared(), true);
+ QCOMPARE(subsB[i]->shareName(), QLatin1String("groupB"));
+ connect(subsB[i], &QMqttSubscription::messageReceived, [i, &messageCounterB, &messageSumB]() {
+ messageCounterB[i]++;
+ messageSumB++;
+ //qDebug() << "B Got message:" << i << ":" << messageCounterB[i];
+ });
+ }
+
+ const int publishedMessages = 10;
+ for (int i = 0; i < publishedMessages; ++i) {
+ QSignalSpy publishSpy(&sender, SIGNAL(messageSent(qint32)));
+ sender.publish(QLatin1String("shared/sub"), QByteArray("Foobidoo"), 1);
+ QTRY_VERIFY2(publishSpy.size() == 1, "Could not publish message.");
+ }
+
+ QTRY_VERIFY2(messageSumA == publishedMessages, "Group A did not receive enough messages.");
+ QTRY_VERIFY2(messageSumB == publishedMessages, "Group A did not receive enough messages.");
+
+ QString formatString;
+ for (int i = 0; i < groupSizeA; ++i) {
+ formatString.append(QString::number(messageCounterA[i]));
+ formatString.append(QLatin1Char(' '));
+ }
+ qDebug() << "Statistics GroupA : " << formatString;
+ formatString.clear();
+ for (int i = 0; i < groupSizeB; ++i) {
+ formatString.append(QString::number(messageCounterB[i]));
+ formatString.append(QLatin1Char(' '));
+ }
+ qDebug() << "Statistics GroupB : " << formatString;
+}
+
+void Tst_QMqttSubscription::sharedNonShared_data()
+{
+ const QString topic(QLatin1String("Qt/Subscription/SharedNonShared"));
+ const QString groupTopic = QString::fromLatin1("$share/somegroup/") + topic;
+
+ QTest::addColumn<QString>("topic1");
+ QTest::addColumn<bool>("shared1");
+ QTest::addColumn<QString>("topic2");
+ QTest::addColumn<bool>("shared2");
+ QTest::addColumn<bool>("expected");
+
+ QTest::newRow("non - non") << topic << false << topic << false << true;
+ QTest::newRow("non - yes") << topic << false << groupTopic << true << false;
+ QTest::newRow("yes - non") << groupTopic << true << topic << false << false;
+ QTest::newRow("yes - yes") << groupTopic << true << groupTopic << true << true;
+}
+
+void Tst_QMqttSubscription::sharedNonShared()
+{
+ QFETCH(QString, topic1);
+ QFETCH(bool, shared1);
+ QFETCH(QString, topic2);
+ QFETCH(bool, shared2);
+ QFETCH(bool, expected);
+
+ // Create / Connect publisher
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ QMqttSubscription *sub1 = client.subscribe(topic1, 1);
+ QCOMPARE(sub1->isShared(), shared1);
+ QVERIFY(sub1->shareName().isEmpty() == !shared1);
+
+ QMqttSubscription *sub2 = client.subscribe(topic2, 1);
+ QCOMPARE(sub2->isShared(), shared2);
+ QVERIFY(sub2->shareName().isEmpty() == !shared2);
+
+ // Verify that a subscription is reused / not reused
+ QCOMPARE(sub1 == sub2, expected);
+
+ // Depending on the broker, it may decide to not send a message twice due to overlapping
+ // subscriptions on the same client (eg. paho).
+ // Using two different clients would make the receival work, but is not part of this test.
+
+// QSignalSpy receivalSpy1(sub1, SIGNAL(messageReceived(QMqttMessage)));
+// QSignalSpy receivalSpy2(sub2, SIGNAL(messageReceived(QMqttMessage)));
+// QSignalSpy publishSpy(&client, SIGNAL(messageSent(qint32)));
+
+// client.publish(QLatin1String("Qt/Subscription/SharedNonShared"), QByteArray(), 1);
+// QTRY_VERIFY(publishSpy.count() == 1);
+
+// // Verify both subscriptions receive the message
+// QTRY_VERIFY(receivalSpy1.count() == 1);
+// QTRY_VERIFY(receivalSpy2.count() == 1);
+}
+
QTEST_MAIN(Tst_QMqttSubscription)
#include "tst_qmqttsubscription.moc"
diff --git a/tests/auto/qmqttsubscriptionproperties/qmqttsubscriptionproperties.pro b/tests/auto/qmqttsubscriptionproperties/qmqttsubscriptionproperties.pro
new file mode 100644
index 0000000..857a7c2
--- /dev/null
+++ b/tests/auto/qmqttsubscriptionproperties/qmqttsubscriptionproperties.pro
@@ -0,0 +1,17 @@
+CONFIG += testcase
+QT += network testlib mqtt
+QT -= gui
+QT_PRIVATE += mqtt-private
+
+TARGET = tst_qmqttsubscriptionproperties
+
+SOURCES += \
+ tst_qmqttsubscriptionproperties.cpp
+
+HEADERS += \
+ $$PWD/../../common/broker_connection.h
+
+INCLUDEPATH += \
+ $$PWD/../../common
+
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/qmqttsubscriptionproperties/tst_qmqttsubscriptionproperties.cpp b/tests/auto/qmqttsubscriptionproperties/tst_qmqttsubscriptionproperties.cpp
new file mode 100644
index 0000000..00c7a3d
--- /dev/null
+++ b/tests/auto/qmqttsubscriptionproperties/tst_qmqttsubscriptionproperties.cpp
@@ -0,0 +1,128 @@
+/******************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtMqtt module.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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 "broker_connection.h"
+
+#include <QtCore/QString>
+#include <QtTest/QtTest>
+#include <QtMqtt/QMqttClient>
+#include <QtMqtt/QMqttConnectionProperties>
+#include <QtMqtt/QMqttSubscription>
+#include <QtMqtt/QMqttSubscriptionProperties>
+
+class tst_QMqttSubscriptionProperties : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_QMqttSubscriptionProperties();
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void getSet();
+ void subscribe();
+
+private:
+ QProcess m_brokerProcess;
+ QString m_testBroker;
+ quint16 m_port{1883};
+};
+
+tst_QMqttSubscriptionProperties::tst_QMqttSubscriptionProperties()
+{
+}
+
+void tst_QMqttSubscriptionProperties::initTestCase()
+{
+ m_testBroker = invokeOrInitializeBroker(&m_brokerProcess);
+ if (m_testBroker.isEmpty())
+ qFatal("No MQTT broker present to test against.");
+}
+
+void tst_QMqttSubscriptionProperties::cleanupTestCase()
+{
+}
+
+void tst_QMqttSubscriptionProperties::getSet()
+{
+ QMqttSubscriptionProperties properties;
+
+ const quint32 id = 123;
+ properties.setSubscriptionIdentifier(id);
+ QCOMPARE(properties.subscriptionIdentifier(), id);
+
+ const QString userKey1 = QLatin1String("UserName1");
+ const QString userValue1 = QLatin1String("SomeValue");
+ const QString userKey2 = QLatin1String("UserName2");
+ const QString userValue2 = QLatin1String("OtherValue");
+ QMqttUserProperties userProperty;
+ QCOMPARE(properties.userProperties(), userProperty);
+ userProperty.append(QMqttStringPair(userKey1, userValue1));
+ userProperty.append(QMqttStringPair(userKey2, userValue2));
+ properties.setUserProperties(userProperty);
+ QCOMPARE(properties.userProperties(), userProperty);
+}
+
+void tst_QMqttSubscriptionProperties::subscribe()
+{
+ QMqttClient client;
+ client.setProtocolVersion(QMqttClient::MQTT_5_0);
+ client.setHostname(m_testBroker);
+ client.setPort(m_port);
+
+ QMqttConnectionProperties conProperties;
+ conProperties.setRequestResponseInformation(true);
+ conProperties.setRequestProblemInformation(true);
+ client.setConnectionProperties(conProperties);
+
+ client.connectToHost();
+ QTRY_VERIFY2(client.state() == QMqttClient::Connected, "Could not connect to broker.");
+
+ const QString topic = QLatin1String("sub/props");
+
+ QMqttSubscriptionProperties properties;
+ properties.setSubscriptionIdentifier(10);
+ const QString userKey1 = QLatin1String("UserName1");
+ const QString userValue1 = QLatin1String("SomeValue");
+ const QString userKey2 = QLatin1String("UserName2");
+ const QString userValue2 = QLatin1String("OtherValue");
+ QMqttUserProperties userProperty;
+ userProperty.append(QMqttStringPair(userKey1, userValue1));
+ userProperty.append(QMqttStringPair(userKey2, userValue2));
+
+ auto sub = client.subscribe(topic, properties, 1);
+ QTRY_COMPARE(sub->state(), QMqttSubscription::Subscribed);
+
+ // ### TODO: Try to create a subscription which generates a reason code
+ // and/or user properties.
+}
+
+QTEST_MAIN(tst_QMqttSubscriptionProperties)
+
+#include "tst_qmqttsubscriptionproperties.moc"
diff --git a/tests/auto/qmqtttopicfilter/tst_qmqtttopicfilter.cpp b/tests/auto/qmqtttopicfilter/tst_qmqtttopicfilter.cpp
index f83999c..ce1cd67 100644
--- a/tests/auto/qmqtttopicfilter/tst_qmqtttopicfilter.cpp
+++ b/tests/auto/qmqtttopicfilter/tst_qmqtttopicfilter.cpp
@@ -75,6 +75,14 @@ void Tst_QMqttTopicFilter::checkValidity()
QVERIFY(!QMqttTopicFilter("++").isValid());
QVERIFY(!QMqttTopicFilter(QString(3, QChar(QChar::Null))).isValid());
+
+ QVERIFY(QMqttTopicFilter("$share/group/topic").isValid());
+ QVERIFY(QMqttTopicFilter("$share/group/topic/subtopic").isValid());
+ QVERIFY(QMqttTopicFilter("$share/group/topic/+/someother").isValid());
+ QVERIFY(QMqttTopicFilter("$share/group/topic/#").isValid());
+ QVERIFY(!QMqttTopicFilter("$share/groupnotopic").isValid());
+ QVERIFY(!QMqttTopicFilter("$share/").isValid());
+ QVERIFY(!QMqttTopicFilter("$share//foo").isValid());
}
void Tst_QMqttTopicFilter::matches()
diff --git a/tests/benchmarks/qmqttclient/tst_qmqttclient.cpp b/tests/benchmarks/qmqttclient/tst_qmqttclient.cpp
index f0856cc..27001a1 100644
--- a/tests/benchmarks/qmqttclient/tst_qmqttclient.cpp
+++ b/tests/benchmarks/qmqttclient/tst_qmqttclient.cpp
@@ -90,7 +90,7 @@ void Tst_QMqttClient::stressTest()
quint64 messageCount = 0;
QMqttSubscription *subscription;
- const QString testTopic = QLatin1String("test/topic");
+ const QString testTopic = QLatin1String("Qt/benchmark/test/topic");
connect(&subscriber, &QMqttClient::connected, [&](){
subscription = subscriber.subscribe(testTopic);
@@ -148,7 +148,7 @@ void Tst_QMqttClient::stressTest2()
publisher.connectToHost();
QTRY_COMPARE(spy.count(), 1);
- const QString topic = QLatin1String("SomeTopic/Sub");
+ const QString topic = QLatin1String("Qt/benchmark/SomeTopic/Sub");
const QByteArray message("messageContent");
for (qint32 i = 0; i < msgCount; ++i) {
diff --git a/tests/common/broker_connection.h b/tests/common/broker_connection.h
index 7a04468..372f860 100644
--- a/tests/common/broker_connection.h
+++ b/tests/common/broker_connection.h
@@ -29,6 +29,7 @@
#include <QtCore/QFile>
#include <QtCore/QProcess>
#include <QtCore/QString>
+#include <QtMqtt/QMqttClient>
#include <QtNetwork/QTcpSocket>
#include <QtTest/QTest>
@@ -66,7 +67,18 @@ QString invokeOrInitializeBroker(QProcess *gBrokerProcess)
#endif
}
- const QStringList arguments = QStringList() << brokerLocation;
+ QStringList arguments = {brokerLocation};
+
+ // MQTT5 tests use the same configuration as mosquitto
+ const QString configuration = QLatin1String("localhost_testing.conf");
+ const QDir brokerDir = QFileInfo(brokerLocation).absoluteDir();
+ if (brokerDir.exists(configuration)) {
+ arguments << QLatin1String("-c") << QDir::toNativeSeparators(brokerDir.absoluteFilePath(configuration));
+ // Configuration files use relative paths, hence the working directory of the broker
+ // process needs to be set correctly
+ gBrokerProcess->setWorkingDirectory(brokerDir.absolutePath());
+ }
+
qDebug() << "Launching broker:" << python << arguments;
gBrokerProcess->start(python, arguments);
if (!gBrokerProcess->waitForStarted())
@@ -84,6 +96,17 @@ QString invokeOrInitializeBroker(QProcess *gBrokerProcess)
QTest::qWait(5000);
}
- qWarning("Could not launch MQTT test broker.");
+ qWarning() << "Could not launch MQTT test broker.";
return QString();
}
+
+Q_DECLARE_METATYPE(QMqttClient::ProtocolVersion)
+#define VersionClient(MQTTVERSION, CLIENTNAME) QMqttClient CLIENTNAME; CLIENTNAME.setProtocolVersion(MQTTVERSION)
+
+#define DefaultVersionTestData(FUNCTION) \
+void FUNCTION() \
+{ \
+ QTest::addColumn<QMqttClient::ProtocolVersion>("mqttVersion"); \
+ QTest::newRow("V3.1.1") << QMqttClient::MQTT_3_1_1; \
+ QTest::newRow("V5.0.0") << QMqttClient::MQTT_5_0; \
+}