1
0
mirror of https://github.com/qt/qtmqtt.git synced 2026-10-25 16:16:26 +00:00

MQTT5: Add support for last will properties

Task-number: QTPM-1451
Change-Id: I26a12b944eddb029f7fffe8c1af1610ff79cc494
Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
This commit is contained in:
Maurice Kalinowski 2018-07-23 09:50:29 +02:00
parent 29420b9f6c
commit ba94777b84
11 changed files with 438 additions and 4 deletions

View File

@ -527,6 +527,18 @@ QMqttConnectionProperties QMqttClient::connectionProperties() const
return d->m_connectionProperties;
}
void QMqttClient::setLastWillProperties(const QMqttLastWillProperties &prop)
{
Q_D(QMqttClient);
d->m_lastWillProperties = prop;
}
QMqttLastWillProperties QMqttClient::lastWillProperties() const
{
Q_D(const QMqttClient);
return d->m_lastWillProperties;
}
QMqttServerConnectionProperties QMqttClient::serverConnectionProperties() const
{
Q_D(const QMqttClient);

View File

@ -143,6 +143,9 @@ public:
void setConnectionProperties(const QMqttConnectionProperties &prop);
QMqttConnectionProperties connectionProperties() const;
void setLastWillProperties(const QMqttLastWillProperties &prop);
QMqttLastWillProperties lastWillProperties() const;
QMqttServerConnectionProperties serverConnectionProperties() const;
Q_SIGNALS:
void connected();

View File

@ -74,6 +74,7 @@ public:
QString m_password;
bool m_cleanSession{true};
QMqttConnectionProperties m_connectionProperties;
QMqttLastWillProperties m_lastWillProperties;
QMqttServerConnectionProperties m_serverConnectionProperties;
};

View File

@ -269,10 +269,8 @@ bool QMqttConnection::sendControlConnect()
}
if (!m_clientPrivate->m_willMessage.isEmpty()) {
if (m_clientPrivate->m_protocolVersion == QMqttClient::MQTT_5_0) {
// ### TODO: Add Will properties
packet.append(char(0));
}
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);
@ -960,6 +958,73 @@ QByteArray QMqttConnection::writeConnectProperties()
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 (auto it : userProperties) {
properties.append(char(0x26));
properties.append(it.name().toUtf8());
properties.append(it.value().toUtf8());
}
}
return properties.serializePayload();
}
QByteArray QMqttConnection::writePublishProperties(const QMqttPublishProperties &properties)
{
QMqttControlPacket packet;

View File

@ -122,6 +122,7 @@ private:
void readPublishProperties(QMqttPublishProperties &properties);
void readSubscriptionProperties(QMqttSubscription *sub);
QByteArray writeConnectProperties();
QByteArray writeLastWillProperties() const;
QByteArray writePublishProperties(const QMqttPublishProperties &properties);
QByteArray writeSubscriptionProperties(const QMqttSubscriptionProperties &properties);
void closeConnection(QMqttClient::ClientError error);

View File

@ -35,6 +35,95 @@
QT_BEGIN_NAMESPACE
QMqttLastWillProperties::QMqttLastWillProperties() : data(new QMqttLastWillPropertiesData)
{
}
QMqttLastWillProperties::QMqttLastWillProperties(const QMqttLastWillProperties &rhs) : data(rhs.data)
{
}
QMqttLastWillProperties &QMqttLastWillProperties::operator=(const QMqttLastWillProperties &rhs)
{
if (this != &rhs)
data.operator=(rhs.data);
return *this;
}
QMqttLastWillProperties::~QMqttLastWillProperties()
{
}
quint32 QMqttLastWillProperties::willDelayInterval() const
{
return data->willDelayInterval;
}
QMqtt::PayloadFormatIndicator QMqttLastWillProperties::payloadFormatIndicator() const
{
return data->formatIndicator;
}
quint32 QMqttLastWillProperties::messageExpiryInterval() const
{
return data->messageExpiryInterval;
}
QString QMqttLastWillProperties::contentType() const
{
return data->contentType;
}
QString QMqttLastWillProperties::responseTopic() const
{
return data->responseTopic;
}
QByteArray QMqttLastWillProperties::correlationData() const
{
return data->correlationData;
}
QMqttUserProperties QMqttLastWillProperties::userProperties() const
{
return data->userProperties;
}
void QMqttLastWillProperties::setWillDelayInterval(quint32 delay)
{
data->willDelayInterval = delay;
}
void QMqttLastWillProperties::setPayloadFormatIndicator(QMqtt::PayloadFormatIndicator p)
{
data->formatIndicator = p;
}
void QMqttLastWillProperties::setMessageExpiryInterval(quint32 expiry)
{
data->messageExpiryInterval = expiry;
}
void QMqttLastWillProperties::setContentType(const QString &content)
{
data->contentType = content;
}
void QMqttLastWillProperties::setResponseTopic(const QString &response)
{
data->responseTopic = response;
}
void QMqttLastWillProperties::setCorrelationData(const QByteArray &correlation)
{
data->correlationData = correlation;
}
void QMqttLastWillProperties::setUserProperties(const QMqttUserProperties &properties)
{
data->userProperties = properties;
}
QMqttConnectionProperties::QMqttConnectionProperties() : data(new QMqttConnectionPropertiesData)
{

View File

@ -39,8 +39,38 @@
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

View File

@ -45,6 +45,18 @@
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:

View File

@ -5,6 +5,7 @@ win32|if(linux:!cross_compile): SUBDIRS += cmake \
qmqttconnectionproperties \
qmqttcontrolpacket \
qmqttclient \
qmqttlastwillproperties \
qmqttpublishproperties \
qmqttsubscription \
qmqttsubscriptionproperties \

View File

@ -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/\\\"

View File

@ -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"