diff options
-rw-r--r-- | .qmake.conf | 2 | ||||
-rw-r--r-- | examples/mqtt/consolepubsub/configuration.h | 307 | ||||
-rw-r--r-- | examples/mqtt/consolepubsub/consolepubsub.pro | 6 | ||||
-rw-r--r-- | examples/mqtt/consolepubsub/main_pub.cpp | 112 | ||||
-rw-r--r-- | examples/mqtt/consolepubsub/main_sub.cpp | 112 | ||||
-rw-r--r-- | examples/mqtt/consolepubsub/qtmqtt_pub.pro | 25 | ||||
-rw-r--r-- | examples/mqtt/consolepubsub/qtmqtt_sub.pro | 25 | ||||
-rw-r--r-- | examples/mqtt/mqtt.pro | 1 | ||||
-rw-r--r-- | src/mqtt/qmqttclient.cpp | 3 | ||||
-rw-r--r-- | src/mqtt/qmqttconnection.cpp | 12 | ||||
-rw-r--r-- | src/mqtt/qmqttconnection_p.h | 1 | ||||
-rw-r--r-- | tests/libfuzzer/mqtt/data_receive/data_receive.pro | 8 | ||||
-rw-r--r-- | tests/libfuzzer/mqtt/data_receive/main.cpp | 145 |
13 files changed, 758 insertions, 1 deletions
diff --git a/.qmake.conf b/.qmake.conf index a3c853d..7b49e2c 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,4 +1,4 @@ load(qt_build_config) CONFIG += warning_clean -MODULE_VERSION = 5.12.3 +MODULE_VERSION = 5.13.0 diff --git a/examples/mqtt/consolepubsub/configuration.h b/examples/mqtt/consolepubsub/configuration.h new file mode 100644 index 0000000..b25920f --- /dev/null +++ b/examples/mqtt/consolepubsub/configuration.h @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** 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 <QByteArray> +#include <QCommandLineParser> +#include <QCommandLineOption> +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QLoggingCategory> +#include <QString> +#include <QMqttClient> +#include <QSslSocket> + +struct Configuration +{ + QString topic; + QByteArray content; + quint8 qos; + bool retain{false}; + bool useEncryption{false}; +}; + +QMqttClient *createClientWithConfiguration(QCoreApplication *app, + Configuration *msg, + bool publish) +{ + QCommandLineParser parser; + if (publish) + parser.setApplicationDescription("Qt MQTT publish tool"); + else + parser.setApplicationDescription("Qt MQTT subscription tool"); + + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption optionDebug("d", + QLatin1String("Enable debug messages / logging categories")); + parser.addOption(optionDebug); + + QCommandLineOption optionFile("f", + QLatin1String("Specify the content of a file as message."), + QLatin1String("filename")); + if (publish) + parser.addOption(optionFile); + + QCommandLineOption optionClientId("i", + QLatin1String("Specify a client ID. Defaults to random value."), + QLatin1String("clientid")); + parser.addOption(optionClientId); + + QCommandLineOption optionKeepAlive("k", + QLatin1String("Specify the keep-alive value in seconds."), + QLatin1String("keepAlive")); + parser.addOption(optionKeepAlive); + + QCommandLineOption optionMessageContent("m", + QLatin1String("Specify the message content. Defaults to" + " a null message."), + QLatin1String("messageContent")); + if (publish) + parser.addOption(optionMessageContent); + + QCommandLineOption optionPassword("P", + QLatin1String("Provide a password."), + QLatin1String("password")); + parser.addOption(optionPassword); + + QCommandLineOption optionPort("p", + QLatin1String("Network port to connect to. Defaults to 1883."), + QLatin1String("hostPort"), + QLatin1String("1883")); + parser.addOption(optionPort); + + QCommandLineOption optionQos("q", + QLatin1String("Quality of service level to use for all messages." + "Defaults to 0."), + QLatin1String("qos"), + QLatin1String("0")); + parser.addOption(optionQos); + + QCommandLineOption optionRetain("r", + QLatin1String("Specify the retain flag for a message.")); + if (publish) + parser.addOption(optionRetain); + + QCommandLineOption optionHost("s", + QLatin1String("MQTT server to connect to. Defaults to localhost."), + QLatin1String("hostName"), + QLatin1String("localhost")); + parser.addOption(optionHost); + + QCommandLineOption optionMessageTopic("t", + QLatin1String("Specify the message topic."), + QLatin1String("messageTopic")); + parser.addOption(optionMessageTopic); + + QCommandLineOption optionUser("u", + QLatin1String("Provide a username."), + QLatin1String("username")); + parser.addOption(optionUser); + + QCommandLineOption optionVersion("V", + QLatin1String("Specify the protocol version. Options are " + "mqtt31, mqtt311, mqtt5. Defaults to mqtt311."), + QLatin1String("protocolVersion"), + QLatin1String("mqtt311")); + parser.addOption(optionVersion); + + QCommandLineOption optionCaFile("cafile", + QLatin1String("Specify a file containing trusted CA " + "certificates to enable encrypted communication."), + QLatin1String("cafile")); + parser.addOption(optionCaFile); + + QCommandLineOption optionCaPath("capath", + QLatin1String("Specify a directory containing trusted CA " + "certificates to enable encrypted communication."), + QLatin1String("capath")); + parser.addOption(optionCaPath); + + parser.process(*app); + + auto *client = new QMqttClient(app); + + client->setHostname(parser.value(optionHost)); + bool ok = true; + quint16 port = static_cast<quint16>(parser.value(optionPort).toInt(&ok)); + if (!ok) { + qWarning() << "Invalid port specified:" << parser.value(optionPort); + return nullptr; + } + client->setPort(port); + + if (parser.isSet(optionUser)) + client->setUsername(parser.value(optionUser)); + if (parser.isSet(optionPassword)) + client->setPassword(parser.value(optionPassword)); + if (parser.isSet(optionClientId)) + client->setClientId(parser.value(optionClientId)); + if (parser.isSet(optionVersion)) { + const QString version = parser.value(optionVersion); + if (version == QLatin1String("mqtt31")) + client->setProtocolVersion(QMqttClient::MQTT_3_1); + else if (version == QLatin1String("mqtt311")) + client->setProtocolVersion(QMqttClient::MQTT_3_1_1); + else if (version == QLatin1String("mqtt5")) + client->setProtocolVersion(QMqttClient::MQTT_5_0); + else { + qWarning() << "Invalid protocol version specified:" << version; + return nullptr; + } + } + + if (parser.isSet(optionCaFile) || parser.isSet(optionCaPath)) { +#ifdef QT_NO_SSL + qWarning() << "Qt has not been compiled with SSL support."; + return nullptr; +#else + QList<QString> fileNames; + if (parser.isSet(optionCaFile)) + fileNames.append(parser.value(optionCaFile)); + + if (parser.isSet(optionCaPath)) { + QFileInfo path(parser.value(optionCaPath)); + if (!path.isDir()) { + qWarning() << "Specified capath is not a directory"; + return nullptr; + } + auto entries = QDir(parser.value(optionCaPath)).entryInfoList(QDir::Files | QDir::NoDotAndDotDot); + for (auto entry : entries) + fileNames.append(entry.absoluteFilePath()); + } + if (fileNames.isEmpty()) { + qWarning() << "No certificate file found."; + return nullptr; + } + + QList<QSslCertificate> defaultCerts; + for (auto it : fileNames) { + auto certificates = QSslCertificate::fromPath(it); + if (certificates.isEmpty() && parser.isSet(optionDebug)) + qWarning() << "File " << it << " does not contain any certificates"; + defaultCerts.append(certificates); + } + if (defaultCerts.isEmpty()) { + qWarning() << "No certificate could be loaded."; + return nullptr; + } + + QSslSocket::addDefaultCaCertificates(defaultCerts); + msg->useEncryption = true; +#endif + } + + if (parser.isSet(optionDebug)) + QLoggingCategory::setFilterRules(QLatin1String("qt.mqtt.*=true")); + + msg->qos = static_cast<quint8>(parser.value(optionQos).toInt(&ok)); + if (!ok || msg->qos > 2) { + qWarning() << "Invalid quality of service for message specified:" << msg->qos; + return nullptr; + } + + if (parser.isSet(optionKeepAlive)) { + const quint16 keep = static_cast<quint16>(parser.value(optionKeepAlive).toUInt(&ok)); + if (!ok) { + qWarning() << "Invalid keep alive value specified"; + return nullptr; + } + client->setKeepAlive(keep); + } + + if (publish) { + if (parser.isSet(optionFile) && parser.isSet(optionMessageContent)) { + qWarning() << "You cannot specify a file and a text as message."; + return nullptr; + } + if (parser.isSet(optionFile)) { + QFile file(parser.value(optionFile)); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Could not open specified file for reading."; + return nullptr; + } + msg->content = file.readAll(); + file.close(); + } + if (parser.isSet(optionMessageContent)) + msg->content = parser.value(optionMessageContent).toUtf8(); + } + + if (!parser.isSet(optionMessageTopic)) { + qWarning() << "You must specify a topic to publish a message."; + return nullptr; + } + msg->topic = parser.value(optionMessageTopic); + + if (publish && !QMqttTopicName(msg->topic).isValid()) { + qWarning() << "The specified message topic is invalid."; + return nullptr; + } + + if (!publish && !QMqttTopicFilter(msg->topic).isValid()) { + qWarning() << "The specified subscription topic is invalid."; + return nullptr; + } + + if (publish && parser.isSet(optionRetain)) + msg->retain = true; + + // Output: + qInfo() << "Client configuration:"; + qInfo() << " Host:" << client->hostname() << " Port:" << client->port() + << " Protocol:" << client->protocolVersion(); + qInfo() << " Username:" << client->username() << " Password:" << !client->password().isEmpty(); + qInfo() << " Client ID:" << client->clientId() << "Keep Alive:" << client->keepAlive(); + + return client; +} + diff --git a/examples/mqtt/consolepubsub/consolepubsub.pro b/examples/mqtt/consolepubsub/consolepubsub.pro new file mode 100644 index 0000000..6aee476 --- /dev/null +++ b/examples/mqtt/consolepubsub/consolepubsub.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + qtmqtt_pub.pro \ + qtmqtt_sub.pro + diff --git a/examples/mqtt/consolepubsub/main_pub.cpp b/examples/mqtt/consolepubsub/main_pub.cpp new file mode 100644 index 0000000..b47fb8e --- /dev/null +++ b/examples/mqtt/consolepubsub/main_pub.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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 "configuration.h" + +#include <QCoreApplication> +#include <QMqttClient> +#include <QSslSocket> +#include <QTimer> + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("qtmqtt_pub")); + QCoreApplication::setApplicationVersion(QStringLiteral("1.0")); + + // Create the client + Configuration description; + auto *client = createClientWithConfiguration(&a, &description, true); + + if (!client) + return -1; + + a.connect(client, &QMqttClient::errorChanged, [&client](const QMqttClient::ClientError e) { + if (e == QMqttClient::NoError) + return; + + qWarning() << "Error Occurred:" << e << " Client state:" << client->state(); + client->disconnectFromHost(); + }); + + a.connect(client, &QMqttClient::messageSent, [&client] (quint32 id) { + qInfo() << "Message with ID:" << id << " sent"; + client->disconnectFromHost(); + }); + + a.connect(client, &QMqttClient::stateChanged, [&client] (QMqttClient::ClientState s) { + if (s == QMqttClient::Disconnected) { + client->deleteLater(); + qApp->quit(); + } + }); + + a.connect(client, &QMqttClient::connected, [&client, description]() { + qInfo() << "Message:"; + qInfo() << " Topic:" << description.topic << " QoS:" << description.qos + << " Retain:" << description.retain; + qInfo() << " Content: " << description.content.left(50); + client->publish(description.topic, + description.content, + description.qos, + description.retain); + if (description.qos == 0)// 0 has no acknowledgment + QTimer::singleShot(500, client, &QMqttClient::disconnectFromHost); + }); + +#ifndef QT_NO_SSL + if (description.useEncryption) + client->connectToHostEncrypted(); + else +#endif + client->connectToHost(); + + return a.exec(); +} diff --git a/examples/mqtt/consolepubsub/main_sub.cpp b/examples/mqtt/consolepubsub/main_sub.cpp new file mode 100644 index 0000000..9e94037 --- /dev/null +++ b/examples/mqtt/consolepubsub/main_sub.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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 "configuration.h" + +#include <QCoreApplication> +#include <QMqttClient> +#include <QSslSocket> +#include <QTimer> + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("qtmqtt_sub")); + QCoreApplication::setApplicationVersion(QStringLiteral("1.0")); + + // Create the client + Configuration description; + auto *client = createClientWithConfiguration(&a, &description, false); + + if (!client) + return -1; + + a.connect(client, &QMqttClient::errorChanged, [&client](const QMqttClient::ClientError e) { + if (e == QMqttClient::NoError) + return; + + qWarning() << "Error Occurred:" << e << " Client state:" << client->state(); + client->disconnectFromHost(); + }); + + a.connect(client, &QMqttClient::stateChanged, [&client] (QMqttClient::ClientState s) { + if (s == QMqttClient::Disconnected) { + client->deleteLater(); + qApp->quit(); + } + }); + + a.connect(client, &QMqttClient::connected, [&client, description]() { + auto sub = client->subscribe(description.topic, description.qos); + client->connect(sub, &QMqttSubscription::stateChanged, [&client](QMqttSubscription::SubscriptionState s) { + qInfo() << "Subscription state:" << s; + if (s == QMqttSubscription::Unsubscribed) + client->disconnectFromHost(); + }); + + client->connect(sub, &QMqttSubscription::messageReceived, [](const QMqttMessage &msg) { + qInfo() << "ID:" << msg.id() + << "Topic:" << msg.topic().name() + << "QoS:" << msg.qos() + << "Retain:" << msg.retain() + << "Duplicate:" << msg.duplicate() + << "Payload:" << msg.payload().left(50) << (msg.payload().size() > 50 ? "..." : ""); + }); + }); + +#ifndef QT_NO_SSL + if (description.useEncryption) + client->connectToHostEncrypted(); + else +#endif + client->connectToHost(); + + return a.exec(); +} diff --git a/examples/mqtt/consolepubsub/qtmqtt_pub.pro b/examples/mqtt/consolepubsub/qtmqtt_pub.pro new file mode 100644 index 0000000..bfa84b7 --- /dev/null +++ b/examples/mqtt/consolepubsub/qtmqtt_pub.pro @@ -0,0 +1,25 @@ +QT -= gui +QT += mqtt + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has 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 it uses 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 + +SOURCES += \ + main_pub.cpp + +HEADERS += \ + configuration.h + +target.path = $$[QT_INSTALL_EXAMPLES]/mqtt/consolepubsub +INSTALLS += target diff --git a/examples/mqtt/consolepubsub/qtmqtt_sub.pro b/examples/mqtt/consolepubsub/qtmqtt_sub.pro new file mode 100644 index 0000000..c022650 --- /dev/null +++ b/examples/mqtt/consolepubsub/qtmqtt_sub.pro @@ -0,0 +1,25 @@ +QT -= gui +QT += mqtt + +CONFIG += c++11 console +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has 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 it uses 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 + +SOURCES += \ + main_sub.cpp + +HEADERS += \ + configuration.h + +target.path = $$[QT_INSTALL_EXAMPLES]/mqtt/consolepubsub +INSTALLS += target diff --git a/examples/mqtt/mqtt.pro b/examples/mqtt/mqtt.pro index c734248..db620d0 100644 --- a/examples/mqtt/mqtt.pro +++ b/examples/mqtt/mqtt.pro @@ -1,5 +1,6 @@ TEMPLATE = subdirs SUBDIRS += \ + consolepubsub \ simpleclient \ subscriptions diff --git a/src/mqtt/qmqttclient.cpp b/src/mqtt/qmqttclient.cpp index 0ea0f1e..c8fc73f 100644 --- a/src/mqtt/qmqttclient.cpp +++ b/src/mqtt/qmqttclient.cpp @@ -93,6 +93,9 @@ Q_LOGGING_CATEGORY(lcMqttClient, "qt.mqtt.client") The interval is specified in milliseconds. However, most brokers are not capable of using such a high granularity and will fall back to an interval specified in seconds. + + If the broker does not respond within a grace period the connection will be + closed. */ /*! diff --git a/src/mqtt/qmqttconnection.cpp b/src/mqtt/qmqttconnection.cpp index 61a6ef0..483e6f4 100644 --- a/src/mqtt/qmqttconnection.cpp +++ b/src/mqtt/qmqttconnection.cpp @@ -577,11 +577,19 @@ bool QMqttConnection::sendControlPingRequest() if (m_internalState != QMqttConnection::BrokerConnected) return false; + // 3.1.2.10 If a Client does not receive a PINGRESP packet within a reasonable amount of time + // after it has sent a PINGREQ, it SHOULD close the Network Connection to the Server + // Consider two pending PINGRESP as reasonable. + if (m_pingTimeout > 1) { + closeConnection(QMqttClient::ServerUnavailable); + return false; + } const QMqttControlPacket packet(QMqttControlPacket::PINGREQ); if (!writePacketToTransport(packet)) { qCDebug(lcMqttConnection) << "Failed to write PINGREQ to transport."; return false; } + m_pingTimeout++; return true; } @@ -590,6 +598,7 @@ bool QMqttConnection::sendControlDisconnect() qCDebug(lcMqttConnection) << Q_FUNC_INFO; m_pingTimer.stop(); + m_pingTimeout = 0; m_activeSubscriptions.clear(); @@ -675,6 +684,7 @@ void QMqttConnection::transportConnectionClosed() m_readBuffer.clear(); m_readPosition = 0; m_pingTimer.stop(); + m_pingTimeout = 0; if (m_internalState == BrokerDisconnected) // We manually disconnected m_clientPrivate->setStateAndError(QMqttClient::Disconnected, QMqttClient::NoError); else @@ -733,6 +743,7 @@ void QMqttConnection::closeConnection(QMqttClient::ClientError error) m_readBuffer.clear(); m_readPosition = 0; m_pingTimer.stop(); + m_pingTimeout = 0; m_activeSubscriptions.clear(); m_internalState = BrokerDisconnected; m_transport->disconnect(); @@ -1628,6 +1639,7 @@ void QMqttConnection::finalize_pingresp() closeConnection(QMqttClient::ProtocolViolation); return; } + m_pingTimeout--; emit m_clientPrivate->m_client->pingResponseReceived(); } diff --git a/src/mqtt/qmqttconnection_p.h b/src/mqtt/qmqttconnection_p.h index 84dc875..a14f914 100644 --- a/src/mqtt/qmqttconnection_p.h +++ b/src/mqtt/qmqttconnection_p.h @@ -154,6 +154,7 @@ private: QMap<quint16, QSharedPointer<QMqttControlPacket>> m_pendingReleaseMessages; InternalConnectionState m_internalState{BrokerDisconnected}; QTimer m_pingTimer; + int m_pingTimeout{0}; QVector<QMqttTopicName> m_receiveAliases; QVector<QMqttTopicName> m_publishAliases; diff --git a/tests/libfuzzer/mqtt/data_receive/data_receive.pro b/tests/libfuzzer/mqtt/data_receive/data_receive.pro new file mode 100644 index 0000000..1b9e3bc --- /dev/null +++ b/tests/libfuzzer/mqtt/data_receive/data_receive.pro @@ -0,0 +1,8 @@ +QT -= gui +QT += mqtt testlib +QT += mqtt-private + +SOURCES += \ + main.cpp + +LIBS += -fsanitize=fuzzer diff --git a/tests/libfuzzer/mqtt/data_receive/main.cpp b/tests/libfuzzer/mqtt/data_receive/main.cpp new file mode 100644 index 0000000..307db64 --- /dev/null +++ b/tests/libfuzzer/mqtt/data_receive/main.cpp @@ -0,0 +1,145 @@ +/****************************************************************************** +** +** Copyright (C) 2019 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 <QtMqtt/private/qmqttcontrolpacket_p.h> + +#include <QtMqtt> +#include <QtTest> +#include <QtCore> + +Q_DECLARE_METATYPE(QMqttSubscription::SubscriptionState) + +class FakeServer : public QIODevice +{ + Q_OBJECT +public: + explicit FakeServer(QObject *parent); + + qint64 readData(char *data, qint64 maxlen) override; + qint64 writeData(const char *data, qint64 len) override; + + int m_messageState{0}; + QByteArray m_readBuffer; +}; + +FakeServer::FakeServer(QObject *parent) + : QIODevice(parent) +{ + m_messageState = 0; + open(QIODevice::ReadWrite | QIODevice::Unbuffered); +} + +qint64 FakeServer::readData(char *data, qint64 maxlen) +{ + const qint64 dataToWrite = qMax<qint64>(0, qMin<qint64>(maxlen, m_readBuffer.size())); + memcpy(data, m_readBuffer.constData(), static_cast<size_t>(dataToWrite)); + m_readBuffer = m_readBuffer.mid(static_cast<int>(dataToWrite)); + return dataToWrite; +} + +void bufferAppend(QByteArray &buffer, quint16 value) +{ + const quint16 msb = qToBigEndian<quint16>(value); + const char * msb_c = reinterpret_cast<const char*>(&msb); + buffer.append(msb_c, 2); +} + +qint64 FakeServer::writeData(const char *data, qint64 len) +{ + if (m_messageState == 0) { // Received CONNECT + QByteArray response; + response += 0x20; + response += quint8(2); // Payload size + response += char(0); // ackFlags + response += char(0); // result + + m_readBuffer = response; + emit readyRead(); + m_messageState = 1; + } else if (m_messageState == 1) { // Received SUBSCRIBE + // Byte 0 == 0x82 (0x80 SUBSCRIBE 0x02 standard) + quint8 msg = reinterpret_cast<const quint8 *>(data)[0]; + if (msg != quint8(0x82)) + qFatal("Expected subscribe message"); + // Byte 1+2 == ID + const quint16 id_big = *reinterpret_cast<const quint16 *>(&data[2]); + const quint16 id = qFromBigEndian<quint16>(id_big); + + QMqttControlPacket packet(QMqttControlPacket::SUBACK); + packet.append(id); + const quint8 qosLevel = 1; + packet.append(static_cast<char>(qosLevel)); + m_readBuffer = packet.serialize(); + // We need to be async to have QMqttConnection prepare its internals + QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); + + m_messageState = 2; + } + // Ignore any follow up data + + return len; +} + +extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) +{ + static int argc = 1; + static char *argv_1[] = {qstrdup("fuzzprepare")}; + static char **argv = argv_1; + static QCoreApplication a(argc, argv); + + FakeServer serv(&a); + QMqttClient client; + client.setHostname(QLatin1String("localhost")); + client.setPort(1883); + + client.setTransport(&serv, QMqttClient::IODevice); + client.connectToHost(); + + QSignalSpy spy(&client, &QMqttClient::connected); + spy.wait(5); + + if (client.state() != QMqttClient::Connected) { + qFatal("Not able to run test propertly"); + return -1; + } + + auto sub = client.subscribe(QLatin1String("a"), 1); + qRegisterMetaType<QMqttSubscription::SubscriptionState>("SubscriptionState"); + QSignalSpy subSpy(sub, &QMqttSubscription::stateChanged); + spy.wait(5); + if (sub->state() != QMqttSubscription::Subscribed) + spy.wait(10); + if (sub->state() != QMqttSubscription::Subscribed) + qFatal("Could not subscribe"); + + serv.m_readBuffer = QByteArray(Data, static_cast<int>(Size)); + QMetaObject::invokeMethod(&serv, "readyRead"); + QCoreApplication::processEvents(); + return 0; +} + +#include "main.moc" |