Commit 4a16d48b7ec1a14d06eb1afe31a6d97192d5df27
1 parent
abe7fa16
Fix QoS reduction of queued messages
Also add a test to test all scenarios. This also fixes session clean_start, which was never done since the MQTT5 refactor to clean_start (vs clean session).
Showing
5 changed files
with
83 additions
and
7 deletions
FlashMQTests/tst_maintests.cpp
| ... | ... | @@ -133,6 +133,8 @@ private slots: |
| 133 | 133 | |
| 134 | 134 | void testReceivingRetainedMessageWithQoS(); |
| 135 | 135 | |
| 136 | + void testQosDowngradeOnOfflineClients(); | |
| 137 | + | |
| 136 | 138 | }; |
| 137 | 139 | |
| 138 | 140 | MainTests::MainTests() |
| ... | ... | @@ -1750,6 +1752,66 @@ void MainTests::testReceivingRetainedMessageWithQoS() |
| 1750 | 1752 | MYCASTCOMPARE(9, testCount); |
| 1751 | 1753 | } |
| 1752 | 1754 | |
| 1755 | +void MainTests::testQosDowngradeOnOfflineClients() | |
| 1756 | +{ | |
| 1757 | + int testCount = 0; | |
| 1758 | + | |
| 1759 | + std::vector<std::string> subscribePaths {"topic1/FOOBAR", "+/+", "#"}; | |
| 1760 | + | |
| 1761 | + for (char sendQos = 1; sendQos < 3; sendQos++) | |
| 1762 | + { | |
| 1763 | + for (char subscribeQos = 1; subscribeQos < 3; subscribeQos++) | |
| 1764 | + { | |
| 1765 | + for (const std::string &subscribePath : subscribePaths) | |
| 1766 | + { | |
| 1767 | + testCount++; | |
| 1768 | + | |
| 1769 | + // First start with clean_start to reset the session. | |
| 1770 | + std::unique_ptr<FlashMQTestClient> receiver = std::make_unique<FlashMQTestClient>(); | |
| 1771 | + receiver->start(); | |
| 1772 | + receiver->connectClient(ProtocolVersion::Mqtt5, true, 600, [](Connect &connect) { | |
| 1773 | + connect.clientid = "TheReceiver"; | |
| 1774 | + }); | |
| 1775 | + receiver->subscribe(subscribePath, subscribeQos); | |
| 1776 | + receiver->disconnect(ReasonCodes::Success); | |
| 1777 | + receiver.reset(); | |
| 1778 | + | |
| 1779 | + const std::string payload = "We are testing"; | |
| 1780 | + | |
| 1781 | + FlashMQTestClient sender; | |
| 1782 | + sender.start(); | |
| 1783 | + sender.connectClient(ProtocolVersion::Mqtt311); | |
| 1784 | + | |
| 1785 | + Publish p1("topic1/FOOBAR", payload, sendQos); | |
| 1786 | + | |
| 1787 | + for (int i = 0; i < 10; i++) | |
| 1788 | + { | |
| 1789 | + sender.publish(p1); | |
| 1790 | + } | |
| 1791 | + | |
| 1792 | + // Now we connect again, and we should now pick up the existing session. | |
| 1793 | + receiver = std::make_unique<FlashMQTestClient>(); | |
| 1794 | + receiver->start(); | |
| 1795 | + receiver->connectClient(ProtocolVersion::Mqtt5, false, 600, [](Connect &connect) { | |
| 1796 | + connect.clientid = "TheReceiver"; | |
| 1797 | + }); | |
| 1798 | + | |
| 1799 | + receiver->waitForMessageCount(10); | |
| 1800 | + | |
| 1801 | + const char expQos = std::min<char>(sendQos, subscribeQos); | |
| 1802 | + | |
| 1803 | + MYCASTCOMPARE(receiver->receivedPublishes.size(), 10); | |
| 1804 | + | |
| 1805 | + QVERIFY(std::all_of(receiver->receivedPublishes.begin(), receiver->receivedPublishes.end(), [&](MqttPacket &pack) { return pack.getQos() == expQos;})); | |
| 1806 | + QVERIFY(std::all_of(receiver->receivedPublishes.begin(), receiver->receivedPublishes.end(), [&](MqttPacket &pack) { return pack.getTopic() == "topic1/FOOBAR";})); | |
| 1807 | + QVERIFY(std::all_of(receiver->receivedPublishes.begin(), receiver->receivedPublishes.end(), [&](MqttPacket &pack) { return pack.getPayloadCopy() == payload;})); | |
| 1808 | + } | |
| 1809 | + } | |
| 1810 | + } | |
| 1811 | + | |
| 1812 | + MYCASTCOMPARE(12, testCount); | |
| 1813 | +} | |
| 1814 | + | |
| 1753 | 1815 | int main(int argc, char *argv[]) |
| 1754 | 1816 | { |
| 1755 | 1817 | QCoreApplication app(argc, argv); | ... | ... |
publishcopyfactory.cpp
| ... | ... | @@ -95,22 +95,36 @@ bool PublishCopyFactory::getRetain() const |
| 95 | 95 | return publish->retain; |
| 96 | 96 | } |
| 97 | 97 | |
| 98 | -Publish PublishCopyFactory::getNewPublish() const | |
| 98 | +/** | |
| 99 | + * @brief PublishCopyFactory::getNewPublish gets a new publish object from an existing packet or publish. | |
| 100 | + * @param new_max_qos | |
| 101 | + * @return | |
| 102 | + * | |
| 103 | + * It being a public function, the idea is that it's only needed for creating publish objects for storing QoS messages for off-line | |
| 104 | + * clients. For on-line clients, you're always making a packet (with getOptimumPacket()). | |
| 105 | + */ | |
| 106 | +Publish PublishCopyFactory::getNewPublish(char new_max_qos) const | |
| 99 | 107 | { |
| 108 | + // (At time of writing) we only need to construct new publishes for QoS (because we're storing QoS publishes for offline clients). If | |
| 109 | + // you're doing it elsewhere, it's a bug. | |
| 110 | + assert(orgQos > 0); | |
| 111 | + assert(new_max_qos > 0); | |
| 112 | + | |
| 113 | + const char actualQos = getEffectiveQos(new_max_qos); | |
| 114 | + | |
| 100 | 115 | if (packet) |
| 101 | 116 | { |
| 102 | 117 | assert(packet->getQos() > 0); |
| 103 | - assert(orgQos > 0); // We only need to construct new publishes for QoS. If you're doing it elsewhere, it's a bug. | |
| 104 | 118 | |
| 105 | 119 | Publish p(packet->getPublishData()); |
| 106 | - p.qos = orgQos; | |
| 120 | + p.qos = actualQos; | |
| 107 | 121 | return p; |
| 108 | 122 | } |
| 109 | 123 | |
| 110 | 124 | assert(publish->qos > 0); // Same check as above, but then for Publish objects. |
| 111 | 125 | |
| 112 | 126 | Publish p(*publish); |
| 113 | - p.qos = orgQos; | |
| 127 | + p.qos = actualQos; | |
| 114 | 128 | return p; |
| 115 | 129 | } |
| 116 | 130 | ... | ... |
publishcopyfactory.h
| ... | ... | @@ -34,7 +34,7 @@ public: |
| 34 | 34 | const std::string &getTopic() const; |
| 35 | 35 | const std::vector<std::string> &getSubtopics(); |
| 36 | 36 | bool getRetain() const; |
| 37 | - Publish getNewPublish() const; | |
| 37 | + Publish getNewPublish(char new_max_qos) const; | |
| 38 | 38 | std::shared_ptr<Client> getSender(); |
| 39 | 39 | const std::vector<std::pair<std::string, std::string>> *getUserProperties() const; |
| 40 | 40 | ... | ... |
qospacketqueue.cpp
| ... | ... | @@ -76,7 +76,7 @@ void QoSPublishQueue::queuePublish(PublishCopyFactory &copyFactory, uint16_t id, |
| 76 | 76 | assert(new_max_qos > 0); |
| 77 | 77 | assert(id > 0); |
| 78 | 78 | |
| 79 | - Publish pub = copyFactory.getNewPublish(); | |
| 79 | + Publish pub = copyFactory.getNewPublish(new_max_qos); | |
| 80 | 80 | queue.emplace_back(std::move(pub), id); |
| 81 | 81 | qosQueueBytes += queue.back().getApproximateMemoryFootprint(); |
| 82 | 82 | } | ... | ... |
subscriptionstore.cpp
| ... | ... | @@ -253,7 +253,7 @@ void SubscriptionStore::registerClientAndKickExistingOne(std::shared_ptr<Client> |
| 253 | 253 | } |
| 254 | 254 | } |
| 255 | 255 | |
| 256 | - if (!session || session->getDestroyOnDisconnect()) | |
| 256 | + if (!session || session->getDestroyOnDisconnect() || clean_start) | |
| 257 | 257 | { |
| 258 | 258 | session = std::make_shared<Session>(); |
| 259 | 259 | ... | ... |