You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@artemis.apache.org by jb...@apache.org on 2026/10/22 16:09:33 UTC
(artemis) branch main updated: ARTEMIS-6314 MQTT 5 retained PUBLISH not delivered to an established wildcard or shared subscription
This is an automated email from the ASF dual-hosted git repository.
jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git
The following commit(s) were added to refs/heads/main by this push:
new 3eeb4fe721 ARTEMIS-6314 MQTT 5 retained PUBLISH not delivered to an established wildcard or shared subscription
3eeb4fe721 is described below
commit 3eeb4fe721fe8f754eec08e795bfcc769d84e563
Author: grepjava <gr...@gmail.com>
AuthorDate: Wed Oct 21 21:21:24 2026 +0800
ARTEMIS-6314 MQTT 5 retained PUBLISH not delivered to an established wildcard or shared subscription
---
.../core/protocol/mqtt/MQTTPublishManager.java | 12 ++---
.../core/protocol/mqtt/MQTTSessionState.java | 14 +++++
.../mqtt5/spec/controlpackets/PublishTests.java | 61 ++++++++++++++++++++++
3 files changed, 81 insertions(+), 6 deletions(-)
diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTPublishManager.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTPublishManager.java
index 3865dc1723..4e2330f64e 100644
--- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTPublishManager.java
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTPublishManager.java
@@ -31,7 +31,6 @@ import io.netty.handler.codec.mqtt.MqttProperties;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import io.netty.handler.codec.mqtt.MqttPublishVariableHeader;
import io.netty.handler.codec.mqtt.MqttQoS;
-import io.netty.handler.codec.mqtt.MqttTopicSubscription;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.ICoreMessage;
@@ -109,7 +108,7 @@ public class MQTTPublishManager {
if (qos == 0) {
// [MQTT-2.2.1-2] Hard-code the packet ID to 0 as QoS0 PUBLISH packets don't have a packet ID
// [MQTT-3.3.1-2] The DUP flag MUST be set to 0 for all QoS 0 messages.
- if (publishToClient(0, message, false, qos)) {
+ if (publishToClient(0, message, consumer.getID(), false, qos)) {
consumer.individualAcknowledge(null, message.getMessageID());
}
} else if (qos == 1 || qos == 2) {
@@ -131,7 +130,7 @@ public class MQTTPublishManager {
state.putCoreDeliveryInfo(packetIdToUse, CoreDeliveryInfo.of(consumer.getID(), correlationKey));
state.incrementSendQuota();
}
- publishToClient(packetIdToUse, message, redelivery, qos);
+ publishToClient(packetIdToUse, message, consumer.getID(), redelivery, qos);
} else {
// Client must have disconnected and it's Subscription QoS cleared
consumer.individualCancel(message.getMessageID(), false);
@@ -417,7 +416,7 @@ public class MQTTPublishManager {
}
}
- private boolean publishToClient(int packetId, ICoreMessage coreMessage, boolean redelivery, int qos) throws Exception {
+ private boolean publishToClient(int packetId, ICoreMessage coreMessage, long consumerId, boolean redelivery, int qos) throws Exception {
String topic = MQTTUtil.getMqttTopicFromCoreAddress(Objects.requireNonNullElse(coreMessage.getAddress(), ""), session.getWildcardConfiguration());
ByteBuf payload;
@@ -442,8 +441,9 @@ public class MQTTPublishManager {
if (session.getVersion() == MQTTVersion.MQTT_5) {
mqttProperties = getPublishProperties(coreMessage);
if (!isRetain && coreMessage.getBooleanProperty(MQTT_MESSAGE_RETAIN_KEY)) {
- MqttTopicSubscription sub = session.getState().getSubscriptionItem(topic).getSubscription();
- if (sub != null && sub.option().isRetainAsPublished()) {
+ // the subscription this consumer serves, whose topic filter need not be the message's topic
+ SubscriptionItem item = session.getState().getSubscriptionItemForConsumer(consumerId);
+ if (item != null && item.getSubscription().option().isRetainAsPublished()) {
isRetain = true;
}
}
diff --git a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSessionState.java b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSessionState.java
index 7b74378bf4..4edc863bd8 100644
--- a/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSessionState.java
+++ b/artemis-protocols/artemis-mqtt-protocol/src/main/java/org/apache/activemq/artemis/core/protocol/mqtt/MQTTSessionState.java
@@ -40,6 +40,7 @@ import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.message.impl.CoreMessage;
import org.apache.activemq.artemis.core.postoffice.Address;
import org.apache.activemq.artemis.core.postoffice.impl.AddressImpl;
+import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -224,6 +225,19 @@ public class MQTTSessionState {
return subscriptionItems.get(topicFilter);
}
+ /**
+ * {@return the subscription whose consumer has the given ID, or {@code null} if none does}
+ */
+ public SubscriptionItem getSubscriptionItemForConsumer(long consumerId) {
+ for (SubscriptionItem item : subscriptionItems.values()) {
+ ServerConsumer consumer = item.getConsumer();
+ if (consumer != null && consumer.getID() == consumerId) {
+ return item;
+ }
+ }
+ return null;
+ }
+
public List<Integer> getMatchingSubscriptionIdentifiers(String address) {
String topic = MQTTUtil.getMqttTopicFromCoreAddress(address, session.getServer().getConfiguration().getWildcardConfiguration());
Address topicToMatch = new AddressImpl(SimpleString.of(topic), MQTTUtil.MQTT_WILDCARD);
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt5/spec/controlpackets/PublishTests.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt5/spec/controlpackets/PublishTests.java
index 0ae5c34004..a81a6d22c1 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt5/spec/controlpackets/PublishTests.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt5/spec/controlpackets/PublishTests.java
@@ -685,6 +685,67 @@ public class PublishTests extends MQTT5TestSupport {
consumer.close();
}
+ /**
+ * [MQTT-3.3.1-13] as above, for a subscription whose topic filter is not the topic name of the message.
+ */
+ @Test
+ @Timeout(DEFAULT_TIMEOUT_SEC)
+ public void testRetainAsPublishedOneOnEstablishedWildcardSubscription() throws Exception {
+ testRetainAsPublishedOnEstablishedSubscription(this.getTopicName() + "/#", true);
+ }
+
+ /**
+ * [MQTT-3.3.1-12] as above, for a subscription whose topic filter is not the topic name of the message.
+ */
+ @Test
+ @Timeout(DEFAULT_TIMEOUT_SEC)
+ public void testRetainAsPublishedZeroOnEstablishedWildcardSubscription() throws Exception {
+ testRetainAsPublishedOnEstablishedSubscription(this.getTopicName() + "/#", false);
+ }
+
+ /**
+ * [MQTT-3.3.1-13] as above, for a shared subscription.
+ */
+ @Test
+ @Timeout(DEFAULT_TIMEOUT_SEC)
+ public void testRetainAsPublishedOneOnEstablishedSharedSubscription() throws Exception {
+ testRetainAsPublishedOnEstablishedSubscription("$share/group/" + this.getTopicName() + "/a", true);
+ }
+
+ private void testRetainAsPublishedOnEstablishedSubscription(String topicFilter, boolean retainAsPublished) throws Exception {
+ final String CONSUMER_ID = RandomUtil.randomUUIDString();
+ final String TOPIC = this.getTopicName() + "/a";
+
+ // create consumer
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicBoolean retained = new AtomicBoolean(!retainAsPublished);
+ MqttClient consumer = createPahoClient(CONSUMER_ID);
+ consumer.setCallback(new DefaultMqttCallback() {
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ assertEqualsByteArrays("retained".getBytes(StandardCharsets.UTF_8), message.getPayload());
+ retained.set(message.isRetained());
+ latch.countDown();
+ }
+ });
+ consumer.connect();
+ MqttSubscription subscription = new MqttSubscription(topicFilter, 1);
+ subscription.setRetainAsPublished(retainAsPublished);
+ consumer.subscribe(new MqttSubscription[]{subscription});
+
+ // send retained message
+ MqttClient producer = createPahoClient("producer");
+ producer.connect();
+ producer.publish(TOPIC, "retained".getBytes(), 1, true);
+ producer.disconnect();
+ producer.close();
+
+ assertTrue(latch.await(2, TimeUnit.SECONDS));
+ assertEquals(retainAsPublished, retained.get());
+ consumer.disconnect();
+ consumer.close();
+ }
+
/**
* [MQTT-3.3.2-3] The Topic Name in a PUBLISH packet sent by a Server to a subscribing Client MUST match the
* Subscription’s Topic Filter.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@artemis.apache.org
For additional commands, e-mail: commits-help@artemis.apache.org