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:
parent
29420b9f6c
commit
ba94777b84
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -74,6 +74,7 @@ public:
|
||||
QString m_password;
|
||||
bool m_cleanSession{true};
|
||||
QMqttConnectionProperties m_connectionProperties;
|
||||
QMqttLastWillProperties m_lastWillProperties;
|
||||
QMqttServerConnectionProperties m_serverConnectionProperties;
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -5,6 +5,7 @@ win32|if(linux:!cross_compile): SUBDIRS += cmake \
|
||||
qmqttconnectionproperties \
|
||||
qmqttcontrolpacket \
|
||||
qmqttclient \
|
||||
qmqttlastwillproperties \
|
||||
qmqttpublishproperties \
|
||||
qmqttsubscription \
|
||||
qmqttsubscriptionproperties \
|
||||
|
||||
@ -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/\\\"
|
||||
@ -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"
|
||||
Loading…
Reference in New Issue
Block a user