From b18f0f9c9119c21c5519c6622e7c14ddacc23d86 Mon Sep 17 00:00:00 2001 From: javasabr Date: Fri, 7 Nov 2025 20:38:06 +0100 Subject: [PATCH 1/6] add supporting replacing subscriptions --- gradle/libs.versions.toml | 2 +- .../model/topic/tree/ConcurrentTopicTree.java | 6 +- .../mqtt/model/topic/tree/TopicNode.java | 11 +- .../mqtt/model/topic/tree/TopicTreeBase.java | 22 ++- .../model/topic/tree/TopicTreeTest.groovy | 80 +++++++++ .../javasabr/mqtt/network/MqttSession.java | 2 + .../impl/InMemorySubscriptionService.java | 5 +- .../session/impl/InMemoryMqttSession.java | 10 ++ .../InMemorySubscriptionServiceTest.groovy | 161 +++++++++++++++++- .../IntegrationServiceSpecification.groovy | 7 +- 10 files changed, 285 insertions(+), 21 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95c406d..922cbe7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # https://gitlab.com/JavaSaBr/maven-repo/-/packages -rlib = "10.0.alpha6" +rlib = "10.0.alpha7" # https://mvnrepository.com/artifact/org.projectlombok/lombok lombok = "1.18.38" # https://mvnrepository.com/artifact/org.jspecify/jspecify diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java index 9628485..b095419 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java @@ -10,6 +10,7 @@ import javasabr.rlib.common.ThreadSafe; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class ConcurrentTopicTree implements ThreadSafe { @@ -20,8 +21,9 @@ public ConcurrentTopicTree() { this.rootNode = new TopicNode(); } - public void subscribe(SubscriptionOwner owner, Subscription subscription) { - rootNode.subscribe(0, owner, subscription, subscription.topicFilter()); + @Nullable + public SingleSubscriber subscribe(SubscriptionOwner owner, Subscription subscription) { + return rootNode.subscribe(0, owner, subscription, subscription.topicFilter()); } public boolean unsubscribe(SubscriptionOwner owner, TopicFilter topicFilter) { diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java index 57ba119..03a624b 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java @@ -35,13 +35,16 @@ class TopicNode extends TopicTreeBase { @Nullable volatile LockableArray subscribers; - public void subscribe(int level, SubscriptionOwner owner, Subscription subscription, TopicFilter topicFilter) { + /** + * @return the previous subscription from the same owner + */ + @Nullable + public SingleSubscriber subscribe(int level, SubscriptionOwner owner, Subscription subscription, TopicFilter topicFilter) { if (level == topicFilter.levelsCount()) { - addSubscriber(getOrCreateSubscribers(), owner, subscription, topicFilter); - return; + return addSubscriber(getOrCreateSubscribers(), owner, subscription, topicFilter); } TopicNode childNode = getOrCreateChildNode(topicFilter.segment(level)); - childNode.subscribe(level + 1, owner, subscription, topicFilter); + return childNode.subscribe(level + 1, owner, subscription, topicFilter); } public boolean unsubscribe(int level, SubscriptionOwner owner, TopicFilter topicFilter) { diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java index 6caba8a..7f0dbb2 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java @@ -20,7 +20,11 @@ @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) abstract class TopicTreeBase { - protected static void addSubscriber( + /** + * @return previous subscriber with the same owner + */ + @Nullable + protected static SingleSubscriber addSubscriber( LockableArray subscribers, SubscriptionOwner owner, Subscription subscription, @@ -29,14 +33,30 @@ protected static void addSubscriber( try { if (topicFilter instanceof SharedTopicFilter stf) { addSharedSubscriber(subscribers, owner, subscription, stf); + return null; } else { + SingleSubscriber previous = removePreviousIfExist(subscribers, owner); subscribers.add(new SingleSubscriber(owner, subscription)); + return previous; } } finally { subscribers.writeUnlock(stamp); } } + @Nullable + private static SingleSubscriber removePreviousIfExist( + LockableArray subscribers, + SubscriptionOwner owner) { + int index = subscribers.indexOf(Subscriber::resolveOwner, owner); + if (index < 0) { + return null; + } + return subscribers + .remove(index) + .resolveSingle(); + } + private static void addSharedSubscriber( LockableArray subscribers, SubscriptionOwner owner, diff --git a/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy index 0176e23..a2827ee 100644 --- a/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy +++ b/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy @@ -539,6 +539,86 @@ class TopicTreeTest extends UnitSpecification { !id3WasUnsubscribed } + def "should replace the same subscriptions"() { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + def owner1 = makeOwner("id1") + def originalSub = makeSubscription('topic/name1') + def replacementSub = makeSubscription('topic/name1') + topicTree.subscribe(makeOwner("id2"), makeSubscription('topic/name1')) + topicTree.subscribe(makeOwner("id3"), makeSubscription('topic/name1')) + when: + def previous = topicTree.subscribe(owner1, originalSub) + def matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 3 + previous == null; + when: + previous = topicTree.subscribe(owner1, replacementSub) + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 3 + matched.first().subscription() == replacementSub + previous != null + previous.subscription() == originalSub + } + + def "should extend shared subscription group on multiply subscribing by the same topic"() { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + def owner1 = makeOwner("id1") + def owner2 = makeOwner("id2") + topicTree.subscribe(owner1, makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(owner2, makeSharedSubscription('$share/group1/topic/name1')) + when: + def matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 1 + matched.first().owner() == owner2 + when: + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 1 + matched.first().owner() == owner1 + when: + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 1 + matched.first().owner() == owner2 + when: + topicTree.subscribe(owner1, makeSharedSubscription('$share/group1/topic/name1')) + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 1 + matched.first().owner() == owner2 + when: + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 1 + matched.first().owner() == owner1 + when: + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .toSet() + then: + matched.size() == 1 + matched.first().owner() == owner1 + } + static def makeOwner(String id) { return new TestSubscriptionOwner(id) } diff --git a/network/src/main/java/javasabr/mqtt/network/MqttSession.java b/network/src/main/java/javasabr/mqtt/network/MqttSession.java index 0cfb6fa..e0b392b 100644 --- a/network/src/main/java/javasabr/mqtt/network/MqttSession.java +++ b/network/src/main/java/javasabr/mqtt/network/MqttSession.java @@ -60,6 +60,8 @@ default void resend(MqttClient client, Publish publish) {} void removeSubscription(TopicFilter subscribe); + void removeSubscription(Subscription subscription); + Array storedSubscriptions(); Array findStoredSubscriptionWithId(int subscriptionId); diff --git a/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java b/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java index c9420bb..a99df0e 100644 --- a/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java +++ b/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java @@ -89,8 +89,11 @@ private SubscribeAckReasonCode addSubscription(MqttClient client, MqttSession se } else if (!serverConfig.wildcardSubscriptionAvailable() && topicFilter.wildcard()) { return SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; } + SingleSubscriber previous = topicTree.subscribe(client, subscription); + if (previous != null) { + session.removeSubscription(previous.subscription()); + } session.storeSubscription(subscription); - topicTree.subscribe(client, subscription); return subscription.qos().subscribeAckReasonCode(); } diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java index 2149e42..7b1e58e 100644 --- a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java @@ -198,6 +198,16 @@ public void removeSubscription(TopicFilter topicFilter) { } } + @Override + public void removeSubscription(Subscription subscription) { + long stamp = subscriptions.writeLock(); + try { + subscriptions.remove(subscription); + } finally { + subscriptions.writeUnlock(stamp); + } + } + @Override public Array storedSubscriptions() { if (subscriptions.isEmpty()) { diff --git a/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy b/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy index cc2eaf4..bfdbcea 100644 --- a/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy +++ b/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy @@ -4,6 +4,7 @@ import javasabr.mqtt.model.MqttVersion import javasabr.mqtt.model.QoS import javasabr.mqtt.model.SubscribeRetainHandling import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode import javasabr.mqtt.model.subscribtion.Subscription import javasabr.mqtt.service.impl.InMemorySubscriptionService import javasabr.rlib.collections.array.Array @@ -17,7 +18,7 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { def serverConfig = defaultExternalServerConnectionConfig def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) def mqttClient = mqttConnection.client() - def subscriptions = Array.typed(Subscription, + def subscriptions = Array.of( new Subscription( defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), 30, @@ -64,7 +65,7 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { .withWildcardSubscriptionAvailable(false) def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) def mqttClient = mqttConnection.client() - def subscriptions = Array.typed(Subscription, + def subscriptions = Array.of( new Subscription( defaultTopicService.createTopicFilter(mqttClient, "topic/filter/+"), 5, @@ -104,8 +105,7 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { def result = subscriptionService.subscribe(mqttClient, subscriptions) then: result.size() == 5 - result == Array.typed( - SubscribeAckReasonCode.class, + result == Array.of( SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, @@ -148,8 +148,7 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { SubscribeRetainHandling.SEND, true, true) - - def subscriptions = Array.typed(Subscription, sub1, sub2, sub3, sub4) + def subscriptions = Array.of(sub1, sub2, sub3, sub4) when: def result = subscriptionService.subscribe(mqttClient, subscriptions) then: @@ -169,4 +168,154 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { subsWithId15 == Array.of(sub1, sub2) subsWithId30 == Array.of(sub3, sub4) } + + def "should unsubscribe with expected results"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def mqttClient = mqttConnection.client() + def subscriptions = Array.of( + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + 30, + QoS.AT_MOST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/2"), + 30, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true)) + subscriptionService.subscribe(mqttClient, subscriptions) + def topicsToUnsubscribe = Array.of( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/notexist"), + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/invalid##")) + when: + def result = subscriptionService.unsubscribe(mqttClient, topicsToUnsubscribe) + then: + result.size() == 4 + result == Array.of( + UnsubscribeAckReasonCode.SUCCESS, + UnsubscribeAckReasonCode.SUCCESS, + UnsubscribeAckReasonCode.NO_SUBSCRIPTION_EXISTED, + UnsubscribeAckReasonCode.TOPIC_FILTER_INVALID) + } + + def "should store and clean subscriptions in MQTT session"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def mqttClient = mqttConnection.client() + def mqttSession = mqttClient.session() + def subscriptions = Array.of( + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + 30, + QoS.AT_MOST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/2"), + 30, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true)) + def topicsToUnsubscribe = Array.of( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3")) + when: + subscriptionService.subscribe(mqttClient, subscriptions) + def storedSubscriptions = mqttSession.storedSubscriptions() + then: + storedSubscriptions.size() == 3 + storedSubscriptions == subscriptions + when: + subscriptionService.unsubscribe(mqttClient, topicsToUnsubscribe) + storedSubscriptions = mqttSession.storedSubscriptions() + then: + storedSubscriptions.size() == 1 + storedSubscriptions.get(0) == subscriptions.get(1) + } + + def "should replace already existed subscriptions"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def mqttClient = mqttConnection.client() + def mqttSession = mqttClient.session() + def subscriptions = Array.of( + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + 30, + QoS.AT_MOST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/2"), + 30, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true)) + def subscriptions2 = Array.typed(Subscription, + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + 55, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + 55, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true)) + def resultSubscriptions = Array.of( + subscriptions2.get(0), + subscriptions.get(1), + subscriptions2.get(1)) + when: + subscriptionService.subscribe(mqttClient, subscriptions) + def storedSubscriptions = mqttSession.storedSubscriptions() + then: + storedSubscriptions.size() == 3 + storedSubscriptions == subscriptions + when: + subscriptionService.subscribe(mqttClient, subscriptions2) + storedSubscriptions = mqttSession.storedSubscriptions() + then: + storedSubscriptions.size() == 3 + storedSubscriptions ==~ resultSubscriptions + } } diff --git a/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy index 1fe8160..1dd533f 100644 --- a/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy +++ b/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -1,12 +1,7 @@ package javasabr.mqtt.service -import javasabr.mqtt.model.MqttClientConnectionConfig -import javasabr.mqtt.model.MqttProperties -import javasabr.mqtt.model.MqttServerConnectionConfig -import javasabr.mqtt.model.MqttVersion -import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.* import javasabr.mqtt.network.MqttConnection -import javasabr.mqtt.network.MqttSession import javasabr.mqtt.network.handler.MqttClientReleaseHandler import javasabr.mqtt.network.impl.ExternalMqttClient import javasabr.mqtt.service.impl.DefaultTopicService From ea377996bf6190bf24c13c301fcc3ccbda53aa5b Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 9 Nov 2025 18:11:40 +0100 Subject: [PATCH 2/6] improve error handling during subscribing --- .../model/MqttClientConnectionConfig.java | 27 ++- .../javasabr/mqtt/model/MqttProperties.java | 1 - .../model/MqttServerConnectionConfig.java | 19 ++ .../main/java/javasabr/mqtt/model/QoS.java | 22 +- .../javasabr/mqtt/network/MqttClient.java | 9 +- .../mqtt/network/impl/AbstractMqttClient.java | 11 +- .../message/in/PublishMqttInMessage.java | 2 +- .../message/in/SubscribeMqttInMessage.java | 6 +- .../message/in/TrackableMqttInMessage.java | 2 +- .../out/ConnectAckMqtt5OutMessage.java | 16 +- .../out/DisconnectMqtt5OutMessage.java | 4 + .../out/SubscribeAckMqtt311OutMessage.java | 4 + .../out/SubscribeAckMqtt5OutMessage.java | 4 + .../message/out/TrackableMqttOutMessage.java | 4 + .../network/session/ActiveSubscriptions.java | 18 ++ .../mqtt/network/session/MessageTacker.java | 7 + .../network/{ => session}/MqttSession.java | 21 +- .../mqtt/network/session/package-info.java | 4 + .../mqtt/network/util/ExtraErrorReasons.java | 5 + .../mqtt/service/SubscriptionService.java | 6 +- .../AbstractMqttClientReleaseHandler.java | 10 +- .../service/impl/DefaultTopicService.java | 12 +- .../impl/InMemorySubscriptionService.java | 54 ++--- .../impl/ConnectInMqttInMessageHandler.java | 2 +- ...endingOutResponseMqttInMessageHandler.java | 2 +- .../impl/PublishMqttInMessageHandler.java | 2 +- .../PublishReleaseMqttInMessageHandler.java | 2 +- .../impl/SubscribeMqttInMessageHandler.java | 121 ++++++++--- .../impl/UnsubscribeMqttInMessageHandler.java | 2 +- .../out/factory/Mqtt5MessageOutFactory.java | 2 - .../out/factory/MqttMessageOutFactory.java | 31 ++- ...PersistedMqttPublishOutMessageHandler.java | 4 +- .../impl/Qos2MqttPublishInMessageHandler.java | 4 +- .../service/session/MqttSessionService.java | 2 +- .../impl/InMemoryActiveSubscriptions.java | 90 ++++++++ .../session/impl/InMemoryMessageTacker.java | 50 +++++ .../session/impl/InMemoryMqttSession.java | 106 +++------- .../impl/InMemoryMqttSessionService.java | 4 +- .../IntegrationServiceSpecification.groovy | 43 ++-- .../service/TestExternalMqttClient.groovy | 40 ++++ .../InMemorySubscriptionServiceTest.groovy | 27 ++- .../SubscribeMqttInMessageHandlerTest.groovy | 193 ++++++++++++++++++ 42 files changed, 744 insertions(+), 251 deletions(-) create mode 100644 network/src/main/java/javasabr/mqtt/network/session/ActiveSubscriptions.java create mode 100644 network/src/main/java/javasabr/mqtt/network/session/MessageTacker.java rename network/src/main/java/javasabr/mqtt/network/{ => session}/MqttSession.java (72%) create mode 100644 network/src/main/java/javasabr/mqtt/network/session/package-info.java create mode 100644 network/src/main/java/javasabr/mqtt/network/util/ExtraErrorReasons.java create mode 100644 service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java create mode 100644 service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java create mode 100644 service/src/test/groovy/javasabr/mqtt/service/TestExternalMqttClient.groovy rename service/src/test/groovy/javasabr/mqtt/service/{ => impl}/InMemorySubscriptionServiceTest.groovy (91%) create mode 100644 service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy diff --git a/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java b/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java index ca78b81..48c9dd0 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java @@ -10,4 +10,29 @@ public record MqttClientConnectionConfig( int topicAliasMaxValue, int keepAlive, boolean requestResponseInformation, - boolean requestProblemInformation) {} + boolean requestProblemInformation) { + + public boolean subscriptionIdAvailable() { + return server.subscriptionIdAvailable(); + } + + public boolean retainAvailable() { + return server.retainAvailable(); + } + + public boolean wildcardSubscriptionAvailable() { + return server.wildcardSubscriptionAvailable(); + } + + public boolean sharedSubscriptionAvailable() { + return server.sharedSubscriptionAvailable(); + } + + public boolean sessionsEnabled() { + return server.sessionsEnabled(); + } + + public int maxTopicLevels() { + return server.maxTopicLevels(); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java index 334559b..b45723b 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java @@ -47,7 +47,6 @@ public interface MqttProperties { int SUBSCRIPTION_ID_UNDEFINED = 0; - int MESSAGE_ID_UNDEFINED = -1; int MESSAGE_ID_IS_NOT_SET = 0; boolean SESSIONS_ENABLED_DEFAULT = true; diff --git a/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java b/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java index c41095b..5692076 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java @@ -126,4 +126,23 @@ public MqttServerConnectionConfig withSharedSubscriptionAvailable(boolean shared subscriptionIdAvailable, sharedSubscriptionAvailable); } + + public MqttServerConnectionConfig withSubscriptionIdAvailable(boolean subscriptionIdAvailable) { + return new MqttServerConnectionConfig( + maxQos, + maxMessageSize, + maxStringLength, + maxBinarySize, + maxTopicLevels, + minKeepAliveTime, + receiveMaxPublishes, + topicAliasMaxValue, + defaultSessionExpiryInterval, + keepAliveEnabled, + sessionsEnabled, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable); + } } diff --git a/model/src/main/java/javasabr/mqtt/model/QoS.java b/model/src/main/java/javasabr/mqtt/model/QoS.java index 20be115..cc4342e 100644 --- a/model/src/main/java/javasabr/mqtt/model/QoS.java +++ b/model/src/main/java/javasabr/mqtt/model/QoS.java @@ -1,6 +1,8 @@ package javasabr.mqtt.model; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.rlib.common.util.NumberedEnum; +import javasabr.rlib.common.util.NumberedEnumMap; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -11,22 +13,28 @@ @RequiredArgsConstructor @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public enum QoS { +public enum QoS implements NumberedEnum { AT_MOST_ONCE(0, SubscribeAckReasonCode.GRANTED_QOS_0), AT_LEAST_ONCE(1, SubscribeAckReasonCode.GRANTED_QOS_1), EXACTLY_ONCE(2, SubscribeAckReasonCode.GRANTED_QOS_2), INVALID(3, SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR); - private static final QoS[] VALUES = values(); + private static final NumberedEnumMap NUMBERED_MAP = + new NumberedEnumMap<>(QoS.class); public static QoS ofCode(int level) { - if (level < 0 || level > EXACTLY_ONCE.ordinal()) { - return INVALID; - } else { - return VALUES[level]; - } + return NUMBERED_MAP.resolve(level, QoS.INVALID); } int level; SubscribeAckReasonCode subscribeAckReasonCode; + + @Override + public int number() { + return level; + } + + public QoS lower(QoS alternative) { + return level > alternative.level ? alternative : this; + } } diff --git a/network/src/main/java/javasabr/mqtt/network/MqttClient.java b/network/src/main/java/javasabr/mqtt/network/MqttClient.java index 4cc3813..21f81ab 100644 --- a/network/src/main/java/javasabr/mqtt/network/MqttClient.java +++ b/network/src/main/java/javasabr/mqtt/network/MqttClient.java @@ -5,6 +5,7 @@ import javasabr.mqtt.model.subscribtion.SubscriptionOwner; import javasabr.mqtt.network.message.out.ConnectAckMqtt311OutMessage; import javasabr.mqtt.network.message.out.MqttOutMessage; +import javasabr.mqtt.network.session.MqttSession; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; @@ -32,7 +33,13 @@ interface UnsafeMqttClient extends MqttClient { void send(MqttOutMessage message); + /** + * @return the feature with result of delivering the message + */ CompletableFuture sendWithFeedback(MqttOutMessage message); - void closeWithReason(MqttOutMessage message); + /** + * @return the feature with result of delivering the reason before closing + */ + CompletableFuture closeWithReason(MqttOutMessage message); } diff --git a/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java b/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java index 2e9f01c..25caa18 100644 --- a/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java +++ b/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java @@ -6,10 +6,10 @@ import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.network.MqttClient.UnsafeMqttClient; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.handler.MqttClientReleaseHandler; import javasabr.mqtt.network.message.out.ConnectAckMqtt311OutMessage; import javasabr.mqtt.network.message.out.MqttOutMessage; +import javasabr.mqtt.network.session.MqttSession; import lombok.AccessLevel; import lombok.CustomLog; import lombok.Getter; @@ -61,9 +61,12 @@ public CompletableFuture sendWithFeedback(MqttOutMessage message) { } @Override - public void closeWithReason(MqttOutMessage message) { - sendWithFeedback(message) - .thenAccept(_ -> connection.close()); + public CompletableFuture closeWithReason(MqttOutMessage message) { + return sendWithFeedback(message) + .thenApply(sent -> { + connection.close(); + return sent; + }); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java index 7c344c6..427c10d 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java @@ -306,7 +306,7 @@ public byte messageType() { protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718039 rawTopicName = readString(buffer, Integer.MAX_VALUE); - messageId = qos != QoS.AT_MOST_ONCE ? readShortUnsigned(buffer) : 0; + messageId = qos != QoS.AT_MOST_ONCE ? readShortUnsigned(buffer) : MqttProperties.MESSAGE_ID_IS_NOT_SET; } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java index 97ed1ca..49af26d 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java @@ -27,7 +27,7 @@ */ @Getter @Accessors(fluent = true) -@FieldDefaults(level = AccessLevel.PRIVATE) +@FieldDefaults(level = AccessLevel.PROTECTED) public class SubscribeMqttInMessage extends TrackableMqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.SUBSCRIBE.ordinal(); @@ -129,6 +129,10 @@ public Array subscriptions() { return subscriptions; } + public int subscriptionsCount() { + return subscriptions.size(); + } + private static void validateMqtt311Options(int options) { // for MQTT 3.1.1 these bits must be zero if ((options & 0b0000_0100) != 0) { diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java index a1928db..6c7a0c0 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java @@ -22,7 +22,7 @@ public abstract class TrackableMqttInMessage extends MqttInMessage { public TrackableMqttInMessage(byte info) { super(info); - this.messageId = MqttProperties.MESSAGE_ID_UNDEFINED; + this.messageId = MqttProperties.MESSAGE_ID_IS_NOT_SET; } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java index 3dd52a4..707ce34 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java @@ -7,7 +7,6 @@ import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -231,8 +230,6 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { String clientId; - MqttClientConnectionConfig connectionConfig; - String responseInformation; String reason; String serverReference; @@ -248,7 +245,6 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { Array userProperties; public ConnectAckMqtt5OutMessage( - MqttClientConnectionConfig connectionConfig, ConnectAckReasonCode reasonCode, boolean sessionPresent, String clientId, @@ -263,7 +259,6 @@ public ConnectAckMqtt5OutMessage( byte[] authenticationData, Array userProperties) { super(reasonCode, sessionPresent); - this.connectionConfig = connectionConfig; this.clientId = clientId; this.requestedClientId = requestedClientId; this.requestedSessionExpiryInterval = requestedSessionExpiryInterval; @@ -294,8 +289,7 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { - - MqttServerConnectionConfig serverConfig = connectionConfig.server(); + MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901080 writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); writeNotEmptyProperty(buffer, MqttMessageProperty.RESPONSE_INFORMATION, responseInformation); @@ -311,7 +305,7 @@ protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { writeProperty( buffer, MqttMessageProperty.RETAIN_AVAILABLE, - serverConfig.retainAvailable(), + connectionConfig.retainAvailable(), MqttProperties.RETAIN_AVAILABLE_DEFAULT); writeProperty( buffer, @@ -333,17 +327,17 @@ protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { writeProperty( buffer, MqttMessageProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, - serverConfig.wildcardSubscriptionAvailable(), + connectionConfig.wildcardSubscriptionAvailable(), MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT); writeProperty( buffer, MqttMessageProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, - serverConfig.subscriptionIdAvailable(), + connectionConfig.subscriptionIdAvailable(), MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT); writeProperty( buffer, MqttMessageProperty.SHARED_SUBSCRIPTION_AVAILABLE, - serverConfig.sharedSubscriptionAvailable(), + connectionConfig.sharedSubscriptionAvailable(), MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT); writeProperty(buffer, MqttMessageProperty.SERVER_KEEP_ALIVE, connectionConfig.keepAlive(), requestedKeepAlive); } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java index 9ce7b45..6f9ed4b 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java @@ -10,12 +10,16 @@ import javasabr.mqtt.network.MqttConnection; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; /** * Disconnect notification. */ +@Getter +@Accessors(fluent = true) @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class DisconnectMqtt5OutMessage extends DisconnectMqtt311OutMessage { diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java index d471fa2..df81510 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java @@ -7,11 +7,15 @@ import javasabr.mqtt.network.message.MqttMessageType; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; /** * Subscribe acknowledgement. */ +@Getter +@Accessors(fluent = true) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class SubscribeAckMqtt311OutMessage extends TrackableMqttOutMessage { diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java index 71a3aa6..fb02a18 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java @@ -10,11 +10,15 @@ import javasabr.mqtt.network.MqttConnection; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; /** * Subscribe acknowledgement. */ +@Getter +@Accessors(fluent = true) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class SubscribeAckMqtt5OutMessage extends SubscribeAckMqtt311OutMessage { diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java index 5b2e382..d163799 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java @@ -4,9 +4,13 @@ import javasabr.mqtt.base.util.DebugUtils; import javasabr.mqtt.network.MqttConnection; import lombok.AccessLevel; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; +@Getter +@Accessors(fluent = true) @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public abstract class TrackableMqttOutMessage extends MqttOutMessage { diff --git a/network/src/main/java/javasabr/mqtt/network/session/ActiveSubscriptions.java b/network/src/main/java/javasabr/mqtt/network/session/ActiveSubscriptions.java new file mode 100644 index 0000000..e3d6126 --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/session/ActiveSubscriptions.java @@ -0,0 +1,18 @@ +package javasabr.mqtt.network.session; + +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.rlib.collections.array.Array; + +public interface ActiveSubscriptions { + + void add(Subscription subscription); + + void remove(Subscription subscription); + + void removeByTopicFilter(TopicFilter topicFilter); + + Array subscriptions(); + + Array findBySubscriptionId(int subscriptionId); +} diff --git a/network/src/main/java/javasabr/mqtt/network/session/MessageTacker.java b/network/src/main/java/javasabr/mqtt/network/session/MessageTacker.java new file mode 100644 index 0000000..4d3ab81 --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/session/MessageTacker.java @@ -0,0 +1,7 @@ +package javasabr.mqtt.network.session; + +public interface MessageTacker { + boolean isInUse(int messageId); + void add(int messageId); + void remove(int messageId); +} diff --git a/network/src/main/java/javasabr/mqtt/network/MqttSession.java b/network/src/main/java/javasabr/mqtt/network/session/MqttSession.java similarity index 72% rename from network/src/main/java/javasabr/mqtt/network/MqttSession.java rename to network/src/main/java/javasabr/mqtt/network/session/MqttSession.java index e0b392b..1dc05ad 100644 --- a/network/src/main/java/javasabr/mqtt/network/MqttSession.java +++ b/network/src/main/java/javasabr/mqtt/network/session/MqttSession.java @@ -1,10 +1,8 @@ -package javasabr.mqtt.network; +package javasabr.mqtt.network.session; import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.publishing.Publish; -import javasabr.mqtt.model.subscribtion.Subscription; -import javasabr.mqtt.model.topic.TopicFilter; -import javasabr.rlib.collections.array.Array; +import javasabr.mqtt.network.MqttClient; public interface MqttSession { @@ -40,6 +38,11 @@ default void resend(MqttClient client, Publish publish) {} void resendPendingPackets(MqttClient client); + MessageTacker inMessageTracker(); + MessageTacker outMessageTracker(); + + ActiveSubscriptions activeSubscriptions(); + boolean hasOutPending(); boolean hasInPending(); @@ -55,14 +58,4 @@ default void resend(MqttClient client, Publish publish) {} void updateOutPendingPacket(MqttClient client, TrackableMessage response); void updateInPendingPacket(MqttClient client, TrackableMessage response); - - void storeSubscription(Subscription subscription); - - void removeSubscription(TopicFilter subscribe); - - void removeSubscription(Subscription subscription); - - Array storedSubscriptions(); - - Array findStoredSubscriptionWithId(int subscriptionId); } diff --git a/network/src/main/java/javasabr/mqtt/network/session/package-info.java b/network/src/main/java/javasabr/mqtt/network/session/package-info.java new file mode 100644 index 0000000..86ec8b4 --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/session/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.network.session; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/network/src/main/java/javasabr/mqtt/network/util/ExtraErrorReasons.java b/network/src/main/java/javasabr/mqtt/network/util/ExtraErrorReasons.java new file mode 100644 index 0000000..3397fbf --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/util/ExtraErrorReasons.java @@ -0,0 +1,5 @@ +package javasabr.mqtt.network.util; + +public interface ExtraErrorReasons { + String SESSION_IS_ALREADY_CLOSED = "Session is already closed"; +} diff --git a/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java b/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java index 641f49b..f75771a 100644 --- a/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java +++ b/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java @@ -8,7 +8,7 @@ import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.MqttSession; +import javasabr.mqtt.network.session.MqttSession; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.MutableArray; @@ -32,7 +32,7 @@ default Array findSubscribers(TopicName topicName) { * @param subscriptions the list of request to subscribe topics * @return array of subscribe ack reason codes */ - Array subscribe(MqttClient client, Array subscriptions); + Array subscribe(MqttClient client, MqttSession session, Array subscriptions); /** * Removes MQTT client from listening to the topics. @@ -41,7 +41,7 @@ default Array findSubscribers(TopicName topicName) { * @param topicFilters topic filters * @return array of unsubscribe ack reason codes */ - Array unsubscribe(MqttClient client, Array topicFilters); + Array unsubscribe(MqttClient client, MqttSession session, Array topicFilters); void cleanSubscriptions(MqttClient client, MqttSession session); diff --git a/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java b/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java index 07dd849..393d0f0 100644 --- a/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java @@ -1,11 +1,10 @@ package javasabr.mqtt.service.handler.client; import javasabr.mqtt.model.MqttClientConnectionConfig; -import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.network.MqttClient.UnsafeMqttClient; -import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.handler.MqttClientReleaseHandler; import javasabr.mqtt.network.impl.AbstractMqttClient; +import javasabr.mqtt.network.session.MqttSession; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.session.MqttSessionService; @@ -49,10 +48,9 @@ protected Mono releaseImpl(T client) { if (session != null) { subscriptionService.cleanSubscriptions(client, session); - MqttClientConnectionConfig clientConfig = client.connectionConfig(); - MqttServerConnectionConfig serverConfig = clientConfig.server(); - if (serverConfig.sessionsEnabled()) { - asyncActions = sessionService.store(clientId, session, clientConfig.sessionExpiryInterval()); + MqttClientConnectionConfig connectionConfig = client.connectionConfig(); + if (connectionConfig.sessionsEnabled()) { + asyncActions = sessionService.store(clientId, session, connectionConfig.sessionExpiryInterval()); client.session(null); } } diff --git a/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java b/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java index f8d99e4..cbd5161 100644 --- a/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java +++ b/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java @@ -1,6 +1,6 @@ package javasabr.mqtt.service.impl; -import javasabr.mqtt.model.MqttServerConnectionConfig; +import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.topic.SharedTopicFilter; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; @@ -52,10 +52,7 @@ private TopicFilter createSharedTopicFilter(MqttClient client, String rawTopicFi } SharedTopicFilter sharedTopicFilter = SharedTopicFilter.valueOf(rawTopicFilter); - MqttServerConnectionConfig connectionConfig = client - .connectionConfig() - .server(); - + MqttClientConnectionConfig connectionConfig = client.connectionConfig(); if (sharedTopicFilter.levelsCount() > connectionConfig.maxTopicLevels()) { log.warning(client.clientId(), rawTopicFilter, "[%s] Too deep shared topic filter:[%s]"::formatted); return TopicFilter.INVALID_TOPIC_FILTER; @@ -71,10 +68,7 @@ private TopicFilter createStandardTopicFilter(MqttClient client, String rawTopic } TopicFilter topicFilter = TopicFilter.valueOf(rawTopicFilter); - MqttServerConnectionConfig connectionConfig = client - .connectionConfig() - .server(); - + MqttClientConnectionConfig connectionConfig = client.connectionConfig(); if (topicFilter.levelsCount() > connectionConfig.maxTopicLevels()) { log.warning(client.clientId(), rawTopicFilter, "[%s] Too deep topic filter:[%s]"::formatted); return TopicFilter.INVALID_TOPIC_FILTER; diff --git a/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java b/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java index a99df0e..cb1d44f 100644 --- a/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java +++ b/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java @@ -4,7 +4,6 @@ import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.SUCCESS; import javasabr.mqtt.model.MqttClientConnectionConfig; -import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; import javasabr.mqtt.model.subscriber.SingleSubscriber; @@ -15,7 +14,8 @@ import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.model.topic.tree.ConcurrentTopicTree; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.MqttSession; +import javasabr.mqtt.network.session.ActiveSubscriptions; +import javasabr.mqtt.network.session.MqttSession; import javasabr.mqtt.service.SubscriptionService; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; @@ -55,22 +55,13 @@ public Array findSubscribersTo(MutableArray @Override public Array subscribe( MqttClient client, + MqttSession session, Array subscriptions) { MutableArray subscribeResults = ArrayFactory.mutableArray( SubscribeAckReasonCode.class, subscriptions.size()); - MqttSession session = client.session(); - if (session == null) { - // without session just fill error for each topic filter - log.warning(client.clientId(), "[%s] Cannot add subscription for client without session"::formatted); - for (int i = 0, length = subscriptions.size(); i < length; i++) { - subscribeResults.add(SubscribeAckReasonCode.UNSPECIFIED_ERROR); - } - return subscribeResults; - } - for (Subscription subscription : subscriptions) { subscribeResults.add(addSubscription(client, session, subscription)); } @@ -79,41 +70,34 @@ public Array subscribe( } private SubscribeAckReasonCode addSubscription(MqttClient client, MqttSession session, Subscription subscription) { - MqttClientConnectionConfig clientConfig = client.connectionConfig(); - MqttServerConnectionConfig serverConfig = clientConfig.server(); + MqttClientConnectionConfig connectionConfig = client.connectionConfig(); TopicFilter topicFilter = subscription.topicFilter(); if (topicFilter.isInvalid()) { return SubscribeAckReasonCode.TOPIC_FILTER_INVALID; - } else if (!serverConfig.sharedSubscriptionAvailable() && topicFilter instanceof SharedTopicFilter) { + } else if (!connectionConfig.sharedSubscriptionAvailable() && topicFilter instanceof SharedTopicFilter) { return SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; - } else if (!serverConfig.wildcardSubscriptionAvailable() && topicFilter.wildcard()) { + } else if (!connectionConfig.wildcardSubscriptionAvailable() && topicFilter.wildcard()) { return SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; } + ActiveSubscriptions activeSubscriptions = session.activeSubscriptions(); SingleSubscriber previous = topicTree.subscribe(client, subscription); if (previous != null) { - session.removeSubscription(previous.subscription()); + activeSubscriptions.remove(previous.subscription()); } - session.storeSubscription(subscription); + activeSubscriptions.add(subscription); return subscription.qos().subscribeAckReasonCode(); } @Override - public Array unsubscribe(MqttClient client, Array topicFilters) { + public Array unsubscribe( + MqttClient client, + MqttSession session, + Array topicFilters) { MutableArray unsubscribeResults = ArrayFactory.mutableArray( UnsubscribeAckReasonCode.class, topicFilters.size()); - MqttSession session = client.session(); - if (session == null) { - // without session just fill error for each topic filter - log.warning(client.clientId(), "[%s] Cannot add subscription for client without session"::formatted); - for (int i = 0, length = topicFilters.size(); i < length; i++) { - unsubscribeResults.add(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR); - } - return unsubscribeResults; - } - for (TopicFilter topicFilter : topicFilters) { unsubscribeResults.add(removeSubscription(client, session, topicFilter)); } @@ -125,7 +109,9 @@ private UnsubscribeAckReasonCode removeSubscription(MqttClient client, MqttSessi if (topicFilter.isInvalid()) { return UnsubscribeAckReasonCode.TOPIC_FILTER_INVALID; } else if (topicTree.unsubscribe(client, topicFilter)) { - session.removeSubscription(topicFilter); + session + .activeSubscriptions() + .removeByTopicFilter(topicFilter); return SUCCESS; } else { return NO_SUBSCRIPTION_EXISTED; @@ -134,7 +120,9 @@ private UnsubscribeAckReasonCode removeSubscription(MqttClient client, MqttSessi @Override public void cleanSubscriptions(MqttClient client, MqttSession session) { - Array subscriptions = session.storedSubscriptions(); + Array subscriptions = session + .activeSubscriptions() + .subscriptions(); for (Subscription subscription : subscriptions) { topicTree.unsubscribe(client, subscription.topicFilter()); } @@ -142,7 +130,9 @@ public void cleanSubscriptions(MqttClient client, MqttSession session) { @Override public void restoreSubscriptions(MqttClient client, MqttSession session) { - Array subscriptions = session.storedSubscriptions(); + Array subscriptions = session + .activeSubscriptions() + .subscriptions(); for (Subscription subscription : subscriptions) { topicTree.subscribe(client, subscription); } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 8c10076..f77fc86 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -19,11 +19,11 @@ import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.ConnectMqttInMessage; import javasabr.mqtt.network.message.out.MqttOutMessage; +import javasabr.mqtt.network.session.MqttSession; import javasabr.mqtt.service.AuthenticationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.MessageOutFactoryService; diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java index fe136dc..27aaac9 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java @@ -2,9 +2,9 @@ import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.in.MqttInMessage; +import javasabr.mqtt.network.session.MqttSession; import javasabr.mqtt.service.MessageOutFactoryService; public abstract class PendingOutResponseMqttInMessageHandler

diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index d9b2652..f2b6057 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -6,10 +6,10 @@ import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.model.topic.TopicValidator; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.session.MqttSession; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.TopicService; diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java index 4f0b3ca..040b9ad 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java @@ -1,10 +1,10 @@ package javasabr.mqtt.service.message.handler.impl; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage; +import javasabr.mqtt.network.session.MqttSession; import javasabr.mqtt.service.MessageOutFactoryService; public class PublishReleaseMqttInMessageHandler diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java index 968a367..4ae5f1e 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java @@ -6,26 +6,31 @@ import java.util.Set; import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.MqttServerConnectionConfig; +import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.model.subscribtion.RequestedSubscription; import javasabr.mqtt.model.subscribtion.Subscription; -import javasabr.mqtt.network.MqttClient; +import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.SubscribeMqttInMessage; +import javasabr.mqtt.network.message.out.MqttOutMessage; +import javasabr.mqtt.network.session.MessageTacker; +import javasabr.mqtt.network.session.MqttSession; +import javasabr.mqtt.network.util.ExtraErrorReasons; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.TopicService; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; +import lombok.CustomLog; import lombok.experimental.FieldDefaults; +@CustomLog @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class SubscribeMqttInMessageHandler extends AbstractMqttInMessageHandler { @@ -34,9 +39,6 @@ public class SubscribeMqttInMessageHandler extends SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED); - private static final Array SUBSCRIPTION_ID_NOT_SUPPORTED = - Array.of(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED); - SubscriptionService subscriptionService; TopicService topicService; @@ -58,54 +60,77 @@ public MqttMessageType messageType() { protected void processReceived( MqttConnection connection, ExternalMqttClient client, - SubscribeMqttInMessage message) { + SubscribeMqttInMessage subscribeMessage) { + + MqttClientConnectionConfig connectionConfig = client.connectionConfig(); + MqttSession session = client.session(); + if (session == null) { + log.warning(client.clientId(), "[%s] Session is already closed"::formatted); + handleSessionIsAlreadyClosed(client); + return; + } + + int messageId = subscribeMessage.messageId(); + MessageTacker messageTacker = session.inMessageTracker(); + if (messageTacker.isInUse(messageId)) { + log.warning(client.clientId(), messageId, "[%s] Message id:[%d] is already in use"::formatted); + handleMessageIdIsInUse(client, subscribeMessage); + return; + } - MqttClientConnectionConfig clientConfig = client.connectionConfig(); - MqttServerConnectionConfig serverConfig = clientConfig.server(); + messageTacker.add(subscribeMessage.messageId()); - if (message.subscriptionId() != MqttProperties.SUBSCRIPTION_ID_UNDEFINED) { - if (!serverConfig.subscriptionIdAvailable()) { - sendSubscriptionIdNotSupported(client, message); + int subscriptionId = subscribeMessage.subscriptionId(); + if (subscriptionId != MqttProperties.SUBSCRIPTION_ID_UNDEFINED) { + if (!connectionConfig.subscriptionIdAvailable()) { + log.warning(client.clientId(), subscriptionId, + "[%s] Provided subscription id:[%d] but server doesn't allow it"::formatted); + handleSubscriptionIdNotSupported(client, session, subscribeMessage); return; } } Array subscriptions = transformSubscriptions( + connectionConfig, client, - message.subscriptions(), - message.subscriptionId()); + subscribeMessage.subscriptions(), subscriptionId); Array subscriptionResults = subscriptionService - .subscribe(client, subscriptions); + .subscribe(client, session, subscriptions); - sendSubscriptionResults(client, message, subscriptionResults); + sendSubscriptionResults(client, session, subscribeMessage, subscriptionResults); SubscribeAckReasonCode anyReasonToDisconnect = subscriptionResults - .reversedIterations() + .iterations() + .reversedArgs() .findAny(DISCONNECT_CASES, Set::contains); if (anyReasonToDisconnect != null) { + log.info(client.clientId(), anyReasonToDisconnect, "[%s] Will be forced closing by reason:[%s]"::formatted); DisconnectReasonCode reasonCode = DisconnectReasonCode.ofCode(anyReasonToDisconnect.code()); client.closeWithReason(messageOutFactoryService .resolveFactory(client) - .newDisconnect(client, reasonCode, message.userProperties())); + .newDisconnect(client, reasonCode)); } } private Array transformSubscriptions( - MqttClient client, + MqttClientConnectionConfig connectionConfig, + ExternalMqttClient client, Array requestedSubscriptions, int subscriptionId) { + QoS maxQos = connectionConfig.maxQos(); MutableArray subscriptions = ArrayFactory.mutableArray(Subscription.class, requestedSubscriptions.size()); for (RequestedSubscription requested : requestedSubscriptions) { - String rawTopicFilter = requested.rawTopicFilter(); + TopicFilter topicFilter = topicService.createTopicFilter(client, requested.rawTopicFilter()); + QoS resultQos = maxQos.lower(requested.qos()); subscriptions.add(new Subscription( - topicService.createTopicFilter(client, rawTopicFilter), + topicFilter, subscriptionId, - requested.qos(), + resultQos, requested.retainHandling(), requested.noLocal(), requested.retainAsPublished())); @@ -114,22 +139,56 @@ private Array transformSubscriptions( return subscriptions; } - private void sendSubscriptionIdNotSupported(ExternalMqttClient client, SubscribeMqttInMessage message) { + private void handleSessionIsAlreadyClosed(ExternalMqttClient client) { + MqttOutMessage response = messageOutFactoryService + .resolveFactory(client) + .newDisconnect( + client, + DisconnectReasonCode.UNSPECIFIED_ERROR, + ExtraErrorReasons.SESSION_IS_ALREADY_CLOSED); + client.closeWithReason(response); + } + + private void handleMessageIdIsInUse( + ExternalMqttClient client, + SubscribeMqttInMessage subscribeMessage) { + Array subscriptionResults = Array.repeated( + SubscribeAckReasonCode.PACKET_IDENTIFIER_IN_USE, + subscribeMessage.subscriptionsCount()); client.send(messageOutFactoryService .resolveFactory(client) - .newSubscribeAck( - message.messageId(), - SUBSCRIPTION_ID_NOT_SUPPORTED, - StringUtils.EMPTY, - message.userProperties())); + .newSubscribeAck(subscribeMessage.messageId(), subscriptionResults)); + } + + private void handleSubscriptionIdNotSupported( + ExternalMqttClient client, + MqttSession session, + SubscribeMqttInMessage subscribeMessage) { + Array subscriptionResults = Array.repeated( + SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED, + subscribeMessage.subscriptionsCount()); + int messageId = subscribeMessage.messageId(); + MqttOutMessage response = messageOutFactoryService + .resolveFactory(client) + .newSubscribeAck(messageId, subscriptionResults); + client.sendWithFeedback(response) + .thenAccept(_ -> session + .inMessageTracker() + .remove(messageId)); } private void sendSubscriptionResults( ExternalMqttClient client, - SubscribeMqttInMessage message, + MqttSession session, + SubscribeMqttInMessage subscribeMessage, Array subscriptionResults) { - client.send(messageOutFactoryService + int messageId = subscribeMessage.messageId(); + MqttOutMessage response = messageOutFactoryService .resolveFactory(client) - .newSubscribeAck(message.messageId(), subscriptionResults, message.userProperties())); + .newSubscribeAck(messageId, subscriptionResults); + client.sendWithFeedback(response) + .thenAccept(_ -> session + .inMessageTracker() + .remove(messageId)); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java index 8098942..9367bb7 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java @@ -48,7 +48,7 @@ protected void processReceived( .collect(ArrayCollectors.toArray(TopicFilter.class)); Array unsubscribeResults = subscriptionService - .unsubscribe(client, topicFilters); + .unsubscribe(client, client.session(), topicFilters); client.send(messageOutFactoryService .resolveFactory(client) diff --git a/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java b/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java index 1238847..55226e7 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java +++ b/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java @@ -51,9 +51,7 @@ public MqttOutMessage newConnectAck( String authenticationMethod, byte[] authenticationData, Array userProperties) { - MqttClientConnectionConfig connectionConfig = client.connectionConfig(); return new ConnectAckMqtt5OutMessage( - connectionConfig, reasonCode, sessionPresent, client.clientId(), diff --git a/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java b/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java index b860794..ef8b7a5 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java +++ b/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java @@ -24,6 +24,8 @@ public abstract class MqttMessageOutFactory { + public static final Array EMPTY_USER_PROPERTIES = Array.empty(StringPair.class); + public abstract MqttVersion mqttVersion(); public abstract MqttOutMessage newConnectAck( @@ -124,7 +126,7 @@ public abstract MqttOutMessage newPublishAck( Array userProperties); public MqttOutMessage newPublishAck(int packetId, PublishAckReasonCode reasonCode) { - return newPublishAck(packetId, reasonCode, StringUtils.EMPTY, Array.empty(StringPair.class)); + return newPublishAck(packetId, reasonCode, StringUtils.EMPTY, EMPTY_USER_PROPERTIES); } public abstract MqttOutMessage newSubscribeAck( @@ -134,7 +136,7 @@ public abstract MqttOutMessage newSubscribeAck( Array userProperties); public MqttOutMessage newSubscribeAck(int messageId, Array reasonCodes) { - return newSubscribeAck(messageId, reasonCodes, StringUtils.EMPTY, Array.empty(StringPair.class)); + return newSubscribeAck(messageId, reasonCodes, StringUtils.EMPTY, EMPTY_USER_PROPERTIES); } public MqttOutMessage newSubscribeAck( @@ -151,7 +153,7 @@ public abstract MqttOutMessage newUnsubscribeAck( String reason); public MqttOutMessage newUnsubscribeAck(int messageId, Array reasonCodes) { - return newUnsubscribeAck(messageId, reasonCodes, Array.empty(StringPair.class), StringUtils.EMPTY); + return newUnsubscribeAck(messageId, reasonCodes, EMPTY_USER_PROPERTIES, StringUtils.EMPTY); } public MqttOutMessage newUnsubscribeAck( @@ -172,7 +174,7 @@ public MqttOutMessage newDisconnect(MqttClient client, DisconnectReasonCode reas return newDisconnect( client, reasonCode, - Array.empty(StringPair.class), + EMPTY_USER_PROPERTIES, StringUtils.EMPTY, StringUtils.EMPTY); } @@ -189,6 +191,18 @@ public MqttOutMessage newDisconnect( StringUtils.EMPTY); } + public MqttOutMessage newDisconnect( + MqttClient client, + DisconnectReasonCode reasonCode, + String reason) { + return newDisconnect( + client, + reasonCode, + EMPTY_USER_PROPERTIES, + reason, + StringUtils.EMPTY); + } + public abstract MqttOutMessage newAuthenticate( AuthenticateReasonCode reasonCode, String authenticateMethod, @@ -203,8 +217,7 @@ public MqttOutMessage newAuthenticate( return newAuthenticate( reasonCode, authenticateMethod, - authenticateData, - Array.empty(StringPair.class), + authenticateData, EMPTY_USER_PROPERTIES, StringUtils.EMPTY); } @@ -219,7 +232,7 @@ public abstract MqttOutMessage newPublishRelease( String reason); public MqttOutMessage newPublishRelease(int packetId, PublishReleaseReasonCode reasonCode) { - return newPublishRelease(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); + return newPublishRelease(packetId, reasonCode, EMPTY_USER_PROPERTIES, StringUtils.EMPTY); } public abstract MqttOutMessage newPublishReceived( @@ -229,7 +242,7 @@ public abstract MqttOutMessage newPublishReceived( String reason); public MqttOutMessage newPublishReceived(int packetId, PublishReceivedReasonCode reasonCode) { - return newPublishReceived(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); + return newPublishReceived(packetId, reasonCode, EMPTY_USER_PROPERTIES, StringUtils.EMPTY); } public abstract MqttOutMessage newPublishCompleted( @@ -239,6 +252,6 @@ public abstract MqttOutMessage newPublishCompleted( String reason); public MqttOutMessage newPublishCompleted(int packetId, PublishCompletedReasonCode reasonCode) { - return newPublishCompleted(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); + return newPublishCompleted(packetId, reasonCode, EMPTY_USER_PROPERTIES, StringUtils.EMPTY); } } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java index 8f14dce..9bfd0e9 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java @@ -4,9 +4,9 @@ import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.MqttSession; -import javasabr.mqtt.network.MqttSession.PendingMessageHandler; import javasabr.mqtt.network.impl.ExternalMqttClient; +import javasabr.mqtt.network.session.MqttSession; +import javasabr.mqtt.network.session.MqttSession.PendingMessageHandler; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java index b7ecc51..63da8f5 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java @@ -6,10 +6,10 @@ import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.MqttSession; -import javasabr.mqtt.network.MqttSession.PendingMessageHandler; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage; +import javasabr.mqtt.network.session.MqttSession; +import javasabr.mqtt.network.session.MqttSession.PendingMessageHandler; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.SubscriptionService; diff --git a/service/src/main/java/javasabr/mqtt/service/session/MqttSessionService.java b/service/src/main/java/javasabr/mqtt/service/session/MqttSessionService.java index 94b1f01..2cf0092 100644 --- a/service/src/main/java/javasabr/mqtt/service/session/MqttSessionService.java +++ b/service/src/main/java/javasabr/mqtt/service/session/MqttSessionService.java @@ -1,6 +1,6 @@ package javasabr.mqtt.service.session; -import javasabr.mqtt.network.MqttSession; +import javasabr.mqtt.network.session.MqttSession; import reactor.core.publisher.Mono; public interface MqttSessionService { diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java new file mode 100644 index 0000000..5f27dcc --- /dev/null +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java @@ -0,0 +1,90 @@ +package javasabr.mqtt.service.session.impl; + +import java.util.concurrent.locks.StampedLock; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.network.session.ActiveSubscriptions; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import lombok.AccessLevel; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Accessors(fluent = true) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class InMemoryActiveSubscriptions implements ActiveSubscriptions { + + private static final Array EMPTY_SUBSCRIPTIONS = Array.empty(Subscription.class); + + MutableArray subscriptions; + + StampedLock lock; + + public InMemoryActiveSubscriptions() { + this.subscriptions = MutableArray.ofType(Subscription.class); + this.lock = new StampedLock(); + } + + @Override + public void add(Subscription subscription) { + long stamp = lock.writeLock(); + try { + subscriptions.add(subscription); + } finally { + lock.unlockWrite(stamp); + } + } + + @Override + public void remove(Subscription subscription) { + long stamp = lock.writeLock(); + try { + subscriptions.remove(subscription); + } finally { + lock.unlockWrite(stamp); + } + } + + @Override + public void removeByTopicFilter(TopicFilter topicFilter) { + long stamp = lock.writeLock(); + try { + int index = subscriptions.indexOf(Subscription::topicFilter, topicFilter); + if (index >= 0) { + subscriptions.remove(index); + } + } finally { + lock.unlockWrite(stamp); + } + } + + @Override + public Array subscriptions() { + long stamp = lock.readLock(); + try { + if (subscriptions.isEmpty()) { + return EMPTY_SUBSCRIPTIONS; + } + return Array.copyOf(subscriptions); + } finally { + lock.unlockRead(stamp); + } + } + + @Override + public Array findBySubscriptionId(int subscriptionId) { + MutableArray result = ArrayFactory.mutableArray(Subscription.class); + long stamp = lock.readLock(); + try { + for (Subscription subscription : subscriptions) { + if (subscription.subscriptionId() == subscriptionId) { + result.add(subscription); + } + } + } finally { + lock.unlockRead(stamp); + } + return result; + } +} diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java new file mode 100644 index 0000000..4553678 --- /dev/null +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java @@ -0,0 +1,50 @@ +package javasabr.mqtt.service.session.impl; + +import java.util.concurrent.locks.StampedLock; +import javasabr.mqtt.network.session.MessageTacker; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableIntArray; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class InMemoryMessageTacker implements MessageTacker { + + MutableIntArray usedMessageIds; + StampedLock lock; + + public InMemoryMessageTacker() { + this.usedMessageIds = ArrayFactory.mutableIntArray(); + this.lock = new StampedLock(); + } + + @Override + public boolean isInUse(int messageId) { + long stamp = lock.readLock(); + try { + return usedMessageIds.contains(messageId); + } finally { + lock.unlockRead(stamp); + } + } + + @Override + public void add(int messageId) { + long stamp = lock.writeLock(); + try { + usedMessageIds.add(messageId); + } finally { + lock.unlockWrite(stamp); + } + } + + @Override + public void remove(int messageId) { + long stamp = lock.writeLock(); + try { + usedMessageIds.remove(messageId); + } finally { + lock.unlockWrite(stamp); + } + } +} diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java index 7b1e58e..77f5c53 100644 --- a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java @@ -5,29 +5,28 @@ import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.publishing.Publish; -import javasabr.mqtt.model.subscribtion.Subscription; -import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.MqttSession.UnsafeMqttSession; -import javasabr.rlib.collections.array.Array; +import javasabr.mqtt.network.session.ActiveSubscriptions; +import javasabr.mqtt.network.session.MessageTacker; +import javasabr.mqtt.network.session.MqttSession.UnsafeMqttSession; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.LockableArray; -import javasabr.rlib.collections.array.MutableArray; +import lombok.AccessLevel; import lombok.CustomLog; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; @CustomLog @ToString(of = "clientId") @EqualsAndHashCode(of = "clientId") @Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PRIVATE) public class InMemoryMqttSession implements UnsafeMqttSession { - private static final Array EMPTY_SUBSCRIPTIONS = Array.empty(Subscription.class); - private record PendingPublish(Publish publish, PendingMessageHandler handler) {} private static void registerPublish( @@ -71,31 +70,39 @@ private static void updatePendingPacket( } } - private final String clientId; - private final LockableArray pendingOutPublishes; - private final LockableArray pendingInPublishes; - private final AtomicInteger packetIdGenerator; - private final LockableArray subscriptions; + final String clientId; + final AtomicInteger messageIdGenerator; + final LockableArray pendingOutPublishes; + final LockableArray pendingInPublishes; + + @Getter + final MessageTacker inMessageTracker; + @Getter + final MessageTacker outMessageTracker; + @Getter + final ActiveSubscriptions activeSubscriptions; @Getter @Setter - private volatile long expirationTime = -1; + volatile long expirationTime = -1; public InMemoryMqttSession(String clientId) { this.clientId = clientId; this.pendingOutPublishes = ArrayFactory.stampedLockBasedArray(PendingPublish.class); this.pendingInPublishes = ArrayFactory.stampedLockBasedArray(PendingPublish.class); - this.packetIdGenerator = new AtomicInteger(0); - this.subscriptions = ArrayFactory.stampedLockBasedArray(Subscription.class); + this.messageIdGenerator = new AtomicInteger(0); + this.inMessageTracker = new InMemoryMessageTacker(); + this.outMessageTracker = new InMemoryMessageTacker(); + this.activeSubscriptions = new InMemoryActiveSubscriptions(); } @Override public int nextMessageId() { - var nextId = packetIdGenerator.incrementAndGet(); + int nextId = messageIdGenerator.incrementAndGet(); if (nextId >= MqttProperties.MAXIMUM_PACKET_ID) { - packetIdGenerator.compareAndSet(nextId, 0); + messageIdGenerator.compareAndSet(nextId, 0); return nextMessageId(); } @@ -175,71 +182,6 @@ public void updateInPendingPacket(MqttClient client, TrackableMessage response) updatePendingPacket(client, response, pendingInPublishes, clientId); } - @Override - public void storeSubscription(Subscription subscription) { - long stamp = subscriptions.writeLock(); - try { - subscriptions.add(subscription); - } finally { - subscriptions.writeUnlock(stamp); - } - } - - @Override - public void removeSubscription(TopicFilter topicFilter) { - long stamp = subscriptions.writeLock(); - try { - int index = subscriptions.indexOf(Subscription::topicFilter, topicFilter); - if (index >= 0) { - subscriptions.remove(index); - } - } finally { - subscriptions.writeUnlock(stamp); - } - } - - @Override - public void removeSubscription(Subscription subscription) { - long stamp = subscriptions.writeLock(); - try { - subscriptions.remove(subscription); - } finally { - subscriptions.writeUnlock(stamp); - } - } - - @Override - public Array storedSubscriptions() { - if (subscriptions.isEmpty()) { - return EMPTY_SUBSCRIPTIONS; - } - long stamp = subscriptions.readLock(); - try { - return Array.copyOf(subscriptions); - } finally { - subscriptions.readUnlock(stamp); - } - } - - @Override - public Array findStoredSubscriptionWithId(int subscriptionId) { - if (subscriptions.isEmpty()) { - return EMPTY_SUBSCRIPTIONS; - } - MutableArray result = ArrayFactory.mutableArray(Subscription.class); - long stamp = subscriptions.readLock(); - try { - for (Subscription subscription : subscriptions) { - if (subscription.subscriptionId() == subscriptionId) { - result.add(subscription); - } - } - } finally { - subscriptions.readUnlock(stamp); - } - return result; - } - @Override public void clear() { pendingInPublishes diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java index 5e301fa..0517c75 100644 --- a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java @@ -1,8 +1,8 @@ package javasabr.mqtt.service.session.impl; import java.io.Closeable; -import javasabr.mqtt.network.MqttSession; -import javasabr.mqtt.network.MqttSession.UnsafeMqttSession; +import javasabr.mqtt.network.session.MqttSession; +import javasabr.mqtt.network.session.MqttSession.UnsafeMqttSession; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; diff --git a/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy index 1dd533f..5ef51c6 100644 --- a/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy +++ b/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -3,11 +3,15 @@ package javasabr.mqtt.service import javasabr.mqtt.model.* import javasabr.mqtt.network.MqttConnection import javasabr.mqtt.network.handler.MqttClientReleaseHandler -import javasabr.mqtt.network.impl.ExternalMqttClient +import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService import javasabr.mqtt.service.impl.DefaultTopicService +import javasabr.mqtt.service.impl.InMemorySubscriptionService +import javasabr.mqtt.service.message.out.factory.Mqtt311MessageOutFactory +import javasabr.mqtt.service.message.out.factory.Mqtt5MessageOutFactory import javasabr.mqtt.service.session.impl.InMemoryMqttSessionService -import javasabr.rlib.network.BufferAllocator import javasabr.rlib.network.Network +import javasabr.rlib.network.ServerNetworkConfig.SimpleServerNetworkConfig +import javasabr.rlib.network.impl.DefaultBufferAllocator import spock.lang.Shared import spock.lang.Specification @@ -22,6 +26,18 @@ class IntegrationServiceSpecification extends Specification { @Shared def defaultTopicService = new DefaultTopicService() + @Shared + def defaultSubscriptionService = new InMemorySubscriptionService() + + @Shared + def defaultMessageOutFactoryService = new DefaultMessageOutFactoryService([ + new Mqtt311MessageOutFactory(), + new Mqtt5MessageOutFactory() + ]) + + @Shared + def defaultBufferAllocator = new DefaultBufferAllocator(SimpleServerNetworkConfig.builder().build()) + @Shared def defaultMqttSessionService = new InMemoryMqttSessionService(60_000); @@ -43,21 +59,24 @@ class IntegrationServiceSpecification extends Specification { true, true) - def mockedExternalConnection( - MqttServerConnectionConfig serverConnectionConfig, - MqttVersion mqttVersion) { + def mockedExternalConnection(MqttVersion mqttVersion) { + return mockedExternalConnection(defaultExternalServerConnectionConfig, mqttVersion) + } + + def mockedExternalConnection(MqttServerConnectionConfig serverConnectionConfig, MqttVersion mqttVersion) { def connection = new MqttConnection( Mock(Network), Mock(AsynchronousSocketChannel), - Mock(BufferAllocator), + defaultBufferAllocator, 100, serverConnectionConfig, - { MqttConnection connection -> - def client = new ExternalMqttClient(connection, Mock(MqttClientReleaseHandler)) - def clientId = "mockedClient_${clientIdGenerator.incrementAndGet()}" - client.clientId(clientId) - client.session(defaultMqttSessionService.create(clientId).block()) + { MqttConnection ownedConnection -> + def generatedClientId = "mockedClient_${clientIdGenerator.incrementAndGet()}" + def createdSession = defaultMqttSessionService.create(generatedClientId).block() + def client = new TestExternalMqttClient(ownedConnection, Mock(MqttClientReleaseHandler)) + client.session(createdSession) + client.clientId(generatedClientId) return client }) @@ -73,6 +92,6 @@ class IntegrationServiceSpecification extends Specification { false, false)) - return connection + return Spy(connection) } } diff --git a/service/src/test/groovy/javasabr/mqtt/service/TestExternalMqttClient.groovy b/service/src/test/groovy/javasabr/mqtt/service/TestExternalMqttClient.groovy new file mode 100644 index 0000000..c0598e5 --- /dev/null +++ b/service/src/test/groovy/javasabr/mqtt/service/TestExternalMqttClient.groovy @@ -0,0 +1,40 @@ +package javasabr.mqtt.service + +import javasabr.mqtt.network.MqttConnection +import javasabr.mqtt.network.handler.MqttClientReleaseHandler +import javasabr.mqtt.network.impl.ExternalMqttClient +import javasabr.mqtt.network.message.out.MqttOutMessage +import javasabr.rlib.collections.array.MutableArray + +import java.util.concurrent.CompletableFuture + +class TestExternalMqttClient extends ExternalMqttClient { + + private final MutableArray sentMessages + + TestExternalMqttClient(MqttConnection connection, MqttClientReleaseHandler releaseHandler) { + super(connection, releaseHandler) + this.sentMessages = MutableArray.ofType(MqttOutMessage) + } + + @Override + void send(MqttOutMessage message) { + sentMessages.add(message) + } + + @Override + CompletableFuture sendWithFeedback(MqttOutMessage message) { + sentMessages.add(message) + return CompletableFuture.completedFuture(true) + } + + @Override + CompletableFuture closeWithReason(MqttOutMessage message) { + sentMessages.add(message) + return CompletableFuture.completedFuture(true) + } + + M nextSentMessage(Class type) { + return type.cast(sentMessages.remove(0)) + } +} diff --git a/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy b/service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy similarity index 91% rename from service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy rename to service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy index bfdbcea..0938d8e 100644 --- a/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy +++ b/service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy @@ -1,4 +1,4 @@ -package javasabr.mqtt.service +package javasabr.mqtt.service.impl import javasabr.mqtt.model.MqttVersion import javasabr.mqtt.model.QoS @@ -6,7 +6,8 @@ import javasabr.mqtt.model.SubscribeRetainHandling import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode import javasabr.mqtt.model.subscribtion.Subscription -import javasabr.mqtt.service.impl.InMemorySubscriptionService +import javasabr.mqtt.service.IntegrationServiceSpecification +import javasabr.mqtt.service.SubscriptionService import javasabr.rlib.collections.array.Array class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { @@ -48,7 +49,8 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { true, true)) when: - def result = subscriptionService.subscribe(mqttClient, subscriptions) + def result = subscriptionService + .subscribe(mqttClient, mqttClient.session(), subscriptions) then: result.size() == 4 result == Array.of( @@ -102,7 +104,8 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { true, true)) when: - def result = subscriptionService.subscribe(mqttClient, subscriptions) + def result = subscriptionService + .subscribe(mqttClient, mqttClient.session(), subscriptions) then: result.size() == 5 result == Array.of( @@ -150,7 +153,8 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { true) def subscriptions = Array.of(sub1, sub2, sub3, sub4) when: - def result = subscriptionService.subscribe(mqttClient, subscriptions) + def result = subscriptionService + .subscribe(mqttClient, mqttClient.session(), subscriptions) then: result.size() == 4 result == Array.of( @@ -196,14 +200,15 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { SubscribeRetainHandling.SEND, true, true)) - subscriptionService.subscribe(mqttClient, subscriptions) + subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions) def topicsToUnsubscribe = Array.of( defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), defaultTopicService.createTopicFilter(mqttClient, "topic/filter/notexist"), defaultTopicService.createTopicFilter(mqttClient, "topic/filter/invalid##")) when: - def result = subscriptionService.unsubscribe(mqttClient, topicsToUnsubscribe) + def result = subscriptionService + .unsubscribe(mqttClient, mqttClient.session(), topicsToUnsubscribe) then: result.size() == 4 result == Array.of( @@ -245,13 +250,13 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3")) when: - subscriptionService.subscribe(mqttClient, subscriptions) + subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions) def storedSubscriptions = mqttSession.storedSubscriptions() then: storedSubscriptions.size() == 3 storedSubscriptions == subscriptions when: - subscriptionService.unsubscribe(mqttClient, topicsToUnsubscribe) + subscriptionService.unsubscribe(mqttClient, mqttClient.session(), topicsToUnsubscribe) storedSubscriptions = mqttSession.storedSubscriptions() then: storedSubscriptions.size() == 1 @@ -306,13 +311,13 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { subscriptions.get(1), subscriptions2.get(1)) when: - subscriptionService.subscribe(mqttClient, subscriptions) + subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions) def storedSubscriptions = mqttSession.storedSubscriptions() then: storedSubscriptions.size() == 3 storedSubscriptions == subscriptions when: - subscriptionService.subscribe(mqttClient, subscriptions2) + subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions2) storedSubscriptions = mqttSession.storedSubscriptions() then: storedSubscriptions.size() == 3 diff --git a/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy b/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy new file mode 100644 index 0000000..e7dac07 --- /dev/null +++ b/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy @@ -0,0 +1,193 @@ +package javasabr.mqtt.service.message.handler.impl + +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.reason.code.DisconnectReasonCode +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.mqtt.model.subscribtion.RequestedSubscription +import javasabr.mqtt.network.message.in.SubscribeMqttInMessage +import javasabr.mqtt.network.message.out.DisconnectMqtt5OutMessage +import javasabr.mqtt.network.message.out.SubscribeAckMqtt5OutMessage +import javasabr.mqtt.network.util.ExtraErrorReasons +import javasabr.mqtt.service.IntegrationServiceSpecification +import javasabr.mqtt.service.TestExternalMqttClient +import javasabr.rlib.collections.array.Array +import javasabr.rlib.logger.api.LoggerLevel +import javasabr.rlib.logger.api.LoggerManager + +class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification { + + static { + LoggerManager.enable(SubscribeMqttInMessageHandler, LoggerLevel.WARNING) + LoggerManager.enable(SubscribeMqttInMessageHandler, LoggerLevel.INFO) + } + + def "should close connection by reason that session is already closed"() { + given: + def mqttConnection = mockedExternalConnection(MqttVersion.MQTT_5) + def messageHandler = new SubscribeMqttInMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService, + defaultTopicService) + def mqttClient = mqttConnection.client() as TestExternalMqttClient + mqttClient.session(null) + when: + def subscribeMessage = new SubscribeMqttInMessage(0 as byte) + messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + then: + def disconnectReason = mqttClient.nextSentMessage(DisconnectMqtt5OutMessage) + disconnectReason.reasonCode() == DisconnectReasonCode.UNSPECIFIED_ERROR + && disconnectReason.reason() == ExtraErrorReasons.SESSION_IS_ALREADY_CLOSED + && disconnectReason.serverReference() == "" + } + + def "should response that message id is in use"() { + given: + def mqttConnection = mockedExternalConnection(MqttVersion.MQTT_5) + def messageHandler = new SubscribeMqttInMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService, + defaultTopicService) + def expectedMessageId = 15 + def mqttClient = mqttConnection.client() as TestExternalMqttClient + def session = mqttClient.session() + session.inMessageTracker().add(expectedMessageId) + when: + def subscribeMessage = new SubscribeMqttInMessage(0 as byte) {{ + this.messageId = expectedMessageId + this.subscriptions.addAll(Array.of( + RequestedSubscription.minimal("topic1", QoS.EXACTLY_ONCE), + RequestedSubscription.minimal("topic2", QoS.EXACTLY_ONCE))) + }} + messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + then: + def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) + def reasonCodes = subscribeAck.reasonCodes() + reasonCodes.size() == 2 + && reasonCodes.get(0) == SubscribeAckReasonCode.PACKET_IDENTIFIER_IN_USE + && reasonCodes.get(1) == SubscribeAckReasonCode.PACKET_IDENTIFIER_IN_USE + && subscribeAck.messageId() == expectedMessageId + } + + def "should response that subscription id is not supported"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + .withSubscriptionIdAvailable(false) + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def messageHandler = new SubscribeMqttInMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService, + defaultTopicService) + def expectedMessageId = 15 + def mqttClient = mqttConnection.client() as TestExternalMqttClient + when: + def subscribeMessage = new SubscribeMqttInMessage(0 as byte) {{ + this.messageId = expectedMessageId + this.subscriptionId = 25 + this.subscriptions.addAll(Array.of( + RequestedSubscription.minimal("topic1", QoS.EXACTLY_ONCE), + RequestedSubscription.minimal("topic2", QoS.EXACTLY_ONCE))) + }} + messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + then: + def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) + def reasonCodes = subscribeAck.reasonCodes() + reasonCodes.size() == 2 + && reasonCodes.get(0) == SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED + && reasonCodes.get(1) == SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED + && subscribeAck.messageId() == expectedMessageId + } + + def "should subscribe with lower QoS by server limitation"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + .withMaxQos(QoS.AT_MOST_ONCE) + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def messageHandler = new SubscribeMqttInMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService, + defaultTopicService) + def expectedMessageId = 15 + def mqttClient = mqttConnection.client() as TestExternalMqttClient + when: + def subscribeMessage = new SubscribeMqttInMessage(0 as byte) {{ + this.messageId = expectedMessageId + this.subscriptionId = 25 + this.subscriptions.addAll(Array.of( + RequestedSubscription.minimal("topic1", QoS.EXACTLY_ONCE), + RequestedSubscription.minimal("topic2", QoS.EXACTLY_ONCE))) + }} + messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + then: + def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) + def reasonCodes = subscribeAck.reasonCodes() + reasonCodes.size() == 2 + && reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 + && reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_0 + && subscribeAck.messageId() == expectedMessageId + } + + def "should close connection by trying to subscribe not supported wildcard topic filter"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + .withWildcardSubscriptionAvailable(false) + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def messageHandler = new SubscribeMqttInMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService, + defaultTopicService) + def expectedMessageId = 15 + def mqttClient = mqttConnection.client() as TestExternalMqttClient + when: + def subscribeMessage = new SubscribeMqttInMessage(0 as byte) {{ + this.messageId = expectedMessageId + this.subscriptions.addAll(Array.of( + RequestedSubscription.minimal("topic1/#", QoS.EXACTLY_ONCE), + RequestedSubscription.minimal("topic2/+", QoS.EXACTLY_ONCE))) + }} + messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + then: + def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) + def reasonCodes = subscribeAck.reasonCodes() + reasonCodes.size() == 2 + && reasonCodes.get(0) == SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + && reasonCodes.get(1) == SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + && subscribeAck.messageId() == expectedMessageId + def disconnectReason = mqttClient.nextSentMessage(DisconnectMqtt5OutMessage) + disconnectReason.reasonCode() == DisconnectReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + && disconnectReason.reason() == "" + && disconnectReason.serverReference() == "" + } + + def "should close connection by trying to subscribe not supported shared topic filter"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + .withSharedSubscriptionAvailable(false) + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def messageHandler = new SubscribeMqttInMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService, + defaultTopicService) + def expectedMessageId = 15 + def mqttClient = mqttConnection.client() as TestExternalMqttClient + when: + def subscribeMessage = new SubscribeMqttInMessage(0 as byte) {{ + this.messageId = expectedMessageId + this.subscriptions.addAll(Array.of( + RequestedSubscription.minimal("\$share/group1/topic1/#", QoS.EXACTLY_ONCE), + RequestedSubscription.minimal("\$share/group1/topic2/+", QoS.EXACTLY_ONCE))) + }} + messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + then: + def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) + def reasonCodes = subscribeAck.reasonCodes() + reasonCodes.size() == 2 + && reasonCodes.get(0) == SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED + && reasonCodes.get(1) == SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED + && subscribeAck.messageId() == expectedMessageId + def disconnectReason = mqttClient.nextSentMessage(DisconnectMqtt5OutMessage) + disconnectReason.reasonCode() == DisconnectReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED + && disconnectReason.reason() == "" + && disconnectReason.serverReference() == "" + } +} From a6a09712084979d08c7237ad719df9b055d2fd4c Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 10 Nov 2025 07:55:54 +0100 Subject: [PATCH 3/6] update tests --- .../network/message/in/MqttInMessage.java | 4 +- .../network/message/out/MqttOutMessage.java | 2 +- .../in/AuthenticationMqttInMessageTest.groovy | 3 +- .../in/DisconnectMqttInMessageTest.groovy | 3 +- .../in/PublishAckMqttInMessageTest.groovy | 5 +-- .../PublishCompleteMqttInMessageTest.groovy | 5 +-- .../in/PublishMqttInMessageTest.groovy | 6 +-- .../PublishReceivedMqttInMessageTest.groovy | 9 ++--- .../in/PublishReleaseMqttInMessageTest.groovy | 5 +-- .../in/SubscribeAckMqttInMessageTest.groovy | 3 +- .../in/SubscribeMqttInMessageTest.groovy | 5 +-- .../in/UnsubscribeAckMqttInMessageTest.groovy | 5 +-- .../in/UnsubscribeMqttInMessageTest.groovy | 17 ++++----- .../ConnectAckMqtt311OutMessageTest.groovy | 37 +++++++++---------- .../out/ConnectAckMqtt5OutMessageTest.groovy | 9 +++-- .../out/ConnectMqtt311OutMessageTest.groovy | 3 +- .../PublishAckMqtt311OutMessageTest.groovy | 3 +- ...ublishCompleteMqtt311OutMessageTest.groovy | 3 +- .../out/PublishMqtt311OutMessageTest.groovy | 5 +-- .../InMemorySubscriptionServiceTest.groovy | 18 +++++---- 20 files changed, 69 insertions(+), 81 deletions(-) diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java index bb851d9..b8351de 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java @@ -37,7 +37,7 @@ public abstract class MqttInMessage extends AbstractReadableNetworkPacket EMPTY_PROPERTIES = Array.empty(StringPair.class); + public static final Array EMPTY_USER_PROPERTIES = Array.empty(StringPair.class); protected static final Array EMPTY_STRINGS = Array.empty(String.class); private record Utf8Decoder(CharsetDecoder decoder, ByteBuffer inBuffer, CharBuffer outBuffer) {} @@ -73,7 +73,7 @@ protected MqttInMessage(byte info) {} public abstract byte messageType(); public Array userProperties() { - return userProperties == null ? EMPTY_PROPERTIES : userProperties; + return userProperties == null ? EMPTY_USER_PROPERTIES : userProperties; } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java index 66257f8..c145989 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java @@ -16,7 +16,7 @@ @RequiredArgsConstructor public abstract class MqttOutMessage extends AbstractWritableNetworkPacket { - protected static final Array EMPTY_USER_PROPERTIES = Array.empty(StringPair.class); + public static final Array EMPTY_USER_PROPERTIES = Array.empty(StringPair.class); private static final ThreadLocal LOCAL_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate( 1024 * 1024)); diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/AuthenticationMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/AuthenticationMqttInMessageTest.groovy index cbc0e78..b6fb96a 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/AuthenticationMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/AuthenticationMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.AuthenticateReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { @@ -69,6 +68,6 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { packet.authenticationMethod == authMethod packet.authenticationData == authData packet.reason == "" - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/DisconnectMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/DisconnectMqttInMessageTest.groovy index d61d317..376fed6 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/DisconnectMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/DisconnectMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.DisconnectReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class DisconnectMqttInMessageTest extends BaseMqttInMessageTest { @@ -48,6 +47,6 @@ class DisconnectMqttInMessageTest extends BaseMqttInMessageTest { inMessage.serverReference() == serverReference inMessage.reasonCode() == DisconnectReasonCode.PACKET_TOO_LARGE inMessage.sessionExpiryInterval() == sessionExpiryInterval - inMessage.userProperties() == Array.empty() + inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishAckMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishAckMqttInMessageTest.groovy index 5db05a5..9385b47 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishAckMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishAckMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishAckReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { @@ -20,7 +19,7 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishAckReasonCode.SUCCESS - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } def "should read packet correctly as mqtt 5.0"() { @@ -57,6 +56,6 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessageTest.groovy index cd1693d..2b0c2e0 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { @@ -20,7 +19,7 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishCompletedReasonCode.SUCCESS - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } def "should read packet correctly as mqtt 5.0"() { @@ -57,6 +56,6 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishMqttInMessageTest.groovy index ed131ca..75f2e46 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishMqttInMessageTest.groovy @@ -4,8 +4,6 @@ import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttProperties import javasabr.mqtt.model.PayloadFormat import javasabr.mqtt.model.QoS -import javasabr.mqtt.model.data.type.StringPair -import javasabr.rlib.collections.array.Array import javasabr.rlib.collections.array.IntArray import javasabr.rlib.common.util.BufferUtils @@ -32,7 +30,7 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { message.correlationData() == null message.payload() == publishPayload message.messageId() == messageId - message.userProperties() == Array.empty() + message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES message.messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED message.topicAlias() == MqttProperties.TOPIC_ALIAS_UNDEFINED message.payloadFormat() == PayloadFormat.UNDEFINED @@ -95,7 +93,7 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { message.correlationData() == null message.payload() == publishPayload message.messageId() == messageId - message.userProperties() == Array.empty(StringPair) + message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES message.messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED message.topicAlias() == MqttProperties.TOPIC_ALIAS_UNDEFINED message.payloadFormat() == PayloadFormat.UNDEFINED diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessageTest.groovy index a45ecbc..d454bc5 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { @@ -20,7 +19,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishReceivedReasonCode.SUCCESS - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } def "should read packet correctly as mqtt 5.0"() { @@ -31,7 +30,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putShort(messageId) - it.put(PublishReceivedReasonCode.QUOTA_EXCEEDED.value) + it.put(PublishReceivedReasonCode.QUOTA_EXCEEDED.value()) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) } @@ -47,7 +46,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { when: dataBuffer = BufferUtils.prepareBuffer(512) { it.putShort(messageId) - it.put(PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) + it.put(PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value()) it.putMbi(0) } packet = new PublishReceivedMqttInMessage(0b0101_0000 as byte) @@ -57,6 +56,6 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy index 37eba8f..2ea89f7 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { @@ -20,7 +19,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishReleaseReasonCode.SUCCESS - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } def "should read packet correctly as mqtt 5.0"() { @@ -57,6 +56,6 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { packet.reason() == "" packet.messageId() == messageId packet.reasonCode() == PublishReleaseReasonCode.SUCCESS - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessageTest.groovy index 97e2e66..fe7584e 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { @@ -78,6 +77,6 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { inMessage.reasonCodes().get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 inMessage.reasonCodes().get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 inMessage.reasonCodes().get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR - inMessage.userProperties() == Array.empty() + inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy index 944d773..cc8056f 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy @@ -4,7 +4,6 @@ import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttProperties import javasabr.mqtt.model.QoS import javasabr.mqtt.model.SubscribeRetainHandling -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { @@ -35,7 +34,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { message.subscriptions().get(1).retainAsPublished() message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND message.messageId() == messageId - message.userProperties() == Array.empty() + message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES message.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED } @@ -98,7 +97,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { !message.subscriptions().get(1).retainAsPublished() message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND message.messageId() == messageId - message.userProperties() == Array.empty() + message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES message.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessageTest.groovy index d7740ab..d682cf4 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { @@ -19,7 +18,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { result packet.reason() == "" packet.messageId() == messageId - packet.reasonCodes() == Array.empty() + packet.reasonCodes() == MqttInMessage.EMPTY_USER_PROPERTIES } def "should read packet correctly as mqtt 5.0"() { @@ -66,6 +65,6 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { packet.reasonCodes().size() == 2 packet.reasonCodes().get(0) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR packet.reasonCodes().get(1) == UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy index 9726ffb..095ca14 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy @@ -1,7 +1,6 @@ package javasabr.mqtt.network.message.in import javasabr.mqtt.model.MqttMessageProperty -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { @@ -18,11 +17,11 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - packet.rawTopicFilters.size() == 2 - packet.rawTopicFilters.get(0).toString() == topicFilter - packet.rawTopicFilters.get(1).toString() == topicFilter2 + packet.rawTopicFilters().size() == 2 + packet.rawTopicFilters().get(0).toString() == topicFilter + packet.rawTopicFilters().get(1).toString() == topicFilter2 packet.messageId == messageId - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } def "should read packet correctly as mqtt 5.0"() { @@ -42,9 +41,9 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.rawTopicFilters.size() == 2 - packet.rawTopicFilters.get(0).toString() == topicFilter - packet.rawTopicFilters.get(1).toString() == topicFilter2 + packet.rawTopicFilters().size() == 2 + packet.rawTopicFilters().get(0).toString() == topicFilter + packet.rawTopicFilters().get(1).toString() == topicFilter2 packet.messageId == messageId packet.userProperties() == userProperties when: @@ -62,6 +61,6 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { packet.rawTopicFilters.get(0).toString() == topicFilter packet.rawTopicFilters.get(1).toString() == topicFilter2 packet.messageId == messageId - packet.userProperties() == Array.empty() + packet.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy index 67342b1..5745e55 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy @@ -3,7 +3,6 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.MqttProperties import javasabr.mqtt.model.reason.code.ConnectAckReasonCode import javasabr.mqtt.network.message.in.ConnectAckMqttInMessage -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.ArrayUtils import javasabr.rlib.common.util.BufferUtils @@ -22,23 +21,23 @@ class ConnectAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD - reader.sessionPresent == sessionPresent - reader.assignedClientId == "" - reader.reason == "" - reader.userProperties() == Array.empty() - reader.retainAvailable == MqttProperties.RETAIN_AVAILABLE_DEFAULT - reader.wildcardSubscriptionAvailable == MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT - reader.subscriptionIdAvailable == MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT - reader.sharedSubscriptionAvailable == MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT - reader.responseInformation == "" - reader.serverReference == "" - reader.authenticationData == ArrayUtils.EMPTY_BYTE_ARRAY - reader.authenticationMethod == "" - reader.topicAliasMaxValue == MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED - reader.serverKeepAlive == MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED - reader.receiveMaxPublishes == MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED - reader.sessionExpiryInterval == MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED - reader.maxMessageSize == MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED + reader.reasonCode() == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD + reader.sessionPresent() == sessionPresent + reader.assignedClientId() == "" + reader.reason() == "" + reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + reader.retainAvailable() == MqttProperties.RETAIN_AVAILABLE_DEFAULT + reader.wildcardSubscriptionAvailable() == MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT + reader.subscriptionIdAvailable() == MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT + reader.sharedSubscriptionAvailable() == MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT + reader.responseInformation() == "" + reader.serverReference() == "" + reader.authenticationData() == ArrayUtils.EMPTY_BYTE_ARRAY + reader.authenticationMethod() == "" + reader.topicAliasMaxValue() == MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED + reader.serverKeepAlive() == MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED + reader.receiveMaxPublishes() == MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED + reader.sessionExpiryInterval() == MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED + reader.maxMessageSize() == MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessageTest.groovy index c0a1ae0..c0fed0c 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessageTest.groovy @@ -20,12 +20,15 @@ class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { 30, false, false); + def connection = mqttConnection( + defaultServerConnectionConfig(), + clientConfig, + mqtt5ClientId) def requestedClientId = "-1" def requestedSessionExpireInterval = 360 def requestedKeepAlive = 120 def requestedReceiveMaxPublishes = 500 def outMessage = new ConnectAckMqtt5OutMessage( - clientConfig, ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD, sessionPresent, mqtt311ClientId, @@ -41,10 +44,10 @@ class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { userProperties) when: def dataBuffer = BufferUtils.prepareBuffer(512) { - outMessage.write(defaultMqtt5Connection, it) + outMessage.write(connection, it) } def inMessage = new ConnectAckMqttInMessage(0b0010_0000 as byte) - def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def result = inMessage.read(connection, dataBuffer, dataBuffer.limit()) then: result inMessage.reasonCode() == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt311OutMessageTest.groovy index 289adda..86dc3ae 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt311OutMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.QoS import javasabr.mqtt.network.message.in.ConnectMqttInMessage -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.ArrayUtils import javasabr.rlib.common.util.BufferUtils @@ -32,7 +31,7 @@ class ConnectMqtt311OutMessageTest extends BaseMqttOutMessageTest { reader.clientId() == mqtt311ClientId reader.password() == userPassword reader.keepAlive() == keepAlive - reader.userProperties() == Array.empty() + reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES reader.cleanStart() == cleanStart reader.willRetain == willRetain } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessageTest.groovy index ce989ad..22cf1cd 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.reason.code.PublishAckReasonCode import javasabr.mqtt.network.message.in.PublishAckMqttInMessage -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { @@ -20,7 +19,7 @@ class PublishAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { result reader.reasonCode() == PublishAckReasonCode.SUCCESS reader.messageId() == messageId - reader.userProperties() == Array.empty() + reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES reader.reason() == "" } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessageTest.groovy index 39382af..06e232d 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishCompleteMqtt311OutMessageTest extends BaseMqttOutMessageTest { @@ -20,7 +19,7 @@ class PublishCompleteMqtt311OutMessageTest extends BaseMqttOutMessageTest { result reader.reasonCode() == PublishCompletedReasonCode.SUCCESS reader.messageId() == messageId - reader.userProperties() == Array.empty() + reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES reader.reason() == "" } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt311OutMessageTest.groovy index 9364146..4815463 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt311OutMessageTest.groovy @@ -2,7 +2,6 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.QoS import javasabr.mqtt.network.message.in.PublishMqttInMessage -import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { @@ -30,7 +29,7 @@ class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { inMessage.duplicate() inMessage.payload() == publishPayload inMessage.rawTopicName() == publishTopic.rawTopic() - inMessage.userProperties() == Array.empty() + inMessage.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES when: outMessage = new PublishMqtt311OutMessage( messageId, @@ -52,6 +51,6 @@ class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { !inMessage.duplicate() inMessage.payload() == publishPayload inMessage.rawTopicName() == publishTopic.rawTopic() - inMessage.userProperties() == Array.empty() + inMessage.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES } } diff --git a/service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy b/service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy index 0938d8e..042eac3 100644 --- a/service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy +++ b/service/src/test/groovy/javasabr/mqtt/service/impl/InMemorySubscriptionServiceTest.groovy @@ -121,7 +121,6 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { def serverConfig = defaultExternalServerConnectionConfig def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) def mqttClient = mqttConnection.client() - def sub1 = new Subscription( defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), 15, @@ -163,9 +162,10 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { SubscribeAckReasonCode.GRANTED_QOS_2, SubscribeAckReasonCode.GRANTED_QOS_2) when: - def session = mqttClient.session() - def subsWithId15 = session.findStoredSubscriptionWithId(15) - def subsWithId30 = session.findStoredSubscriptionWithId(30) + def mqttSession = mqttClient.session() + def activeSubscriptions = mqttSession.activeSubscriptions() + def subsWithId15 = activeSubscriptions.findBySubscriptionId(15) + def subsWithId30 = activeSubscriptions.findBySubscriptionId(30) then: subsWithId15.size() == 2 subsWithId30.size() == 2 @@ -224,6 +224,7 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) def mqttClient = mqttConnection.client() def mqttSession = mqttClient.session() + def activeSubscriptions = mqttSession.activeSubscriptions() def subscriptions = Array.of( new Subscription( defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), @@ -251,13 +252,13 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3")) when: subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions) - def storedSubscriptions = mqttSession.storedSubscriptions() + def storedSubscriptions = activeSubscriptions.subscriptions() then: storedSubscriptions.size() == 3 storedSubscriptions == subscriptions when: subscriptionService.unsubscribe(mqttClient, mqttClient.session(), topicsToUnsubscribe) - storedSubscriptions = mqttSession.storedSubscriptions() + storedSubscriptions = activeSubscriptions.subscriptions() then: storedSubscriptions.size() == 1 storedSubscriptions.get(0) == subscriptions.get(1) @@ -269,6 +270,7 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) def mqttClient = mqttConnection.client() def mqttSession = mqttClient.session() + def activeSubscriptions = mqttSession.activeSubscriptions() def subscriptions = Array.of( new Subscription( defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), @@ -312,13 +314,13 @@ class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { subscriptions2.get(1)) when: subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions) - def storedSubscriptions = mqttSession.storedSubscriptions() + def storedSubscriptions = activeSubscriptions.subscriptions() then: storedSubscriptions.size() == 3 storedSubscriptions == subscriptions when: subscriptionService.subscribe(mqttClient, mqttClient.session(), subscriptions2) - storedSubscriptions = mqttSession.storedSubscriptions() + storedSubscriptions = activeSubscriptions.subscriptions() then: storedSubscriptions.size() == 3 storedSubscriptions ==~ resultSubscriptions From a988e3c2c09aa6e2f9c07b72632679d1193e8298 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 10 Nov 2025 10:47:02 +0100 Subject: [PATCH 4/6] add validation packet flags --- gradle/libs.versions.toml | 2 +- .../javasabr/mqtt/model/MqttProperties.java | 3 +- .../mqtt/model/subscribtion/Subscription.java | 2 +- .../model/topic/tree/TopicTreeTest.groovy | 6 +- .../javasabr/mqtt/network/MqttConnection.java | 3 +- .../network/message/MqttMessageReader.java | 7 +- .../network/message/in/MqttInMessage.java | 8 ++ .../in/PublishReleaseMqttInMessage.java | 5 + .../message/in/SubscribeMqttInMessage.java | 6 +- .../message/in/UnsubscribeMqttInMessage.java | 4 + .../out/PublishReleaseMqtt311OutMessage.java | 2 +- .../out/SubscribeMqtt311OutMessage.java | 5 + .../message/out/SubscribeMqtt5OutMessage.java | 4 +- .../mqtt/network/util/MqttDataUtils.java | 8 ++ .../in/PublishReleaseMqttInMessageTest.groovy | 6 +- .../in/SubscribeMqttInMessageTest.groovy | 113 +++++++++--------- .../in/UnsubscribeMqttInMessageTest.groovy | 6 +- ...PublishReleaseMqtt311OutMessageTest.groovy | 2 +- .../PublishReleaseMqtt5OutMessageTest.groovy | 2 +- .../out/SubscribeMqtt311OutMessageTest.groovy | 4 +- .../out/SubscribeMqtt5OutMessageTest.groovy | 8 +- .../impl/DefaultConnectionService.java | 42 +++++-- .../message/handler/MqttInMessageHandler.java | 4 +- .../impl/AbstractMqttInMessageHandler.java | 29 +++-- .../impl/ConnectInMqttInMessageHandler.java | 22 +--- .../impl/DisconnectMqttInMessageHandler.java | 2 +- ...endingOutResponseMqttInMessageHandler.java | 2 +- .../impl/PublishMqttInMessageHandler.java | 2 +- .../PublishReleaseMqttInMessageHandler.java | 2 +- .../impl/SubscribeMqttInMessageHandler.java | 4 +- .../impl/UnsubscribeMqttInMessageHandler.java | 2 +- .../SubscribeMqttInMessageHandlerTest.groovy | 12 +- 32 files changed, 197 insertions(+), 132 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 922cbe7..998b2bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # https://gitlab.com/JavaSaBr/maven-repo/-/packages -rlib = "10.0.alpha7" +rlib = "10.0.alpha8" # https://mvnrepository.com/artifact/org.projectlombok/lombok lombok = "1.18.38" # https://mvnrepository.com/artifact/org.jspecify/jspecify diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java index b45723b..34269b8 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java @@ -45,8 +45,7 @@ public interface MqttProperties { int TOPIC_ALIAS_MAX = 0xFFFF; int TOPIC_ALIAS_NOT_SET = 0; - int SUBSCRIPTION_ID_UNDEFINED = 0; - + int SUBSCRIPTION_ID_IS_NOT_SET = 0; int MESSAGE_ID_IS_NOT_SET = 0; boolean SESSIONS_ENABLED_DEFAULT = true; diff --git a/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java b/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java index f58f83d..2c066ad 100644 --- a/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java +++ b/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java @@ -41,7 +41,7 @@ public record Subscription( public static Subscription minimal(TopicFilter topicFilter, QoS qos) { return new Subscription( topicFilter, - MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET, qos, SubscribeRetainHandling.SEND, true, diff --git a/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy index a2827ee..fe5aa51 100644 --- a/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy +++ b/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy @@ -626,7 +626,7 @@ class TopicTreeTest extends UnitSpecification { static def makeSubscription(String topicFilter) { return new Subscription( TopicFilter.valueOf(topicFilter), - MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET, QoS.AT_LEAST_ONCE, SubscribeRetainHandling.SEND, true, @@ -636,7 +636,7 @@ class TopicTreeTest extends UnitSpecification { static def makeSharedSubscription(String topicFilter) { return new Subscription( SharedTopicFilter.valueOf(topicFilter), - MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET, QoS.AT_LEAST_ONCE, SubscribeRetainHandling.SEND, true, @@ -646,7 +646,7 @@ class TopicTreeTest extends UnitSpecification { static def makeSubscription(String topicFilter, int qos) { return new Subscription( TopicFilter.valueOf(topicFilter), - MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET, QoS.ofCode(qos), SubscribeRetainHandling.SEND, true, diff --git a/network/src/main/java/javasabr/mqtt/network/MqttConnection.java b/network/src/main/java/javasabr/mqtt/network/MqttConnection.java index 5e654bb..61ad22a 100644 --- a/network/src/main/java/javasabr/mqtt/network/MqttConnection.java +++ b/network/src/main/java/javasabr/mqtt/network/MqttConnection.java @@ -83,7 +83,8 @@ private NetworkPacketReader createPacketReader() { return new MqttMessageReader( this, this::updateLastActivity, - this::handleReceivedPacket, + this::handleReceivedValidPacket, + this::handleReceivedInvalidPacket, maxPacketsByRead); } diff --git a/network/src/main/java/javasabr/mqtt/network/message/MqttMessageReader.java b/network/src/main/java/javasabr/mqtt/network/message/MqttMessageReader.java index b99293d..af967aa 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/MqttMessageReader.java +++ b/network/src/main/java/javasabr/mqtt/network/message/MqttMessageReader.java @@ -56,9 +56,10 @@ public class MqttMessageReader extends AbstractNetworkPacketReader readPacketHandler, + Consumer validPacketHandler, + Consumer invalidPacketHandler, int maxPacketsByRead) { - super(connection, updateActivityFunction, readPacketHandler, maxPacketsByRead); + super(connection, updateActivityFunction, validPacketHandler, invalidPacketHandler, maxPacketsByRead); } @Override @@ -89,12 +90,10 @@ protected MqttInMessage createPacketFor( int startPacketPosition, int packetLength, int dataLength) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901021 int firstByte = Byte.toUnsignedInt(buffer.get(startPacketPosition)); byte type = NumberUtils.getHighByteBits(firstByte); byte info = NumberUtils.getLowByteBits(firstByte); - try { return PACKET_FACTORIES[type].apply(info); } catch (NoSuchElementException | NullPointerException e) { diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java index b8351de..c355100 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java @@ -76,6 +76,14 @@ public Array userProperties() { return userProperties == null ? EMPTY_USER_PROPERTIES : userProperties; } + @Override + public boolean read(MqttConnection connection, ByteBuffer buffer, int remainingDataLength) { + if (exception != null) { + return false; + } + return super.read(connection, buffer, remainingDataLength); + } + @Override protected void readImpl(MqttConnection connection, ByteBuffer buffer) { readVariableHeader(connection, buffer); diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java index 081f48e..b1ba975 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java @@ -7,9 +7,11 @@ import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.TrackableMessage; +import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; +import javasabr.mqtt.network.util.MqttDataUtils; import lombok.Getter; import lombok.experimental.Accessors; @@ -55,6 +57,9 @@ public class PublishReleaseMqttInMessage extends MqttInMessage implements Tracka public PublishReleaseMqttInMessage(byte info) { super(info); + if (info != 0b0000_0010) { + exception = new MalformedProtocolMqttException("Unexpected info bits:" + MqttDataUtils.toUnsignedBinary(info)); + } this.reasonCode = PublishReleaseReasonCode.SUCCESS; this.reason = ""; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java index 49af26d..11c8405 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java @@ -14,6 +14,7 @@ import javasabr.mqtt.model.subscribtion.RequestedSubscription; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; +import javasabr.mqtt.network.util.MqttDataUtils; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; @@ -60,8 +61,11 @@ public class SubscribeMqttInMessage extends TrackableMqttInMessage { public SubscribeMqttInMessage(byte info) { super(info); + if (info != 0b0000_0010) { + exception = new MalformedProtocolMqttException("Unexpected info bits:" + MqttDataUtils.toUnsignedBinary(info)); + } this.subscriptions = ArrayFactory.mutableArray(RequestedSubscription.class); - this.subscriptionId = MqttProperties.SUBSCRIPTION_ID_UNDEFINED; + this.subscriptionId = MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET; } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java index 7b7e4ff..6f8ba99 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java @@ -8,6 +8,7 @@ import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; +import javasabr.mqtt.network.util.MqttDataUtils; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; @@ -43,6 +44,9 @@ public class UnsubscribeMqttInMessage extends TrackableMqttInMessage { public UnsubscribeMqttInMessage(byte info) { super(info); + if (info != 0b0000_0010) { + exception = new MalformedProtocolMqttException("Unexpected info bits:" + MqttDataUtils.toUnsignedBinary(info)); + } } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java index a99e304..160416f 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java @@ -25,7 +25,7 @@ protected byte messageType() { @Override protected byte packetFlags() { - return 2; + return 0b0000_0010; } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java index 73a208a..ff67ef4 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java @@ -29,6 +29,11 @@ protected byte messageType() { return MESSAGE_TYPE; } + @Override + protected byte packetFlags() { + return 0b0000_0010; + } + @Override protected void writePayload(MqttConnection connection, ByteBuffer buffer) { // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java index e18b1f1..9c1e72b 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java @@ -42,7 +42,7 @@ public class SubscribeMqtt5OutMessage extends SubscribeMqtt311OutMessage { int subscriptionId; public SubscribeMqtt5OutMessage(int messageId, Array subscriptions) { - this(messageId, subscriptions, EMPTY_USER_PROPERTIES, MqttProperties.SUBSCRIPTION_ID_UNDEFINED); + this(messageId, subscriptions, EMPTY_USER_PROPERTIES, MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET); } public SubscribeMqtt5OutMessage( @@ -90,6 +90,6 @@ protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { buffer, MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId, - MqttProperties.SUBSCRIPTION_ID_UNDEFINED); + MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET); } } diff --git a/network/src/main/java/javasabr/mqtt/network/util/MqttDataUtils.java b/network/src/main/java/javasabr/mqtt/network/util/MqttDataUtils.java index cb425c6..7a59d3f 100644 --- a/network/src/main/java/javasabr/mqtt/network/util/MqttDataUtils.java +++ b/network/src/main/java/javasabr/mqtt/network/util/MqttDataUtils.java @@ -82,4 +82,12 @@ public static int sizeOfMbi(int number) { return sizeInBytes; } + + public static String toUnsignedBinary(byte value) { + String binary = Integer.toString(Byte.toUnsignedInt(value), 2); + if (binary.length() < 8) { + binary = "0".repeat(8 - binary.length()) + binary; + } + return "0b" + binary.substring(0, 4) + "_" + binary.substring(4); + } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy index 2ea89f7..6bce05e 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessageTest.groovy @@ -12,7 +12,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { it.putShort(messageId) } when: - def packet = new PublishReleaseMqttInMessage(0b0110_0000 as byte) + def packet = new PublishReleaseMqttInMessage(0b0000_0010 as byte) def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result @@ -35,7 +35,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { it.put(propertiesBuffer) } when: - def packet = new PublishReleaseMqttInMessage(0b0110_0000 as byte) + def packet = new PublishReleaseMqttInMessage(0b0000_0010 as byte) def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result @@ -49,7 +49,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { it.put(PublishReleaseReasonCode.SUCCESS.value) it.putMbi(0) } - packet = new PublishReleaseMqttInMessage(0b0110_0000 as byte) + packet = new PublishReleaseMqttInMessage(0b0000_0010 as byte) result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy index cc8056f..ec54dc3 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy @@ -13,29 +13,30 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { def dataBuffer = BufferUtils.prepareBuffer(512) { it.putShort(messageId) it.putString(topicFilter) - it.put(0b0000_0001 as byte) + it.put(0b0000_0001 as byte) // QoS.AT_LEAST_ONCE it.putString(topicFilter2) - it.put(0b0000_0010 as byte) + it.put(0b0000_0010 as byte) // QoS.EXACTLY_ONCE } when: - def message = new SubscribeMqttInMessage(0b1000_0000 as byte) - def result = message.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) + def successful = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: - result - message.subscriptions().size() == 2 - message.subscriptions().get(0).qos() == QoS.AT_LEAST_ONCE - message.subscriptions().get(0).rawTopicFilter() == topicFilter - message.subscriptions().get(0).noLocal() - message.subscriptions().get(0).retainAsPublished() - message.subscriptions().get(0).retainHandling() == SubscribeRetainHandling.SEND - message.subscriptions().get(1).qos() == QoS.EXACTLY_ONCE - message.subscriptions().get(1).rawTopicFilter().toString() == topicFilter2 - message.subscriptions().get(1).noLocal() - message.subscriptions().get(1).retainAsPublished() - message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND - message.messageId() == messageId - message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES - message.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + successful + def subscriptions = inMessage.subscriptions() + subscriptions.size() == 2 + subscriptions.get(0).qos() == QoS.AT_LEAST_ONCE + subscriptions.get(0).rawTopicFilter() == topicFilter + subscriptions.get(0).noLocal() + subscriptions.get(0).retainAsPublished() + subscriptions.get(0).retainHandling() == SubscribeRetainHandling.SEND + subscriptions.get(1).qos() == QoS.EXACTLY_ONCE + subscriptions.get(1).rawTopicFilter().toString() == topicFilter2 + subscriptions.get(1).noLocal() + subscriptions.get(1).retainAsPublished() + subscriptions.get(1).retainHandling() == SubscribeRetainHandling.SEND + inMessage.messageId() == messageId + inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET } def "should read message correctly as mqtt 5.0"() { @@ -49,29 +50,30 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.putString(topicFilter) - it.put(0b0000_1001 as byte) + it.put(0b0000_1001 as byte) // QoS.AT_LEAST_ONCE it.putString(topicFilter2) - it.put(0b0001_0110 as byte) + it.put(0b0001_0110 as byte) // QoS.AT_LEAST_ONCE } when: - def message = new SubscribeMqttInMessage(0b0110_0000 as byte) - def result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) + def successful = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def subscriptions = inMessage.subscriptions() then: - result - message.subscriptions().size() == 2 - message.subscriptions().get(0).qos() == QoS.AT_LEAST_ONCE - message.subscriptions().get(0).rawTopicFilter().toString() == topicFilter - !message.subscriptions().get(0).noLocal() - message.subscriptions().get(0).retainAsPublished() - message.subscriptions().get(0).retainHandling() == SubscribeRetainHandling.SEND - message.subscriptions().get(1).qos() == QoS.EXACTLY_ONCE - message.subscriptions().get(1).rawTopicFilter().toString() == topicFilter2 - message.subscriptions().get(1).noLocal() - !message.subscriptions().get(1).retainAsPublished() - message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST - message.messageId() == messageId - message.userProperties() == userProperties - message.subscriptionId() == subscriptionId + successful + subscriptions.size() == 2 + subscriptions.get(0).qos() == QoS.AT_LEAST_ONCE + subscriptions.get(0).rawTopicFilter() == topicFilter + !subscriptions.get(0).noLocal() + subscriptions.get(0).retainAsPublished() + subscriptions.get(0).retainHandling() == SubscribeRetainHandling.SEND + subscriptions.get(1).qos() == QoS.EXACTLY_ONCE + subscriptions.get(1).rawTopicFilter() == topicFilter2 + subscriptions.get(1).noLocal() + !subscriptions.get(1).retainAsPublished() + subscriptions.get(1).retainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST + inMessage.messageId() == messageId + inMessage.userProperties() == userProperties + inMessage.subscriptionId() == subscriptionId when: dataBuffer = BufferUtils.prepareBuffer(512) { it.putShort(messageId) @@ -81,23 +83,24 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putString(topicFilter2) it.put(0b0000_0010 as byte) } - message = new SubscribeMqttInMessage(0b0110_0000 as byte) - result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) + successful = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + subscriptions = inMessage.subscriptions() then: - result - message.subscriptions().size() == 2 - message.subscriptions().get(0).qos() == QoS.AT_LEAST_ONCE - message.subscriptions().get(0).rawTopicFilter().toString() == topicFilter - !message.subscriptions().get(0).noLocal() - !message.subscriptions().get(0).retainAsPublished() - message.subscriptions().get(0).retainHandling() == SubscribeRetainHandling.SEND - message.subscriptions().get(1).qos() == QoS.EXACTLY_ONCE - message.subscriptions().get(1).rawTopicFilter().toString() == topicFilter2 - !message.subscriptions().get(1).noLocal() - !message.subscriptions().get(1).retainAsPublished() - message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND - message.messageId() == messageId - message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES - message.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + successful + subscriptions.size() == 2 + subscriptions.get(0).qos() == QoS.AT_LEAST_ONCE + subscriptions.get(0).rawTopicFilter() == topicFilter + !subscriptions.get(0).noLocal() + !subscriptions.get(0).retainAsPublished() + subscriptions.get(0).retainHandling() == SubscribeRetainHandling.SEND + subscriptions.get(1).qos() == QoS.EXACTLY_ONCE + subscriptions.get(1).rawTopicFilter() == topicFilter2 + !subscriptions.get(1).noLocal() + !subscriptions.get(1).retainAsPublished() + subscriptions.get(1).retainHandling() == SubscribeRetainHandling.SEND + inMessage.messageId() == messageId + inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy index 095ca14..fd6c1d3 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy @@ -13,7 +13,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putString(topicFilter2) } when: - def packet = new UnsubscribeMqttInMessage(0b1011_0000 as byte) + def packet = new UnsubscribeMqttInMessage(0b0000_0010 as byte) def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result @@ -37,7 +37,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putString(topicFilter2) } when: - def packet = new UnsubscribeMqttInMessage(0b1011_0000 as byte) + def packet = new UnsubscribeMqttInMessage(0b0000_0010 as byte) def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result @@ -53,7 +53,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putString(topicFilter) it.putString(topicFilter2) } - packet = new UnsubscribeMqttInMessage(0b1011_0000 as byte) + packet = new UnsubscribeMqttInMessage(0b0000_0010 as byte) result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessageTest.groovy index 3439dbf..2745226 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessageTest.groovy @@ -15,7 +15,7 @@ class PublishReleaseMqtt311OutMessageTest extends BaseMqttOutMessageTest { def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) } - def reader = new PublishReleaseMqttInMessage(0b0110_0000 as byte) + def reader = new PublishReleaseMqttInMessage(0b0000_0010 as byte) def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessageTest.groovy index c6fa121..13f87ed 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessageTest.groovy @@ -17,7 +17,7 @@ class PublishReleaseMqtt5OutMessageTest extends BaseMqttOutMessageTest { def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt5Connection, it) } - def reader = new PublishReleaseMqttInMessage(0b0110_0000 as byte) + def reader = new PublishReleaseMqttInMessage(0b0000_0010 as byte) def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy index 1255ed0..d3ad6f6 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy @@ -29,13 +29,13 @@ class SubscribeMqtt311OutMessageTest extends BaseMqttOutMessageTest { def dataBuffer = BufferUtils.prepareBuffer(512) { outMessage.write(defaultMqtt311Connection, it) } - def inMessage = new SubscribeMqttInMessage(0b1000_0000 as byte) + def inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result inMessage.messageId() == 1 inMessage.subscriptions() == requestedSubscriptions inMessage.userProperties() == Array.empty(StringPair) - inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessageTest.groovy index f930c14..c3923d8 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessageTest.groovy @@ -37,19 +37,19 @@ class SubscribeMqtt5OutMessageTest extends BaseMqttOutMessageTest { 1, subscriptions, userProperties, - MqttProperties.SUBSCRIPTION_ID_UNDEFINED) + MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET) when: def dataBuffer = BufferUtils.prepareBuffer(512) { outMessage.write(defaultMqtt5Connection, it) } - def inMessage = new SubscribeMqttInMessage(0b1000_0000 as byte) + def inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result inMessage.messageId() == 1 inMessage.subscriptions() == requestedSubscriptions inMessage.userProperties() == userProperties - inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET when: outMessage = new SubscribeMqtt5OutMessage( 25, @@ -59,7 +59,7 @@ class SubscribeMqtt5OutMessageTest extends BaseMqttOutMessageTest { dataBuffer = BufferUtils.prepareBuffer(512) { outMessage.write(defaultMqtt5Connection, it) } - inMessage = new SubscribeMqttInMessage(0b1000_0000 as byte) + inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result diff --git a/service/src/main/java/javasabr/mqtt/service/impl/DefaultConnectionService.java b/service/src/main/java/javasabr/mqtt/service/impl/DefaultConnectionService.java index 30cb895..79d36b5 100644 --- a/service/src/main/java/javasabr/mqtt/service/impl/DefaultConnectionService.java +++ b/service/src/main/java/javasabr/mqtt/service/impl/DefaultConnectionService.java @@ -44,29 +44,55 @@ public DefaultConnectionService(Collection known @Override public void processAcceptedConnection(MqttConnection connection) { log.info(connection.remoteAddress(), "Accept new connection:[%s]"::formatted); - connection.onReceive(this::processReceivedMessage); + connection.onReceiveValidPacket(this::processReceivedValidMessage); + connection.onReceiveInvalidPacket(this::processReceivedInvalidMessage); } - protected void processReceivedMessage( + protected void processReceivedValidMessage( MqttConnection connection, ReadableNetworkPacket networkPacket) { - if (!(networkPacket instanceof MqttInMessage mrp)) { + if (!(networkPacket instanceof MqttInMessage mqttInMessage)) { log.warning(networkPacket, "Received not processable network packet:[%s]"::formatted); return; } log.debug( connection.client().clientId(), - networkPacket.name(), - networkPacket, - "[%s] Received from client message:[%s] %s"::formatted); + mqttInMessage.name(), + mqttInMessage, + "[%s] Received from client valid message:[%s] %s"::formatted); try { + MqttInMessageHandler messageHandler = inMessageHandlers[mqttInMessage.messageType()]; //noinspection DataFlowIssue - inMessageHandlers[mrp.messageType()].processReceived(connection, mrp); + messageHandler.processReceivedValidMessage(connection, mqttInMessage); } catch (IndexOutOfBoundsException | NullPointerException ex) { - log.warning(mrp, "Received not supported MQTT message:[%s]"::formatted); + log.warning(mqttInMessage, "Received not supported MQTT message:[%s]"::formatted); + } + } + + protected void processReceivedInvalidMessage( + MqttConnection connection, + ReadableNetworkPacket networkPacket) { + + if (!(networkPacket instanceof MqttInMessage mqttInMessage)) { + log.warning(networkPacket, "Received not processable network packet:[%s]"::formatted); + return; + } + + log.warning( + connection.client().clientId(), + mqttInMessage.name(), + mqttInMessage, + "[%s] Received from client invalid message:[%s] %s"::formatted); + + try { + MqttInMessageHandler messageHandler = inMessageHandlers[mqttInMessage.messageType()]; + //noinspection DataFlowIssue + messageHandler.processReceivedInvalidMessage(connection, mqttInMessage); + } catch (IndexOutOfBoundsException | NullPointerException ex) { + log.warning(mqttInMessage, "Received not supported MQTT message:[%s]"::formatted); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/MqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/MqttInMessageHandler.java index 47d0ef1..fcd26b1 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/MqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/MqttInMessageHandler.java @@ -8,5 +8,7 @@ public interface MqttInMessageHandler { MqttMessageType messageType(); - void processReceived(MqttConnection connection, MqttInMessage networkPacket); + void processReceivedValidMessage(MqttConnection connection, MqttInMessage mqttInMessage); + + void processReceivedInvalidMessage(MqttConnection connection, MqttInMessage mqttInMessage); } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java index 8137aaa..2571ce2 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java @@ -24,27 +24,38 @@ public abstract class AbstractMqttInMessageHandler { +public class ConnectInMqttInMessageHandler + extends AbstractMqttInMessageHandler { ClientIdRegistry clientIdRegistry; AuthenticationService authenticationService; @@ -63,7 +63,7 @@ public MqttMessageType messageType() { } @Override - protected void processReceived( + protected void processReceivedValidMessage( MqttConnection connection, ExternalMqttClient client, ConnectMqttInMessage message) { @@ -218,7 +218,7 @@ private boolean onSentConnAck(MqttClient.UnsafeMqttClient client, MqttSession se } @Override - protected boolean checkMessageException( + protected boolean processReceivedInvalidMessage( MqttConnection connection, ExternalMqttClient client, ConnectMqttInMessage message) { @@ -227,19 +227,9 @@ protected boolean checkMessageException( MqttOutMessage feedback = messageOutFactoryService .resolveFactory(client) .newConnectAck(client, cre.getReasonCode()); - client - .sendWithFeedback(feedback) - .thenAccept(_ -> connection.close()); - return true; - } else if (exception instanceof MalformedProtocolMqttException) { - MqttOutMessage feedback = messageOutFactoryService - .resolveFactory(client) - .newConnectAck(client, ConnectAckReasonCode.MALFORMED_PACKET); - client - .sendWithFeedback(feedback) - .thenAccept(_ -> connection.close()); + client.closeWithReason(feedback); return true; } - return super.checkMessageException(connection, client, message); + return super.processReceivedInvalidMessage(connection, client, message); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java index 7420988..de45bea 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java @@ -21,7 +21,7 @@ public MqttMessageType messageType() { } @Override - protected void processReceived( + protected void processReceivedValidMessage( MqttConnection connection, ExternalMqttClient client, DisconnectMqttInMessage message) { diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java index 27aaac9..771ab12 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java @@ -17,7 +17,7 @@ protected PendingOutResponseMqttInMessageHandler( } @Override - protected void processReceived(MqttConnection connection, ExternalMqttClient client, P message) { + protected void processReceivedValidMessage(MqttConnection connection, ExternalMqttClient client, P message) { MqttSession session = client.session(); if (session != null) { session.updateOutPendingPacket(client, message); diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index f2b6057..212d7b9 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -36,7 +36,7 @@ public PublishMqttInMessageHandler( } @Override - protected void processReceived( + protected void processReceivedValidMessage( MqttConnection connection, ExternalMqttClient client, PublishMqttInMessage message) { diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java index 040b9ad..56f6b7a 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java @@ -20,7 +20,7 @@ public MqttMessageType messageType() { } @Override - protected void processReceived( + protected void processReceivedValidMessage( MqttConnection connection, ExternalMqttClient client, PublishReleaseMqttInMessage message) { diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java index 4ae5f1e..2855cbb 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java @@ -57,7 +57,7 @@ public MqttMessageType messageType() { } @Override - protected void processReceived( + protected void processReceivedValidMessage( MqttConnection connection, ExternalMqttClient client, SubscribeMqttInMessage subscribeMessage) { @@ -81,7 +81,7 @@ protected void processReceived( messageTacker.add(subscribeMessage.messageId()); int subscriptionId = subscribeMessage.subscriptionId(); - if (subscriptionId != MqttProperties.SUBSCRIPTION_ID_UNDEFINED) { + if (subscriptionId != MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET) { if (!connectionConfig.subscriptionIdAvailable()) { log.warning(client.clientId(), subscriptionId, "[%s] Provided subscription id:[%d] but server doesn't allow it"::formatted); diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java index 9367bb7..a78dfde 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java @@ -36,7 +36,7 @@ public MqttMessageType messageType() { } @Override - protected void processReceived( + protected void processReceivedValidMessage( MqttConnection connection, ExternalMqttClient client, UnsubscribeMqttInMessage message) { diff --git a/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy b/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy index e7dac07..1df9d3e 100644 --- a/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy +++ b/service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandlerTest.groovy @@ -33,7 +33,7 @@ class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification mqttClient.session(null) when: def subscribeMessage = new SubscribeMqttInMessage(0 as byte) - messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + messageHandler.processReceivedValidMessage(mqttConnection, mqttClient, subscribeMessage) then: def disconnectReason = mqttClient.nextSentMessage(DisconnectMqtt5OutMessage) disconnectReason.reasonCode() == DisconnectReasonCode.UNSPECIFIED_ERROR @@ -59,7 +59,7 @@ class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification RequestedSubscription.minimal("topic1", QoS.EXACTLY_ONCE), RequestedSubscription.minimal("topic2", QoS.EXACTLY_ONCE))) }} - messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + messageHandler.processReceivedValidMessage(mqttConnection, mqttClient, subscribeMessage) then: def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) def reasonCodes = subscribeAck.reasonCodes() @@ -88,7 +88,7 @@ class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification RequestedSubscription.minimal("topic1", QoS.EXACTLY_ONCE), RequestedSubscription.minimal("topic2", QoS.EXACTLY_ONCE))) }} - messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + messageHandler.processReceivedValidMessage(mqttConnection, mqttClient, subscribeMessage) then: def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) def reasonCodes = subscribeAck.reasonCodes() @@ -117,7 +117,7 @@ class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification RequestedSubscription.minimal("topic1", QoS.EXACTLY_ONCE), RequestedSubscription.minimal("topic2", QoS.EXACTLY_ONCE))) }} - messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + messageHandler.processReceivedValidMessage(mqttConnection, mqttClient, subscribeMessage) then: def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) def reasonCodes = subscribeAck.reasonCodes() @@ -145,7 +145,7 @@ class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification RequestedSubscription.minimal("topic1/#", QoS.EXACTLY_ONCE), RequestedSubscription.minimal("topic2/+", QoS.EXACTLY_ONCE))) }} - messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + messageHandler.processReceivedValidMessage(mqttConnection, mqttClient, subscribeMessage) then: def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) def reasonCodes = subscribeAck.reasonCodes() @@ -177,7 +177,7 @@ class SubscribeMqttInMessageHandlerTest extends IntegrationServiceSpecification RequestedSubscription.minimal("\$share/group1/topic1/#", QoS.EXACTLY_ONCE), RequestedSubscription.minimal("\$share/group1/topic2/+", QoS.EXACTLY_ONCE))) }} - messageHandler.processReceived(mqttConnection, mqttClient, subscribeMessage) + messageHandler.processReceivedValidMessage(mqttConnection, mqttClient, subscribeMessage) then: def subscribeAck = mqttClient.nextSentMessage(SubscribeAckMqtt5OutMessage) def reasonCodes = subscribeAck.reasonCodes() From 04b40500eefa432a861e979ddde5be10e6a49914 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 10 Nov 2025 11:04:09 +0100 Subject: [PATCH 5/6] update validation message flags --- .../mqtt/network/message/MqttMessageWriter.java | 2 +- .../message/in/AuthenticationMqttInMessage.java | 4 ++-- .../message/in/ConnectAckMqttInMessage.java | 4 ++-- .../network/message/in/ConnectMqttInMessage.java | 4 ++-- .../message/in/DisconnectMqttInMessage.java | 4 ++-- .../mqtt/network/message/in/MqttInMessage.java | 10 +++++++++- .../message/in/PingRequestMqttInMessage.java | 4 ++-- .../message/in/PingResponseMqttInMessage.java | 4 ++-- .../message/in/PublishAckMqttInMessage.java | 4 ++-- .../message/in/PublishCompleteMqttInMessage.java | 4 ++-- .../network/message/in/PublishMqttInMessage.java | 10 +++++----- .../message/in/PublishReceivedMqttInMessage.java | 4 ++-- .../message/in/PublishReleaseMqttInMessage.java | 14 +++++++------- .../message/in/SubscribeAckMqttInMessage.java | 4 ++-- .../network/message/in/SubscribeMqttInMessage.java | 9 +++++---- .../network/message/in/TrackableMqttInMessage.java | 4 ++-- .../message/in/UnsubscribeAckMqttInMessage.java | 4 ++-- .../message/in/UnsubscribeMqttInMessage.java | 13 +++++++------ .../mqtt/network/message/out/MqttOutMessage.java | 6 +++--- .../message/out/PublishMqtt311OutMessage.java | 2 +- .../out/PublishReleaseMqtt311OutMessage.java | 2 +- .../message/out/SubscribeMqtt311OutMessage.java | 2 +- .../javasabr/mqtt/network/MqttMockClient.groovy | 2 +- 23 files changed, 65 insertions(+), 55 deletions(-) diff --git a/network/src/main/java/javasabr/mqtt/network/message/MqttMessageWriter.java b/network/src/main/java/javasabr/mqtt/network/message/MqttMessageWriter.java index eb80337..2d401a0 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/MqttMessageWriter.java +++ b/network/src/main/java/javasabr/mqtt/network/message/MqttMessageWriter.java @@ -57,7 +57,7 @@ protected boolean onAfterSerialize( writeBuffer .position(offset) - .put((byte) packet.packetTypeAndFlags()); + .put((byte) packet.messageTypeAndFlags()); MqttDataUtils .writeMbi(dataLength, writeBuffer) diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java index 36d59b5..5ef1be0 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java @@ -63,8 +63,8 @@ public class AuthenticationMqttInMessage extends MqttInMessage { byte[] authenticationData; - public AuthenticationMqttInMessage(byte info) { - super(info); + public AuthenticationMqttInMessage(byte messageFlags) { + super(messageFlags); this.reasonCode = AuthenticateReasonCode.SUCCESS; this.reason = StringUtils.EMPTY; this.authenticationMethod = StringUtils.EMPTY; diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java index f736e69..7fff505 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java @@ -270,8 +270,8 @@ public class ConnectAckMqttInMessage extends MqttInMessage { boolean sharedSubscriptionAvailable; boolean subscriptionIdAvailable; - public ConnectAckMqttInMessage(byte info) { - super(info); + public ConnectAckMqttInMessage(byte messageFlags) { + super(messageFlags); this.userProperties = MutableArray.ofType(StringPair.class); this.reasonCode = ConnectAckReasonCode.SUCCESS; this.maximumQos = QoS.EXACTLY_ONCE; diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java index f1f6a31..4540221 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java @@ -221,8 +221,8 @@ public class ConnectMqttInMessage extends MqttInMessage { boolean requestResponseInformation = false; boolean requestProblemInformation = false; - public ConnectMqttInMessage(byte info) { - super(info); + public ConnectMqttInMessage(byte messageFlags) { + super(messageFlags); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java index da85bc8..096080c 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java @@ -70,8 +70,8 @@ public class DisconnectMqttInMessage extends MqttInMessage { long sessionExpiryInterval; - public DisconnectMqttInMessage(byte info) { - super(info); + public DisconnectMqttInMessage(byte messageFlags) { + super(messageFlags); this.reasonCode = DisconnectReasonCode.NORMAL_DISCONNECTION; this.reason = StringUtils.EMPTY; this.serverReference = StringUtils.EMPTY; diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java index c355100..097ac07 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/MqttInMessage.java @@ -68,7 +68,15 @@ private record Utf8Decoder(CharsetDecoder decoder, ByteBuffer inBuffer, CharBuff @Nullable Exception exception; - protected MqttInMessage(byte info) {} + protected MqttInMessage(byte messageFlags) { + if (!validMessageFlags(messageFlags)) { + exception = new MalformedProtocolMqttException("Unexpected flags bits:" + MqttDataUtils.toUnsignedBinary(messageFlags)); + } + } + + protected boolean validMessageFlags(byte messageFlags) { + return true; + } public abstract byte messageType(); diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PingRequestMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PingRequestMqttInMessage.java index 80774f3..5ed2929 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PingRequestMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PingRequestMqttInMessage.java @@ -9,8 +9,8 @@ public class PingRequestMqttInMessage extends MqttInMessage { public static final byte MESSAGE_TYPE = (byte) MqttMessageType.PING_REQUEST.ordinal(); - public PingRequestMqttInMessage(byte info) { - super(info); + public PingRequestMqttInMessage(byte messageFlags) { + super(messageFlags); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PingResponseMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PingResponseMqttInMessage.java index d439ea1..7a595b0 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PingResponseMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PingResponseMqttInMessage.java @@ -9,8 +9,8 @@ public class PingResponseMqttInMessage extends MqttInMessage { public static final byte MESSAGE_TYPE = (byte) MqttMessageType.PING_RESPONSE.ordinal(); - public PingResponseMqttInMessage(byte info) { - super(info); + public PingResponseMqttInMessage(byte messageFlags) { + super(messageFlags); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishAckMqttInMessage.java index a8e5937..d74b74a 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishAckMqttInMessage.java @@ -56,8 +56,8 @@ public class PublishAckMqttInMessage extends MqttInMessage implements TrackableM // properties String reason; - public PublishAckMqttInMessage(byte info) { - super(info); + public PublishAckMqttInMessage(byte messageFlags) { + super(messageFlags); this.reasonCode = PublishAckReasonCode.SUCCESS; this.reason = ""; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessage.java index 39c4630..e549b0b 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishCompleteMqttInMessage.java @@ -56,8 +56,8 @@ public class PublishCompleteMqttInMessage extends MqttInMessage implements Track // properties String reason; - public PublishCompleteMqttInMessage(byte info) { - super(info); + public PublishCompleteMqttInMessage(byte messageFlags) { + super(messageFlags); this.reasonCode = PublishCompletedReasonCode.SUCCESS; this.reason = ""; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java index 427c10d..81b6c93 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishMqttInMessage.java @@ -285,11 +285,11 @@ public class PublishMqttInMessage extends TrackableMqttInMessage { int topicAlias; PayloadFormat payloadFormat; - public PublishMqttInMessage(byte info) { - super(info); - this.qos = QoS.ofCode((info & 0b0110) >> 1); - this.retained = (info & 0b0001) != 0; - this.duplicate = (info & 0b1000) != 0; + public PublishMqttInMessage(byte messageFlags) { + super(messageFlags); + this.qos = QoS.ofCode((messageFlags & 0b0110) >> 1); + this.retained = (messageFlags & 0b0001) != 0; + this.duplicate = (messageFlags & 0b1000) != 0; this.rawTopicName = StringUtils.EMPTY; this.payload = ArrayUtils.EMPTY_BYTE_ARRAY; this.messageExpiryInterval = MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED; diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessage.java index 3367ecb..b6a67b7 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessage.java @@ -57,8 +57,8 @@ public class PublishReceivedMqttInMessage extends MqttInMessage implements Track // properties String reason; - public PublishReceivedMqttInMessage(byte info) { - super(info); + public PublishReceivedMqttInMessage(byte messageFlags) { + super(messageFlags); this.reasonCode = PublishReceivedReasonCode.SUCCESS; this.reason = StringUtils.EMPTY; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java index b1ba975..7ad6184 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/PublishReleaseMqttInMessage.java @@ -7,11 +7,9 @@ import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.TrackableMessage; -import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; -import javasabr.mqtt.network.util.MqttDataUtils; import lombok.Getter; import lombok.experimental.Accessors; @@ -55,11 +53,8 @@ public class PublishReleaseMqttInMessage extends MqttInMessage implements Tracka // properties String reason; - public PublishReleaseMqttInMessage(byte info) { - super(info); - if (info != 0b0000_0010) { - exception = new MalformedProtocolMqttException("Unexpected info bits:" + MqttDataUtils.toUnsignedBinary(info)); - } + public PublishReleaseMqttInMessage(byte messageFlags) { + super(messageFlags); this.reasonCode = PublishReleaseReasonCode.SUCCESS; this.reason = ""; } @@ -69,6 +64,11 @@ public byte messageType() { return MESSAGE_TYPE; } + @Override + protected boolean validMessageFlags(byte messageFlags) { + return messageFlags == 0b0000_0010; + } + @Override protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { super.readVariableHeader(connection, buffer); diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java index 868f205..fc7f824 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java @@ -54,8 +54,8 @@ public class SubscribeAckMqttInMessage extends MqttInMessage { // properties String reason; - public SubscribeAckMqttInMessage(byte info) { - super(info); + public SubscribeAckMqttInMessage(byte messageFlags) { + super(messageFlags); this.reason = StringUtils.EMPTY; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java index 11c8405..feb7300 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java @@ -14,7 +14,6 @@ import javasabr.mqtt.model.subscribtion.RequestedSubscription; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; -import javasabr.mqtt.network.util.MqttDataUtils; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; @@ -61,9 +60,6 @@ public class SubscribeMqttInMessage extends TrackableMqttInMessage { public SubscribeMqttInMessage(byte info) { super(info); - if (info != 0b0000_0010) { - exception = new MalformedProtocolMqttException("Unexpected info bits:" + MqttDataUtils.toUnsignedBinary(info)); - } this.subscriptions = ArrayFactory.mutableArray(RequestedSubscription.class); this.subscriptionId = MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET; } @@ -73,6 +69,11 @@ public byte messageType() { return MESSAGE_TYPE; } + @Override + protected boolean validMessageFlags(byte messageFlags) { + return messageFlags == 0b0000_0010; + } + @Override protected void readPayload(MqttConnection connection, ByteBuffer buffer) { if (buffer.remaining() < 1) { diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java index 6c7a0c0..5af416d 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java @@ -20,8 +20,8 @@ public abstract class TrackableMqttInMessage extends MqttInMessage { int messageId; - public TrackableMqttInMessage(byte info) { - super(info); + public TrackableMqttInMessage(byte messageFlags) { + super(messageFlags); this.messageId = MqttProperties.MESSAGE_ID_IS_NOT_SET; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java index c84f10a..aba4b8d 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java @@ -58,8 +58,8 @@ public class UnsubscribeAckMqttInMessage extends MqttInMessage { // properties String reason = StringUtils.EMPTY; - public UnsubscribeAckMqttInMessage(byte info) { - super(info); + public UnsubscribeAckMqttInMessage(byte messageFlags) { + super(messageFlags); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java index 6f8ba99..3e99436 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java @@ -8,7 +8,6 @@ import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; -import javasabr.mqtt.network.util.MqttDataUtils; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; @@ -42,11 +41,8 @@ public class UnsubscribeMqttInMessage extends TrackableMqttInMessage { @Nullable MutableArray rawTopicFilters; - public UnsubscribeMqttInMessage(byte info) { - super(info); - if (info != 0b0000_0010) { - exception = new MalformedProtocolMqttException("Unexpected info bits:" + MqttDataUtils.toUnsignedBinary(info)); - } + public UnsubscribeMqttInMessage(byte messageFlags) { + super(messageFlags); } @Override @@ -54,6 +50,11 @@ public byte messageType() { return MESSAGE_TYPE; } + @Override + protected boolean validMessageFlags(byte messageFlags) { + return messageFlags == 0b0000_0010; + } + @Override protected void readPayload(MqttConnection connection, ByteBuffer buffer) { if (buffer.remaining() < 1) { diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java index c145989..f8da16b 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java @@ -44,9 +44,9 @@ protected boolean isPropertiesSupported(MqttConnection connection) { protected void writeProperties(MqttConnection connection, ByteBuffer buffer) {} - public final int packetTypeAndFlags() { + public final int messageTypeAndFlags() { byte type = messageType(); - byte controlFlags = packetFlags(); + byte controlFlags = messageFlags(); return NumberUtils.setHighByteBits(controlFlags, type); } @@ -54,7 +54,7 @@ protected byte messageType() { throw new UnsupportedOperationException(); } - protected byte packetFlags() { + protected byte messageFlags() { return 0; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt311OutMessage.java index a90ad07..ab20acd 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt311OutMessage.java @@ -42,7 +42,7 @@ public int expectedLength(MqttConnection connection) { } @Override - protected byte packetFlags() { + protected byte messageFlags() { byte info = (byte) (qos.ordinal() << 1); diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java index 160416f..41fb6da 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt311OutMessage.java @@ -24,7 +24,7 @@ protected byte messageType() { } @Override - protected byte packetFlags() { + protected byte messageFlags() { return 0b0000_0010; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java index ff67ef4..ed8e854 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java @@ -30,7 +30,7 @@ protected byte messageType() { } @Override - protected byte packetFlags() { + protected byte messageFlags() { return 0b0000_0010; } diff --git a/network/src/testFixtures/groovy/javasabr/mqtt/network/MqttMockClient.groovy b/network/src/testFixtures/groovy/javasabr/mqtt/network/MqttMockClient.groovy index 6f2535d..eb5b5a9 100644 --- a/network/src/testFixtures/groovy/javasabr/mqtt/network/MqttMockClient.groovy +++ b/network/src/testFixtures/groovy/javasabr/mqtt/network/MqttMockClient.groovy @@ -39,7 +39,7 @@ class MqttMockClient { dataBuffer.flip() def finalBuffer = ByteBuffer.allocate(1024) - finalBuffer.put((byte) packet.packetTypeAndFlags()) + finalBuffer.put((byte) packet.messageTypeAndFlags()) MqttDataUtils.writeMbi(dataBuffer.remaining(), finalBuffer) From 1103c37ab1be5297d2dd3ab878052fd9768af552 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 10 Nov 2025 18:27:10 +0100 Subject: [PATCH 6/6] extend tests for subscription process --- .../in/SubscribeMqttInMessageTest.groovy | 55 ++++++++++++++++++- .../network/NetworkUnitSpecification.groovy | 2 + 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy index ec54dc3..f68a3ee 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/SubscribeMqttInMessageTest.groovy @@ -4,6 +4,7 @@ import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttProperties import javasabr.mqtt.model.QoS import javasabr.mqtt.model.SubscribeRetainHandling +import javasabr.mqtt.model.exception.MalformedProtocolMqttException import javasabr.rlib.common.util.BufferUtils class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { @@ -50,9 +51,11 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.putString(topicFilter) - it.put(0b0000_1001 as byte) // QoS.AT_LEAST_ONCE + it.put(0b0000_1001 as byte) // QoS.AT_LEAST_ONCE, with local, retainAsPublished, SubscribeRetainHandling.SEND it.putString(topicFilter2) - it.put(0b0001_0110 as byte) // QoS.AT_LEAST_ONCE + it.put(0b0001_0110 as byte) // QoS.EXACTLY_ONCE, no local, no retainAsPublished, SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST + it.putString(topicFilter3) + it.put(0b0010_0100 as byte) // QoS.AT_MOST_ONCE, no local, retainAsPublished, SubscribeRetainHandling.DO_NOT_SEND } when: def inMessage = new SubscribeMqttInMessage(0b0000_0010 as byte) @@ -60,7 +63,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { def subscriptions = inMessage.subscriptions() then: successful - subscriptions.size() == 2 + subscriptions.size() == 3 subscriptions.get(0).qos() == QoS.AT_LEAST_ONCE subscriptions.get(0).rawTopicFilter() == topicFilter !subscriptions.get(0).noLocal() @@ -71,6 +74,11 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { subscriptions.get(1).noLocal() !subscriptions.get(1).retainAsPublished() subscriptions.get(1).retainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST + subscriptions.get(2).qos() == QoS.AT_MOST_ONCE + subscriptions.get(2).rawTopicFilter() == topicFilter3 + subscriptions.get(2).noLocal() + !subscriptions.get(2).retainAsPublished() + subscriptions.get(2).retainHandling() == SubscribeRetainHandling.DO_NOT_SEND inMessage.messageId() == messageId inMessage.userProperties() == userProperties inMessage.subscriptionId() == subscriptionId @@ -103,4 +111,45 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET } + + def "should not read invalid message as mqtt 5.0"() { + given: + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(messageId) + it.putMbi(0) + it.putString(topicFilter) + it.put(0b0000_0001 as byte) + } + when: + def inMessage = new SubscribeMqttInMessage(0b0000_0000 as byte) + def successful = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + !successful + inMessage.exception() instanceof MalformedProtocolMqttException + inMessage.exception().message == 'Unexpected flags bits:0b0000_0000' + when: + def dataBuffer2 = BufferUtils.prepareBuffer(512) { + it.putShort(messageId) + it.putMbi(0) + it.putString(topicFilter) + it.put(0b0011_1001 as byte) + } + def inMessage2 = new SubscribeMqttInMessage(0b0000_0010 as byte) + def successful2 = inMessage2.read(defaultMqtt5Connection, dataBuffer2, dataBuffer2.limit()) + then: + !successful2 + inMessage2.exception() instanceof MalformedProtocolMqttException + inMessage2.exception().message == 'Unsupported qos or retain handling' + when: + def dataBuffer3 = BufferUtils.prepareBuffer(512) { + it.putShort(messageId) + it.putMbi(0) + } + def inMessage3 = new SubscribeMqttInMessage(0b0000_0010 as byte) + def successful3 = inMessage3.read(defaultMqtt5Connection, dataBuffer3, dataBuffer3.limit()) + then: + !successful3 + inMessage3.exception() instanceof MalformedProtocolMqttException + inMessage3.exception().message == 'No any topic filters' + } } diff --git a/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy b/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy index e6c0b18..ce5c690 100644 --- a/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy +++ b/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy @@ -73,6 +73,8 @@ class NetworkUnitSpecification extends UnitSpecification { SubscribeRetainHandling.DO_NOT_SEND, true, false) + public static final topicFilter3 = "topic/Filter3" + public static final topicFilter4 = "topic/Filter4" public static final serverReference = "serverReference" public static final contentType = "application/json"